fix(player): a stale session answer can no longer stop the playback that won
- The engine tracks the request out per item: A, then B, then back to A sent a second request for A beside the one still out. - A hardware transcode superseded during its fallback answered with the NEWER session; the losing request's player then stopped it as stale. It now answers with its own (dead) session. - A replaced hls.js instance's late manifest no longer marks the player ready or plays the outgoing media. - A pause given after the answer stands: the manifest and the first canplay read the live intent. - The debounced A/V-offset restart is dropped on an item change instead of restarting the previous item under the next one's title.
This commit is contained in:
@@ -840,12 +840,13 @@ def start_session(
|
||||
)
|
||||
respawned = _spawn(False, False, replace_only=s)
|
||||
if respawned is None:
|
||||
# Superseded/culled mid-fallback — yield whoever is registered now (a newer seek-restart), or
|
||||
# our own now-dead session if the key was cap-culled. Never None here: the POST /session route
|
||||
# turns a None return into a "Media file not found on disk" 404 — dishonest for a file that
|
||||
# exists. A (dead) session instead degrades to a retryable 404 ("No active playback session" /
|
||||
# "Not available yet") the client re-requests.
|
||||
return current_session(user_id, key) or s
|
||||
# Superseded/culled mid-fallback — answer with our own now-dead session. Never the newer one
|
||||
# registered meanwhile: this answer goes to the request that LOST, whose player drops it as
|
||||
# stale and stops the session it names (`stop_session`), which stopped the playback that won.
|
||||
# Never None either: the POST /session route turns a None return into a "Media file not found
|
||||
# on disk" 404 — dishonest for a file that exists. A dead session instead degrades to a
|
||||
# retryable 404 ("No active playback session" / "Not available yet") the client re-requests.
|
||||
return s
|
||||
s = respawned
|
||||
ready = wait_for(_seg(0), timeout=25.0, proc=s.proc)
|
||||
if ready:
|
||||
|
||||
@@ -695,7 +695,9 @@ class TestTranscodeStartSession:
|
||||
# F2/race: start_session waits on seg_0 OUTSIDE _lock. If a concurrent same-key start (a rapid
|
||||
# seek-restart) or _enforce_cap replaces _sessions[key] while we wait, our proc dying must NOT
|
||||
# trigger the SW fallback — that would tear down the NEWER session and breach the transcode cap.
|
||||
# The identity guard must detect it's been superseded and yield the currently-registered session.
|
||||
# The identity guard must detect it's been superseded. It answers with its OWN (dead) session,
|
||||
# never the newer one: the answer goes to the request that lost, whose player drops it as stale
|
||||
# and stops the session it names — naming the newer one stopped the playback that won.
|
||||
monkeypatch.setattr(
|
||||
stream, "_select_encode", lambda db, item, transcode, src: (True, False)
|
||||
)
|
||||
@@ -708,9 +710,12 @@ class TestTranscodeStartSession:
|
||||
|
||||
newer = _Newer()
|
||||
|
||||
captured = {}
|
||||
|
||||
def wait_then_supersede(path, timeout=20.0, proc=None):
|
||||
# Simulate the concurrent replacement happening during our out-of-lock wait, then report the
|
||||
# (now-killed) HW proc produced nothing.
|
||||
captured["s"] = stream._sessions[(7, "12345")]
|
||||
stream._sessions[(7, "12345")] = newer
|
||||
return False
|
||||
|
||||
@@ -720,7 +725,7 @@ class TestTranscodeStartSession:
|
||||
)
|
||||
# No SW respawn — only the initial HW _spawn ran; the newer session was left intact.
|
||||
assert len(_CapturingProc.calls) == 1
|
||||
assert s is newer # yielded the currently-registered session, did not clobber it
|
||||
assert s is captured["s"] # its own session, not the newer one
|
||||
assert stream._sessions[(7, "12345")] is newer
|
||||
|
||||
def test_a_cap_culled_hw_session_does_not_respawn_and_is_not_reported_missing(
|
||||
|
||||
Reference in New Issue
Block a user