Merge branch 'pr/593' into integration/pr-review-smoke-2026-07-13
This commit is contained in:
2 files changed
+75
-5
No files matched your search
@@ -3909,6 +3909,10 @@ impl Player {
|
|||||||
self.state.begin_play()
|
self.state.begin_play()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A passing check is a snapshot, not a lock: a newer intent can still
|
||||||
|
/// begin between this check and the subsequent AudioCommand send. That
|
||||||
|
/// residual window is accepted; closing it would require the generation
|
||||||
|
/// to travel inside the audio command itself.
|
||||||
fn is_current_play(&self, gen: u64) -> bool {
|
fn is_current_play(&self, gen: u64) -> bool {
|
||||||
self.state.is_current_play(gen)
|
self.state.is_current_play(gen)
|
||||||
}
|
}
|
||||||
@@ -4036,7 +4040,8 @@ impl Player {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create the streaming buffer and start playback immediately.
|
// Create the streaming buffer and start playback immediately.
|
||||||
let buffer_writer = self.play_streaming_dynamic(
|
// Non-bumping variant: this intent already holds `gen`.
|
||||||
|
let buffer_writer = self.apply_play_streaming_dynamic(
|
||||||
track_id,
|
track_id,
|
||||||
sample_rate,
|
sample_rate,
|
||||||
channels,
|
channels,
|
||||||
@@ -4640,6 +4645,7 @@ impl Player {
|
|||||||
/// track. DST-compressed DFF and >2ch files are rejected with a
|
/// track. DST-compressed DFF and >2ch files are rejected with a
|
||||||
/// readable error before anything starts.
|
/// readable error before anything starts.
|
||||||
pub fn play_dsd_file(&self, path: std::path::PathBuf, track_id: u64) -> Result<(), String> {
|
pub fn play_dsd_file(&self, path: std::path::PathBuf, track_id: u64) -> Result<(), String> {
|
||||||
|
let _gen = self.begin_play();
|
||||||
let demux = qbz_dsd::open_dsd(&path).map_err(|e| e.to_string())?;
|
let demux = qbz_dsd::open_dsd(&path).map_err(|e| e.to_string())?;
|
||||||
let dsd_rate = demux.info().dsd_rate;
|
let dsd_rate = demux.info().dsd_rate;
|
||||||
|
|
||||||
@@ -4728,8 +4734,15 @@ impl Player {
|
|||||||
);
|
);
|
||||||
self.state.set_stream_quality(rate, 24);
|
self.state.set_stream_quality(rate, 24);
|
||||||
|
|
||||||
let writer =
|
let writer = self.apply_play_streaming(
|
||||||
self.play_streaming(track_id, rate, channels, content_length, 3, duration_secs, 0)?;
|
track_id,
|
||||||
|
rate,
|
||||||
|
channels,
|
||||||
|
content_length,
|
||||||
|
3,
|
||||||
|
duration_secs,
|
||||||
|
0,
|
||||||
|
)?;
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
if writer
|
if writer
|
||||||
@@ -4816,6 +4829,32 @@ impl Player {
|
|||||||
buffer_seconds: u8,
|
buffer_seconds: u8,
|
||||||
duration_secs: u64,
|
duration_secs: u64,
|
||||||
start_position_secs: u64,
|
start_position_secs: u64,
|
||||||
|
) -> Result<BufferWriter, String> {
|
||||||
|
let _gen = self.begin_play();
|
||||||
|
self.apply_play_streaming(
|
||||||
|
track_id,
|
||||||
|
sample_rate,
|
||||||
|
channels,
|
||||||
|
content_length,
|
||||||
|
buffer_seconds,
|
||||||
|
duration_secs,
|
||||||
|
start_position_secs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `play_streaming` without bumping generation (used by play paths that
|
||||||
|
/// already did their own `begin_play` + supersede checks, like
|
||||||
|
/// `play_dsd_file`; a mid-intent bump here would invalidate a strictly
|
||||||
|
/// newer play that started in the meantime).
|
||||||
|
fn apply_play_streaming(
|
||||||
|
&self,
|
||||||
|
track_id: u64,
|
||||||
|
sample_rate: u32,
|
||||||
|
channels: u16,
|
||||||
|
content_length: u64,
|
||||||
|
buffer_seconds: u8,
|
||||||
|
duration_secs: u64,
|
||||||
|
start_position_secs: u64,
|
||||||
) -> Result<BufferWriter, String> {
|
) -> Result<BufferWriter, String> {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Player: Starting streaming playback for track {} ({}Hz, {}ch, {} bytes total, {}s, start={}s)",
|
"Player: Starting streaming playback for track {} ({}Hz, {}ch, {} bytes total, {}s, start={}s)",
|
||||||
@@ -4865,6 +4904,34 @@ impl Player {
|
|||||||
speed_mbps: f64,
|
speed_mbps: f64,
|
||||||
duration_secs: u64,
|
duration_secs: u64,
|
||||||
start_position_secs: u64,
|
start_position_secs: u64,
|
||||||
|
) -> Result<BufferWriter, String> {
|
||||||
|
let _gen = self.begin_play();
|
||||||
|
self.apply_play_streaming_dynamic(
|
||||||
|
track_id,
|
||||||
|
sample_rate,
|
||||||
|
channels,
|
||||||
|
bit_depth,
|
||||||
|
content_length,
|
||||||
|
speed_mbps,
|
||||||
|
duration_secs,
|
||||||
|
start_position_secs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `play_streaming_dynamic` without bumping generation (used by
|
||||||
|
/// `play_track`'s CMAF path, which already holds a generation token; a
|
||||||
|
/// mid-intent bump here would invalidate a strictly newer play that
|
||||||
|
/// started in the meantime).
|
||||||
|
fn apply_play_streaming_dynamic(
|
||||||
|
&self,
|
||||||
|
track_id: u64,
|
||||||
|
sample_rate: u32,
|
||||||
|
channels: u16,
|
||||||
|
bit_depth: u32,
|
||||||
|
content_length: u64,
|
||||||
|
speed_mbps: f64,
|
||||||
|
duration_secs: u64,
|
||||||
|
start_position_secs: u64,
|
||||||
) -> Result<BufferWriter, String> {
|
) -> Result<BufferWriter, String> {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Player: Starting dynamic streaming for track {} ({}Hz, {}ch, {}-bit, {:.2} MB, {:.1} MB/s, {}s, start={}s)",
|
"Player: Starting dynamic streaming for track {} ({}Hz, {}ch, {}-bit, {:.2} MB, {:.1} MB/s, {}s, start={}s)",
|
||||||
@@ -4961,6 +5028,9 @@ impl Player {
|
|||||||
|
|
||||||
/// Stop playback
|
/// Stop playback
|
||||||
pub fn stop(&self) -> Result<(), String> {
|
pub fn stop(&self) -> Result<(), String> {
|
||||||
|
// Supersede any in-flight play_track so a slow CMAF/legacy fetch cannot
|
||||||
|
// restart audio after the user stopped.
|
||||||
|
let _ = self.begin_play();
|
||||||
self.tx
|
self.tx
|
||||||
.send(AudioCommand::Stop)
|
.send(AudioCommand::Stop)
|
||||||
.map_err(|e| format!("Failed to send stop command: {}", e))
|
.map_err(|e| format!("Failed to send stop command: {}", e))
|
||||||
|
|||||||
@@ -614,7 +614,7 @@ async fn play_audible(runtime: &Runtime, weak: &slint::Weak<AppWindow>, track_id
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("[qbz-slint] playback: play_track {track_id} failed: {e}");
|
log::error!("[qbz-slint] playback: play_track {track_id} failed: {e}");
|
||||||
// Superseded fetch: the user already started another play while this
|
// Superseded fetch: the user already started another play while this
|
||||||
// one was resolving — that newer play owns the cursor; do NOT skip.
|
// one was resolving. That newer play owns the cursor; do NOT skip.
|
||||||
let still_current = PENDING_PLAY_ID.load(std::sync::atomic::Ordering::Relaxed)
|
let still_current = PENDING_PLAY_ID.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
== track_id;
|
== track_id;
|
||||||
// The fetch failed: no audio will advance, so the poll loop would never
|
// The fetch failed: no audio will advance, so the poll loop would never
|
||||||
@@ -623,7 +623,7 @@ async fn play_audible(runtime: &Runtime, weak: &slint::Weak<AppWindow>, track_id
|
|||||||
// Tauri-parity regression fix: an unavailable track used to be
|
// Tauri-parity regression fix: an unavailable track used to be
|
||||||
// auto-skipped by the frontend (playbackService `autoSkipToNext`,
|
// auto-skipped by the frontend (playbackService `autoSkipToNext`,
|
||||||
// bounded, issue #467). Without this the queue cursor parks on the
|
// bounded, issue #467). Without this the queue cursor parks on the
|
||||||
// dead track and playback stops. Terminal errors only — transient
|
// dead track and playback stops. Terminal errors only: transient
|
||||||
// failures were already retried by the client and should not skip.
|
// failures were already retried by the client and should not skip.
|
||||||
if still_current && is_terminal_unavailable(&e) {
|
if still_current && is_terminal_unavailable(&e) {
|
||||||
auto_skip_unavailable(runtime, weak, track_id).await;
|
auto_skip_unavailable(runtime, weak, track_id).await;
|
||||||
|
|||||||
Reference in new issue
Block a user