Fix linux playback
Some checks failed
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m38s
Traceability Validation / Check Requirement Traces (push) Successful in 22s
🏗️ Build and Test JellyTau / Build Android APK (push) Has been cancelled

This commit is contained in:
Duncan Tourolle 2026-06-21 10:16:25 +02:00
parent 975936b902
commit 87762c03b6

View File

@ -193,9 +193,16 @@ impl OnlineRepository {
})
}
/// Get a video stream URL for playback with optional seeking support.
/// For direct streams, uses /Videos/{id}/stream.mp4 with transcoding to support seeking.
/// For transcoded streams (HEVC/10-bit), includes StartTimeTicks parameter.
/// Get a video stream URL for playback at an arbitrary position (resume,
/// transcoded seeking, audio-track switching).
///
/// Returns an HLS master playlist (`/Videos/{id}/master.m3u8`) transcoded to
/// h264/aac. HLS is used rather than a progressive `stream.mp4` because the
/// HTML5 `<video>` element (via HLS.js) starts playing within seconds and can
/// seek within the stream, whereas a progressive MP4 transcode of HEVC source
/// forces the server to transcode the whole file before playback can begin —
/// which manifests as playback never starting. `StartTimeTicks` makes the
/// server begin the transcode at the requested position.
pub async fn get_video_stream_url(
&self,
item_id: &str,
@ -210,12 +217,11 @@ impl OnlineRepository {
// Use provided audio stream index, or default to 0
let audio_index = audio_stream_index.unwrap_or(0).to_string();
// Build URL with progressive MP4 transcoding for seeking support
// This works for both direct streams and HEVC content
// Build an HLS transcode URL. VideoCodec lists h264 first so the server
// transcodes HEVC/10-bit/unsupported sources to h264 the WebView can decode.
let mut params = vec![
("api_key", self.access_token.clone()),
("DeviceId", "jellytau-tauri".to_string()),
("Container", "mp4".to_string()),
("VideoCodec", "h264".to_string()),
("AudioCodec", "aac".to_string()),
("AudioStreamIndex", audio_index),
@ -223,6 +229,9 @@ impl OnlineRepository {
("VideoBitrate", "18000000".to_string()),
("AudioBitrate", "384000".to_string()),
("TranscodingMaxAudioChannels", "2".to_string()),
("SegmentContainer", "ts".to_string()),
("TranscodingContainer", "ts".to_string()),
("TranscodingProtocol", "hls".to_string()),
];
if let Some(source_id) = media_source_id {
@ -241,7 +250,7 @@ impl OnlineRepository {
.join("&");
let url = format!(
"{}/Videos/{}/stream.mp4?{}",
"{}/Videos/{}/master.m3u8?{}",
self.server_url, item_id, query
);
@ -1403,6 +1412,46 @@ mod tests {
);
}
#[tokio::test]
async fn test_get_video_stream_url_returns_hls_with_position() {
// Transcoded video resume/seek must produce an HLS master playlist with
// StartTimeTicks, not a progressive stream.mp4 (which never starts playing
// for HEVC sources). See get_video_stream_url docs.
let repo = create_test_repository();
let url = repo
.get_video_stream_url("vid-1", Some("source-1"), Some(193.0), Some(1))
.await
.unwrap();
assert!(
url.starts_with("https://test.server.com/Videos/vid-1/master.m3u8?"),
"expected HLS master playlist, got: {url}"
);
assert!(url.contains("VideoCodec=h264"));
assert!(url.contains("MediaSourceId=source-1"));
assert!(url.contains("AudioStreamIndex=1"));
// 193.0 seconds * 10_000_000 ticks/sec
assert!(url.contains("StartTimeTicks=1930000000"), "url: {url}");
assert!(!url.contains("stream.mp4"));
}
#[tokio::test]
async fn test_get_video_stream_url_omits_position_when_absent() {
let repo = create_test_repository();
let url = repo
.get_video_stream_url("vid-1", None, None, None)
.await
.unwrap();
assert!(url.starts_with("https://test.server.com/Videos/vid-1/master.m3u8?"));
assert!(!url.contains("StartTimeTicks"));
assert!(!url.contains("MediaSourceId"));
// Defaults to first audio stream
assert!(url.contains("AudioStreamIndex=0"));
}
#[tokio::test]
async fn test_get_audio_stream_url_with_special_characters() {
let repo = create_test_repository();