Fix linux playback
This commit is contained in:
parent
975936b902
commit
87762c03b6
@ -193,9 +193,16 @@ impl OnlineRepository {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a video stream URL for playback with optional seeking support.
|
/// Get a video stream URL for playback at an arbitrary position (resume,
|
||||||
/// For direct streams, uses /Videos/{id}/stream.mp4 with transcoding to support seeking.
|
/// transcoded seeking, audio-track switching).
|
||||||
/// For transcoded streams (HEVC/10-bit), includes StartTimeTicks parameter.
|
///
|
||||||
|
/// 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(
|
pub async fn get_video_stream_url(
|
||||||
&self,
|
&self,
|
||||||
item_id: &str,
|
item_id: &str,
|
||||||
@ -210,12 +217,11 @@ impl OnlineRepository {
|
|||||||
// Use provided audio stream index, or default to 0
|
// Use provided audio stream index, or default to 0
|
||||||
let audio_index = audio_stream_index.unwrap_or(0).to_string();
|
let audio_index = audio_stream_index.unwrap_or(0).to_string();
|
||||||
|
|
||||||
// Build URL with progressive MP4 transcoding for seeking support
|
// Build an HLS transcode URL. VideoCodec lists h264 first so the server
|
||||||
// This works for both direct streams and HEVC content
|
// transcodes HEVC/10-bit/unsupported sources to h264 the WebView can decode.
|
||||||
let mut params = vec![
|
let mut params = vec![
|
||||||
("api_key", self.access_token.clone()),
|
("api_key", self.access_token.clone()),
|
||||||
("DeviceId", "jellytau-tauri".to_string()),
|
("DeviceId", "jellytau-tauri".to_string()),
|
||||||
("Container", "mp4".to_string()),
|
|
||||||
("VideoCodec", "h264".to_string()),
|
("VideoCodec", "h264".to_string()),
|
||||||
("AudioCodec", "aac".to_string()),
|
("AudioCodec", "aac".to_string()),
|
||||||
("AudioStreamIndex", audio_index),
|
("AudioStreamIndex", audio_index),
|
||||||
@ -223,6 +229,9 @@ impl OnlineRepository {
|
|||||||
("VideoBitrate", "18000000".to_string()),
|
("VideoBitrate", "18000000".to_string()),
|
||||||
("AudioBitrate", "384000".to_string()),
|
("AudioBitrate", "384000".to_string()),
|
||||||
("TranscodingMaxAudioChannels", "2".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 {
|
if let Some(source_id) = media_source_id {
|
||||||
@ -241,7 +250,7 @@ impl OnlineRepository {
|
|||||||
.join("&");
|
.join("&");
|
||||||
|
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/Videos/{}/stream.mp4?{}",
|
"{}/Videos/{}/master.m3u8?{}",
|
||||||
self.server_url, item_id, query
|
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]
|
#[tokio::test]
|
||||||
async fn test_get_audio_stream_url_with_special_characters() {
|
async fn test_get_audio_stream_url_with_special_characters() {
|
||||||
let repo = create_test_repository();
|
let repo = create_test_repository();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user