fix(player): preserve audible state on source failures

Preflight fallible queue transitions and publish now-playing state only after the requested source is audible, leaving the prior stream intact when a file or server is unavailable.

Carry source-owned request headers through remote probes, streaming and fallback downloads without exposing sensitive values.

cx
This commit is contained in:
vicrodh committed 2026-08-25 18:26:35 -06:00
1 parent d244213a64
commit ecae778d58
7 files changed
+528 -152

No files matched your search

+26
View File
@@ -73,6 +73,14 @@ pub enum PlaybackTicket {
/// Log prefix the feeder stamps its lines with (`"PLEX"`). Static
/// because it names the SOURCE, never the item.
log_tag: &'static str,
/// Source-owned HTTP request headers required by the media endpoint.
///
/// These are transport facts, not audio policy. Plex, for example,
/// accepts the token in the URL but can reject a part request without
/// its `X-Plex-*` client identity. Carrying the headers on the ticket
/// keeps the shared player transport source-agnostic while preserving
/// the request contract the source used to fetch the same bytes.
request_headers: Vec<(String, String)>,
},
/// Let the core resolve + stream it: `core().play_track_resolved(track_id, …)`.
Catalog { track_id: u64 },
@@ -142,6 +150,7 @@ impl std::fmt::Debug for PlaybackTicket {
duration_secs,
start_secs,
log_tag,
request_headers,
} => f
.debug_struct("Stream")
.field("url", &redact_url(url))
@@ -149,6 +158,15 @@ impl std::fmt::Debug for PlaybackTicket {
.field("duration_secs", duration_secs)
.field("start_secs", start_secs)
.field("log_tag", log_tag)
// Header VALUES may become sensitive for a future source.
// Names are enough to diagnose a missing request profile.
.field(
"request_headers",
&request_headers
.iter()
.map(|(name, _)| name.as_str())
.collect::<Vec<_>>(),
)
.finish(),
PlaybackTicket::Catalog { track_id } => f
.debug_struct("Catalog")
@@ -215,9 +233,15 @@ mod tests {
duration_secs: 188,
start_secs: 0,
log_tag: "PLEX",
request_headers: vec![("X-Plex-Product".into(), "secret-value".into())],
};
let s = format!("{t:?}");
assert!(!s.contains("sekrit"), "the token leaked into Debug: {s}");
assert!(s.contains("X-Plex-Product"), "{s}");
assert!(
!s.contains("secret-value"),
"header value leaked into Debug: {s}"
);
assert!(s.contains("/library/parts/1/f.flac?<redacted>"), "{s}");
// The origin SURVIVES: telling a LAN address from a plex.tv relay is
// the question those perf log lines exist to answer.
@@ -232,6 +256,7 @@ mod tests {
duration_secs: 0,
start_secs: 0,
log_tag: "PLEX",
request_headers: Vec::new(),
};
assert!(format!("{t:?}").contains("http://nas.local/music/a.flac"));
}
@@ -244,6 +269,7 @@ mod tests {
duration_secs: 0,
start_secs: 0,
log_tag: "PLEX",
request_headers: Vec::new(),
};
assert_eq!(t.play_id(), Some(42));
// `Catalog` is the one variant with no player-side id — the core mints