Merge pull request 'push-osyzsknqnpom' (#88) from push-osyzsknqnpom into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m55s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m55s
Reviewed-on: #88
This commit was merged in pull request #88.
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4,7 +4,7 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "PMOMusic"
|
name = "PMOMusic"
|
||||||
version = "0.3.35"
|
version = "0.3.36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.8.7",
|
"axum 0.8.7",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "PMOMusic"
|
name = "PMOMusic"
|
||||||
version = "0.3.35"
|
version = "0.3.37"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -36,10 +36,16 @@ const myUdn = computed(() => webRenderer.rendererUdn.value);
|
|||||||
|
|
||||||
const filteredRenderers = computed(() => {
|
const filteredRenderers = computed(() => {
|
||||||
const udn = myUdn.value;
|
const udn = myUdn.value;
|
||||||
|
const normalizedUdn = udn?.replace(/^uuid:/, '') ?? null;
|
||||||
return allRenderers.value.filter((r: RendererSummary) => {
|
return allRenderers.value.filter((r: RendererSummary) => {
|
||||||
if (r.model_name !== "WebRenderer") return true;
|
// WebRenderers have "Web Audio – " as friendly_name prefix
|
||||||
if (udn === null) return false;
|
const isWebRenderer = r.friendly_name?.startsWith("Web Audio – ") ?? false;
|
||||||
return r.id === udn;
|
if (isWebRenderer) {
|
||||||
|
if (normalizedUdn === null) return false;
|
||||||
|
const normalizedId = r.id.replace(/^uuid:/, '');
|
||||||
|
return normalizedId === normalizedUdn;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ function ensureSSEInitialized() {
|
|||||||
id: rendererId,
|
id: rendererId,
|
||||||
friendly_name: event.friendly_name,
|
friendly_name: event.friendly_name,
|
||||||
model_name: event.model_name,
|
model_name: event.model_name,
|
||||||
|
manufacturer: event.manufacturer,
|
||||||
protocol: "upnp", // Valeur par défaut, sera mise à jour par le fetch
|
protocol: "upnp", // Valeur par défaut, sera mise à jour par le fetch
|
||||||
capabilities: {
|
capabilities: {
|
||||||
has_avtransport: false,
|
has_avtransport: false,
|
||||||
@@ -269,9 +270,12 @@ function selectRenderer(id: string | null) {
|
|||||||
snapshotState.selectedRendererId = id;
|
snapshotState.selectedRendererId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchRenderers(force = false) {
|
async function fetchRenderers(force = false, retries = 2) {
|
||||||
ensureSSEInitialized();
|
ensureSSEInitialized();
|
||||||
|
|
||||||
|
let lastError: Error | null = null;
|
||||||
|
|
||||||
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
@@ -287,12 +291,19 @@ async function fetchRenderers(force = false) {
|
|||||||
renderersCache.value = new Map(
|
renderersCache.value = new Map(
|
||||||
data.map((renderer) => [renderer.id, renderer]),
|
data.map((renderer) => [renderer.id, renderer]),
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = err instanceof Error ? err.message : "Erreur fetch renderers";
|
lastError = err instanceof Error ? err : new Error("Erreur fetch renderers");
|
||||||
console.error("[useRenderers] Erreur fetch:", err);
|
console.error("[useRenderers] Erreur fetch (attempt " + (attempt + 1) + "):", lastError);
|
||||||
|
if (attempt < retries) {
|
||||||
|
await new Promise(r => setTimeout(r, 500 * (attempt + 1)));
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error.value = lastError?.message ?? "Erreur fetch renderers";
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchRendererSnapshot(
|
async function fetchRendererSnapshot(
|
||||||
|
|||||||
@@ -22,6 +22,24 @@ function generateUUID(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const INSTANCE_ID_KEY = "pmomusic_webrenderer_instance_id";
|
const INSTANCE_ID_KEY = "pmomusic_webrenderer_instance_id";
|
||||||
|
const RENDERER_UDN_KEY = "pmomusic_webrenderer_udn";
|
||||||
|
|
||||||
|
// Load persisted UDN on module load
|
||||||
|
function loadPersistedUdn(): string | null {
|
||||||
|
try {
|
||||||
|
return sessionStorage.getItem(RENDERER_UDN_KEY);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePersistedUdn(udn: string) {
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(RENDERER_UDN_KEY, udn);
|
||||||
|
} catch {
|
||||||
|
// ignore storage errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Module-level singleton for PMOPlayer to prevent duplicate instances
|
// Module-level singleton for PMOPlayer to prevent duplicate instances
|
||||||
let globalPlayer: PMOPlayer | null = null;
|
let globalPlayer: PMOPlayer | null = null;
|
||||||
@@ -31,7 +49,7 @@ let registering = false;
|
|||||||
// Module-level reactive state shared across all composable invocations
|
// Module-level reactive state shared across all composable invocations
|
||||||
const sharedConnected = ref(false);
|
const sharedConnected = ref(false);
|
||||||
const sharedStreamUrl = ref<string | null>(null);
|
const sharedStreamUrl = ref<string | null>(null);
|
||||||
const sharedRendererUdn = ref<string | null>(null);
|
const sharedRendererUdn = ref<string | null>(loadPersistedUdn());
|
||||||
|
|
||||||
function getOrCreateInstanceId(): string {
|
function getOrCreateInstanceId(): string {
|
||||||
try {
|
try {
|
||||||
@@ -55,17 +73,41 @@ export function useWebRenderer() {
|
|||||||
let onConnectedCallback: (() => void) | null = null;
|
let onConnectedCallback: (() => void) | null = null;
|
||||||
|
|
||||||
async function register(): Promise<void> {
|
async function register(): Promise<void> {
|
||||||
// Prevent concurrent registrations (race condition → double player)
|
// Allow re-registration if we have a stale player but no connection
|
||||||
if (globalPlayer || registering) {
|
if (globalPlayer && !connected.value) {
|
||||||
|
console.log('[WebRenderer] stale globalPlayer, clearing');
|
||||||
|
globalPlayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (globalPlayer) {
|
if (globalPlayer) {
|
||||||
player = globalPlayer;
|
player = globalPlayer;
|
||||||
connected.value = true;
|
connected.value = true;
|
||||||
}
|
console.log('[WebRenderer] reusing existing player');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (registering) {
|
||||||
|
console.log('[WebRenderer] already registering, waiting...');
|
||||||
|
// Wait a bit and try once
|
||||||
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
if (globalPlayer) {
|
||||||
|
player = globalPlayer;
|
||||||
|
connected.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
registering = true;
|
registering = true;
|
||||||
|
|
||||||
const instanceId = getOrCreateInstanceId();
|
let instanceId;
|
||||||
|
try {
|
||||||
|
instanceId = getOrCreateInstanceId();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[WebRenderer] failed to get instanceId:', e);
|
||||||
|
registering = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[WebRenderer] registering with instanceId:', instanceId);
|
console.log('[WebRenderer] registering with instanceId:', instanceId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -86,6 +128,7 @@ export function useWebRenderer() {
|
|||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
streamUrl.value = data.stream_url;
|
streamUrl.value = data.stream_url;
|
||||||
rendererUdn.value = data.udn;
|
rendererUdn.value = data.udn;
|
||||||
|
savePersistedUdn(data.udn);
|
||||||
|
|
||||||
player = new PMOPlayer(instanceId);
|
player = new PMOPlayer(instanceId);
|
||||||
globalPlayer = player;
|
globalPlayer = player;
|
||||||
@@ -103,6 +146,13 @@ export function useWebRenderer() {
|
|||||||
|
|
||||||
connected.value = true;
|
connected.value = true;
|
||||||
onConnectedCallback?.();
|
onConnectedCallback?.();
|
||||||
|
|
||||||
|
// Force refresh renderers list after registration
|
||||||
|
// The SSE might miss the initial event
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('[WebRenderer] forcing renderers refresh');
|
||||||
|
window.dispatchEvent(new CustomEvent('webrenderer-registered'));
|
||||||
|
}, 500);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[WebRenderer] register error:", e);
|
console.error("[WebRenderer] register error:", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface RendererSummary {
|
|||||||
id: string;
|
id: string;
|
||||||
friendly_name: string;
|
friendly_name: string;
|
||||||
model_name: string;
|
model_name: string;
|
||||||
|
manufacturer?: string;
|
||||||
protocol: RendererProtocolSummary;
|
protocol: RendererProtocolSummary;
|
||||||
capabilities: RendererCapabilitiesSummary;
|
capabilities: RendererCapabilitiesSummary;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
|
|||||||
@@ -77,9 +77,9 @@ const onlineServersCount = computed(
|
|||||||
() => allServers.value.filter((s) => s.online).length,
|
() => allServers.value.filter((s) => s.online).length,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Nombre de renderers online pour afficher dans le badge
|
// Nombre de renderers online (filtrés) pour afficher dans le badge
|
||||||
const onlineRenderersCount = computed(
|
const onlineRenderersCount = computed(
|
||||||
() => allRenderers.value.filter((r) => r.online).length,
|
() => filterRenderers(allRenderers.value).filter((r) => r.online).length,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Gestion de l'ouverture du drawer depuis le bouton
|
// Gestion de l'ouverture du drawer depuis le bouton
|
||||||
@@ -103,14 +103,19 @@ function handleRendererSelect(rendererId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtre la liste des renderers pour exclure les WebRenderers d'autres navigateurs.
|
// Filter to show only our own WebRenderer based on UDN match
|
||||||
// Seul le WebRenderer créé par ce navigateur (identifié par son UDN) reste visible.
|
|
||||||
function filterRenderers(renderers: typeof allRenderers.value) {
|
function filterRenderers(renderers: typeof allRenderers.value) {
|
||||||
const myUdn = webRenderer.rendererUdn.value;
|
const myUdn = webRenderer.rendererUdn.value;
|
||||||
|
const normalizedUdn = myUdn?.replace(/^uuid:/, '') ?? null;
|
||||||
return renderers.filter((r) => {
|
return renderers.filter((r) => {
|
||||||
if (r.model_name !== "WebRenderer") return true; // renderer classique : toujours visible
|
// WebRenderers have "Web Audio – " as friendly_name prefix
|
||||||
if (myUdn === null) return false; // pas encore de session : masquer tous les WebRenderers
|
const isWebRenderer = r.friendly_name?.startsWith("Web Audio – ") ?? false;
|
||||||
return r.id === myUdn; // ne garder que le nôtre
|
if (isWebRenderer) {
|
||||||
|
if (normalizedUdn === null) return false;
|
||||||
|
const normalizedId = r.id.replace(/^uuid:/, '');
|
||||||
|
return normalizedId === normalizedUdn;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ impl MediaRendererRegistry {
|
|||||||
instance_id: &str,
|
instance_id: &str,
|
||||||
stream_url_base: &str,
|
stream_url_base: &str,
|
||||||
renderer_name: &str,
|
renderer_name: &str,
|
||||||
|
friendly_name: &str,
|
||||||
adapter_fn: impl FnOnce(SharedState) -> Arc<dyn DeviceAdapter>,
|
adapter_fn: impl FnOnce(SharedState) -> Arc<dyn DeviceAdapter>,
|
||||||
) -> Result<(String, String, bool), MediaRendererError> {
|
) -> Result<(String, String, bool), MediaRendererError> {
|
||||||
if let Some(cancel) = self.pending_unregister.write().remove(instance_id) {
|
if let Some(cancel) = self.pending_unregister.write().remove(instance_id) {
|
||||||
@@ -77,7 +78,7 @@ impl MediaRendererRegistry {
|
|||||||
if let Some(existing) = instances.get(instance_id) {
|
if let Some(existing) = instances.get(instance_id) {
|
||||||
tracing::info!(instance_id = %instance_id, "MediaRenderer: reconnecting existing instance");
|
tracing::info!(instance_id = %instance_id, "MediaRenderer: reconnecting existing instance");
|
||||||
#[cfg(feature = "pmoserver")]
|
#[cfg(feature = "pmoserver")]
|
||||||
self.register_with_control_point(&existing.device_instance, renderer_name)?;
|
self.register_with_control_point(&existing.device_instance, renderer_name, &existing.udn)?;
|
||||||
let stream_url = format!("{}/{}/stream", stream_url_base, instance_id);
|
let stream_url = format!("{}/{}/stream", stream_url_base, instance_id);
|
||||||
let should_play = {
|
let should_play = {
|
||||||
let s = existing.state.read();
|
let s = existing.state.read();
|
||||||
@@ -90,7 +91,7 @@ impl MediaRendererRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let instance = self.create_instance_with_adapter(instance_id, stream_url_base, renderer_name, adapter_fn).await?;
|
let instance = self.create_instance_with_adapter(instance_id, stream_url_base, renderer_name, friendly_name, adapter_fn).await?;
|
||||||
let instance = Arc::new(instance);
|
let instance = Arc::new(instance);
|
||||||
let stream_url = format!("{}/{}/stream", stream_url_base, instance_id);
|
let stream_url = format!("{}/{}/stream", stream_url_base, instance_id);
|
||||||
let udn = instance.udn.clone();
|
let udn = instance.udn.clone();
|
||||||
@@ -231,18 +232,14 @@ impl MediaRendererRegistry {
|
|||||||
instance_id: &str,
|
instance_id: &str,
|
||||||
stream_url_base: &str,
|
stream_url_base: &str,
|
||||||
renderer_name: &str,
|
renderer_name: &str,
|
||||||
|
friendly_name: &str,
|
||||||
adapter_fn: impl FnOnce(SharedState) -> Arc<dyn DeviceAdapter>,
|
adapter_fn: impl FnOnce(SharedState) -> Arc<dyn DeviceAdapter>,
|
||||||
) -> Result<MediaRendererInstance, MediaRendererError> {
|
) -> Result<MediaRendererInstance, MediaRendererError> {
|
||||||
let candidate_udn = instance_id.to_ascii_lowercase();
|
let candidate_udn = instance_id.to_ascii_lowercase();
|
||||||
let full_udn = format!("uuid:{}", candidate_udn);
|
let full_udn = format!("uuid:{}", candidate_udn);
|
||||||
|
|
||||||
if let Err(e) = pmoconfig::get_config().set_device_udn(
|
// Note: WebRenderers don't need to persist UDN to config since they're tied to browser tabs
|
||||||
"MediaRenderer",
|
// (instance_id comes from sessionStorage, not a persistent device)
|
||||||
instance_id,
|
|
||||||
candidate_udn.clone(),
|
|
||||||
) {
|
|
||||||
tracing::warn!("MediaRenderer: failed to persist UDN: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
let state: SharedState = Arc::new(parking_lot::RwLock::new(RendererState::default()));
|
let state: SharedState = Arc::new(parking_lot::RwLock::new(RendererState::default()));
|
||||||
let adapter = adapter_fn(state.clone());
|
let adapter = adapter_fn(state.clone());
|
||||||
@@ -275,7 +272,7 @@ impl MediaRendererRegistry {
|
|||||||
let device = MediaRendererFactory::create_device_with_pipeline(
|
let device = MediaRendererFactory::create_device_with_pipeline(
|
||||||
instance_id,
|
instance_id,
|
||||||
"MediaRenderer",
|
"MediaRenderer",
|
||||||
"WebRenderer",
|
friendly_name,
|
||||||
pipeline.clone(),
|
pipeline.clone(),
|
||||||
state.clone(),
|
state.clone(),
|
||||||
stream_url_base,
|
stream_url_base,
|
||||||
@@ -289,7 +286,7 @@ impl MediaRendererRegistry {
|
|||||||
.map_err(|e| MediaRendererError::RegistrationError(e.to_string()))?
|
.map_err(|e| MediaRendererError::RegistrationError(e.to_string()))?
|
||||||
};
|
};
|
||||||
|
|
||||||
self.register_with_control_point(&di, renderer_name)?;
|
self.register_with_control_point(&di, renderer_name, &full_udn)?;
|
||||||
(di, ip)
|
(di, ip)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -307,7 +304,7 @@ impl MediaRendererRegistry {
|
|||||||
let device = MediaRendererFactory::create_device_with_pipeline(
|
let device = MediaRendererFactory::create_device_with_pipeline(
|
||||||
instance_id,
|
instance_id,
|
||||||
"MediaRenderer",
|
"MediaRenderer",
|
||||||
"WebRenderer",
|
friendly_name,
|
||||||
pipeline.clone(),
|
pipeline.clone(),
|
||||||
state.clone(),
|
state.clone(),
|
||||||
stream_url_base,
|
stream_url_base,
|
||||||
@@ -334,9 +331,11 @@ impl MediaRendererRegistry {
|
|||||||
&self,
|
&self,
|
||||||
di: &Arc<DeviceInstance>,
|
di: &Arc<DeviceInstance>,
|
||||||
renderer_name: &str,
|
renderer_name: &str,
|
||||||
|
instance_udn: &str,
|
||||||
) -> Result<(), MediaRendererError> {
|
) -> Result<(), MediaRendererError> {
|
||||||
let base_url = di.base_url().to_string();
|
let base_url = di.base_url().to_string();
|
||||||
let udn = di.udn().to_ascii_lowercase();
|
// Use the instance's UDN, not the device's stored UDN
|
||||||
|
let udn = instance_udn.trim_start_matches("uuid:").to_ascii_lowercase();
|
||||||
let udn_with_prefix = format!("uuid:{}", udn);
|
let udn_with_prefix = format!("uuid:{}", udn);
|
||||||
let device_route = di.route();
|
let device_route = di.route();
|
||||||
let model = di.get_model();
|
let model = di.get_model();
|
||||||
@@ -359,7 +358,7 @@ impl MediaRendererRegistry {
|
|||||||
udn_with_prefix.clone(),
|
udn_with_prefix.clone(),
|
||||||
model.friendly_name().to_string(),
|
model.friendly_name().to_string(),
|
||||||
model.model_name().to_string(),
|
model.model_name().to_string(),
|
||||||
"PMOMusic".to_string(),
|
"PMOMusic-WebRenderer".to_string(),
|
||||||
RendererProtocol::UpnpAvOnly,
|
RendererProtocol::UpnpAvOnly,
|
||||||
RendererCapabilities {
|
RendererCapabilities {
|
||||||
has_avtransport: true,
|
has_avtransport: true,
|
||||||
|
|||||||
@@ -87,19 +87,24 @@ impl MediaRendererFactory {
|
|||||||
pub fn create_device_with_pipeline(
|
pub fn create_device_with_pipeline(
|
||||||
device_name: &str,
|
device_name: &str,
|
||||||
device_type: &str,
|
device_type: &str,
|
||||||
friendly_name_suffix: &str,
|
friendly_name: &str,
|
||||||
pipeline: PipelineHandle,
|
pipeline: PipelineHandle,
|
||||||
state: SharedState,
|
state: SharedState,
|
||||||
stream_url_base: &str,
|
stream_url_base: &str,
|
||||||
) -> Result<Device, FactoryError> {
|
) -> Result<Device, FactoryError> {
|
||||||
let avtransport = Self::build_avtransport(pipeline.clone(), state.clone(), device_name, stream_url_base)?;
|
let avtransport = Self::build_avtransport(
|
||||||
|
pipeline.clone(),
|
||||||
|
state.clone(),
|
||||||
|
device_name,
|
||||||
|
stream_url_base,
|
||||||
|
)?;
|
||||||
let renderingcontrol = Self::build_renderingcontrol(state.clone())?;
|
let renderingcontrol = Self::build_renderingcontrol(state.clone())?;
|
||||||
let connectionmanager = Self::build_connectionmanager()?;
|
let connectionmanager = Self::build_connectionmanager()?;
|
||||||
|
|
||||||
let device = Device::new_from_config(
|
let device = Device::new(
|
||||||
device_name.to_string(),
|
device_name.to_string(),
|
||||||
device_type.to_string(),
|
device_type.to_string(),
|
||||||
friendly_name_suffix.to_string(),
|
friendly_name.to_string(),
|
||||||
);
|
);
|
||||||
device
|
device
|
||||||
.add_service(Arc::new(avtransport))
|
.add_service(Arc::new(avtransport))
|
||||||
|
|||||||
15
pmowebrenderer/src/helpers.rs
Normal file
15
pmowebrenderer/src/helpers.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
pub fn extract_browser_name(ua: &str) -> &str {
|
||||||
|
if ua.contains("Edg/") || ua.contains("EdgA/") {
|
||||||
|
"Edge"
|
||||||
|
} else if ua.contains("OPR/") || ua.contains("Opera") {
|
||||||
|
"Opera"
|
||||||
|
} else if ua.contains("Chrome/") {
|
||||||
|
"Chrome"
|
||||||
|
} else if ua.contains("Firefox/") {
|
||||||
|
"Firefox"
|
||||||
|
} else if ua.contains("Safari/") {
|
||||||
|
"Safari"
|
||||||
|
} else {
|
||||||
|
"Browser"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
//! - Les commandes UPnP sont relayées vers le pipeline audio via PipelineControl
|
//! - Les commandes UPnP sont relayées vers le pipeline audio via PipelineControl
|
||||||
|
|
||||||
mod adapter;
|
mod adapter;
|
||||||
|
mod helpers;
|
||||||
mod register;
|
mod register;
|
||||||
mod stream;
|
mod stream;
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ mod stream;
|
|||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
pub use adapter::BrowserAdapter;
|
pub use adapter::BrowserAdapter;
|
||||||
|
pub use helpers::extract_browser_name;
|
||||||
|
|
||||||
#[cfg(feature = "pmoserver")]
|
#[cfg(feature = "pmoserver")]
|
||||||
pub use config::WebRendererExt;
|
pub use config::WebRendererExt;
|
||||||
@@ -17,6 +17,7 @@ use pmomediarenderer::PipelineControl;
|
|||||||
use pmomediarenderer::{DeviceCommand, MediaRendererRegistry};
|
use pmomediarenderer::{DeviceCommand, MediaRendererRegistry};
|
||||||
|
|
||||||
use crate::adapter::BrowserAdapter;
|
use crate::adapter::BrowserAdapter;
|
||||||
|
use crate::helpers::extract_browser_name;
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, serde::Deserialize)]
|
||||||
pub struct PlayerStateReport {
|
pub struct PlayerStateReport {
|
||||||
@@ -51,11 +52,15 @@ pub async fn register_handler(
|
|||||||
"WebRenderer: register request"
|
"WebRenderer: register request"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let browser_name = extract_browser_name(&req.user_agent);
|
||||||
|
let friendly_name = format!("Web Audio – {}", browser_name);
|
||||||
|
|
||||||
match registry
|
match registry
|
||||||
.register_or_reconnect(
|
.register_or_reconnect(
|
||||||
&req.instance_id,
|
&req.instance_id,
|
||||||
"/api/webrenderer",
|
"/api/webrenderer",
|
||||||
"PMOMusic WebRenderer/2.0",
|
"PMOMusic WebRenderer/2.0",
|
||||||
|
&friendly_name,
|
||||||
|state| Arc::new(BrowserAdapter::new(state)),
|
|state| Arc::new(BrowserAdapter::new(state)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.3.35
|
0.3.37
|
||||||
|
|||||||
Reference in New Issue
Block a user