From 892341b77330df8fe6aabbb52b69de3cfed3e276 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Mon, 26 Jan 2026 07:56:08 -0600 Subject: [PATCH] fix(dlna): use dynamic protocolInfo and add debug logging PROBLEM: - DLNA device detection and connection worked - Audio playback on DLNA devices did not work ROOT CAUSE: - DIDL-Lite metadata had hardcoded protocolInfo='audio/flac' - Many DLNA devices silently reject content when protocolInfo doesn't match the actual MIME type - No debug logging to diagnose SOAP action failures FIX: - Pass actual content_type from Qobuz stream to DIDL builder - Generate protocolInfo dynamically based on real MIME type - Add extensive debug logging for: - MediaServer URL generated - Content-Type from Qobuz - Complete DIDL-Lite metadata - SOAP action responses (SetAVTransportURI, Play) - Error details when actions fail Files modified: - src-tauri/src/cast/dlna/device.rs - src-tauri/src/cast/dlna/commands.rs --- src-tauri/src/cast/dlna/commands.rs | 5 +++- src-tauri/src/cast/dlna/device.rs | 37 +++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src-tauri/src/cast/dlna/commands.rs b/src-tauri/src/cast/dlna/commands.rs index 0c3fa2333..244b276a1 100644 --- a/src-tauri/src/cast/dlna/commands.rs +++ b/src-tauri/src/cast/dlna/commands.rs @@ -152,11 +152,14 @@ pub async fn dlna_play_track( url.ok_or_else(|| "Failed to build media URL".to_string())? }; + log::info!("DLNA: Playing track {} via MediaServer URL: {}", track_id, url); + log::debug!("DLNA: Content-Type from Qobuz: {}", content_type); + // Load media on DLNA device { let mut connection = dlna_state.connection.lock().await; let conn = connection.as_mut().ok_or_else(|| "Not connected".to_string())?; - conn.load_media(&url, &metadata).await.map_err(|e| e.to_string())?; + conn.load_media(&url, &metadata, &content_type).await.map_err(|e| e.to_string())?; } // Start playback diff --git a/src-tauri/src/cast/dlna/device.rs b/src-tauri/src/cast/dlna/device.rs index d8e56f45f..f53a71c41 100644 --- a/src-tauri/src/cast/dlna/device.rs +++ b/src-tauri/src/cast/dlna/device.rs @@ -101,7 +101,7 @@ impl DlnaConnection { } /// Set the media URI and start playback - pub async fn load_media(&mut self, uri: &str, metadata: &DlnaMetadata) -> Result<(), DlnaError> { + pub async fn load_media(&mut self, uri: &str, metadata: &DlnaMetadata, content_type: &str) -> Result<(), DlnaError> { if !self.connected { return Err(DlnaError::NotConnected); } @@ -109,8 +109,12 @@ impl DlnaConnection { let av_service = self.av_transport_service.as_ref() .ok_or_else(|| DlnaError::Playback("Device has no AVTransport service".to_string()))?; - // Build DIDL-Lite metadata - let didl_metadata = build_didl_metadata(uri, metadata); + // Build DIDL-Lite metadata with actual content type + let didl_metadata = build_didl_metadata(uri, metadata, content_type); + + log::debug!("DLNA: Loading media URI: {}", uri); + log::debug!("DLNA: Content-Type: {}", content_type); + log::debug!("DLNA: DIDL Metadata:\n{}", didl_metadata); let payload = format!( "0{}{}", @@ -118,11 +122,15 @@ impl DlnaConnection { xml_escape(&didl_metadata) ); - av_service + let response = av_service .action(&self.device_url, "SetAVTransportURI", &payload) .await - .map_err(|e| DlnaError::Playback(e.to_string()))?; + .map_err(|e| { + log::error!("DLNA: SetAVTransportURI failed: {}", e); + DlnaError::Playback(e.to_string()) + })?; + log::debug!("DLNA: SetAVTransportURI response: {:?}", response); self.current_uri = Some(uri.to_string()); log::info!("DLNA: Set URI to {}", uri); @@ -138,13 +146,17 @@ impl DlnaConnection { let av_service = self.av_transport_service.as_ref() .ok_or_else(|| DlnaError::Playback("Device has no AVTransport service".to_string()))?; - av_service + let response = av_service .action(&self.device_url, "Play", "01") .await - .map_err(|e| DlnaError::Playback(e.to_string()))?; + .map_err(|e| { + log::error!("DLNA: Play action failed: {}", e); + DlnaError::Playback(e.to_string()) + })?; + log::debug!("DLNA: Play response: {:?}", response); self.is_playing = true; - log::info!("DLNA: Play"); + log::info!("DLNA: Play started successfully"); Ok(()) } @@ -244,7 +256,7 @@ impl DlnaConnection { } /// Build DIDL-Lite metadata for a track -fn build_didl_metadata(uri: &str, metadata: &DlnaMetadata) -> String { +fn build_didl_metadata(uri: &str, metadata: &DlnaMetadata, content_type: &str) -> String { let duration = metadata.duration_secs.map(|d| { let hours = d / 3600; let minutes = (d % 3600) / 60; @@ -256,6 +268,10 @@ fn build_didl_metadata(uri: &str, metadata: &DlnaMetadata) -> String { .map(|url| format!(r#"{}"#, xml_escape(url))) .unwrap_or_default(); + // Use actual content type for protocolInfo - critical for DLNA compatibility + // Many devices reject content if protocolInfo doesn't match actual MIME type + let protocol_info = format!("http-get:*:{}:*", content_type); + format!( r#" @@ -264,7 +280,7 @@ fn build_didl_metadata(uri: &str, metadata: &DlnaMetadata) -> String { {} {} {} - {} + {} object.item.audioItem.musicTrack "#, @@ -274,6 +290,7 @@ fn build_didl_metadata(uri: &str, metadata: &DlnaMetadata) -> String { xml_escape(&metadata.artist), artwork, duration, + protocol_info, xml_escape(uri) ) }