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:
1 parent
892341b773
commit
003468a28e
3 files changed
+66
-25
No files matched your search
@@ -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
|
||||
{
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user