mirror of
https://github.com/Cuteminded/jellyframe.git
synced 2026-10-17 16:00:29 +02:00
Updates player navigation so the Library action returns to the currently playing movie or series details instead of the generic library view. For episodes, it restores the correct series season and keeps series context open; selecting the active title resumes playback without restarting the stream. Adds a new `test_player_return` integration test and wires it into Linux/Windows CI, plus README updates documenting the behavior and test command.
132 lines
4.6 KiB
GDScript
132 lines
4.6 KiB
GDScript
class_name SeriesBrowser
|
|
extends VBoxContainer
|
|
|
|
signal play_requested(item: Dictionary)
|
|
signal section_changed
|
|
|
|
var api: JellyfinClient
|
|
var artwork: ArtworkLoader
|
|
var series_id := ""
|
|
var season_grid: HFlowContainer
|
|
var season_section: VBoxContainer
|
|
var episode_section: VBoxContainer
|
|
var episode_list: VBoxContainer
|
|
var episode_heading: Label
|
|
var season_description: Label
|
|
var all_seasons_button: Button
|
|
var generation := 0
|
|
var selected_season: Dictionary = {}
|
|
|
|
func _ready() -> void:
|
|
add_theme_constant_override("separation", 20)
|
|
season_section = VBoxContainer.new()
|
|
season_section.add_theme_constant_override("separation", 18)
|
|
add_child(season_section)
|
|
Widgets.label("Seasons", season_section, 28)
|
|
season_grid = HFlowContainer.new()
|
|
season_grid.add_theme_constant_override("h_separation", 18)
|
|
season_grid.add_theme_constant_override("v_separation", 18)
|
|
season_section.add_child(season_grid)
|
|
episode_section = VBoxContainer.new()
|
|
episode_section.add_theme_constant_override("separation", 18)
|
|
add_child(episode_section)
|
|
var navigation := HBoxContainer.new()
|
|
episode_section.add_child(navigation)
|
|
all_seasons_button = Widgets.button("All seasons", _show_seasons, navigation)
|
|
episode_heading = _label("", episode_section, 28)
|
|
season_description = _label("", episode_section, 22)
|
|
episode_list = VBoxContainer.new()
|
|
episode_list.add_theme_constant_override("separation", 16)
|
|
episode_section.add_child(episode_list)
|
|
episode_section.hide()
|
|
if selected_season.is_empty(): load_seasons()
|
|
else: open_season(selected_season)
|
|
|
|
func load_seasons() -> void:
|
|
generation += 1
|
|
var current := generation
|
|
_clear(season_grid)
|
|
_label("Loading seasons...", season_grid, 22)
|
|
var result := await api.seasons(series_id)
|
|
if not _current(current): return
|
|
_clear(season_grid)
|
|
if not result.ok:
|
|
_label("Could not load seasons.", season_grid, 22)
|
|
Widgets.button("Retry seasons", load_seasons, season_grid)
|
|
return
|
|
var seasons: Array = result.data.get("Items", [])
|
|
if seasons.is_empty():
|
|
_label("No seasons available.", season_grid, 22)
|
|
for season in seasons:
|
|
var card := MediaCard.new()
|
|
card.item = season
|
|
card.artwork = artwork
|
|
card.chosen.connect(open_season)
|
|
season_grid.add_child(card)
|
|
|
|
func open_season(season: Dictionary) -> void:
|
|
selected_season = season.duplicate(true)
|
|
generation += 1
|
|
var current := generation
|
|
season_section.hide()
|
|
episode_section.show()
|
|
all_seasons_button.grab_focus()
|
|
episode_heading.text = str(season.get("Name", "Season")) + " episodes"
|
|
season_description.text = str(season.get("Overview") if season.get("Overview") else "")
|
|
season_description.visible = not season_description.text.is_empty()
|
|
_clear(episode_list)
|
|
_label("Loading episodes...", episode_list, 22)
|
|
section_changed.emit()
|
|
var items: Array = []
|
|
while true:
|
|
var result := await api.episodes(series_id, str(season.Id), items.size())
|
|
if not _current(current): return
|
|
if not result.ok:
|
|
_clear(episode_list)
|
|
_label("Could not load episodes.", episode_list, 22)
|
|
Widgets.button("Retry episodes", open_season.bind(season), episode_list)
|
|
section_changed.emit()
|
|
return
|
|
var batch: Array = result.data.get("Items", [])
|
|
items.append_array(batch)
|
|
if batch.is_empty() or items.size() >= int(result.data.get("TotalRecordCount", items.size())): break
|
|
_clear(episode_list)
|
|
if items.is_empty(): _label("No episodes available in this season.", episode_list, 22)
|
|
for episode in items:
|
|
var card := EpisodeCard.new()
|
|
card.item = episode.duplicate(true)
|
|
card.item["Type"] = "Episode"
|
|
card.item["SeriesId"] = series_id
|
|
card.item["SeasonId"] = str(season.Id)
|
|
if card.item.get("ParentIndexNumber") == null:
|
|
card.item["ParentIndexNumber"] = season.get("IndexNumber")
|
|
card.artwork = artwork
|
|
card.chosen.connect(func(chosen: Dictionary): play_requested.emit(chosen))
|
|
episode_list.add_child(card)
|
|
section_changed.emit()
|
|
|
|
func _show_seasons() -> void:
|
|
selected_season = {}
|
|
generation += 1
|
|
episode_section.hide()
|
|
season_section.show()
|
|
section_changed.emit()
|
|
if season_grid.get_child_count() == 0: load_seasons()
|
|
if season_grid.get_child_count() > 0:
|
|
var first := season_grid.get_child(0) as Control
|
|
if first and first.focus_mode != Control.FOCUS_NONE: first.grab_focus()
|
|
|
|
func _current(current: int) -> bool:
|
|
return current == generation and is_inside_tree()
|
|
|
|
func _clear(parent: Node) -> void:
|
|
for child in parent.get_children():
|
|
parent.remove_child(child)
|
|
child.queue_free()
|
|
|
|
func _label(value: String, parent: Node, font_size: int) -> Label:
|
|
var label := Widgets.label(value, parent, font_size)
|
|
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
return label
|