debug du lecteur web
This commit is contained in:
@@ -194,6 +194,9 @@
|
||||
@loadedmetadata="handleLoadedMetadata"
|
||||
@durationchange="handleDurationChange"
|
||||
@canplay="handleCanPlay"
|
||||
@waiting="handleAudioWaiting"
|
||||
@stalled="handleAudioStalled"
|
||||
@progress="handleAudioProgress"
|
||||
></audio>
|
||||
</div>
|
||||
</div>
|
||||
@@ -251,12 +254,15 @@ const audioError = ref<string | null>(null)
|
||||
const audioPlayer = ref<HTMLAudioElement | null>(null)
|
||||
const currentTime = ref(0)
|
||||
const duration = ref(0)
|
||||
const lastTimeUpdateTs = ref<number | null>(null)
|
||||
const lastLoggedTimeupdate = ref<number>(0)
|
||||
const lastReadyState = ref<number | null>(null)
|
||||
const lastNetworkState = ref<number | null>(null)
|
||||
|
||||
// Retry logic for fragile browser decoders
|
||||
const MAX_AUDIO_RETRIES = 3
|
||||
const RETRY_DELAY_MS = 800 // Réduit pour des transitions plus rapides
|
||||
const retryCount = ref(0)
|
||||
// Retry timer cleanup (auto-reload désactivé pour les streams live)
|
||||
let retryTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let stallMonitor: ReturnType<typeof setInterval> | null = null
|
||||
let stateMonitor: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
// Metadata refresh via SSE
|
||||
let metadataEventSource: EventSource | null = null
|
||||
@@ -264,12 +270,69 @@ let metadataEventSource: EventSource | null = null
|
||||
// Load sources on mount
|
||||
onMounted(async () => {
|
||||
await loadSources()
|
||||
// Monitor pour détecter une éventuelle stagnation du currentTime
|
||||
stallMonitor = setInterval(() => {
|
||||
const p = audioPlayer.value
|
||||
if (!p || !isPlaying.value) return
|
||||
if (!lastTimeUpdateTs.value) return
|
||||
const now = Date.now()
|
||||
// Si aucun timeupdate depuis >5s et lecture en cours, logguer l'état du buffer
|
||||
if (now - lastTimeUpdateTs.value > 5000) {
|
||||
const buffered: string[] = []
|
||||
for (let i = 0; i < p.buffered.length; i++) {
|
||||
buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
|
||||
}
|
||||
console.debug('[PlayerDebug] stall-detected', {
|
||||
ts: now,
|
||||
currentTime: p.currentTime.toFixed(3),
|
||||
duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
|
||||
readyState: p.readyState,
|
||||
networkState: p.networkState,
|
||||
paused: p.paused,
|
||||
buffered,
|
||||
})
|
||||
// Eviter le spam : reset le marqueur pour n'émettre qu'une fois par stagnation
|
||||
lastTimeUpdateTs.value = null
|
||||
}
|
||||
}, 2000)
|
||||
|
||||
// Monitor périodique des changements de readyState/networkState
|
||||
stateMonitor = setInterval(() => {
|
||||
const p = audioPlayer.value
|
||||
if (!p) return
|
||||
const rs = p.readyState
|
||||
const ns = p.networkState
|
||||
if (rs !== lastReadyState.value || ns !== lastNetworkState.value) {
|
||||
const buffered: string[] = []
|
||||
for (let i = 0; i < p.buffered.length; i++) {
|
||||
buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
|
||||
}
|
||||
console.debug('[PlayerDebug] state-change', {
|
||||
ts: Date.now(),
|
||||
currentTime: p.currentTime.toFixed(3),
|
||||
readyState: rs,
|
||||
networkState: ns,
|
||||
paused: p.paused,
|
||||
buffered,
|
||||
})
|
||||
lastReadyState.value = rs
|
||||
lastNetworkState.value = ns
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
// Cleanup on unmount
|
||||
onUnmounted(() => {
|
||||
stopMetadataRefresh()
|
||||
clearRetryTimer()
|
||||
if (stallMonitor) {
|
||||
clearInterval(stallMonitor)
|
||||
stallMonitor = null
|
||||
}
|
||||
if (stateMonitor) {
|
||||
clearInterval(stateMonitor)
|
||||
stateMonitor = null
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -516,6 +579,18 @@ function handleAudioEnded() {
|
||||
currentTime.value = 0
|
||||
stopMetadataRefresh()
|
||||
// On peut implémenter ici une logique de lecture automatique du prochain morceau
|
||||
const p = audioPlayer.value
|
||||
if (p) {
|
||||
console.debug('[PlayerDebug] ended', {
|
||||
ts: Date.now(),
|
||||
currentTime: p.currentTime.toFixed(3),
|
||||
duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
|
||||
readyState: p.readyState,
|
||||
networkState: p.networkState,
|
||||
})
|
||||
} else {
|
||||
console.debug('[PlayerDebug] ended (no player)')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -530,16 +605,46 @@ function handleAudioError() {
|
||||
? `Erreur de lecture audio (code ${errorCode})`
|
||||
: 'Erreur de lecture audio inconnue'
|
||||
|
||||
if (selectedSource.value && currentTrack.value && retryCount.value < MAX_AUDIO_RETRIES) {
|
||||
isPlaying.value = false
|
||||
retryCount.value += 1
|
||||
audioError.value = `${baseMessage} - tentative de reprise (${retryCount.value}/${MAX_AUDIO_RETRIES})`
|
||||
scheduleRetryPlayback()
|
||||
return
|
||||
}
|
||||
|
||||
// Pour les streams live (Radio Paradise), ne pas recharger le flux : cela remet la progression à 0
|
||||
// et crée un lag perceptible. On se contente d'afficher l'erreur et on tente un play() direct si possible.
|
||||
audioError.value = baseMessage
|
||||
isPlaying.value = false
|
||||
|
||||
// Tenter une reprise douce sans reload si le player a déjà des données bufferisées
|
||||
const player = audioPlayer.value
|
||||
if (player && !player.paused) {
|
||||
// Rien à faire, il lit encore ou se relancera tout seul
|
||||
return
|
||||
}
|
||||
if (player && player.readyState >= 2) {
|
||||
console.debug('[PlayerDebug] error', {
|
||||
ts: Date.now(),
|
||||
message: baseMessage,
|
||||
code: audio?.error?.code,
|
||||
currentTime: player.currentTime.toFixed(3),
|
||||
duration: isFinite(player.duration) ? player.duration.toFixed(3) : 'NaN',
|
||||
readyState: player.readyState,
|
||||
networkState: player.networkState,
|
||||
buffered: (() => {
|
||||
const arr: string[] = []
|
||||
for (let i = 0; i < player.buffered.length; i++) {
|
||||
arr.push(`${player.buffered.start(i).toFixed(3)}-${player.buffered.end(i).toFixed(3)}`)
|
||||
}
|
||||
return arr
|
||||
})(),
|
||||
})
|
||||
void player.play().catch(() => {
|
||||
// Si play échoue (user gesture requis ou decode error), on laisse l'erreur affichée
|
||||
})
|
||||
} else {
|
||||
console.debug('[PlayerDebug] error (no retry)', {
|
||||
ts: Date.now(),
|
||||
message: baseMessage,
|
||||
code: audio?.error?.code,
|
||||
readyState: player?.readyState,
|
||||
networkState: player?.networkState,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -557,6 +662,21 @@ function handleAudioPlay() {
|
||||
function handleAudioPause() {
|
||||
isPlaying.value = false
|
||||
// Don't stop refresh on pause, user might resume
|
||||
const p = audioPlayer.value
|
||||
if (p) {
|
||||
const buffered: string[] = []
|
||||
for (let i = 0; i < p.buffered.length; i++) {
|
||||
buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
|
||||
}
|
||||
console.debug('[PlayerDebug] pause', {
|
||||
ts: Date.now(),
|
||||
currentTime: p.currentTime.toFixed(3),
|
||||
readyState: p.readyState,
|
||||
networkState: p.networkState,
|
||||
paused: p.paused,
|
||||
buffered,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,6 +685,45 @@ function handleAudioPause() {
|
||||
function handleTimeUpdate() {
|
||||
if (audioPlayer.value) {
|
||||
currentTime.value = audioPlayer.value.currentTime
|
||||
lastTimeUpdateTs.value = Date.now()
|
||||
|
||||
// Log périodiquement l'état du buffer pour diagnostiquer les coupures
|
||||
const logEvery = 2 // secondes (plus serré pour capturer les transitions)
|
||||
if (Math.floor(currentTime.value) % logEvery === 0 && Math.floor(currentTime.value) !== lastLoggedTimeupdate.value) {
|
||||
const p = audioPlayer.value
|
||||
const buffered: string[] = []
|
||||
for (let i = 0; i < p.buffered.length; i++) {
|
||||
buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
|
||||
}
|
||||
console.debug('[PlayerDebug] timeupdate', {
|
||||
ts: Date.now(),
|
||||
currentTime: p.currentTime.toFixed(3),
|
||||
duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
|
||||
readyState: p.readyState,
|
||||
networkState: p.networkState,
|
||||
paused: p.paused,
|
||||
buffered,
|
||||
})
|
||||
lastLoggedTimeupdate.value = Math.floor(currentTime.value)
|
||||
}
|
||||
|
||||
// Si le temps recule (changement de piste) logguer explicitement l'état
|
||||
if (currentTime.value < lastLoggedTimeupdate.value) {
|
||||
const p = audioPlayer.value
|
||||
const buffered: string[] = []
|
||||
for (let i = 0; i < p.buffered.length; i++) {
|
||||
buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
|
||||
}
|
||||
console.debug('[PlayerDebug] time-jump', {
|
||||
ts: Date.now(),
|
||||
currentTime: p.currentTime.toFixed(3),
|
||||
duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
|
||||
readyState: p.readyState,
|
||||
networkState: p.networkState,
|
||||
paused: p.paused,
|
||||
buffered,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,6 +752,60 @@ function handleCanPlay() {
|
||||
console.log('Audio ready to play (canplay event)')
|
||||
}
|
||||
|
||||
function handleAudioWaiting() {
|
||||
const p = audioPlayer.value
|
||||
if (!p) return
|
||||
const buffered: string[] = []
|
||||
for (let i = 0; i < p.buffered.length; i++) {
|
||||
buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
|
||||
}
|
||||
console.debug('[PlayerDebug] waiting', {
|
||||
ts: Date.now(),
|
||||
currentTime: p.currentTime.toFixed(3),
|
||||
duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
|
||||
readyState: p.readyState,
|
||||
networkState: p.networkState,
|
||||
paused: p.paused,
|
||||
buffered,
|
||||
})
|
||||
}
|
||||
|
||||
function handleAudioStalled() {
|
||||
const p = audioPlayer.value
|
||||
if (!p) return
|
||||
const buffered: string[] = []
|
||||
for (let i = 0; i < p.buffered.length; i++) {
|
||||
buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
|
||||
}
|
||||
console.debug('[PlayerDebug] stalled', {
|
||||
ts: Date.now(),
|
||||
currentTime: p.currentTime.toFixed(3),
|
||||
duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
|
||||
readyState: p.readyState,
|
||||
networkState: p.networkState,
|
||||
paused: p.paused,
|
||||
buffered,
|
||||
})
|
||||
}
|
||||
|
||||
function handleAudioProgress() {
|
||||
const p = audioPlayer.value
|
||||
if (!p) return
|
||||
const buffered: string[] = []
|
||||
for (let i = 0; i < p.buffered.length; i++) {
|
||||
buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`)
|
||||
}
|
||||
console.debug('[PlayerDebug] progress', {
|
||||
ts: Date.now(),
|
||||
currentTime: p.currentTime.toFixed(3),
|
||||
duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN',
|
||||
readyState: p.readyState,
|
||||
networkState: p.networkState,
|
||||
paused: p.paused,
|
||||
buffered,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatte le temps en MM:SS
|
||||
*/
|
||||
@@ -752,25 +965,11 @@ function clearRetryTimer() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise les compteurs de retry
|
||||
*/
|
||||
// Réinitialise l'état de retry (timer uniquement)
|
||||
function resetAudioRetries() {
|
||||
retryCount.value = 0
|
||||
clearRetryTimer()
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance un retry différé pour laisser le temps au backend de reproposer le flux
|
||||
*/
|
||||
function scheduleRetryPlayback() {
|
||||
clearRetryTimer()
|
||||
retryTimer = setTimeout(() => {
|
||||
retryTimer = null
|
||||
void retryPlayback()
|
||||
}, RETRY_DELAY_MS)
|
||||
}
|
||||
|
||||
/**
|
||||
* (Ré)initialise la source audio et démarre la lecture
|
||||
*/
|
||||
@@ -852,20 +1051,6 @@ async function waitForSufficientBuffer(player: HTMLAudioElement): Promise<void>
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Tente de relancer la lecture après une erreur (nouvelle résolution d'URI)
|
||||
*/
|
||||
async function retryPlayback() {
|
||||
if (!selectedSource.value || !currentTrack.value) return
|
||||
|
||||
try {
|
||||
const result = await resolveUri(selectedSource.value.id, currentTrack.value.id)
|
||||
await startPlaybackFromUri(result.uri)
|
||||
} catch (e: any) {
|
||||
audioError.value = `Erreur lors de la reprise: ${e.message}`
|
||||
isPlaying.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -76,7 +76,7 @@ use pmoflac::{EncoderOptions, FlacEncodedStream};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, trace, warn};
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
use crate::byte_stream_reader::{PcmChunk};
|
||||
use crate::chunk_to_pcm::chunk_to_pcm_bytes;
|
||||
@@ -317,6 +317,25 @@ impl NodeLogic for StreamingFlacSinkLogic {
|
||||
}
|
||||
|
||||
debug!("StreamingFlacSink: SyncMarker::TrackBoundary {:?}",metadata.read().await.get_duration().await);
|
||||
let current_ts = *self.ctx.current_timestamp.read().await;
|
||||
// Durée attendue du morceau qui se termine : on lit les métadonnées courantes du sink
|
||||
let (prev_title, prev_artist, prev_expected) = {
|
||||
let meta = self.ctx.metadata.read().await;
|
||||
let title = meta.title.clone().unwrap_or_else(|| "Unknown".into());
|
||||
let artist = meta.artist.clone().unwrap_or_else(|| "Unknown".into());
|
||||
let expected = meta
|
||||
.duration
|
||||
.map(|d| format!("{:.3}s", d.as_secs_f64()))
|
||||
.unwrap_or_else(|| "unknown".into());
|
||||
(title, artist, expected)
|
||||
};
|
||||
info!(
|
||||
"StreamingFlacSink: track complete ts={:.3}s (title=\"{}\" artist=\"{}\" expected={})",
|
||||
current_ts,
|
||||
prev_title,
|
||||
prev_artist,
|
||||
prev_expected
|
||||
);
|
||||
|
||||
if self.ctx.restart_encoder_on_track_boundary {
|
||||
// Only restart encoder if it's already initialized (not the first track)
|
||||
|
||||
@@ -259,6 +259,12 @@ impl NodeLogic for PlaylistSourceLogic {
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| "Untitled".to_string());
|
||||
let expected_duration = metadata_guard
|
||||
.get_duration()
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|d| d.as_secs_f64());
|
||||
drop(metadata_guard);
|
||||
let remaining = self.playlist_handle.remaining().await.unwrap_or(0);
|
||||
tracing::info!(
|
||||
@@ -270,7 +276,8 @@ impl NodeLogic for PlaylistSourceLogic {
|
||||
|
||||
let track_start = std::time::Instant::now();
|
||||
tracing::debug!("PlaylistSourceLogic: emitting TrackBoundary");
|
||||
let boundary = AudioSegment::new_track_boundary(0, 0.0, metadata);
|
||||
let metadata_for_boundary = metadata.clone();
|
||||
let boundary = AudioSegment::new_track_boundary(0, 0.0, metadata_for_boundary);
|
||||
send_to_children(node_name, &output, boundary).await?;
|
||||
|
||||
// Obtenir le chemin du fichier
|
||||
@@ -307,6 +314,7 @@ impl NodeLogic for PlaylistSourceLogic {
|
||||
&stop_token,
|
||||
&self.cache,
|
||||
cache_pk,
|
||||
expected_duration,
|
||||
emit_top_zero,
|
||||
)
|
||||
.await
|
||||
@@ -363,6 +371,7 @@ async fn decode_and_emit_track(
|
||||
stop_token: &CancellationToken,
|
||||
cache: &Arc<AudioCache>,
|
||||
cache_pk: &str,
|
||||
expected_duration_sec: Option<f64>,
|
||||
emit_top_zero: bool,
|
||||
) -> Result<(), AudioError> {
|
||||
// Attendre que le fichier soit suffisamment gros pour le sniffing
|
||||
@@ -527,6 +536,20 @@ async fn decode_and_emit_track(
|
||||
);
|
||||
}
|
||||
|
||||
let actual_duration = total_frames as f64 / stream_info.sample_rate as f64;
|
||||
let expected_str = expected_duration_sec
|
||||
.map(|d| format!("{:.3}s", d))
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
tracing::info!(
|
||||
"PlaylistSource: emitted pk={} frames={} sr={}Hz bit_depth={} duration={:.3}s (expected={})",
|
||||
cache_pk,
|
||||
total_frames,
|
||||
stream_info.sample_rate,
|
||||
stream_info.bits_per_sample,
|
||||
actual_duration,
|
||||
expected_str,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ use axum::{
|
||||
Json, Router,
|
||||
body::Body,
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
http::{
|
||||
header::{ACCEPT_RANGES, CACHE_CONTROL, CONNECTION, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
},
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
};
|
||||
@@ -236,7 +239,10 @@ async fn stream_flac(
|
||||
let stream = channel.subscribe_flac();
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/flac")
|
||||
.header(CONTENT_TYPE, "audio/flac")
|
||||
.header(CACHE_CONTROL, "no-store, no-transform")
|
||||
.header(CONNECTION, "keep-alive")
|
||||
.header(ACCEPT_RANGES, "none")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
@@ -249,7 +255,10 @@ async fn stream_ogg(
|
||||
let stream = channel.subscribe_ogg();
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/ogg")
|
||||
.header(CONTENT_TYPE, "application/ogg")
|
||||
.header(CACHE_CONTROL, "no-store, no-transform")
|
||||
.header(CONNECTION, "keep-alive")
|
||||
.header(ACCEPT_RANGES, "none")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
@@ -278,7 +287,10 @@ async fn stream_history_flac(
|
||||
})?;
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/flac")
|
||||
.header(CONTENT_TYPE, "audio/flac")
|
||||
.header(CACHE_CONTROL, "no-store, no-transform")
|
||||
.header(CONNECTION, "keep-alive")
|
||||
.header(ACCEPT_RANGES, "none")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
@@ -298,7 +310,10 @@ async fn stream_history_ogg(
|
||||
})?;
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/ogg")
|
||||
.header(CONTENT_TYPE, "application/ogg")
|
||||
.header(CACHE_CONTROL, "no-store, no-transform")
|
||||
.header(CONNECTION, "keep-alive")
|
||||
.header(ACCEPT_RANGES, "none")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ use std::{fs, sync::Arc};
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
http::{
|
||||
header::{ACCEPT_RANGES, CACHE_CONTROL, CONNECTION, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
},
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
Json, Router,
|
||||
@@ -180,7 +183,10 @@ async fn stream_flac(
|
||||
let stream = channel.subscribe_flac();
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/flac")
|
||||
.header(CONTENT_TYPE, "audio/flac")
|
||||
.header(CACHE_CONTROL, "no-store, no-transform")
|
||||
.header(CONNECTION, "keep-alive")
|
||||
.header(ACCEPT_RANGES, "none")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
@@ -193,7 +199,10 @@ async fn stream_ogg(
|
||||
let stream = channel.subscribe_ogg();
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/ogg")
|
||||
.header(CONTENT_TYPE, "application/ogg")
|
||||
.header(CACHE_CONTROL, "no-store, no-transform")
|
||||
.header(CONNECTION, "keep-alive")
|
||||
.header(ACCEPT_RANGES, "none")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
@@ -206,7 +215,10 @@ async fn stream_icy(
|
||||
let stream = channel.subscribe_icy();
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/flac")
|
||||
.header(CONTENT_TYPE, "audio/flac")
|
||||
.header(CACHE_CONTROL, "no-store, no-transform")
|
||||
.header(CONNECTION, "keep-alive")
|
||||
.header(ACCEPT_RANGES, "none")
|
||||
.header("icy-metaint", "16000")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
@@ -236,7 +248,10 @@ async fn stream_history_flac(
|
||||
})?;
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/flac")
|
||||
.header(CONTENT_TYPE, "audio/flac")
|
||||
.header(CACHE_CONTROL, "no-store, no-transform")
|
||||
.header(CONNECTION, "keep-alive")
|
||||
.header(ACCEPT_RANGES, "none")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
@@ -256,7 +271,10 @@ async fn stream_history_ogg(
|
||||
})?;
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/ogg")
|
||||
.header(CONTENT_TYPE, "application/ogg")
|
||||
.header(CACHE_CONTROL, "no-store, no-transform")
|
||||
.header(CONNECTION, "keep-alive")
|
||||
.header(ACCEPT_RANGES, "none")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user