feat(player): seek strategy follows what the engine says it can do
DR-246. The strategy used to turn on `is_hls` and `use_html5`, decided in a command handler on behalf of engines it does not own. That is how "who renders" came to mean "how do I seek", and why a transcoded seek silently did nothing the moment native video changed the renderer (DR-238). Engines now declare `Capabilities::seeks_transcoded_in_place` — true for hls.js, which seeks within the VOD playlist it was handed and lets the server catch up; false for mpv, whose HLS demuxer cannot make the server transcode from a new offset. The command asks whichever engine is rendering. Adding an engine no longer means editing a shared truth table. The item's transport is not read at the seek site any more; the compiler flagged it unused, which is the URL-shape input finally disappearing. A deviation from the spec, recorded deliberately: it called for the engine to own the decision outright. It cannot. Re-negotiating a stream needs the repository, which sits above the engine, so the engine states the ability and the caller acts on it. That still removes the defect — nobody guesses on another component's behalf — without pretending an engine can reach upward. Also fixes a latent race in the conformance suite, found by running it: the seek case asserted immediately, which passes on an engine that records the target when it accepts a seek and races on one that waits for the decoder to move. `Harness::await_seek` polls instead, the way the Android suite already did. It failed with machine load rather than with the code, which is the kind of test that teaches people to re-run until green. MpvPlayer 9/9 LegacyPlayer 8/9 - still only the mute/rate gap in the old trait 789 tests, clippy -D warnings clean with and without the feature.
This commit is contained in:
@@ -1450,7 +1450,7 @@ pub async fn player_seek_video(
|
||||
|
||||
// Get current playing item to analyze stream characteristics
|
||||
// Clone what we need to avoid holding locks across await points
|
||||
let (needs_transcoding, jellyfin_item_id, is_local, transport) = {
|
||||
let (needs_transcoding, jellyfin_item_id, is_local) = {
|
||||
let controller = player.0.lock().await;
|
||||
let queue_arc = controller.queue();
|
||||
let queue = queue_arc.lock().map_err(|e| e.to_string())?;
|
||||
@@ -1466,31 +1466,34 @@ pub async fn player_seek_video(
|
||||
.ok_or("Current video has no Jellyfin ID")?
|
||||
.to_string();
|
||||
|
||||
// The URL itself is no longer read here: the seek strategy now comes
|
||||
// from the item's own `transport`, not from inspecting the string.
|
||||
// Neither the URL nor the item's transport is read here any more. The
|
||||
// strategy turns on whether the *engine* can seek a transcode in place,
|
||||
// which it declares for itself — so the container the stream happens to
|
||||
// arrive in stopped being a proxy for anything (DR-246).
|
||||
let is_local_file = matches!(current_item.source, MediaSource::Local { .. });
|
||||
|
||||
let needs_trans = current_item.needs_transcoding;
|
||||
let transport = current_item.transport;
|
||||
(needs_trans, jellyfin_id, is_local_file, transport)
|
||||
(current_item.needs_transcoding, jellyfin_id, is_local_file)
|
||||
}; // Locks are dropped here
|
||||
|
||||
// The transport comes from the backend's own decision, not from searching
|
||||
// the URL for `.m3u8` — Rust built that URL and knows what it is. Items
|
||||
// queued without one fall back to `needs_transcoding`, which is exact:
|
||||
// every transcode this app requests is HLS (DR-140).
|
||||
//
|
||||
// TRACES: UR-004, UR-079 | DR-225, DR-230
|
||||
let is_hls = match transport {
|
||||
Some(crate::repository::Transport::Hls) => true,
|
||||
Some(crate::repository::Transport::Progressive)
|
||||
| Some(crate::repository::Transport::LocalFile) => false,
|
||||
None => needs_transcoding,
|
||||
// Whether a transcode can be seeked in place is asked of the engine that is
|
||||
// rendering, not guessed from the URL's shape or from who is rendering.
|
||||
// TRACES: UR-040, UR-079 | DR-238, DR-246
|
||||
let seeks_transcoded_in_place = {
|
||||
let controller = player.0.lock().await;
|
||||
controller.capabilities().seeks_transcoded_in_place
|
||||
};
|
||||
let strategy = determine_video_seek_strategy(is_local, is_hls, needs_transcoding, use_html5);
|
||||
let strategy = determine_video_seek_strategy(
|
||||
is_local,
|
||||
seeks_transcoded_in_place,
|
||||
needs_transcoding,
|
||||
use_html5,
|
||||
);
|
||||
|
||||
info!("[player_seek_video] Stream analysis: is_local={}, is_hls={}, needs_transcoding={}, use_html5={}, strategy={:?}",
|
||||
is_local, is_hls, needs_transcoding, use_html5, strategy);
|
||||
info!(
|
||||
"[player_seek_video] Stream analysis: is_local={}, seeks_transcoded_in_place={}, \
|
||||
needs_transcoding={}, use_html5={}, strategy={:?}",
|
||||
is_local, seeks_transcoded_in_place, needs_transcoding, use_html5, strategy
|
||||
);
|
||||
|
||||
match strategy {
|
||||
VideoSeekStrategy::LocalNativeSeek | VideoSeekStrategy::BackendNativeSeek => {
|
||||
|
||||
Reference in New Issue
Block a user