Add mobile chunked streaming with segmented playback fallback
This commit is contained in:
@@ -6,9 +6,9 @@ use tokio::sync::mpsc;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::api::QobuzApi;
|
||||
use crate::api::{QobuzApi, TrackStream};
|
||||
use crate::config::Config;
|
||||
use crate::player::{AudioPlayer, PlayerCommand, PlayerState};
|
||||
use crate::player::{AudioPlayer, PlayerCommand, PlayerState, StreamSource};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Protobuf helpers (hand-rolled, matching the qconnect.proto schema)
|
||||
@@ -503,8 +503,25 @@ fn parse_incoming_commands(data: &[u8]) -> Vec<QConnectCommand> {
|
||||
get_varint_field(&qvf, 1).unwrap_or(0) as u32
|
||||
})
|
||||
.unwrap_or(0);
|
||||
let current_track = get_bytes_field(&fields, 4).map(parse_queue_track);
|
||||
let next_track = get_bytes_field(&fields, 5).map(parse_queue_track);
|
||||
let current_track = get_bytes_field(&fields, 4)
|
||||
.map(parse_queue_track)
|
||||
.and_then(|t| {
|
||||
if t.track_id <= 0 || t.queue_item_id < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(t)
|
||||
}
|
||||
});
|
||||
let next_track =
|
||||
get_bytes_field(&fields, 5)
|
||||
.map(parse_queue_track)
|
||||
.and_then(|t| {
|
||||
if t.track_id <= 0 || t.queue_item_id < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(t)
|
||||
}
|
||||
});
|
||||
|
||||
info!("[RECV] SET_STATE: playing_state={:?}, position_ms={:?}, current_track={:?}, next_track={:?}, queue_ver={}",
|
||||
playing_state, position_ms, current_track, next_track, queue_version_major);
|
||||
@@ -1026,11 +1043,31 @@ async fn run_connection(
|
||||
Err(e) => { warn!("get_track failed: {}", e); 0 }
|
||||
};
|
||||
current_duration_ms = duration_ms;
|
||||
match api.get_track_url(auth_token, &track_id_str, format_id).await {
|
||||
Ok(url) => {
|
||||
info!("[STATE] Got URL, playing (duration={}ms)", duration_ms);
|
||||
match api.get_track_stream(auth_token, &track_id_str, format_id).await {
|
||||
Ok(stream) => {
|
||||
let player_stream = match stream {
|
||||
TrackStream::DirectUrl { url } => {
|
||||
info!("[STATE] Got direct stream URL (duration={}ms)", duration_ms);
|
||||
StreamSource::DirectUrl(url)
|
||||
}
|
||||
TrackStream::Segmented {
|
||||
url_template,
|
||||
n_segments,
|
||||
encryption_key_hex,
|
||||
} => {
|
||||
info!(
|
||||
"[STATE] Got segmented stream (segments={}, duration={}ms)",
|
||||
n_segments, duration_ms
|
||||
);
|
||||
StreamSource::Segmented {
|
||||
url_template,
|
||||
n_segments,
|
||||
encryption_key_hex,
|
||||
}
|
||||
}
|
||||
};
|
||||
player.send(PlayerCommand::Play {
|
||||
url,
|
||||
stream: player_stream,
|
||||
track_id: track.track_id,
|
||||
queue_item_id: track.queue_item_id,
|
||||
duration_ms,
|
||||
@@ -1216,10 +1253,22 @@ async fn run_connection(
|
||||
Err(e) => { warn!("get_track failed: {}", e); current_duration_ms }
|
||||
};
|
||||
current_duration_ms = duration_ms;
|
||||
match api.get_track_url(auth_token, &track_id_str, format_id).await {
|
||||
Ok(url) => {
|
||||
match api.get_track_stream(auth_token, &track_id_str, format_id).await {
|
||||
Ok(stream) => {
|
||||
let player_stream = match stream {
|
||||
TrackStream::DirectUrl { url } => StreamSource::DirectUrl(url),
|
||||
TrackStream::Segmented {
|
||||
url_template,
|
||||
n_segments,
|
||||
encryption_key_hex,
|
||||
} => StreamSource::Segmented {
|
||||
url_template,
|
||||
n_segments,
|
||||
encryption_key_hex,
|
||||
},
|
||||
};
|
||||
player.send(PlayerCommand::Play {
|
||||
url,
|
||||
stream: player_stream,
|
||||
track_id: current_track_id,
|
||||
queue_item_id: current_queue_item_id,
|
||||
duration_ms,
|
||||
|
||||
Reference in New Issue
Block a user