chore(qconnect): checkpoint current poc state
cx
This commit is contained in:
92 files changed
+3886
-2433
No files matched your search
@@ -2,8 +2,8 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::{self, JoinHandle};
|
||||
|
||||
use mdns_sd::{ServiceDaemon, ServiceEvent};
|
||||
@@ -59,12 +59,12 @@ impl AirPlayDiscovery {
|
||||
let mdns = ServiceDaemon::new()
|
||||
.map_err(|e| AirPlayError::Discovery(format!("Failed to create mDNS daemon: {}", e)))?;
|
||||
|
||||
let raop_rx = mdns
|
||||
.browse(SERVICE_RAOP)
|
||||
.map_err(|e| AirPlayError::Discovery(format!("Failed to browse RAOP services: {}", e)))?;
|
||||
let airplay_rx = mdns
|
||||
.browse(SERVICE_AIRPLAY)
|
||||
.map_err(|e| AirPlayError::Discovery(format!("Failed to browse AirPlay services: {}", e)))?;
|
||||
let raop_rx = mdns.browse(SERVICE_RAOP).map_err(|e| {
|
||||
AirPlayError::Discovery(format!("Failed to browse RAOP services: {}", e))
|
||||
})?;
|
||||
let airplay_rx = mdns.browse(SERVICE_AIRPLAY).map_err(|e| {
|
||||
AirPlayError::Discovery(format!("Failed to browse AirPlay services: {}", e))
|
||||
})?;
|
||||
|
||||
self.running.store(true, Ordering::SeqCst);
|
||||
|
||||
@@ -76,12 +76,8 @@ impl AirPlayDiscovery {
|
||||
state.clone(),
|
||||
running.clone(),
|
||||
));
|
||||
self.handles.push(spawn_receiver(
|
||||
"airplay",
|
||||
airplay_rx,
|
||||
state,
|
||||
running,
|
||||
));
|
||||
self.handles
|
||||
.push(spawn_receiver("airplay", airplay_rx, state, running));
|
||||
|
||||
self.daemon = Some(mdns);
|
||||
Ok(())
|
||||
@@ -150,8 +146,8 @@ fn spawn_receiver(
|
||||
.unwrap_or("Unknown")
|
||||
.to_string();
|
||||
let requires_password = parse_password_required(&info);
|
||||
let ip = pick_ip(info.get_addresses())
|
||||
.unwrap_or_else(|| "127.0.0.1".to_string());
|
||||
let ip =
|
||||
pick_ip(info.get_addresses()).unwrap_or_else(|| "127.0.0.1".to_string());
|
||||
|
||||
let device = DiscoveredAirPlayDevice {
|
||||
id: id.clone(),
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
pub mod device;
|
||||
pub mod discovery;
|
||||
|
||||
pub use crate::AirPlayError;
|
||||
pub use device::{AirPlayConnection, AirPlayMetadata, AirPlayStatus};
|
||||
pub use discovery::{AirPlayDiscovery, DiscoveredAirPlayDevice};
|
||||
pub use crate::AirPlayError;
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use rust_cast::CastDevice;
|
||||
use rust_cast::channels::media::{Image, Media, Metadata, MusicTrackMediaMetadata, StreamType, PlayerState, IdleReason};
|
||||
use rust_cast::channels::media::{
|
||||
IdleReason, Image, Media, Metadata, MusicTrackMediaMetadata, PlayerState, StreamType,
|
||||
};
|
||||
use rust_cast::channels::receiver::{CastDeviceApp, Status as ReceiverStatus};
|
||||
use rust_cast::CastDevice;
|
||||
|
||||
use crate::CastError;
|
||||
|
||||
@@ -90,7 +92,10 @@ impl CastDeviceConnection {
|
||||
/// Disconnect from device
|
||||
pub fn disconnect(&mut self) -> Result<(), CastError> {
|
||||
if let Some(session) = &self.session {
|
||||
let _ = self.device.connection.disconnect(session.transport_id.as_str());
|
||||
let _ = self
|
||||
.device
|
||||
.connection
|
||||
.disconnect(session.transport_id.as_str());
|
||||
}
|
||||
let _ = self.device.connection.disconnect(DEFAULT_RECEIVER_ID);
|
||||
self.session = None;
|
||||
@@ -151,7 +156,11 @@ impl CastDeviceConnection {
|
||||
let status = self
|
||||
.device
|
||||
.media
|
||||
.load(session.transport_id.as_str(), session.session_id.as_str(), &media)
|
||||
.load(
|
||||
session.transport_id.as_str(),
|
||||
session.session_id.as_str(),
|
||||
&media,
|
||||
)
|
||||
.map_err(|e| CastError::Media(e.to_string()))?;
|
||||
|
||||
if let Some(entry) = status.entries.first() {
|
||||
@@ -245,9 +254,7 @@ impl CastDeviceConnection {
|
||||
IdleReason::Error => "ERROR".to_string(),
|
||||
});
|
||||
|
||||
let duration = entry.media.as_ref()
|
||||
.and_then(|m| m.duration)
|
||||
.unwrap_or(0.0) as f64;
|
||||
let duration = entry.media.as_ref().and_then(|m| m.duration).unwrap_or(0.0) as f64;
|
||||
|
||||
let position = entry.current_time.unwrap_or(0.0) as f64;
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::{self, JoinHandle};
|
||||
|
||||
use mdns_sd::{ServiceDaemon, ServiceEvent};
|
||||
|
||||
@@ -8,8 +8,8 @@ use std::thread::{self, JoinHandle};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::chromecast::device::CastDeviceConnection;
|
||||
use crate::chromecast::{CastPositionInfo, CastStatus, MediaMetadata};
|
||||
use crate::CastError;
|
||||
use crate::chromecast::{CastStatus, MediaMetadata, CastPositionInfo};
|
||||
|
||||
/// Commands sent to the Chromecast thread
|
||||
pub enum CastCommand {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! DLNA device connection and playback via AVTransport SOAP
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use rupnp::{Device, Service};
|
||||
use rupnp::http::Uri;
|
||||
use rupnp::ssdp::URN;
|
||||
use rupnp::{Device, Service};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::dlna::DiscoveredDlnaDevice;
|
||||
use crate::DlnaError;
|
||||
@@ -56,13 +56,11 @@ impl DlnaConnection {
|
||||
.parse()
|
||||
.map_err(|e| DlnaError::Connection(format!("Invalid device URL: {}", e)))?;
|
||||
|
||||
let parsed_device = Device::from_url(device_url.clone())
|
||||
.await
|
||||
.map_err(|e| DlnaError::Connection(format!("Failed to load device description: {}", e)))?;
|
||||
let parsed_device = Device::from_url(device_url.clone()).await.map_err(|e| {
|
||||
DlnaError::Connection(format!("Failed to load device description: {}", e))
|
||||
})?;
|
||||
|
||||
let av_transport_service = parsed_device
|
||||
.find_service(&av_transport_urn())
|
||||
.cloned();
|
||||
let av_transport_service = parsed_device.find_service(&av_transport_urn()).cloned();
|
||||
let rendering_control_service = parsed_device
|
||||
.find_service(&rendering_control_urn())
|
||||
.cloned();
|
||||
@@ -110,12 +108,19 @@ impl DlnaConnection {
|
||||
}
|
||||
|
||||
/// Set the media URI and start playback
|
||||
pub async fn load_media(&mut self, uri: &str, metadata: &DlnaMetadata, content_type: &str) -> 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);
|
||||
}
|
||||
|
||||
let av_service = self.av_transport_service.as_ref()
|
||||
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 with actual content type
|
||||
@@ -133,7 +138,7 @@ impl DlnaConnection {
|
||||
|
||||
let response = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(10),
|
||||
av_service.action(&self.device_url, "SetAVTransportURI", &payload)
|
||||
av_service.action(&self.device_url, "SetAVTransportURI", &payload),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
@@ -158,12 +163,18 @@ impl DlnaConnection {
|
||||
return Err(DlnaError::NotConnected);
|
||||
}
|
||||
|
||||
let av_service = self.av_transport_service.as_ref()
|
||||
let av_service = self
|
||||
.av_transport_service
|
||||
.as_ref()
|
||||
.ok_or_else(|| DlnaError::Playback("Device has no AVTransport service".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>")
|
||||
av_service.action(
|
||||
&self.device_url,
|
||||
"Play",
|
||||
"<InstanceID>0</InstanceID><Speed>1</Speed>",
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
@@ -187,7 +198,9 @@ impl DlnaConnection {
|
||||
return Err(DlnaError::NotConnected);
|
||||
}
|
||||
|
||||
let av_service = self.av_transport_service.as_ref()
|
||||
let av_service = self
|
||||
.av_transport_service
|
||||
.as_ref()
|
||||
.ok_or_else(|| DlnaError::Playback("Device has no AVTransport service".to_string()))?;
|
||||
|
||||
av_service
|
||||
@@ -206,7 +219,9 @@ impl DlnaConnection {
|
||||
return Err(DlnaError::NotConnected);
|
||||
}
|
||||
|
||||
let av_service = self.av_transport_service.as_ref()
|
||||
let av_service = self
|
||||
.av_transport_service
|
||||
.as_ref()
|
||||
.ok_or_else(|| DlnaError::Playback("Device has no AVTransport service".to_string()))?;
|
||||
|
||||
av_service
|
||||
@@ -231,7 +246,9 @@ impl DlnaConnection {
|
||||
let seconds = position_secs % 60;
|
||||
let time_str = format!("{:02}:{:02}:{:02}", hours, minutes, seconds);
|
||||
|
||||
let av_service = self.av_transport_service.as_ref()
|
||||
let av_service = self
|
||||
.av_transport_service
|
||||
.as_ref()
|
||||
.ok_or_else(|| DlnaError::Playback("Device has no AVTransport service".to_string()))?;
|
||||
|
||||
let payload = format!(
|
||||
@@ -254,8 +271,9 @@ impl DlnaConnection {
|
||||
return Err(DlnaError::NotConnected);
|
||||
}
|
||||
|
||||
let rc_service = self.rendering_control_service.as_ref()
|
||||
.ok_or_else(|| DlnaError::Playback("Device has no RenderingControl service".to_string()))?;
|
||||
let rc_service = self.rendering_control_service.as_ref().ok_or_else(|| {
|
||||
DlnaError::Playback("Device has no RenderingControl service".to_string())
|
||||
})?;
|
||||
|
||||
// DLNA volume is typically 0-100
|
||||
let dlna_volume = ((volume.clamp(0.0, 1.0) * 100.0) as u32).min(100);
|
||||
@@ -280,13 +298,19 @@ impl DlnaConnection {
|
||||
return Err(DlnaError::NotConnected);
|
||||
}
|
||||
|
||||
let av_service = self.av_transport_service.as_ref()
|
||||
let av_service = self
|
||||
.av_transport_service
|
||||
.as_ref()
|
||||
.ok_or_else(|| DlnaError::Playback("Device has no AVTransport service".to_string()))?;
|
||||
|
||||
// Get position info
|
||||
let position_response = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(5),
|
||||
av_service.action(&self.device_url, "GetPositionInfo", "<InstanceID>0</InstanceID>")
|
||||
av_service.action(
|
||||
&self.device_url,
|
||||
"GetPositionInfo",
|
||||
"<InstanceID>0</InstanceID>",
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| DlnaError::Playback("GetPositionInfo timed out".to_string()))?
|
||||
@@ -295,26 +319,33 @@ impl DlnaConnection {
|
||||
// Get transport state
|
||||
let transport_response = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(5),
|
||||
av_service.action(&self.device_url, "GetTransportInfo", "<InstanceID>0</InstanceID>")
|
||||
av_service.action(
|
||||
&self.device_url,
|
||||
"GetTransportInfo",
|
||||
"<InstanceID>0</InstanceID>",
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| DlnaError::Playback("GetTransportInfo timed out".to_string()))?
|
||||
.map_err(|e| DlnaError::Playback(e.to_string()))?;
|
||||
|
||||
// Parse RelTime (position) - format: "HH:MM:SS" or "H:MM:SS"
|
||||
let rel_time = position_response.get("RelTime")
|
||||
let rel_time = position_response
|
||||
.get("RelTime")
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("0:00:00");
|
||||
let position_secs = parse_time_string(rel_time);
|
||||
|
||||
// Parse TrackDuration - format: "HH:MM:SS"
|
||||
let track_duration = position_response.get("TrackDuration")
|
||||
let track_duration = position_response
|
||||
.get("TrackDuration")
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("0:00:00");
|
||||
let duration_secs = parse_time_string(track_duration);
|
||||
|
||||
// Get transport state (PLAYING, PAUSED_PLAYBACK, STOPPED, etc.)
|
||||
let transport_state = transport_response.get("CurrentTransportState")
|
||||
let transport_state = transport_response
|
||||
.get("CurrentTransportState")
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "UNKNOWN".to_string());
|
||||
|
||||
@@ -324,7 +355,6 @@ impl DlnaConnection {
|
||||
transport_state,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Parse time string "HH:MM:SS" or "H:MM:SS" to seconds
|
||||
@@ -336,7 +366,9 @@ fn parse_time_string(time: &str) -> u64 {
|
||||
|
||||
let hours: u64 = parts[0].parse().unwrap_or(0);
|
||||
let minutes: u64 = parts[1].parse().unwrap_or(0);
|
||||
let seconds: u64 = parts[2].split('.').next()
|
||||
let seconds: u64 = parts[2]
|
||||
.split('.')
|
||||
.next()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
@@ -345,15 +377,25 @@ fn parse_time_string(time: &str) -> u64 {
|
||||
|
||||
/// Build DIDL-Lite metadata for a track
|
||||
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;
|
||||
let seconds = d % 60;
|
||||
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
|
||||
}).unwrap_or_else(|| "00:00:00".to_string());
|
||||
let duration = metadata
|
||||
.duration_secs
|
||||
.map(|d| {
|
||||
let hours = d / 3600;
|
||||
let minutes = (d % 3600) / 60;
|
||||
let seconds = d % 60;
|
||||
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
|
||||
})
|
||||
.unwrap_or_else(|| "00:00:00".to_string());
|
||||
|
||||
let artwork = metadata.artwork_url.as_ref()
|
||||
.map(|url| format!(r#"<upnp:albumArtURI>{}</upnp:albumArtURI>"#, xml_escape(url)))
|
||||
let artwork = metadata
|
||||
.artwork_url
|
||||
.as_ref()
|
||||
.map(|url| {
|
||||
format!(
|
||||
r#"<upnp:albumArtURI>{}</upnp:albumArtURI>"#,
|
||||
xml_escape(url)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// Use actual content type for protocolInfo - critical for DLNA compatibility
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! DLNA/UPnP device discovery via SSDP
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use futures_util::TryStreamExt;
|
||||
@@ -62,7 +62,9 @@ impl DlnaDiscovery {
|
||||
let handle = tokio::spawn(async move {
|
||||
while running.load(Ordering::SeqCst) {
|
||||
let target = SearchTarget::URN(media_renderer_urn());
|
||||
let discover = rupnp::discover(&target, Duration::from_secs(DISCOVERY_WINDOW_SECS), None).await;
|
||||
let discover =
|
||||
rupnp::discover(&target, Duration::from_secs(DISCOVERY_WINDOW_SECS), None)
|
||||
.await;
|
||||
|
||||
if let Ok(stream) = discover {
|
||||
let mut stream = std::pin::pin!(stream);
|
||||
@@ -72,11 +74,7 @@ impl DlnaDiscovery {
|
||||
let manufacturer = device.manufacturer().to_string();
|
||||
let model = device.model_name().to_string();
|
||||
let url = device.url().to_string();
|
||||
let ip = device
|
||||
.url()
|
||||
.host()
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let ip = device.url().host().unwrap_or("unknown").to_string();
|
||||
|
||||
let mut has_av_transport = false;
|
||||
let mut has_rendering_control = false;
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
pub mod device;
|
||||
pub mod discovery;
|
||||
|
||||
pub use crate::DlnaError;
|
||||
pub use device::{DlnaConnection, DlnaMetadata, DlnaPositionInfo, DlnaStatus};
|
||||
pub use discovery::{DiscoveredDlnaDevice, DlnaDiscovery};
|
||||
pub use crate::DlnaError;
|
||||
@@ -15,48 +15,32 @@
|
||||
//! - **MediaServer**: Local HTTP server for streaming audio to cast devices.
|
||||
//! Supports byte-range requests for seeking.
|
||||
|
||||
pub mod airplay;
|
||||
pub mod chromecast;
|
||||
pub mod dlna;
|
||||
pub mod errors;
|
||||
pub mod media_server;
|
||||
pub mod chromecast;
|
||||
pub mod airplay;
|
||||
pub mod dlna;
|
||||
|
||||
// Re-export error types at root
|
||||
pub use errors::{CastError, AirPlayError, DlnaError};
|
||||
pub use errors::{AirPlayError, CastError, DlnaError};
|
||||
|
||||
// Re-export media server
|
||||
pub use media_server::MediaServer;
|
||||
|
||||
// Re-export Chromecast types
|
||||
pub use chromecast::{
|
||||
CastApplication,
|
||||
CastCommand,
|
||||
CastDeviceConnection,
|
||||
CastPositionInfo,
|
||||
CastStatus,
|
||||
ChromecastHandle,
|
||||
DeviceDiscovery,
|
||||
DiscoveredDevice,
|
||||
MediaMetadata,
|
||||
CastApplication, CastCommand, CastDeviceConnection, CastPositionInfo, CastStatus,
|
||||
ChromecastHandle, DeviceDiscovery, DiscoveredDevice, MediaMetadata,
|
||||
};
|
||||
|
||||
// Re-export AirPlay types
|
||||
pub use airplay::{
|
||||
AirPlayConnection,
|
||||
AirPlayDiscovery,
|
||||
AirPlayMetadata,
|
||||
AirPlayStatus,
|
||||
DiscoveredAirPlayDevice,
|
||||
AirPlayConnection, AirPlayDiscovery, AirPlayMetadata, AirPlayStatus, DiscoveredAirPlayDevice,
|
||||
};
|
||||
|
||||
// Re-export DLNA types
|
||||
pub use dlna::{
|
||||
DiscoveredDlnaDevice,
|
||||
DlnaConnection,
|
||||
DlnaDiscovery,
|
||||
DlnaMetadata,
|
||||
DlnaPositionInfo,
|
||||
DlnaStatus,
|
||||
DiscoveredDlnaDevice, DlnaConnection, DlnaDiscovery, DlnaMetadata, DlnaPositionInfo, DlnaStatus,
|
||||
};
|
||||
|
||||
/// Cast device type alias for backwards compatibility
|
||||
|
||||
@@ -60,7 +60,12 @@ 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);
|
||||
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));
|
||||
@@ -70,11 +75,19 @@ impl MediaServer {
|
||||
let port_for_log = port;
|
||||
|
||||
let handle = thread::spawn(move || {
|
||||
log::info!("MediaServer: Thread started, listening on port {}", port_for_log);
|
||||
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)) => {
|
||||
let response = handle_request(request.method(), request.url(), &request, &entries_clone);
|
||||
let response = handle_request(
|
||||
request.method(),
|
||||
request.url(),
|
||||
&request,
|
||||
&entries_clone,
|
||||
);
|
||||
let _ = request.respond(response);
|
||||
}
|
||||
Ok(None) => {}
|
||||
@@ -134,10 +147,7 @@ impl MediaServer {
|
||||
)));
|
||||
}
|
||||
|
||||
let size = path
|
||||
.metadata()
|
||||
.map_err(CastError::Io)?
|
||||
.len();
|
||||
let size = path.metadata().map_err(CastError::Io)?.len();
|
||||
let content_type = content_type_for_path(path);
|
||||
|
||||
let entry = MediaEntry {
|
||||
@@ -194,7 +204,12 @@ fn handle_request(
|
||||
request: &tiny_http::Request,
|
||||
entries: &Arc<Mutex<HashMap<u64, MediaEntry>>>,
|
||||
) -> Response<std::io::Cursor<Vec<u8>>> {
|
||||
log::info!("MediaServer: {} request from {:?} for {}", method, request.remote_addr(), url);
|
||||
log::info!(
|
||||
"MediaServer: {} request from {:?} for {}",
|
||||
method,
|
||||
request.remote_addr(),
|
||||
url
|
||||
);
|
||||
|
||||
if method != &Method::Get {
|
||||
log::warn!("MediaServer: Rejected non-GET request: {}", method);
|
||||
@@ -204,14 +219,22 @@ fn handle_request(
|
||||
let id = match parse_audio_id(url) {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
log::warn!("MediaServer: 404 - Could not parse audio ID from URL: {}", url);
|
||||
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) => {
|
||||
log::info!("MediaServer: Found entry for ID {}, content-type: {}, size: {} bytes", id, entry.content_type, entry.size);
|
||||
log::info!(
|
||||
"MediaServer: Found entry for ID {}, content-type: {}, size: {} bytes",
|
||||
id,
|
||||
entry.content_type,
|
||||
entry.size
|
||||
);
|
||||
entry
|
||||
}
|
||||
None => {
|
||||
@@ -345,7 +368,11 @@ fn format_base_url(ip: IpAddr, port: u16) -> String {
|
||||
}
|
||||
|
||||
fn content_type_for_path(path: &Path) -> String {
|
||||
match path.extension().and_then(|e| e.to_str()).map(|s| s.to_lowercase()) {
|
||||
match path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|s| s.to_lowercase())
|
||||
{
|
||||
Some(ext) if ext == "flac" => "audio/flac".to_string(),
|
||||
Some(ext) if ext == "wav" => "audio/wav".to_string(),
|
||||
Some(ext) if ext == "m4a" => "audio/mp4".to_string(),
|
||||
|
||||
Reference in new issue
Block a user