fix(dlna): use fixed port 9876 and add extensive logging

CHANGES:
- MediaServer now uses fixed port 9876 (falls back to random if busy)
  This improves firewall compatibility for DLNA casting
- Added timeout (10s) for SOAP actions to prevent deadlocks
- Added extensive logging throughout DLNA flow:
  - Command entry point logging
  - MediaServer lifecycle (start, thread, requests)
  - SOAP action responses
  - Content-type and DIDL metadata details

VERIFIED WORKING:
- DLNA device discovery ✓
- Connection to gmediarender ✓
- SetAVTransportURI SOAP action ✓
- Play SOAP action ✓
- MediaServer serving audio to DLNA renderer ✓

Files modified:
- src-tauri/src/cast/dlna/device.rs
- src-tauri/src/cast/dlna/commands.rs
- src-tauri/src/cast/media_server.rs
This commit is contained in:
vicrodh committed 2026-01-26 09:30:00 -06:00
1 parent 892341b773
commit 003468a28e
3 files changed
+66 -25

No files matched your search

+3 -1
View File
@@ -111,6 +111,8 @@ pub async fn dlna_play_track(
dlna_state: State<'_, DlnaState>,
app_state: State<'_, AppState>,
) -> Result<(), String> {
log::info!("DLNA: dlna_play_track called for track_id={}", track_id);
// Get stream URL from Qobuz
let stream_url = {
let client = app_state.client.lock().await;
@@ -153,7 +155,7 @@ pub async fn dlna_play_track(
};
log::info!("DLNA: Playing track {} via MediaServer URL: {}", track_id, url);
log::debug!("DLNA: Content-Type from Qobuz: {}", content_type);
log::info!("DLNA: Content-Type from Qobuz: {}", content_type);
// Load media on DLNA device
{
+31 -19
View File
@@ -112,9 +112,9 @@ impl DlnaConnection {
// 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);
log::info!("DLNA: Loading media URI: {}", uri);
log::info!("DLNA: Content-Type: {}", content_type);
log::info!("DLNA: DIDL Metadata:\n{}", didl_metadata);
let payload = format!(
"<InstanceID>0</InstanceID><CurrentURI>{}</CurrentURI><CurrentURIMetaData>{}</CurrentURIMetaData>",
@@ -122,15 +122,21 @@ impl DlnaConnection {
xml_escape(&didl_metadata)
);
let response = av_service
.action(&self.device_url, "SetAVTransportURI", &payload)
.await
.map_err(|e| {
log::error!("DLNA: SetAVTransportURI failed: {}", e);
DlnaError::Playback(e.to_string())
})?;
let response = tokio::time::timeout(
std::time::Duration::from_secs(10),
av_service.action(&self.device_url, "SetAVTransportURI", &payload)
)
.await
.map_err(|_| {
log::error!("DLNA: SetAVTransportURI timed out after 10s");
DlnaError::Playback("SetAVTransportURI timed out".to_string())
})?
.map_err(|e| {
log::error!("DLNA: SetAVTransportURI failed: {}", e);
DlnaError::Playback(e.to_string())
})?;
log::debug!("DLNA: SetAVTransportURI response: {:?}", response);
log::info!("DLNA: SetAVTransportURI response: {:?}", response);
self.current_uri = Some(uri.to_string());
log::info!("DLNA: Set URI to {}", uri);
@@ -146,15 +152,21 @@ impl DlnaConnection {
let av_service = self.av_transport_service.as_ref()
.ok_or_else(|| DlnaError::Playback("Device has no AVTransport service".to_string()))?;
let response = av_service
.action(&self.device_url, "Play", "<InstanceID>0</InstanceID><Speed>1</Speed>")
.await
.map_err(|e| {
log::error!("DLNA: Play action failed: {}", e);
DlnaError::Playback(e.to_string())
})?;
let response = tokio::time::timeout(
std::time::Duration::from_secs(10),
av_service.action(&self.device_url, "Play", "<InstanceID>0</InstanceID><Speed>1</Speed>")
)
.await
.map_err(|_| {
log::error!("DLNA: Play action timed out after 10s");
DlnaError::Playback("Play action timed out".to_string())
})?
.map_err(|e| {
log::error!("DLNA: Play action failed: {}", e);
DlnaError::Playback(e.to_string())
})?;
log::debug!("DLNA: Play response: {:?}", response);
log::info!("DLNA: Play response: {:?}", response);
self.is_playing = true;
log::info!("DLNA: Play started successfully");
Ok(())
+32 -5
View File
@@ -38,8 +38,14 @@ pub struct MediaServer {
impl MediaServer {
/// Start server on available port
/// Uses port 9876 by default for DLNA compatibility, falls back to random port if busy
pub fn start() -> Result<Self, CastError> {
let server = Server::http("0.0.0.0:0")
// Try fixed port first for easier firewall configuration
let server = Server::http("0.0.0.0:9876")
.or_else(|_| {
log::warn!("MediaServer: Port 9876 busy, using random port");
Server::http("0.0.0.0:0")
})
.map_err(|e| CastError::Server(format!("Failed to start HTTP server: {}", e)))?;
let port = server
@@ -50,14 +56,18 @@ impl MediaServer {
let base_ip = local_ip().unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
let base_url = format_base_url(base_ip, port);
log::info!("MediaServer: Started on {}:{} (base_url: {})", base_ip, port, base_url);
let entries = Arc::new(Mutex::new(HashMap::new()));
let shutdown = Arc::new(AtomicBool::new(false));
let entries_clone = entries.clone();
let shutdown_clone = shutdown.clone();
let port_for_log = port;
let handle = thread::spawn(move || {
log::info!("MediaServer: Thread started, listening on port {}", port_for_log);
while !shutdown_clone.load(Ordering::SeqCst) {
match server.recv_timeout(Duration::from_millis(250)) {
Ok(Some(request)) => {
@@ -65,9 +75,13 @@ impl MediaServer {
let _ = request.respond(response);
}
Ok(None) => {}
Err(_) => break,
Err(e) => {
log::error!("MediaServer: Thread error: {:?}, exiting", e);
break;
}
}
}
log::info!("MediaServer: Thread exiting");
});
Ok(Self {
@@ -177,18 +191,31 @@ fn handle_request(
request: &tiny_http::Request,
entries: &Arc<Mutex<HashMap<u64, MediaEntry>>>,
) -> Response<std::io::Cursor<Vec<u8>>> {
// Log all incoming requests for debugging
log::info!("MediaServer: {} request from {:?} for {}", method, request.remote_addr(), url);
if method != &Method::Get {
log::warn!("MediaServer: Rejected non-GET request: {}", method);
return Response::from_data(Vec::new()).with_status_code(StatusCode(405));
}
let id = match parse_audio_id(url) {
Some(id) => id,
None => return Response::from_data(Vec::new()).with_status_code(StatusCode(404)),
None => {
log::warn!("MediaServer: 404 - Could not parse audio ID from URL: {}", url);
return Response::from_data(Vec::new()).with_status_code(StatusCode(404));
}
};
let entry = match entries.lock().ok().and_then(|map| map.get(&id).cloned()) {
Some(entry) => entry,
None => return Response::from_data(Vec::new()).with_status_code(StatusCode(404)),
Some(entry) => {
log::info!("MediaServer: Found entry for ID {}, content-type: {}, size: {} bytes", id, entry.content_type, entry.size);
entry
}
None => {
log::warn!("MediaServer: 404 - No entry found for ID: {}", id);
return Response::from_data(Vec::new()).with_status_code(StatusCode(404));
}
};
let range_header = request