Files
Lizzy ef41fa1e61 Return player Library to active title
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.
2026-09-27 00:19:02 +02:00

122 lines
5.2 KiB
GDScript

extends SceneTree
class FixtureApi extends JellyfinClient:
var detail_calls: Array[String] = []
var seasons_list := [{"Id": "season-one", "Name": "Season 1", "IndexNumber": 1}, {"Id": "season-two", "Name": "Season 2", "IndexNumber": 2}]
func request(_path: String, _method: HTTPClient.Method = HTTPClient.METHOD_GET, _body: Dictionary = {}) -> Dictionary:
await get_tree().process_frame
return {"ok": true, "data": {"Items": [], "TotalRecordCount": 0}}
func details(id: String) -> Dictionary:
detail_calls.append(id)
await get_tree().process_frame
return {"ok": true, "data": {"Id": id, "Name": "Selected series" if id == "show" else "Selected movie", "Type": "Series" if id == "show" else "Movie"}}
func seasons(_series_id: String) -> Dictionary:
await get_tree().process_frame
return {"ok": true, "data": {"Items": seasons_list}}
func episodes(_series_id: String, season_id: String, _offset: int = 0) -> Dictionary:
await get_tree().process_frame
return {"ok": true, "data": {"Items": [{"Id": season_id + "-episode", "Name": "Selected episode", "IndexNumber": 1}], "TotalRecordCount": 1}}
class FixtureVideo extends VideoBackend:
var stops := 0
func available() -> bool: return true
func stop() -> void: stops += 1
func toggle_pause() -> void: paused = not paused
class FixtureSession extends PlaybackSession:
var opens := 0
func report(_event: String, _paused: bool = false) -> void: pass
func open(selected: Dictionary, _transcode: bool = false) -> void:
item = selected
opens += 1
var failures := 0
func _initialize() -> void:
call_deferred("run_checks")
func settle() -> void:
for frame in range(10): await process_frame
func check(condition: bool, message: String) -> void:
if not condition:
failures += 1
push_error(message)
func run_checks() -> void:
var app: Node = load("res://scenes/main.tscn").instantiate()
root.add_child(app)
app.set_process(false)
app.api.queue_free()
app.video.queue_free()
app.session.queue_free()
var api := FixtureApi.new()
var video := FixtureVideo.new()
var session := FixtureSession.new()
app.add_child(api)
app.add_child(video)
app.add_child(session)
app.api = api
app.video = video
app.session = session
await app._details({"Id": "movie"})
var movie_view: DetailsView = app.page.get_child(0)
await app._start(movie_view.item)
app._player_action("library")
await settle()
check(app.page.get_child(0) == movie_view, "The player's Library button returns to the selected movie details")
check(api.detail_calls == ["movie"], "Returning preserves the existing details page without another lookup")
check(not app.player_overlay.visible and app.playing, "Opening the details hides the controls and preserves playback")
video.paused = true
movie_view.play_button.pressed.emit()
await settle()
check(session.opens == 1 and video.stops == 0 and not video.paused, "Returning to the active movie resumes the current decoder without restarting")
check(app.player_overlay.visible, "The movie's play button returns to the playback controls")
await app._details({"Id": "show"})
await settle()
var series_view: DetailsView = app.page.get_child(0)
series_view.series_browser.open_season(api.seasons_list[1])
await settle()
var card: EpisodeCard = series_view.series_browser.episode_list.get_child(0)
card.play_button.pressed.emit()
await settle()
check(session.item.get("SeasonId") == "season-two", "Playback remembers which season the selected episode belongs to")
app._player_action("library")
await settle()
check(app.page.get_child(0) == series_view and series_view.series_browser.episode_section.visible, "Returning to a series keeps its episode list open")
check(series_view.series_browser.selected_season.Id == "season-two", "The selected season survives the trip to the player")
var opens := session.opens
card.play_button.pressed.emit()
await settle()
check(session.opens == opens, "Returning to the active episode does not restart its stream")
app._screen_settings()
app._player_action("library")
await settle()
var restored: DetailsView = app.page.get_child(0)
check(restored.item.Id == "show" and restored.series_browser.selected_season.Id == "season-two", "Replacing the details page still allows a return to the active series and season")
check(restored.series_browser.episode_list.get_child_count() == 1, "Restored series details load the remembered season's episodes")
restored.series_browser.all_seasons_button.pressed.emit()
await settle()
check(restored.series_browser.season_grid.get_child_count() == 2, "All seasons remains available after restoring the remembered season")
session.item = {"Id": "season-one-episode", "Type": "Episode", "SeriesId": "show", "SeasonId": "season-one", "ParentIndexNumber": 1}
await app._return_to_title()
await settle()
check(restored.series_browser.selected_season.Id == "season-one", "Returning after playback changes seasons opens the current episode's season")
restored.back_button.pressed.emit()
check(app.page.get_child(0) is LibraryView, "Back to library still opens the library grid")
await settle()
app.playing = false
app.queue_free()
await process_frame
print("Player return checks: ", "PASS" if failures == 0 else str(failures) + " failures")
quit(1 if failures else 0)