MusicBee UPnP Plugin Ayuda

Novedades

2.0.9 - 2026-08-23

El aviso de actualización abre sus páginas en tu idioma

Qué: los enlaces Novedades y Descargar del aviso de actualización ahora abren las páginas del plugin en el idioma de MusicBee, en lugar de en inglés.

Por qué: esos dos enlaces restringían el idioma a uno de cuatro —inglés, francés, español o alemán— antes de enviarlo al sitio web, por lo que a todos los demás se les entregaba la página en inglés incluso cuando existía una traducción. Ahora pasan el idioma de MusicBee sin cambios y dejan que el sitio web decida qué servir, que es lo que siempre ha hecho el botón Ayuda que está junto a ellos.

2.0.8 - 2026-08-22

El plugin ahora se presenta correctamente a las apps y dispositivos que lo descubren en tu red, y los ajustes de Perfiles de dispositivo vuelven a estar alineados.

Tus dispositivos muestran el fabricante, modelo y versión correctos

Qué: cuando una app de control, un teléfono o un televisor encuentra MusicBee en tu red, ahora presenta este plugin como hecho por yaiol, apunta al sitio propio del plugin, lo describe como cubriendo los tres roles —servidor, reproductor y renderizador— y reporta la versión que tienes instalada.

Por qué: cada dispositivo UPnP anuncia quién lo hizo y qué es, y las apps de control muestran eso como la identidad del dispositivo. Este plugin seguía anunciando los detalles del plugin original del que fue bifurcado: el nombre de otro autor, el sitio web de MusicBee en lugar del suyo propio, y un número de modelo congelado en "1.0" desde la primera versión. Desde tu teléfono no había forma de saber con qué plugin estabas hablando, y mucho menos qué versión de él. Esos detalles ahora provienen del propio plugin, por lo que la versión mostrada junto al dispositivo se mantiene correcta con cada actualización.

Los ajustes de Perfiles de dispositivo vuelven a estar alineados

Qué: en la pestaña Perfiles de dispositivo, las etiquetas y sus cuadros comparten un borde izquierdo y están espaciados uniformemente, y el rango de frecuencia de muestreo se lee como una sola fila.

Por qué: los campos se habían separado a medida que se añadían opciones a la pestaña con el tiempo, y la etiqueta "a" del rango de frecuencia de muestreo había terminado encima del cuadro contiguo —legible una vez que sabías lo que decía, desconcertante la primera vez que lo veías.

2.0.7 - 2026-08-08

Las pistas más grandes conservan su título y su deslizador de posición

Qué: una pista más grande enviada desde un teléfono —un archivo FLAC largo, de alta resolución o DSD— ahora muestra su título correcto y se puede mover a través de ella, como una pequeña. Anteriormente, algunas de ellas se reproducían a través de la red, con una dirección web en lugar del título y un deslizador que no hacía nada.

Por qué: el plugin espera su copia local antes de comenzar, pero solía decidir de antemano si valía la pena esperar un archivo, basándose en su tamaño. Eso era realmente una suposición sobre la velocidad de tu red, que no tiene forma de saber: una pista de 65 MB se consideraba demasiado grande, luego terminaba de descargarse un segundo después, cómodamente dentro del tiempo de espera que ya había abandonado. Ahora simplemente observa la descarga. Mientras sigue llegando, el plugin sigue esperando, sin importar cuánto tiempo tarde; solo se rinde cuando la transferencia se detiene genuinamente, lo que ahora nota más rápido que el antiguo retraso fijo.

2.0.6 - 2026-08-03

Los álbumes enviados desde un teléfono ahora se reproducen sin pausas entre pistas, y la radio por internet se reconoce como radio en lugar de ser tratada como una canción inusualmente larga.

Los álbumes se reproducen sin pausas

Qué: cuando envías un álbum completo desde tu teléfono, MusicBee ahora pasa de una pista a la siguiente sin pausa, de modo que las grabaciones en vivo, los sets de DJ y las obras clásicas continuas se mantienen unidas.

Por qué: el estándar permite que una aplicación de control diga "esto es lo que viene a continuación", lo que hace posible una unión perfecta. Esa instrucción no se aceptaba en absoluto, por lo que la aplicación no tenía dónde colocar la pista siguiente y la anunciaba como si fuera la actual, la causa del problema del álbum solucionado en la versión anterior. Ahora se acepta correctamente: la siguiente pista se obtiene mientras la actual sigue sonando, y el propio reproductor de MusicBee cruza el límite.

La radio por internet se reconoce como radio

Qué: una estación en vivo enviada a MusicBee se reproduce como un stream y nunca se descarga.

Por qué: descargar una transmisión no tiene sentido —no tiene fin y no hay nada que saltar—, pero el plugin antes no tenía forma de distinguirla de un archivo de música, así que comenzaba a descargar y se detenía una vez que la descarga superaba un tamaño fijo. La aplicación de control indica cuál de los dos está enviando, y eso ahora se lee directamente. Sin configuración, sin adivinanzas.

Las pistas largas de alta resolución conservan su copia

Qué: los archivos DSD y las grabaciones largas de 24 bits ahora se pueden mover como cualquier otra pista.

Por qué: se veían afectados por el límite de tamaño mencionado anteriormente —un movimiento de alta resolución de 20 minutos o una pista DSD de 10 minutos lo excedían—, por lo que su copia se abandonaba y el control deslizante de posición dejaba de funcionar precisamente para el material que más valía la pena revisar. Con la radio identificada correctamente, no se necesita un límite de tamaño.

2.0.5 - 2026-08-03

Dos correcciones para controlar MusicBee desde un teléfono: el volumen ahora significa lo mismo en ambos extremos, y la transmisión de un álbum completo sigue funcionando después de la primera pista.

El volumen de tu teléfono coincide con el volumen de MusicBee

Qué: subir el volumen al máximo en tu teléfono ahora alcanza el máximo en MusicBee, y la propia configuración de MusicBee se lee correctamente en el teléfono.

Por qué: el plugin nunca le dijo a la aplicación de control cuál era su volumen más alto, por lo que cada aplicación tenía que adivinar. Una de ellas se estableció en 69, lo que significaba que su 100% solo alcanzaba el 69% en MusicBee, mientras que el 100% de MusicBee volvía como 144% en el teléfono, y los botones de volumen del teléfono nunca podían alcanzar el máximo. El renderizador ahora indica el rango claramente, por lo que ambos extremos están hablando de la misma escala.

La transmisión de un álbum mantiene sus títulos y su barra de posición

Qué: cada pista de un álbum enviado desde un teléfono ahora muestra su título correcto y se puede mover a través de ella, no solo la primera.

Por qué: una aplicación de control anuncia la siguiente pista una fracción de segundo después de la actual, y ese anuncio estaba cancelando la copia que se estaba obteniendo para la pista que estaba a punto de reproducirse, por lo que la mayoría de las pistas volvieron silenciosamente a reproducirse a través de la red, lo que pierde tanto el título como la capacidad de saltar. Ahora se mantienen copias de varias pistas una al lado de la otra, por lo que un anuncio ya no puede cancelar la que está en uso.

2.0.4 - 2026-08-02

La música enviada a MusicBee desde un teléfono u otro servidor ahora se comporta como una pista real: puedes moverte por ella y muestra su título correcto de inmediato. Además, una solución para los streamers de alta fidelidad que se presentan como un dispositivo combinado.

Muévete por una pista enviada desde otro lugar

Qué: arrastrar el control deslizante de posición ahora funciona para una pista enviada desde tu teléfono, un NAS u otro servidor multimedia. Para que esto sea posible, MusicBee descarga una copia de la pista a una carpeta temporal mientras comienza a reproducirse, y reproduce esa copia. Tarda aproximadamente un segundo en una red doméstica, la copia se elimina tan pronto como envías otra pista, y cualquier resto se borra la próxima vez que se inicia MusicBee.

Por qué: MusicBee puede iniciar y detener algo que está escuchando a través de la red, pero no puede moverse por ello, por lo que el control deslizante parecía saltar y luego volvía directamente a donde estaba, sin explicación. Reproducir un archivo ordinario en tu propio disco elimina la limitación por completo en lugar de sortearla.

Título y duración correctos desde la primera nota

Qué: una pista enviada por una aplicación que no le da a sus archivos una extensión de archivo ordinaria ahora muestra su título y duración reales tan pronto como comienza, en lugar de aparecer como una larga dirección web.

Por qué: MusicBee identifica una pista —y encuentra sus etiquetas— a partir de la extensión del archivo, y algunos reproductores entregan direcciones sin ninguna extensión. La copia local siempre lleva la correcta, por lo que la pista se reconoce independientemente de cómo la llame la aplicación que la envía.

Un salto que no se puede hacer ahora lo indica

Qué: si el renderizador realmente no puede moverse al punto solicitado, se le informa a la aplicación de control y esta lo reporta.

Por qué: anteriormente respondía "hecho" de todos modos, por lo que el control deslizante volvía un segundo después sin nada que explicara por qué. Una negativa honesta es más fácil de manejar que una silenciosa.

Los streamers de alta fidelidad combinados se leen correctamente

Qué: cuando MusicBee reproduce en un dispositivo que se presenta como una unidad combinada —un streamer Marantz o Denon, donde el reproductor se encuentra dentro de un envoltorio del fabricante junto con un servidor multimedia— el plugin ahora lee los detalles propios del reproductor en lugar de los del servidor multimedia.

Por qué: anteriormente le había estado preguntando a la mitad equivocada del dispositivo qué formatos de audio podía manejar, sin obtener una respuesta utilizable, y continuando sin verificar nunca, exactamente el hardware donde el manejo de formatos más necesita ser correcto. La descripción del modelo de un dispositivo también se tiene en cuenta ahora al emparejarlo con un perfil de dispositivo; se leía del lugar equivocado y se descartaba.

2.0.3 - 2026-08-02

El rol de reproducción remota crece: ahora se le puede enviar a MusicBee música que no está en su biblioteca —un archivo en tu teléfono, en un NAS, en otro servidor— en lugar de solo las pistas que ya posee.

Reproduce música enviada desde tu teléfono, no solo desde tu propia biblioteca

Qué: cuando usas una aplicación de control como Symfonium o BubbleUPnP para enviar música a MusicBee, la pista ya no tiene que provenir de la propia biblioteca de MusicBee. Ahora se reproduce un archivo almacenado en el propio teléfono, en un NAS o en otro servidor multimedia. El título y la duración provienen de la aplicación que lo envió, por lo que la pista se muestra correctamente aunque MusicBee nunca haya visto el archivo.

Por qué: el rol de reproducción remota se creó para el caso en que navegas por la biblioteca de esta PC desde tu teléfono y tocas una canción —la pista ya estaba en la PC, por lo que MusicBee simplemente reprodujo su propio archivo. Cualquier cosa que llegara de otro lugar se descartaba silenciosamente, lo que hacía que la función fuera inútil para el caso igualmente natural de enviar música desde el teléfono a los buenos altavoces.

Una pista que no se puede reproducir lo indica

Qué: si el renderizador realmente no puede reproducir lo que se le envió, ahora lo informa a la aplicación que lo envió.

Por qué: anteriormente respondía "recibido" a cualquier cosa, por lo que la aplicación de control seguía adelante y presionaba reproducir. Sin nada realmente cargado, MusicBee reiniciaba cualquier pista que hubiera quedado de antes —y si ese archivo ya no estaba, se quejaba de que su fuente no se podía encontrar. El error nombraba una pista no relacionada y no señalaba el problema real.

Enviar una nueva pista mientras está en pausa ahora reproduce esa pista

Qué: si MusicBee está en pausa y tu aplicación de control le envía algo nuevo, la nueva pista comienza.

Por qué: reanudar tenía prioridad sobre la carga, por lo que la pista en pausa continuaba donde la dejaste y la pista que acababas de elegir se descartaba sin decir una palabra.

2.0.2 - 2026-08-01

La búsqueda es el tema: ahora funciona por artista, devuelve los resultados correctos y es rápida en una biblioteca grande. Además, una nueva forma de dirigir la reproducción aleatoria de tu aplicación de control y una solución para tres botones que no llevaban a ninguna parte.

La búsqueda por artista funciona de verdad

Qué: buscar un artista desde tu aplicación de control ahora devuelve la música de ese artista. La búsqueda coincide tanto con el artista de la pista como con el artista del álbum, por lo que se encuentra una compilación tanto si escribes el nombre del intérprete como el nombre con el que está archivado el álbum.

Por qué: una búsqueda de artista se confundía anteriormente con "dame todo de este tipo" — el artista que escribías se descartaba y se devolvía toda la biblioteca, por lo que una búsqueda que debería haber coincidido con unos pocos cientos de pistas devolvía decenas de miles. Coincidir solo con uno de los dos campos de artista habría perdido silenciosamente la mitad de los resultados, por lo que se verifican ambos.

La página de resultados de búsqueda se pagina correctamente

Qué: desplazarse por una larga lista de resultados de búsqueda ahora se mueve a través de ella. Cada página a la que te desplazas es la página que obtienes.

Por qué: el servidor solía responder a cada solicitud con el primer puñado de resultados mientras informaba el recuento total de coincidencias, por lo que una aplicación que se desplazaba para obtener más seguía recibiendo los mismos elementos y nunca llegaba al final.

La búsqueda es mucho más rápida en una biblioteca grande

Qué: una búsqueda ahora ejecuta una sola consulta para todo el conjunto de resultados y lee solo las etiquetas de la página que estás viendo.

Por qué: cada página anteriormente volvía a ejecutar la consulta contra la biblioteca y luego cargaba las etiquetas de cada coincidencia — miles de ellas — para mostrar una docena. En una colección grande, eso hacía que cada desplazamiento se pausara. Los resultados también se eliminan cada vez que se actualiza la biblioteca, por lo que una edición nunca se sirve obsoleta.

Dirige tu reproducción aleatoria a un filtro

Qué: una nueva configuración Reproducciones aleatorias de en la pestaña Opciones de la biblioteca. Déjala en Toda la música y las carpetas Pistas aleatorias / Álbumes aleatorios de tu aplicación de control se comportarán como antes; elige uno de tus filtros de MusicBee y cada solicitud aleatoria se extraerá de ese filtro en su lugar. También se ofrecen filtros ocultos.

Por qué: una carpeta de reproducción aleatoria pide una porción de "todo", y es la única solicitud que no lleva ninguna pista sobre lo que querías decir — por lo que siempre se extraía de toda la biblioteca, palabra hablada y todo. Aquí es donde dices lo que significa "todo". Abrir una carpeta de reproducción aleatoria desde dentro de un filtro en el dispositivo todavía reproduce aleatoriamente ese filtro: una elección que haces mientras navegas prevalece sobre la configuración.

Ayuda, GitHub y la comprobación de actualizaciones llegan a páginas reales

Qué: los botones Ayuda y GitHub en el cuadro de diálogo de configuración, y la comprobación automática de una nueva versión, ahora abren las páginas que nombran.

Por qué: los tres se construyeron a partir de una forma abreviada del nombre del complemento que nunca ha existido en ninguna página, por lo que cada uno falló silenciosamente — los botones parecían no hacer nada y la comprobación de actualizaciones nunca informó nada, sin importar cuánto tiempo hubiera estado disponible una nueva versión.

2.0.1 - 2026-07-26

Una ronda de correcciones de reproducción y navegación, centrada en podcasts y en las aplicaciones de controlador (como BubbleUPnP) que gestionan la reproducción y la reproducción aleatoria.

Los podcasts empiezan a reproducirse sin pausa

Qué: un episodio de podcast descargado ahora indica su duración real y tamaño de archivo de antemano, directamente desde el archivo en disco a los metadatos del medio.

Por qué: sin una duración indicada, un controlador como BubbleUPnP vuelve a escanear todo el flujo de audio cada vez que pulsas reproducir, solo para averiguar la duración del episodio, por lo que la reproducción comenzaba solo después de una pausa notable. Con la duración ahora anunciada, comienza limpiamente.

Los podcasts aparecen cuando navegas por artista

Qué: el nombre del programa de un podcast ahora se archiva como su artista, reflejado en los campos Artista y Artista del álbum (y sus variantes de ordenación), exactamente como ya rellena el campo Álbum.

Por qué: una ruta de navegación que agrupa por un campo de artista solía encontrar el artista de cada podcast en blanco y terminaba en un nivel vacío. Tratar el programa en sí como el artista, consistente con tratarlo como el álbum, significa que esas rutas ahora llegan a los episodios en lugar de a nada.

"Reproducidos recientemente" y la transmisión funcionan correctamente después de un reinicio

Qué: reproducir un podcast, audiolibro, bandeja de entrada o pista de radio directamente, sin navegar primero a él, como lo hacen la lista "Reproducidos recientemente" de BubbleUPnP y los destinos de transmisión, ya no falla. El complemento ahora carga la pista bajo demanda cuando se le solicita por ID.

Por qué: esas listas solicitan una pista justo después de que MusicBee se reinicia, antes de que se haya navegado por nada, por lo que el complemento nunca había visto la ID y respondía "ID incorrecta". Ahora carga forzosamente las fuentes relevantes en esa primera solicitud directa y encuentra la pista.

"Pistas aleatorias" y "Álbumes aleatorios" devuelven resultados

Qué: las carpetas de reproducción aleatoria "Pistas aleatorias" y "Álbumes aleatorios" de BubbleUPnP, que solicitan una porción aleatoria de toda la biblioteca, ahora vuelven llenas.

Por qué: estas son búsquedas sin título que coincidir, y el complemento anteriormente las respondía desde una lista interna vacía, por lo que siempre mostraban nada. Ahora se sirven, correctamente paginadas, desde la misma ruta de consulta bajo demanda que utiliza el resto de la navegación.

Los álbumes con una etiqueta en blanco listan sus pistas

Qué: abrir un álbum que se agrupó en un valor vacío, por ejemplo, pistas de la bandeja de entrada que no tienen año, ahora muestra sus pistas.

Por qué: la coincidencia que recopila las pistas de un álbum trataba "esta etiqueta está vacía" como "sin coincidencia", por lo que cualquier álbum formado a partir de un campo en blanco no mostraba nada. Un valor de grupo vacío ahora coincide correctamente con las pistas que lo comparten.


2.0.0 - 2026-07-22

Esta es la primera versión pública del fork de código abierto yaiol del plugin UPnP de MusicBee. Se presenta en dos partes: todo lo nuevo de este fork, y luego las correcciones y mejoras realizadas al plugin original. Cada elemento mantiene la forma Qué / Por qué del catálogo de características internas del proyecto para que el razonamiento detrás de cada cambio esté en la página, no solo el cambio.

Novedades de este fork

MediaRenderer - reproducir en MusicBee

N01 - MusicBee como reproductor "play-to"

Qué: normalmente este plugin funciona en una dirección: un teléfono u otro dispositivo navega por la biblioteca de MusicBee y reproduce la música en sí mismo. Esta característica añade la dirección opuesta: permite que MusicBee sea el reproductor. Desde una aplicación de control en tu teléfono (como BubbleUPnP) puedes elegir tu MusicBee de escritorio como el dispositivo que reproduce, y luego controlarlo desde tu mano: reproducir, pausar, detener, saltar hacia adelante o hacia atrás, ir a un punto de la pista y cambiar el volumen o silenciar.

Por qué: convierte tu teléfono en un mando a distancia para la música que ya tienes en tu PC. Siéntate en el sofá, navega por tu biblioteca en el teléfono, toca una pista y saldrá por los altavoces conectados a tu escritorio, con control total desde donde estés sentado. El plugin original nunca incluyó esto como una característica funcional.

Activarlo: está desactivado por defecto, porque activarlo permite que cualquier dispositivo de tu red doméstica inicie la reproducción en tu PC. Lo activas con una casilla de verificación en la pestaña General del cuadro de diálogo de configuración. Los tres roles del plugin tienen su propia casilla de verificación allí: compartir mi biblioteca (Servidor), permitir que otros reproduzcan en mí (Reproductor) y reproducir en otros dispositivos (Punto de control), y el cuadro de diálogo solo muestra las pestañas de configuración que los roles que activaste realmente necesitan, para que nunca te encuentres con opciones que no te aplican.

Distinguir tus máquinas: puedes darle al reproductor el nombre que quieras (comienza como "MusicBee (yaiol)"). Ese nombre es el que aparece en la lista de destinos de reproducción de tu teléfono, así que cuando más de un PC esté ejecutando MusicBee, podrás saber cuál es cuál. Un cambio de nombre surte efecto inmediatamente, sin necesidad de reiniciar.

El mejor sonido posible cuando se reproduce a sí mismo: cuando navegas por la biblioteca propia de MusicBee desde tu teléfono y envías una pista de vuelta a ese mismo MusicBee, el plugin reconoce que se le está pidiendo que reproduzca uno de sus propios archivos y simplemente lo reproduce directamente desde tu disco. El resultado es exacto e instantáneo (bit-perfecto, con el ecualizador y la nivelación de volumen propios de MusicBee aplicados), en lugar de enviar inútilmente el audio a la red y de vuelta a sí mismo.

Ejecutarlo de forma privada: los tres roles funcionan de forma independiente, por lo que puedes activar el reproductor dejando la opción de compartir la biblioteca desactivada. En esa configuración de "solo reproductor", tu biblioteca permanece completamente oculta de la red (solo se anuncia el destino de reproducción) y MusicBee nunca ofrecerá reproducir en sí mismo.


Comportamiento de reproducción

N02 - FLAC 5.1 no se mezcla automáticamente a estéreo

Qué: la restricción de recuento de canales en MediaServerDevice.GetEncodedFile era If StereoOnly OrElse Not isPcmData Then channelCount = 2. La cláusula Not isPcmData mezclaba silenciosamente a estéreo cada transcodificación no PCM (FLAC, MP3, AAC, Ogg) independientemente del recuento de canales de origen, inutilizando los reproductores compatibles con 5.1 cuando el origen era FLAC 5.1. Ahora la segunda cláusula excluye FLAC: Not isPcmData AndAlso encoder.Codec <> FileCodec.Flac. FLAC 5.1 pasa; MP3/AAC/Ogg siguen forzando el estéreo porque los codificadores de línea de comandos de MusicBee para esos formatos esperan una entrada de 2 canales.

Por qué: el objetivo de transcodificar una fuente FLAC 5.1 a una salida FLAC es preservar la mezcla multicanal. La mezcla silenciosa a estéreo hacía que la opción de transcodificación FLAC fuera inútil para la escucha envolvente. Con N02, hace lo correcto.


Arquitectura

El cambio estructural que hace que el fork sea viable en una biblioteca grande, ausente en el plugin original.

N03 - Árbol de navegación perezoso (bajo demanda)

Qué: el plugin original construía todo el árbol de navegación al inicio de MusicBee (enumerar cada pista, Library_GetFileTags completo por archivo, ensamblar toda la jerarquía de contenedores) antes de abrir el puerto HTTP. En una biblioteca real (más de 50k pistas, 5400 episodios de podcast, cientos de estaciones), eso son minutos de arranque en frío, y el árbol permanece en la RAM para siempre, incluyendo ramas que ningún cliente abre. Este fork no construye nada por adelantado: la raíz expone un marcador de posición con prefijo L: por cada punto final (L:music, L:podcast, L:filter:…); cada nivel se calcula solo cuando un cliente navega por él (LazyBrowseEnsureLazyEndpointInMemory → cachés por nivel), y las notificaciones de cambio de biblioteca borran las cachés (SetLibraryDirty).

Por qué: el arranque en frío es esencialmente instantáneo (el puerto HTTP está abierto cuando MusicBee termina la inicialización del plugin) y la memoria permanece proporcional a lo que se ha navegado, no al tamaño de la biblioteca. Compromiso: la primera navegación a un punto final paga su costo de carga; la reentrada se almacena en caché hasta el próximo cambio de biblioteca. Esta es la base de todo lo demás. Notas completas: FIXES.md.


Redes y robustez

Reforzando la ruta de enlace del servidor HTTP. El plugin original muere silenciosamente cuando su puerto no está disponible.

N04 - Enlace de puerto HTTP autorreparable

Qué: el servidor HTTP del plugin ya no muere cuando su puerto configurado no está disponible. Tres cambios interconectados:

  1. Retroceso automático en caso de fallo de enlace. HttpServer.Start intenta el puerto configurado, y en caso de SocketException escanea hasta 20 puertos hacia arriba en busca del primer puerto libre. El puerto real enlazado se registra en un nuevo Plugin.boundServerPort, y todo lo que anuncia el servidor (URL de LOCATION de SSDP (respuesta NOTIFY + M-SEARCH), la URL del dispositivo (PrimaryHostUrl), el reenvío de puertos del router y los autofiltros de SSDP/punto de control) ahora lee boundServerPort en lugar de Settings.ServerPort. Los clientes UPnP descubren el puerto real a través de SSDP, por lo que un puerto movido es transparente para los renderizadores.
  2. Notificación al usuario. Cuando ocurre un retroceso (el puerto guardado no es el que está en uso), un MessageBox localizado (WarnPortInUse) le informa al usuario qué puerto está sirviendo realmente y que los dispositivos aún lo encontrarán, porque el plugin se ejecuta sin interfaz gráfica y un mensaje en el diálogo solo sería visto por alguien que ya sospechara un problema.
  3. Recuperación de reinicio. RestartServer (la ruta de reinicio al guardar la configuración) solía desreferenciar Plugin.controller / Plugin.server ciegamente. Si la Initialise inicial lanzaba una excepción antes de crearlos (exactamente lo que causaba un enlace fallido), el siguiente guardado de configuración provocaba una NullReferenceException, dejando un plugin medio muerto. Ahora los recrea y los inicia cuando son Nothing, por lo que guardar un puerto que funciona revive el plugin sin un reinicio completo de MusicBee.

Por qué: el detonante fue un incidente real de un usuario. El antiguo puerto predeterminado 49382 se encuentra en el rango dinámico de Windows (49152-65535), donde Hyper-V/WSL2/Docker/WinNAT reservan grandes bloques que cambian en cada arranque, por lo que el enlace falló con WSAEACCES ("acceso prohibido") en una máquina donde había funcionado durante meses. Cambiar el valor predeterminado a un puerto libre luego colisionó con Serviio (un servidor DLNA separado ya en el nuevo puerto), fallando con WSAEADDRINUSE. Cada fallo se tragó en Initialise, dejando el plugin silenciosamente muerto y luego NRE-ing en el siguiente guardado de configuración. Después de N04, una colisión de puertos se autorrepara: el servidor sigue ejecutándose en el siguiente puerto libre, se le informa al usuario y los clientes lo redescubren, en lugar de desactivar todo el plugin.

Implementación:

  • Puerto predeterminado movido 493829779 (por debajo del rango dinámico, por lo que Windows nunca lo reserva automáticamente; no es un valor predeterminado de servidor de medios conocido) en las tres declaraciones de ServerPort + el retroceso de fallo de análisis de configuración.
  • Plugin.boundServerPort (nuevo campo compartido) contiene el puerto de escucha en vivo; activeServerPort permanece como la instantánea configurada para que la lógica de la insignia "Se requiere reinicio" no se active falsamente en un retroceso.
  • HttpServer.PortScanRange = 20; el escaneo se detiene en el primer TcpListener.Start() exitoso y lanza la última excepción solo si todos los intentos fallan.
  • Nueva clave de recurso EN WarnPortInUse (las traducciones siguen el pase de localización en tiempo de publicación).

N05 - Anuncios SSDP a través del grupo de multidifusión (VPN / punto a punto)

Qué: los anuncios SSDP se envían al grupo de multidifusión UPnP (239.255.255.250) en lugar de a una dirección de difusión IP. También se suprime el inofensivo error "no se puede acceder a un objeto desechado" registrado cuando una respuesta de búsqueda SSDP compite con un reinicio del servidor.

Por qué: en adaptadores de red punto a punto / VPN, la difusión IP no se aplica (el antiguo envío de difusión fallaba con "argumento no válido" y se perdían los anuncios), por lo que el plugin era invisible para los clientes en esos enlaces. Anunciar al grupo de multidifusión adecuado corrige el descubrimiento en esos adaptadores.

Navegación por la biblioteca

Estos se incluyeron en este fork y no están en el plugin original. Surgieron de la navegación real por la salida del propio plugin desde clientes UPnP reales.

N06 - Exposición de la biblioteca basada en filtros

Qué: las pestañas de filtro de MusicBee (archivos .xautopf en la carpeta de MusicBee del usuario) se convierten en contenedores raíz UPnP en la biblioteca del plugin. Las pistas de cada filtro se pueden navegar en una jerarquía de AlbumArtistSort → Album → Tracks.

Por qué: los usuarios con filtros de MusicBee curados (por ejemplo, "Pistas de 5 estrellas", "Recientemente añadidas", "Clásica → Barroco") esperan encontrarlos al navegar por el plugin desde un cliente UPnP. El plugin original solo exponía el árbol de la biblioteca sin procesar.


N07 - Cableado del campo SortAlbumArtist

Qué: el plugin ahora lee MetaDataType 165 (Sort Album Artist) de MusicBee y lo usa para agrupar/ordenar artistas en las vistas de navegación.

Por qué: los navegadores de alta fidelidad y los audiófilos usan nombres de artistas de clasificación ("Beethoven, Ludwig van" en lugar de "Ludwig van Beethoven") para organizar las bibliotecas. Expectativa estándar para oyentes serios. Ausente en ambos proyectos principales.


N08 - Manejo de AlbumArtist con múltiples valores

Qué: cuando el campo AlbumArtist de un álbum contiene varios artistas separados por "; " (por ejemplo, "yaiol; Ars Ricercata"), la pista ahora aparece bajo cada artista en las vistas de navegación, no bajo un solo artista Frankenstein que combine los nombres.

Por qué: los álbumes colaborativos y las compilaciones deben aparecer bajo cada colaborador. Sin esto, la mitad de las rutas de búsqueda para encontrar el álbum están rotas.


N09 - Carátula del contenedor del álbum (upnp:albumArtURI)

Qué: los nodos de contenedor de álbum en las respuestas de DIDL Browse ahora incluyen un elemento upnp:albumArtURI que apunta a la carátula del álbum.

Por qué: sin esto, cada álbum en la vista de navegación de un cliente UPnP muestra un icono genérico en lugar de la carátula del álbum. Pista visual para la navegación; esperado por todos los navegadores de alta fidelidad modernos.


N10 - Orden de las pistas dentro de los álbumes filtrados

Qué: las pistas dentro de un álbum expuesto por filtro ahora se ordenan por número de disco y luego por número de pista.

Por qué: orden estándar de álbum. Sin una ordenación explícita, las pistas se devolvían en el orden en que el filtro las devolvía, que solía ser aleatorio.


N11 - Corrección del árbol de carpetas de listas de reproducción

Qué: la función LoadLibraryPlaylists (originalmente de Steven Mayall, ~2014) no descendía en las carpetas de listas de reproducción recién creadas. La primera lista de reproducción de cada carpeta, además de las subcarpetas, terminaba huérfana en el nivel raíz.

Por qué: presente en el plugin original durante once años. Visible en 30 segundos al abrir BubbleUPnP y hacer clic en Listas de reproducción. Corregido en yaiol al recursar correctamente en las carpetas recién creadas durante la construcción del árbol.


N12 - Sanitización de caracteres de control XML-ilegales

Qué: cualquier pista con una etiqueta que contuviera un carácter de control C0 (por ejemplo, 0x19 de un paso de codificación incorrecto - UTF-8 → Latin-1 → truncamiento de 0x99 en 0x19) hacía que toda la respuesta de Browse fallara con Action Failed una vez que la pista incorrecta entraba en un lote paginado.

Por qué: XML 1.0 prohíbe la mayoría de los caracteres de control C0, y XmlWriter lanza una excepción cuando se le pide que escriba alguno. Presente en el plugin original. Corregido eliminando los caracteres no válidos en cada punto de salida de Library_GetFileTags a través de XmlConvert.IsXmlChar.


N13 - Listado de radio determinista en Browse paginado

Qué: la navegación para el contenedor de Radio caía en la rama genérica de lista de archivos, que llamaba a files.Sort(AlbumFileComparer) en cada llamada. Las entradas de Radio tienen etiquetas de Álbum/Disco/Pista vacías, por lo que cada comparación devolvía 0. List(Of T).Sort es inestable, produciendo un orden diferente en cada invocación. Los puntos de control UPnP paginan (BubbleUPnP obtiene 0..15 y luego 16..final); entre las dos llamadas, la lista se reorganizaba, por lo que algunas estaciones aparecían en ambas páginas (duplicados) y algunas en ninguna (faltantes), pareciendo aleatorias en cada actualización.

Por qué: presente en el plugin original (su autor nunca navega por la radio a través de UPnP). Corregido aquí con una rama ContainerCategory.Radio dedicada en Browse, sin ordenación por llamada; radioFiles se ordena una vez en el momento de la carga por Título (estable). La navegación paginada ahora ve un orden determinista; la página 1 y la página 2 son disjuntas.


N14 - Búsqueda UPnP de clase álbum devuelve contenedores de álbum

Qué: la búsqueda UPnP para consultas de clase álbum (upnp:class = "object.container.album.musicAlbum", por ejemplo, "Álbumes aleatorios" de BubbleUPnP) devolvía la lista completa de pistas en lugar de contenedores de álbum, por lo que el cliente mostraba cero álbumes. El controlador original solo analizaba criterios entre paréntesis, luego volcaba todas las pistas independientemente de la clase solicitada.

Por qué: corregido aquí: las consultas de clase álbum ahora enumeran álbumes distintos (agrupados por AlbumArtist+Album) y emiten cada uno como un contenedor musicAlbum adecuado con carátula, direccionable a través del espacio de ID virtual Salb<idx> para que el cliente pueda profundizar en un resultado y reproducirlo.


N15 - Búsqueda UPnP funcional y consciente del alcance con clic

Qué: el original no anunciaba capacidades de búsqueda (GetSearchCapabilities devolvía vacío), por lo que los clientes se negaban incluso a enviar una búsqueda; y el antiguo backend leía de musicFiles, permanentemente vacío en la era del árbol perezoso. Este fork anuncia las propiedades realmente buscables, implementa pistas por título y álbumes por título contra la biblioteca perezosa (HandleLazySearch), limita la consulta a la rama actual del cliente cuando se envía un ID de contenedor real (de lo contrario, sustituye L:music para que las búsquedas de la barra superior no arrastren ruido de podcast/radio/audiolibro), y hace que los resultados del álbum sean clicables a través de ID sintéticos Ssrch_alb_* que una rama temprana de Browse mapea de nuevo a las pistas del álbum. (La parte de los resultados de clase álbum como contenedores es N14).

Por qué: la búsqueda en BubbleUPnP pasó de "La biblioteca no admite la búsqueda" a devolver resultados útiles, con alcance y reproducibles. Diseño completo + enfoques rechazados: SEARCH.md.


N16 - Invalidación de caché UPnP (SystemUpdateID)

Qué: el original devolvía un SystemUpdateID=0 constante (el contrato de invalidación de caché de ContentDirectory de UPnP), por lo que los clientes compatibles con las especificaciones (BubbleUPnP) trataban la biblioteca como si nunca cambiara: resultados de navegación obsoletos, miniaturas 404 después de un cambio de esquema de URL y el baile de "reiniciar MusicBee dos veces para ver los cambios". Este fork inicializa SystemUpdateID a partir de segundos de época en la carga (para que cada reinicio esté estrictamente por delante del último) y lo incrementa en cada mutación de la biblioteca y cambio de configuración (SetLibraryDirty / ResetCacheBumpSystemUpdateId).

Por qué: los clientes recogen de forma fiable las ediciones, los nuevos archivos y los cambios de configuración en su próxima navegación. Límite conocido: los clientes suscritos no reciben activamente el nuevo valor a través de GENA (aparcado como trabajo futuro); aún lo ven en su próxima navegación.


N17 - Carátula de suscripción de podcast

Qué: los mosaicos de podcast no mostraban imágenes; cada solicitud de /PodcastThumbnail/ devolvía un 404. Dos errores apilados: la cadena de resolución nunca verificaba la caché de carátulas real de MusicBee (%LocalAppData%\MusicBee\InternalCache\Subscriptions\<name>.jpg, de donde carga la interfaz de escritorio), y la capa HTTP de escape+minúsculas dañaba la clave de ruta de la URL del feed hasta su último segmento de ruta. Este fork resuelve las carátulas de InternalCache de MB y enruta las búsquedas a través de un slug seguro para URL que sobrevive intacto a la capa HTTP (PodcastSlug / podcastSubIdBySlug).

Por qué: la carátula de suscripción ahora se renderiza en las vistas de navegación (las 22 solicitudes que antes daban 404 se resuelven).


N18 - Navegación jerárquica (delimitada) por etiquetas

Qué: cualquier campo puede marcarse como jerárquico en la pestaña Opciones de la biblioteca y asignarle un delimitador de un solo carácter (un selector de campo + cuadro de delimitador con añadir/eliminar, persistido en la configuración del plugin). Si se establece Agrupación en / y un valor como Jazz/Cool Jazz, se navega como Jazz › Cool Jazz en lugar de una entrada plana. Las pistas etiquetadas exactamente en una rama (solo Jazz) obtienen su propio nodo [Jazz] para que nada quede oculto, una rama con un solo hijo se colapsa por sí misma, y se rechaza ; como delimitador porque es el propio separador de múltiples valores de MusicBee.

Por qué: las taxonomías de etiquetas profundas que un usuario ya ha codificado en un solo campo (árboles de géneros, jerarquías de estados de ánimo, "Clásica/Barroco/Concierto") finalmente se navegan como el árbol que describe la etiqueta, en lugar de una pared plana de cadenas separadas por barras que el usuario tiene que leer de principio a fin.


N19 - Ruta raíz única etiquetada por su campo de agrupación

Qué: una única ruta de navegación en la raíz se etiqueta por su campo de agrupación (por ejemplo, "Género") en lugar de su ruta corta completa, coincidiendo con la forma en que se nombran los grupos de primer campo fusionados.

Por qué: el árbol de navegación se lee de forma consistente (una regla de nomenclatura, ya sea que una entrada raíz esté sola o se haya fusionado con hermanos (N20)), en lugar de que una entrada raíz solitaria muestre una ruta interna verbosa mientras sus vecinos fusionados muestran un nombre de campo limpio.


N20 - Fusionar rutas de navegación que comparten un primer campo

Qué: dos rutas de navegación que comparten el mismo primer campo (por ejemplo, "Género / Artista del álbum de clasificación" y "Género / Personas de podcast") se colapsan en una única carpeta raíz Género que primero lista los valores de género y luego se divide en las dos vistas, en lugar de dos entradas "Género / ..." casi duplicadas una al lado de la otra en la raíz.

Por qué: un usuario con varias vistas relacionadas anidadas bajo un campo común veía la raíz abarrotada de entradas de nivel superior casi idénticas. Fusionarlas mantiene la raíz legible y agrupa las vistas relacionadas donde pertenecen, bajo su campo compartido.


N21 - Rutas de navegación tipificadas por categoría (Estándar / Radio / Podcast)

Qué: cada ruta de navegación se clasifica por categoría: Estándar, Radio o Podcast. La lista de plantillas se agrupa en esas tres secciones, el selector de campo de cada plantilla solo ofrece los campos que los datos de esa categoría pueden realmente entregar, y una plantilla solo se puede aplicar a nodos coincidentes en el árbol de vistas (los nodos incompatibles se atenúan y no se pueden marcar). Las plantillas reservadas de Radio y Podcasts no se pueden eliminar, por lo que su sección de categoría nunca desaparece.

Por qué: sin la tipificación, un usuario podría construir un diseño que apareciera silenciosamente vacío (una estación de radio no tiene "álbum", un episodio de podcast no tiene "artista del álbum") y solo descubrirlo navegando a una carpeta muerta desde un cliente UPnP. Restringir el menú de campos y los objetivos de aplicación a los datos reales de la categoría hace que los diseños vacíos no se puedan construir.


N22 - Agrupar podcasts por año de publicación

Qué: se lee la fecha de publicación de cada episodio de podcast, de modo que una ruta de navegación de podcast con un nivel de Año agrupa los episodios por año en lugar de colapsarlos bajo un único "Desconocido".

Por qué: las suscripciones grandes a podcasts se vuelven navegables por año como el resto de la biblioteca, en lugar de que cada episodio termine en un montón sin fecha porque el plugin nunca miró la fecha de publicación por episodio.


N23 - Colapsar niveles de agrupación de un solo resultado

Qué: un nivel de agrupación que se resuelve en un solo valor (un nivel de Tipo de registro que muestra solo "LP" para un artista que solo hizo LPs, o un nivel de letra con una sola letra) se omite automáticamente, llevando al usuario directamente a su contenido.

Por qué: navegar por una carpeta que contiene exactamente una carpeta es pura fricción. Colapsar el nivel de una sola opción elimina el clic muerto sin cambiar lo que el usuario puede alcanzar.


N24 - Agrupación/búsqueda por año contra el campo de fecha de MusicBee

Qué: la condición de año ya no consulta el campo "Año" de fecha completa de MusicBee con un valor de cuatro dígitos, y el alias de campo de año codificado ha desaparecido, por lo que cada campo de agrupación ahora se resuelve genéricamente a partir de la definición de la ruta.

Por qué: para las bibliotecas cuya etiqueta de Año contiene una fecha completa, la agrupación o búsqueda por año anteriormente no devolvía nada; la consulta de cuatro dígitos nunca coincidía con el campo de fecha completa. Consultar el campo correcto hace que la agrupación y búsqueda por año vuelvan a encontrar las pistas.


N25 - Separar los campos de agrupación "Año" y "Año (aaaa)"

Qué: las rutas de agrupación y navegación de álbumes ahora exponen los dos campos de año propios de MusicBee: Año (la etiqueta de fecha completa) y Año (aaaa) (solo el año de cuatro dígitos), para que el usuario pueda elegir cualquiera al definir una agrupación de álbumes o una ruta de navegación.

Por qué: los dos campos significan cosas diferentes en MusicBee, y colapsarlos perdía esa distinción. Mostrar ambos permite al usuario agrupar todos los lanzamientos de un año (aaaa) o mantener el orden exacto por fecha (etiqueta de Año completa), según su intención.


N32 - Filtros y listas de reproducción anclados agrupados por tipo en la raíz

Qué: un filtro anclado ahora aparece directamente debajo de la carpeta Filtros en la raíz de navegación, y una lista de reproducción anclada directamente debajo de la carpeta Listas de reproducción, en lugar de que todos los anclajes se agrupen en un solo montón al final de la raíz. Cada acceso directo anclado se encuentra con su propio tipo.

Por qué: a medida que un usuario ancla más accesos directos, un solo grupo final de filtros y listas de reproducción mezclados se vuelve más difícil de escanear y separa cada acceso directo de la carpeta a la que pertenece. Agrupar los elementos anclados bajo su propia categoría mantiene la raíz legible y cada acceso directo junto a las cosas a las que pertenece.

Cuadro de diálogo de configuración y empaquetado

N26 - Cuadro de diálogo de configuración seccionado

Qué: la página de Preferencias obtuvo un diseño de navegación izquierda con secciones: General / Reproducción / Biblioteca / Perfiles de dispositivo / Diagnóstico.

Por qué: el original era una única lista plana y larga de cada configuración, lo cual estaba bien para el desarrollador que lo construyó, pero era desconcertante para todos los demás. La sección agrupa opciones relacionadas y hace que el cuadro de diálogo se sienta más como la configuración de una aplicación moderna.


Renombrado de ensamblado + plugin (sin id. F - nota de empaquetado)

Qué: la DLL compilada se llama mb_UPnP_yaiol.dll y el plugin se reporta como "MusicBee UPnP (yaiol)". Distinto del original mb_Upnp.dll.

Por qué: los usuarios pueden instalar yaiol junto con el plugin original y comparar el comportamiento lado a lado.


Sistema de insignias - mostrando el estado de tiempo de ejecución (mecanismo detrás de F40)

Qué: un patrón de interfaz de usuario genérico para mostrar condiciones importantes de tiempo de ejecución como insignias de colores visibles en el cuadro de diálogo de Configuración. Instancias actuales:

  • ⚠ Max Conn (N04) - se activa cuando se alcanzó el límite máximo de conexiones al menos una vez desde que se inició MusicBee. Bandera de sesión persistente Plugin.MaxConnectionsHit. Se establece dentro de WaitOnSendBarrier cuando no hay un slot libre.
  • ⚠ Reinicio Requerido - se activa cuando una configuración guardada necesita un reinicio de MusicBee para surtir efecto. Bandera de sesión persistente Plugin.RestartRequired. Se establece en el controlador de Guardar del diálogo cuando el nuevo valor persistido difiere de la instantánea en tiempo de ejecución (Plugin.activeMaxConnections, Plugin.activeServerPort, Plugin.activeIpAddress). Las configuraciones que requieren reinicio se limitan a aquellas que realmente no pueden recargarse en caliente: parámetros de enlace del servidor HTTP y el SemaphoreSlim construido una vez en Initialise.

Por qué: el archivo de registro del plugin está bien para usuarios técnicos que depuran, pero un usuario no técnico que se enfrenta a "el dispositivo suena mal" o "la reproducción es lenta" nunca abrirá Diagnóstico → Ver registro. Las insignias capturan los casos en los que el usuario necesita saber que algo sucedió y lo muestran la próxima vez que abren el plugin, de forma detectable sin leer nada.

Reutilizable para el futuro:

  • Perfil no coincidente detectado (el agente de usuario del dispositivo nunca coincidió con ningún perfil, se recurrió a Genérico).
  • Retroceso de NextURI activado (F13 - sin pausas deshabilitado para la sesión en un dispositivo inestable).
  • Escaneo de biblioteca fallido / parcial.
  • Conexión del reproductor perdida a mitad de sesión.
  • Cualquier otra condición en la que "sucedió una vez, el usuario debe saberlo" sea mejor que "registrado silenciosamente entre otras 1000 líneas".

Convenciones de implementación:

  • Las etiquetas de las insignias se encuentran a nivel del diálogo (no dentro de ningún panel) para que sean visibles independientemente de la sección en la que se encuentre el usuario.
  • Posicionadas a lo largo de la fila inferior cerca de Guardar/Cancelar (actual: y=410 apiladas horizontalmente).
  • Cada insignia tiene una bandera de sesión persistente correspondiente en Plugin que cambia a True cuando ocurre la condición y se restablece solo al reiniciar MusicBee.
  • Recursos: <Condition>Badge (texto de la etiqueta, prefijo con ⚠) + <Condition>BadgeTip (información sobre herramientas que explica la causa + el remedio).
  • Para las insignias "la configuración guardada necesita reinicio", toma una instantánea en tiempo de ejecución en Plugin.Initialise() y compárala con Settings.* después de Settings.SaveSettings() en el controlador de Guardar del diálogo.

N27 - Cancelar descarta las ediciones de rutas/plantillas

Qué: las ediciones realizadas en rutas y plantillas dentro del cuadro de diálogo de configuración ahora se descartan cuando el usuario hace clic en Cancelar, en lugar de aplicarse silenciosamente, y cualquier plantilla reservada eliminada durante la sesión se vuelve a crear. (Las plantillas, de lo contrario, se guardan en vivo a medida que se editan; no hay un botón de Guardar separado en la pestaña Rutas).

Por qué: Cancelar debería significar cancelar. Anteriormente, un usuario que experimentaba con cambios de ruta/plantilla y retrocedía encontraba los cambios ya confirmados, sin forma de deshacerlos salvo rehacer cada uno a mano.


N28 - Ampersands literales en el menú del selector de campos

Qué: un campo cuyo nombre contiene "&" (por ejemplo, "Estado de ánimo & Contexto") renderiza el ampersand literalmente en el menú del selector de campos en lugar de tragarlo como un prefijo mnemotécnico Alt.

Por qué: los nombres de campo con un ampersand se mostraban incorrectamente (el carácter desaparecía y la siguiente letra se convertía en un acelerador), lo que dificultaba el reconocimiento de la entrada del menú.


N29 - Título de ventana de configuración estable y sin traducir

Qué: el título de la ventana de configuración se fija a la cadena de marca "MusicBee UPnP Plugin" y ya no varía con el idioma de la interfaz; la cadena DialogTitle por idioma se eliminó de cada paquete de localización.

Por qué: un título de ventana que cambiaba de redacción por idioma era una superficie traducible sin beneficio; el título es una marca de marca. Fijarlo lo mantiene estable y consistente en todas partes.

Localización

El plugin original solo está en inglés. Este fork es totalmente localizable: cada cadena orientada al usuario fluye a través de un paquete de recursos, y el plugin detecta automáticamente el idioma de la interfaz de MusicBee.

N30 - Interfaz de usuario multilingüe (traducciones pendientes)

Qué: la maquinaria de localización está completa y en funcionamiento. Localisation.vb lee el idioma seleccionado de MusicBee de MusicBee3Settings.ini (endónimo <SystemLanguage>) y aplica la cultura .NET correspondiente al hilo, por lo que My.Resources.Resources.* devuelve la cadena localizada. Cada etiqueta/botón/mensaje orientado al usuario está conectado a una clave de recurso (controles de diseñador a través de ApplyDesignerExtras + sync-en-locale.js; cadenas de tiempo de ejecución como WarnPortInUse añadidas a mano). Lo que no está hecho todavía es la traducción real: solo existe el paquete de origen en inglés (Resources.resx); los paquetes satélite para los otros idiomas se producen en un solo pase por lotes cuando el plugin está completo en cuanto a características (traducir por partes mientras las cadenas aún cambian es un esfuerzo desperdiciado).

Idiomas de destino (el conjunto que ofrece MusicBee, emparejado 1:1 por endonymToCulture para que el plugin siga automáticamente el idioma de MusicBee):

Árabe (ar) Checo (cs) Alemán (de) Griego (el)
Español (es) Francés (fr) Húngaro (hu) Italiano (it)
Coreano (ko) Neerlandés (nl) Noruego (nb) Polaco (pl)
Portugués BR (pt-BR) Portugués PT (pt-PT) Sueco (sv) Turco (tr)
Ucraniano (uk) Ruso (ru) Japonés (ja) Chino simplificado (zh-CN)
Chino tradicional (zh-TW) Inglés (en, fuente)

Política de variantes (según la regla de configuración regional del espacio de trabajo): PT y ZH se dividen en paquetes distintos porque el vocabulario/escritura diverge genuinamente (pt-BR/pt-PT, zh-CN/zh-TW). EN es un solo paquete: "English(US)" (en-US) de MusicBee recurre a en a través de la cadena de cultura de .NET, por lo que no se produce un paquete US separado. ES y FR son igualmente de una sola configuración regional.

Por qué: la configuración de un plugin UPnP ("no usar PCM sin procesar", "forzar PCM little-endian", advertencias de retroceso de puerto) es lo suficientemente críptica en el idioma nativo. Seguir el idioma de la interfaz de usuario de MusicBee, en lugar de forzar el inglés, es la diferencia entre una herramienta que un usuario no angloparlante puede configurar y una que no. Ninguno de los proyectos principales intentó esto.


N31 - El enlace de ayuda se abre en el idioma completo de la interfaz

Qué: al abrir el enlace de Ayuda desde el plugin, se respeta el idioma completo de la interfaz del usuario (por ejemplo, pt-BR, zh-CN) en lugar de colapsar al idioma base, y se envía un identificador de verificación de actualización más claro.

Por qué: un usuario que ejecutaba MusicBee en una variante regional (portugués de Brasil, chino simplificado) era enviado a la página de ayuda en el idioma base. Llevar la cultura completa los lleva a la página de ayuda en el idioma exacto que utilizan.

Correcciones y mejoras al plugin original

Protocolo central y reproducción

F01 - Perfiles de dispositivo DLNA predeterminados actualizados

Qué: incluye perfiles predeterminados actualizados para PlayStation 4, Xbox 360/One y BubbleUPnP moderno, con indicadores de capacidad (frecuencias de muestreo, profundidades de bits, códecs) que reflejan lo que esos dispositivos realmente admiten hoy en día.

Por qué: los valores predeterminados del plugin original se congelaron alrededor de 2014. Desde entonces, PS4/Xbox/BubbleUPnP han ganado soporte de audio de alta resolución. De forma predeterminada, una nueva instalación reproduce la mejor calidad en estos dispositivos sin que el usuario tenga que tocar la configuración del perfil del dispositivo.


F02 - Controlar dispositivos que anuncian MediaRenderer:3

Qué: el plugin sondea la descripción del servicio UPnP de un reproductor para decidir si MusicBee puede controlarlo. El original solo coincidía con urn:schemas-upnp-org:device:MediaRenderer:1. Los dispositivos modernos anuncian :2 o :3. F02 amplía la coincidencia.

Por qué: sin esto, las unidades recientes de Sonos / WiiM / Eversolo simplemente no aparecen como objetivos en la lista de dispositivos "Reproducir en" de MusicBee, a pesar de que hablan el mismo protocolo. Una simple corrección de coincidencia de prefijo de cadena desbloquea toda la generación de dispositivos modernos.


F03 - Opción "Forzar flujo nativo" por perfil (activada por defecto)

Qué: cuando se marca, el plugin envía los bytes del archivo original al dispositivo sin transcodificación, sin DSP, sin procesamiento de ReplayGain aplicado. Solo el archivo original que el usuario eligió, byte por byte (salvo el enmarcado HTTP).

Por qué: según testimonios del foro, esta es la mayor mejora en la calidad de reproducción. Los usuarios de alta fidelidad que compran reproductores caros quieren explícitamente una salida bit-perfecta; cualquier toque de DSP anula el propósito. Activado por defecto porque la mayoría de los dispositivos modernos manejan cualquier códec que el usuario les envíe, y ReplayGain/EQ debería ser opcional. Es por perfil para que puedas mantener la transcodificación para una Xbox antigua mientras envías nativo a un DAC de alta fidelidad.


F04 - "Forzar transcodificación" por perfil

Qué: anulación por perfil que fuerza cada flujo a este dispositivo a través del transcodificador, independientemente del soporte de códec nativo. Lo contrario de F03 (ForceNativeStream). Mutuamente excluyente con F03: la interfaz de usuario desmarca automáticamente el otro cuando se activa uno.

Por qué: un único interruptor global sería contradictorio con el ForceNativeStream por perfil (F03). Caso real: el dispositivo A es un DAC de alta fidelidad que quiere flujos nativos bit-perfectos; el dispositivo B es un antiguo receptor AV que se ahoga con FLAC. Con un interruptor global, el usuario tiene que elegir, a costa del otro dispositivo. Con la opción por perfil, cada dispositivo obtiene la respuesta correcta.

Implementación:

  • StreamingProfile.ForceTranscoding As Boolean = False.
  • El esquema de persistencia se actualizó a la v9. Los archivos anteriores a la v9 cargan el valor global heredado una vez y lo copian en todos los perfiles, preservando el comportamiento antiguo a través de la actualización.
  • Interfaz de usuario: eliminado del panel de Diagnóstico, añadido a la sección Perfiles de dispositivo junto a ForceNativeStream. Controladores de exclusión mutua bidireccionales (CheckedChanged en cada uno anula la suscripción del otro antes de cambiar, para evitar un bucle infinito).
  • Punto de decisión: Settings.ForceTranscodingstreamingProfile.ForceTranscoding en WriteAudioFileDIDL.

F05 - "Forzar PCM little-endian" por perfil

Qué: los flujos PCM (tipos MIME L16/L24) son big-endian por especificación. Algunos dispositivos esperan erróneamente little-endian y reproducen ruido blanco cuando se les entrega datos big-endian correctos. F05 alterna el orden de bytes por perfil.

Por qué: sin esto, ciertos dispositivos emiten una pared de estática. El síntoma es dramático y la causa invisible sin conocimiento de la codificación PCM; el interruptor ofrece a los usuarios una solución de prueba y error.


F06 - "No usar PCM sin procesar" por perfil

Qué: cuando el dispositivo afirma que admite PCM sin procesar, el plugin lo utiliza. Algunos dispositivos mienten: aceptan el handshake SOAP pero distorsionan los datos PCM sin procesar reales, mientras manejan correctamente el PCM envuelto en un contenedor WAVE. F06 fuerza PCM-over-Wave independientemente de lo que anuncie el dispositivo.

Por qué: ciertos modelos de Marantz específicamente: anuncian PCM sin procesar pero solo funciona WAVE. Sin esto, los flujos PCM sin procesar salen distorsionados, sin ningún mensaje de error que lo señale.


F07 - "Longitud del contenido" por perfil

Qué: qué valor enviar en el encabezado HTTP Content-Length. Cuatro opciones:

  • Predeterminado - recuento de bytes real cuando se conoce, omitir cuando se desconoce.
  • Ninguno - nunca enviar el encabezado (solo codificación en fragmentos).
  • Solo PCM - solo enviar para PCM sin procesar; omitir para todo lo demás.
  • Fijo - enviar UInt32.MaxValue - 8192 (un centinela para "longitud desconocida enorme").

Por qué: los dispositivos UPnP/DLNA varían enormemente en cómo reaccionan a Content-Length. Algunos necesitan un número exacto, otros lo odian en los flujos, otros necesitan un valor centinela "realmente grande" para mantener el almacenamiento en búfer. Esto se amplió más tarde de solo PCM a todos los formatos de salida porque los mismos problemas aparecieron en los flujos MP3/AAC transcodificados.


F08 - "No borrar NextURI" por perfil

Qué: normalmente el plugin borra el NextURI en cola del dispositivo cuando la cola se vacía (enviando SetNextAVTransportURI con una URL en blanco). Algunos dispositivos (especialmente Denon) interpretan un NextURI en blanco como "detener todo" y detienen la reproducción inmediatamente. F08 evita que el plugin lo borre.

Por qué: sin esto, los propietarios de Denon experimentan que el dispositivo se corta a mitad de la pista cuando la cola se vacía. Con F08 marcado, el dispositivo mantiene el NextURI obsoleto en la memoria (inofensivo, simplemente se sobrescribe la próxima vez que se pone algo en cola).


F09 - FLAC como formato de salida de transcodificación

Qué: el menú desplegable de formato de transcodificación en Perfiles de dispositivo ahora ofrece FLAC junto con PCM 16/24, MP3, AAC, Ogg. Al seleccionarlo, el codificador BASS se enruta a través de la línea de comandos de conversión FLAC estándar de MusicBee (el mismo mecanismo que ya usan MP3/AAC/Ogg).

Por qué: para dispositivos que manejan bien FLAC pero no pueden decodificar el códec de origen (por ejemplo, un Eversolo que recibe la biblioteca WMA de MusicBee convertida a FLAC), esto preserva la calidad sin pérdidas donde MP3/AAC descartarían datos de audio. Desbloquea N02 (control de mezcla descendente 5.1), que no podía abordarse sin una opción de transcodificación sin pérdidas.

Implementación: una adición de una línea al bloque Select Case Codec de Encoder.StartEncode: FLAC se une a MP3/AAC/Ogg en la rama controlada por línea de comandos. El menú desplegable de la interfaz de usuario obtiene "FLAC" como sexta opción. El mapeo de carga/guardado en SettingsDialog se extiende para reconocer FileCodec.FlacSelectedIndex = 5. El tipo Mime, DLNA y la función de codificación ya estaban cableados en ItemManager.GetMimes / GetDlnaType / GetEncodeFeature de trabajos anteriores (F21, F26).


Sin pausas (SetNextAVTransportURI)

F10 - SetNextAVTransportURI / NextURI core

Qué: reproducción sin pausas real. Cuando el dispositivo anuncia soporte para SetNextAVTransportURI en su descripción de servicio UPnP, el plugin pre-pone en cola la siguiente pista en el dispositivo antes de que termine la actual. El dispositivo realiza la transición internamente sin una pausa audible entre pistas, lo que escuchas en un reproductor de CD. Esto no es el truco de "flujo continuo" (que concatena todo en un flujo largo y pierde los metadatos por pista).

Por qué: la característica insignia de Nivel 2. Los álbumes grabados como una interpretación en vivo continua (discos en vivo, movimientos clásicos, sets de DJ) suenan mal cuando hay un silencio de medio segundo entre pistas. Resolver esto correctamente es una característica insignia, ahora en este fork.

Notas: el audio en cola se sirve a través del servidor HTTP del plugin usando streamHandle=0 (modo de obtención de biblioteca), lo que significa que el motor de audio de MusicBee no está involucrado en la pista en cola. Compromiso: los efectos de ReplayGain/DSP/EQ no se aplican a la siguiente pista. Aceptable cuando "forzar flujo nativo" está activado (el valor predeterminado).


F11 - "Deshabilitar soporte NextURI" por perfil

Qué: incluso si un dispositivo anuncia SetNextAVTransportURI, esta casilla de verificación obliga al plugin a ignorar ese anuncio y volver a la reproducción de una pista a la vez.

Por qué: algunos dispositivos anuncian NextURI pero tienen una implementación con errores (bloqueos, transiciones a medias, cuelgues). En lugar de hacer ingeniería inversa de cada dispositivo defectuoso, el usuario obtiene un interruptor para "simplemente desactivarlo aquí".


F12 - Ciclo de vida de NextURI en la lista de reproducción actual

Qué: cuando MusicBee dispara NowPlayingListChanged, el plugin reevalúa lo que debería estar en cola para una transición sin pausas. Le pide a MusicBee la nueva pista "siguiente" a través de NowPlayingList_GetNextIndex(1) + NowPlayingList_GetListFileUrl, compara con lo que está actualmente en cola en el dispositivo (rastreado a través del nuevo campo nextPlaySourceUrl), y vuelve a poner en cola si cambió (o borra la cola si MusicBee dice que no hay una pista siguiente).

Por qué: sin F12, el dispositivo seguía reproduciendo un NextURI obsoleto cuando el usuario eliminaba/reordenaba la pista en cola. Esto históricamente tomó varias iteraciones porque cada mutación de lista necesita un manejo diferente; nosotros simplificamos confiando en NowPlayingList_GetNextIndex (que ya respeta la reproducción aleatoria y la repetición de todo), por lo que todas las variantes se canalizan a través de la misma comparación.

Implementación:

  • El nuevo campo nextPlaySourceUrl almacena la URL de la biblioteca de MusicBee de lo que está en cola (la URL de transmisión con sufijo de identificador no es comparable a una ruta de biblioteca).
  • Nuevo Public Sub RefreshQueuedNextUri() en MediaRendererDevice. Tres resultados: no hay NextURI en cola → no hace nada; el NextURI en cola coincide con el nuevo "siguiente" → no hace nada; el NextURI en cola difiere → llama a QueueNext con la nueva URL (o QueueNext("") para borrar, lo que respeta F08 DoNotClearNextUri).
  • Conectado en Plugin.ReceiveNotification bajo NotificationType.NowPlayingListChanged.

F13 - Retroceso por fallo de NextURI

Qué: después de 4 fallos consecutivos de SetNextAVTransportURI en el mismo dispositivo, el plugin deshabilita la reproducción sin pausas para ese dispositivo hasta que MusicBee se reinicie.

Por qué: si un dispositivo está realmente defectuoso para NextURI (fallos SOAP intermitentes, fallos de red), el plugin seguiría reintentando en cada pista. F13 detiene el ruido y vuelve a la reproducción de una pista a la vez silenciosamente.


F14 - Integración de modo de repetición + NextURI

Qué: F14 se divide en dos casos manejados en el detector de transición F15 en OnAvTransportStatusCheck:

  • Repetir todo: MusicBee pasa la URL "envolvente" correcta (pista 1 al final de la lista) a Plugin.QueueNext por sí mismo. No se necesita una lógica de plugin especial: el dispositivo hace la transición y el detector F15 llama a Player_PlayNextTrack como de costumbre, lo que envuelve el índice NPL de MusicBee de nuevo a 0.
  • Repetir uno: MusicBee pasa la MISMA URL de pista a Plugin.QueueNext. El dispositivo hace la transición (nuevo identificador de flujo, misma fuente). El detector F15 ahora consulta Player_GetRepeat(): si es RepeatMode.One, omite la llamada a Player_PlayNextTrack para que MusicBee no avance el índice NPL de la pista en bucle.

Por qué: sin la omisión de Repetir uno, llamar a Player_PlayNextTrack en la transición sin pausas avanzaría MusicBee a la siguiente pista de la lista (Repetir uno afecta solo el comportamiento de avance automático al final de la pista en la interfaz de usuario del reproductor; Siguiente pista siempre avanza), contradiciendo lo que significa Repetir uno.

Advertencia de recuento de reproducciones: en Repetir uno, el incremento del recuento de reproducciones depende de que MusicBee 3.7.9563+ detecte la reproducción en bucle. Las versiones anteriores de MusicBee reproducen la repetición sin pausas correctamente pero omiten el incremento del recuento de reproducciones. Documentado; no bloqueante.


F15 - Máquina de estados de detección de transición de pista

Qué: cuando el dispositivo transiciona internamente de la pista actual a NextURI, el plugin necesita notarlo e indicarle a MusicBee que avance su índice de reproducción actual. De lo contrario, MusicBee cree que todavía está en la pista anterior y los recuentos de reproducción / interfaz de usuario / scrobbling se desincronizan.

Implementación: sondea GetPositionInfo.TrackURI en cada tic del temporizador de estado. Cuando la URI reportada coincide con la que pusimos en cola a través de NextURI, llamamos a Player_PlayNextTrack en MusicBee y establecemos suppressNextSoapCall para que el PlayToDevice resultante no vuelva a enviar SetAVTransportURI (lo que interrumpiría la reproducción sin pausas).

Por qué: sin F15, el dispositivo reproduce la siguiente pista pero la interfaz de usuario de MusicBee dice que todavía está en la anterior. Confuso, rompe el scrobbling, rompe el seguimiento del recuento de reproducciones. La detección de transición de pista necesita una iteración larga por reproductor porque cada marca de reproductor tiene sus propias peculiaridades en cuándo informa el cambio de URI (algunos informan TRANSITIONING primero, algunos saltan directamente a PLAYING con la nueva URI, algunos tienen un breve STOPPED en el medio).

Notas: nuestro primer corte funciona en el reproductor BubbleUPnP. Los casos extremos por dispositivo permanecen en B6.


F16 - Corrección de chasquido en transición sin pausas

Qué: el chasquido ocurre cuando el formato de origen (frecuencia de muestreo / canales / códec) de la pista en cola difiere de la pista que se está reproduciendo actualmente, lo que obliga al DAC del dispositivo a volver a bloquearse en la transición. F16 agrega un diagnóstico NextUri:FormatChange que se activa en el momento de la cola cada vez que los formatos difieren, nombrando ambos lados, para que los usuarios que escuchan chasquidos puedan correlacionar.

El diagnóstico también señala la mitigación: marca ForceTranscoding en el perfil del dispositivo. Eso homogeneiza cada pista a un único códec/frecuencia de muestreo/profundidad de bits de transcodificación, eliminando por completo la diferencia de formato de origen.

Por qué se pospuso la corrección real de transcodificación para que coincida: la corrección estructural (transcodificar la pista en cola para que coincida con el formato de la pista que se está reproduciendo) requiere cambios en el esquema de URL del servidor HTTP del plugin; actualmente /encode/{id}0.{ext} sirve el archivo en cola de forma nativa. Una futura v2 de F16 agregaría rutas /encode/{id}0_{rate}_{depth}.{ext} por formato y las conectaría a través del codificador. Ese es un cambio arquitectónico más grande que vale la pena hacer si un dispositivo real exhibe el chasquido después de que ForceTranscoding no sea suficiente.

Implementación actual:

  • El campo lastSourceUrl rastrea la URL de origen que se está reproduciendo actualmente.
  • QueueNext lee FilePropertyType.SampleRate/Channels/Kind para las pistas actual y en cola y registra NextUri:FormatChange en caso de falta de coincidencia.

F17 - Resincronización de la barra de progreso después de la búsqueda

Qué: la función Seek() ya llamaba a GetPlayPositionInformation() después de un SOAP de búsqueda exitoso, lo que corrige el caso de "ninguna resincronización en absoluto". F17 cierra la deriva restante de hasta 1 segundo causada por la cuantificación RelTime de 1 segundo de UPnP: cuando la posición informada del dispositivo se redondea a 1 segundo del objetivo solicitado por el usuario, el plugin ahora confía en el valor subsegundo preciso del usuario en lugar del truncamiento del dispositivo. Solo cuando el dispositivo informa algo drásticamente diferente (más de 1 segundo de diferencia) usamos su valor (la búsqueda aterrizó en otro lugar de lo solicitado, por ejemplo, ajuste a fotograma clave en algunos códecs).

Por qué: sin esto, buscar a 2:30.500 anclado al informe "2:30" del dispositivo haría que la barra de progreso mostrara ~500ms por detrás de la realidad. Después de F17, la barra coincide con la intención del usuario para el caso común de desplazamiento dentro de la pista, y aún respeta el informe del dispositivo para el caso atípico de ajuste a fotograma clave.


F18 - Interbloqueo de flujo continuo / NextURI

Qué: ahora hay dos interbloqueos en su lugar:

  1. Tiempo de ejecución: QueueNext devuelve False anticipadamente cuando Settings.ContinuousOutput está activado. El flujo continuo es su propio mecanismo sin pausas (un flujo largo concatenado); enviar SetNextAVTransportURI además de él confunde al dispositivo sobre si cada pista es una URI discreta o parte del flujo continuo.
  2. Interfaz de usuario: cuando el usuario marca la casilla de flujo continuo global, el forceNativeStream del perfil actualmente mostrado se desmarca automáticamente. El flujo continuo siempre transcodifica, por lo que forzar el nativo no tiene sentido en combinación.

Por qué: evita que el usuario habilite dos mecanismos sin pausas conflictivos simultáneamente. Sin F18, el dispositivo recibiría tanto una URI de flujo continuo como una NextURI para cada pista subsiguiente, con un comportamiento indefinido dependiendo del reproductor.


F19 - Errores de NextURI en blanco ignorados

Qué: cuando se llama a SetNextAVTransportURI con una URL en blanco (por ejemplo, la última pista de la lista), algunos dispositivos devuelven un fallo SOAP. F19 los ignora silenciosamente, se registran pero no se propagan como errores.

Por qué: la condición de "no hay siguiente pista" es normal, no un error. Tratarla como fatal contamina el registro y (en algunos flujos) desencadena tormentas de reintentos.


Tipos MIME y metadatos DLNA

F20 - MP3 mime → audio/mpeg

Qué: el tipo MIME MP3 conforme a los estándares es audio/mpeg, no audio/mp3. Este último es un nombre erróneo común que la mayoría de los dispositivos toleran, pero los reproductores más estrictos lo rechazan.

Por qué: corrige silenciosamente la reproducción en dispositivos más estrictos que siguen el estándar. El código base de yaiol ya lo tenía correcto; no se necesitaba ningún cambio.


F21 - Orden de tipos MIME: variante no x- primero

Qué: cuando un dispositivo anuncia tanto audio/flac como audio/x-flac, el plugin devuelve primero la variante no x-. Lo mismo ocurre con cualquier códec con tipos MIME estándar y experimentales.

Por qué: el prefijo x- marca los tipos MIME experimentales/no oficiales. Algunos reproductores se comportan mejor con el formato estándar. Pequeña reordenación, impacto en el mundo real.


F22 - Soporte de tipo MIME Opus

Qué: reconoce Opus como un códec de audio transmisible; envía el tipo MIME audio/opus al servir pistas Opus.

Por qué: Opus es ahora común (códec de compromiso moderno para voz/música). Sin F22, el plugin se negaría a transmitir archivos Opus incluso a dispositivos que los manejan.


F23 - Soporte de archivos de origen Monkey Audio (APE)

Qué: reconoce los archivos .ape como un códec de origen válido para transmisión/transcodificación.

Por qué: APE es un formato sin pérdidas con una base de usuarios de nicho pero leal. Añadirlo cuesta poco y desbloquea la biblioteca para esos usuarios.


F24 - Retroceso de MIME AAC / ALAC

Qué: si un dispositivo admite AAC o ALAC pero no los anuncia explícitamente en su descripción de servicio UPnP, el plugin los ofrece de todos modos como un retroceso.

Por qué: varios dispositivos que manejan AAC correctamente olvidaron incluirlo en su XML de capacidades. Sin F24, el plugin ni siquiera lo intentaría, forzando la transcodificación. Con F24, el plugin lo intenta y permite que el dispositivo lo maneje de forma nativa si puede.


F25 - Indicador de tipo DLNA para flujos WAV nativos + codificados

Qué: el indicador de tipo DLNA (un identificador de perfil como LPCM, WAVE, MP3) debe coincidir con lo que recibe el dispositivo. F25 garantiza que los flujos nativos y los flujos WAV codificados se marquen correctamente.

Por qué: un tipo DLNA no coincidente hace que algunos dispositivos rechacen la reproducción por completo o apliquen el decodificador incorrecto.


F26 - Encabezado DLNA para archivos FLAC

Qué: los flujos FLAC obtienen el identificador de perfil DLNA adecuado en sus encabezados.

Por qué: sin él, algunos dispositivos que admiten FLAC no reconocen el flujo como tal.


F27 - Corrección del cálculo de la tasa de bits en los metadatos

Qué: la res@bitrate del flujo continuo se calculaba como (sampleRate * channels * bitsPerSample) / 1000 (kbps), con un error de un factor de ~125 con respecto a la especificación UPnP DIDL, que define el atributo como bytes por segundo. Ahora se divide por 8 en lugar de 1000.

Por qué: visualización incorrecta de la tasa de bits en el dispositivo; cosmético en la mayoría de los reproductores, pero algunos asignan búferes de flujo a partir del valor y tartamudean en flujos que parecen ~125 veces más pequeños de lo que son. La ruta del archivo de origen no continuo ya lo tenía correcto ((bitrate_kbps * 1000) \ 8 = bytes/seg); solo la ruta del flujo continuo era incorrecta.


F28 - Corrección del formato de hora de los metadatos (Marantz)

Qué: res@duration en DIDL se formateaba como H:MM:SS (por ejemplo, 0:03:42). La especificación UPnP DIDL define el formato como H+:MM:SS[.F+], estrictamente con segundos fraccionarios opcionales pero recomendados; algunos dispositivos Marantz tratan el formato simple como inválido y dejan su visualización de duración en blanco. Ahora se formatea como H:MM:SS.fff (por ejemplo, 0:03:42.000).

Por qué: problema de visualización específico de una marca; el formato conforme al estilo ISO con segundos fraccionarios lo corrige sin afectar a ningún otro dispositivo. Aplicado en ambos sitios de emisión de DIDL (ruta de archivo de origen + ruta de flujo codificado en WriteAudioFileDIDL).

Corrección adicional en el mismo pase: pv:addedTime y pv:lastPlayedTime usaban hh (reloj de 12 horas) en sus cadenas de formato DateTime en lugar de HH (24 horas). Cualquier pista añadida o reproducida entre las 13:00 y las 23:59 se renderizaría con una hora incorrecta (por ejemplo, 17:42 → "05:42") en los dispositivos que muestran el campo. Ahora usa HH.


F29 - Soporte de búsqueda MP3 codificada (CBR)

Qué: los flujos MP3 transcodificados ahora anuncian DLNA.ORG_OP=11 (búsqueda por byte y por tiempo) en lugar de DLNA.ORG_OP=10 (solo por byte). Los dispositivos que anteriormente rechazaban la búsqueda por tiempo en MP3 transcodificados ahora pueden controlar su barra de progreso/interfaz de usuario de búsqueda normalmente.

Por qué: el transcodificador de MusicBee produce MP3 de tasa de bits constante con el preajuste HighQuality, por lo que el mapeo byte ↔ tiempo es lineal; el dispositivo puede convertir una solicitud de búsqueda por tiempo a una búsqueda por byte de rango HTTP por sí mismo sin ningún soporte del lado del codificador. Anunciar OP=11 desbloquea esa interfaz de usuario en el dispositivo. Sin F29, los usuarios que buscaban dentro de un MP3 transcodificado veían la búsqueda ignorada silenciosamente o se les llevaba al inicio de la pista.

Implementación: se reestructuró GetEncodeFeature en ItemManager.vb para dividir el If en línea en una cadena If/ElseIf/Else legible. MP3 obtiene OP=11 explícitamente; otros códecs no PCM mantienen OP=10. No hay cambios para AAC/FLAC/etc., ya que esos necesitarían una verificación específica del códec de la constancia de la tasa de bits que MusicBee no garantiza.


F30 - Extensión de archivo .mpeg manejada

Qué: los archivos con extensión .mpeg (y el aún más raro .mpe) ahora se reconocen como FileCodec.Mp3 en GetCodec. Antes de F30, devolvían FileCodec.Unknown y eran rechazados silenciosamente de la biblioteca / no podían ser fuentes de transcodificación.

Por qué: los antiguos archivos MPEG-1 Layer 3 a veces usaban .mpeg en lugar de .mp3 (la especificación permite ambos). Un puñado de archivos en una biblioteca de 300k es suficiente para sentir "MusicBee los muestra pero el plugin no", lo que es confuso para el usuario.


Comportamiento de reproducción

F31 - Las transmisiones de radio usan automáticamente el modo continuo

Qué: WriteAudioFileDIDL ahora sondea la propiedad Kind de la URL de origen a través de Library_GetFileProperty y trata cualquier archivo cuya Kind termine en "Stream" (MusicBee informa "MP3 Stream", "Internet Stream", etc. para radio) como continuo, independientemente del interruptor global Settings.ContinuousOutput. Se utiliza la rama DIDL de flujo continuo (Título: "Continuous Stream", id="continuousstream", salida PCM/Wave fija); el dispositivo ve un único flujo de estilo infinito.

Por qué: las transmisiones de radio no tienen límites de pista, ni duración fija, ni búsqueda. Tratarlas como archivos discretos en el DIDL hacía que el plugin anunciara rangos de bytes y duraciones que no existen. El cambio automático cuando MusicBee ya nos dijo "esto es una transmisión" elimina un problema del que el usuario no debería tener que preocuparse.

Alcance: solo se aplica cuando MusicBee está controlando la reproducción (musicBeePlayToMode). La ruta de obtención de la biblioteca (navegación del cliente UPnP) no cambia; las URL de radio son raras allí y el comportamiento orientado al usuario no debería cambiar sin pruebas explícitas.


F32 - Retroceso de anuncio de códec

Qué: si un dispositivo no anuncia ciertos códecs (o el plugin no puede analizar el XML de capacidades del dispositivo), el plugin no rechaza inmediatamente el flujo. En su lugar, intenta servirlo y deja que el dispositivo decida.

Por qué: muchos dispositivos tienen XML de capacidades incompletos o ilegibles, pero en realidad manejan el códec correctamente. F32 cambia una pequeña "mejor suposición e intento" por un rechazo rotundo.


F33 - Mejora de la sincronización de la barra de progreso

Qué: la posición entre encuestas ya se extrapola en tiempo real a partir de un único anclaje (currentPlayStartTicks), por lo que la barra de progreso se actualiza suavemente a una velocidad subsegundo. La fuente de fluctuación restante era el anclaje inicial para una pista recién iniciada: el código anterior asumía position=0 en el momento en que el temporizador de estado notaba por primera vez que el estado pasaba a Reproduciendo, pero para entonces el dispositivo podría haber estado reproduciendo durante 100-500ms (un intervalo de encuesta). La barra de progreso de MusicBee comenzaría en 0, luego avanzaría cuando la realidad se pusiera al día.

Corrección F33: al pasar a Reproduciendo por primera vez en una nueva pista (currentPlayStartTimeEstimated=True), llama a GetPlayPositionInformation() para obtener la posición real actual del dispositivo, luego ancla a eso. UPnP solo informa una resolución de 1 segundo, por lo que el anclaje sigue cuantificado, pero está mucho más cerca de la verdad que asumir 0.

Por qué: visualización de progreso más suave y precisa, especialmente justo después del cambio de pista. No hay forma de evitar la resolución de informes UPnP de 1 segundo en sí misma, eso es una especificación.


F34 - Fluctuación de la barra de progreso después del cambio de pista

Qué: cuando se llama a PlayToDevice para una nueva pista, el plugin solía dejar currentPlayPositionMs y currentPlayStartTicks en sus valores de la pista anterior durante la ventana de ~100ms entre SOAP-Play y la primera encuesta del temporizador de estado que detectaba el nuevo estado de Reproducción. La barra de progreso de MusicBee mostraría brevemente el final de la pista anterior, luego volvería a 0 y luego subiría. F34 pone a cero ambos al entrar en PlayToDevice, en el momento en que sabemos que se está produciendo un cambio de pista, antes de cualquier trabajo SOAP.

Por qué: fallo visual en casos de uso de salto rápido (siguiente manual o transición sin pausas). Ahora, la primera consulta de PlayPositionMs de MusicBee después de Play devuelve 0 limpiamente, luego GetPlayPositionInformation de F33 la refina a la posición real del dispositivo en el primer tic de cambio de estado.

Implementación: cuatro líneas al principio de PlayToDevice, emparejadas con el anclaje preciso en tiempo de transición de F33.


F35 - Error de "Forzar transcodificación"

Qué: la transcodificación forzada aún podía omitir la transcodificación en ciertas combinaciones. Después de la reelaboración por perfil de F04, se sellaron dos brechas específicas:

  1. Precedencia con ForceNativeStream. Cuando ambos eran True (lo que puede ocurrir en una migración de esquema o un archivo de configuración parcial), ForceTranscoding ahora gana directamente (If streamingProfile.ForceTranscoding Then forceEncode = True ElseIf streamingProfile.ForceNativeStream Then forceEncode = False). La exclusión mutua de la interfaz de usuario evita que el usuario marque ambos, pero la protección en tiempo de ejecución maneja cualquier estado que se cargó inconsistente desde el disco.
  2. Lógica de bypassTranscodeDecision. Anteriormente: streamingProfile.ForceNativeStream AndAlso Not Settings.ForceTranscoding. Ahora: streamingProfile.ForceNativeStream AndAlso Not streamingProfile.ForceTranscoding, la misma regla de precedencia pero en el mismo ámbito por perfil.

Por qué: "forzar" debería significar forzar. Si el usuario habilitó explícitamente ForceTranscoding para un dispositivo, el plugin nunca debe pasar silenciosamente a la transmisión nativa, independientemente de cómo se combinen otras banderas.


F36 - Excepción de reproductor cerrado

Qué: se envolvió Plugin.ReceiveNotification en un Try/Catch de nivel superior que registra cualquier excepción no capturada en lugar de dejar que se propague de nuevo al sistema de notificaciones de MusicBee.

Por qué: las notificaciones de MusicBee (PlayStateChanged, VolumeMuteChanged, etc.) se envían a ControlPointManager, que se comunica con el reproductor a través de SOAP. Los sitios de llamada individuales ya tenían Try/Catch alrededor de sus llamadas SOAP, pero un caso de tiempo suficientemente extraño (por ejemplo, el reproductor muriendo entre dos llamadas SOAP en el mismo controlador de notificación) aún podría escapar. El envoltorio de nivel superior es la red de seguridad final para que el usuario nunca vea una ventana emergente genérica de "TargetInvocationException" de MusicBee.

Implementación: se renombró el cuerpo existente a ReceiveNotificationInternal y se añadió un envoltorio delgado ReceiveNotification que hace Try { ReceiveNotificationInternal(...) } Catch { LogError(...) }. La infraestructura Try/Catch preexistente por método dentro de ControlPointManager (alrededor de cada llamada a PostSoapRequest) permanece; F36 es cinturón + tirantes.


F37 - Búsqueda de pista larga que activa una falsa transición

Qué: buscar dentro de una pista larga puede producir un breve ciclo de Detenido→Reproduciendo en algunos reproductores. Sin discriminación, ProcessNewPlayState.Stopped lo trata como un final de pista natural y llama a Player_PlayNextTrack, avanzando MusicBee cuando el usuario solo quería desplazarse. F37 marca lastUserInitiatedSeek en Seek() y añade una protección de 5 segundos en el controlador de Detenido (reflejando la ventana lastUserInitiatedStop existente).

Por qué: el salto silencioso a la siguiente pista durante una búsqueda es uno de esos errores cuya causa nadie puede adivinar; el usuario piensa "qué raro, intenté avanzar y ahora está reproduciendo la siguiente canción". La solución es mecánica: el mismo patrón que la discriminación de detención de usuario ya existente.


F38 - Manejo mejorado de la búsqueda para códecs propensos a fallos

Qué: el bloqueo de BubbleUPnP al buscar MP3 era el síntoma canónico. Después de la auditoría, el código de búsqueda actual de yaiol ya hace lo correcto: la ruta nativa maneja correctamente el rango HTTP (206, Content-Range, AcceptRanges), la ruta codificada anuncia X-AvailableSeekRange y analiza los encabezados timeSeekRange.dlna.org / npt entrantes, los indicadores DLNA.ORG_OP reflejan las capacidades reales del flujo (con DisablePcmTimeSeek opcional para dispositivos Platinum problemáticos). Probado por el usuario en BubbleUPnP 4.6.4 actual: no se observaron bloqueos.

Por qué: el bloqueo de búsqueda de MP3 de BubbleUPnP se informó alrededor de 2024 y la aplicación ha tenido ~16 meses de correcciones desde entonces. F29 (MP3 codificado OP=11) fue la nueva variable que podría haberlo reexpuesto; no lo hace, en las versiones probadas.

Si un bloqueo regresa: la forma de la solución sería un interruptor de "búsqueda limitada" por perfil que fuerce DLNA.ORG_OP=10 (solo por byte) en los códecs marcados, reflejando cómo DisablePcmTimeSeek ya funciona para PCM. Añadirlo entonces, no de forma preventiva.


Interfaz de usuario y registro

F39 - El botón "Añadir" selecciona el nuevo perfil

Qué: al hacer clic en "Añadir" en la lista de perfiles de dispositivo, se crea un nuevo perfil Y se selecciona automáticamente para que el usuario pueda editar los campos inmediatamente. Nuestra refactorización del diálogo seccionado ya hace esto: tanto la ruta de adición directa como la ruta desde la plantilla terminan con Me.activeStreamingProfiles.SelectedIndex = Me.activeStreamingProfiles.Items.Count - 1. Una verificación confirmó que nuestro fork ya maneja esto, no hay nada que cambiar.

Por qué: un pequeño problema de UX que resultó no ser un problema aquí.


F40 - Más conexiones máximas + registro de advertencia

Qué: el límite de flujos concurrentes del plugin (SemaphoreSlim alrededor de Sockets_Stream_File / Sockets_Encoder_Start) era un valor codificado de 4. F40 lo hace configurable por el usuario en la página de configuración General (predeterminado 16, rango 1-256), añade una línea de registro MaxConnections cuando una solicitud tiene que esperar un slot, Y muestra una insignia roja ⚠ Max Conn en la parte inferior izquierda del diálogo de configuración si se alcanzó el límite al menos una vez desde que se inició MusicBee.

Por qué: cuando un dispositivo dispara solicitudes paralelas (algunos Marantz/Linn durante los escaneos de carátulas, las sondas de metadatos de BubbleUPnP junto con la reproducción activa), las solicitudes adicionales se bloqueaban silenciosamente detrás del semáforo; el usuario veía "dispositivo lento" sin una causa visible. La línea de registro es buena para la depuración técnica, pero los usuarios no técnicos nunca leen los registros. La insignia visible en el diálogo de configuración hace que la condición de límite alcanzado sea detectable para cualquiera que abra las preferencias del plugin.

Implementación:

  • Se centralizó la espera en WaitOnSendBarrier(logTag) en MusicBeeUpnp.vb; ambos sitios de llamada (MediaServerDevice.GetFile, Encoder.StartEncode) lo usan.
  • Settings.MaxConnections persistido en la v8 del esquema de configuración.
  • Plugin.MaxConnectionsHit es una bandera de sesión persistente establecida dentro de WaitOnSendBarrier; se restablece solo al reiniciar MusicBee.
  • SettingsDialog.maxConnectionsBadge es una etiqueta roja en negrita en (16, 410) que solo se muestra cuando Plugin.MaxConnectionsHit es True. Tiene una información sobre herramientas que explica la causa y el remedio.
  • El semáforo se inicializa una vez en la carga del tipo, por lo que cambiar la configuración requiere un reinicio de MusicBee (se indica en la etiqueta del campo).

F41 - Registro "codificación debido a ReplayGain/DSP"

Qué: en lugar de líneas de registro separadas "codificando para RG" / "codificando para DSP", la única línea StreamDecision de F42 incluye MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain como razones acumuladas. Mismo valor diagnóstico, menos ruido.

Por qué: los usuarios ven todas las razones por las que se está produciendo la transcodificación para una pista determinada en una sola línea de registro, no dispersas. Consulta F42 para obtener todos los detalles.


F42 - Registrar "el reproductor no admite el códec de origen"

Qué: se añadió una línea de registro StreamDecision por pista de reproducción a dispositivo que dice "CODEC nativo" o "transcodificar CODEC→CODEC razón=…". El campo de razón acumula todas las condiciones que activaron la transcodificación: MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain, WebFile, VirtualFile, ForceTranscoding(global), SampleRate<min/SampleRate>max, DownmixToStereo, DeviceLacksCodec(X), BandwidthConstrained.

Por qué: los usuarios estaban confundidos por picos inesperados de CPU en archivos que esperaban transmitir de forma nativa. Una línea de registro por pista les dice exactamente qué condición causó la transcodificación, y si el campo muestra DeviceLacksCodec(Flac), saben inmediatamente que la información de protocolo del dispositivo estaba incompleta y es posible que quieran que se active el retroceso de F32.

Implementación: una única cadena acumuladora construida incrementalmente a través de la cadena de decisión; registrada una vez al final. Controlada por Settings.LogDebugInfo para evitar ruido en el registro en producción.


F43 - El registro de SetNextAVTransport muestra la URL de origen

Qué: las entradas de registro de QueueNext ahora incluyen source=<ruta de la biblioteca de MusicBee> junto con stream=<URL de transmisión HTTP>. El mismo cambio se aplica a la ruta de éxito y a la ruta de fallo (QueueNext:Failed).

Por qué: al depurar un problema de pista en cola, la URL de transmisión (/encode/aabbccdd0.flac) es opaca por sí misma, la misma para cada pista. La URL de origen es la ruta de la biblioteca que se puede buscar por humanos y que te dice exactamente qué archivo intentó poner en cola MusicBee.


F44 - Mejor registro de errores de tipo MIME

Qué: dos nuevas entradas de registro durante Activate:

  • Activate:MimeUnverified - se activa por cada entrada mal formada en la respuesta GetProtocolInfo del dispositivo, nombrando qué entrada no pudo analizarse (para que el usuario pueda ver, por ejemplo, "el Marantz devolvió http-get:*::* para algún códec; la capacidad no está verificada, el retroceso de F32 adivinará").
  • Activate:NoSinkInfo - se activa una vez si el dispositivo no devolvió ningún elemento <Sink>. Significa que SupportedMimeTypes permanece en Nothing y IsCodecSupported se degrada a "asumir que todo funciona", contexto útil cuando aparecen errores posteriores de "el dispositivo rechazó la transmisión".

Por qué: antes de F44, estas caídas silenciosas de capacidad dejaban a los usuarios adivinando por qué sus pistas eran transcodificadas contra las expectativas o rechazadas por el dispositivo. Ahora, una sola búsqueda de Activate: muestra si la información de capacidad del dispositivo era utilizable.


F45 - Mejor registro de errores de metadatos

Qué: el registro de excepciones de Browse en ContentDirectoryService.vb ya se enriqueció en trabajos anteriores de yaiol (la sesión de errores de Alia Vox) con ObjectID y la traza de la pila. F45 lo amplía aún más con BrowseFlag (metadatos vs. hijos), Filter (qué atributos solicitó el cliente), sortCriteria y partialResultLength (cuántos bytes de DIDL se produjeron antes del fallo, lo que indica qué tan avanzada estaba la pista defectuosa en el lote).

Por qué: cuando algo sale mal a mitad de DIDL, el valor de partial-length te dice si el fallo fue en la primera pista del lote (partial=0) o a mitad (partial=N); combinado con el startingIndex del lote, puedes identificar el índice de la pista ofensiva. Filter y BrowseFlag explican qué tipo de navegación quería el cliente; a veces, una navegación solo de metadatos falla donde una navegación de hijos para el mismo ID tiene éxito.


Redes

F46 - El modo automático solo anuncia en adaptadores de red reales

Qué: en el modo de interfaz Automático, el plugin solía anunciarse (SSDP) en cada adaptador IPv4 operativo. En una máquina que también ejecuta un túnel VPN (NordLynx) o un conmutador virtual (Hyper-V / WSL / Docker), la misma biblioteca se anunciaba también en cada uno de esos adaptadores, por lo que el punto de control desde el que se transmitía descubría el servidor dos o tres veces y listaba la biblioteca como copias duplicadas. El modo automático ahora solo mantiene los adaptadores que tienen una puerta de enlace predeterminada IPv4 real (HasIPv4Gateway), que los adaptadores de túnel y conmutador virtual no tienen, por lo que se eliminan de la lista de anuncios. Una dirección fijada por el usuario sigue teniendo prioridad (anunciar solo en esa interfaz), y si ningún adaptador informa una puerta de enlace, el selector vuelve a todos los adaptadores, por lo que la lista de direcciones anunciadas nunca está vacía y el plugin no puede volverse invisible.

Por qué: el duplicado no es causado por "estar en una VPN", es causado por anunciarse en el adaptador LAN y el adaptador de túnel/virtual al mismo tiempo, por lo que un punto de control ve el mismo servidor en dos direcciones. Una VPN de consumidor (NordVPN/NordLynx) solo tuneliza el tráfico con destino a Internet; el reproductor DLNA vive en la LAN y el tráfico de la subred local omite el túnel, por lo que el adaptador de túnel nunca llega a un reproductor de todos modos; eliminarlo elimina una copia fantasma, nunca una ruta de trabajo. La prueba de la puerta de enlace es la señal barata y confiable que separa un adaptador LAN/Wi-Fi real de un túnel o conmutador virtual. Complementa N05 (que corrigió cómo se envían los anuncios en dichos enlaces, multidifusión en lugar de difusión); F46 rige en qué adaptadores se anuncian.

Límite conocido: una VPN de malla / acceso remoto (Tailscale, ZeroTier, WireGuard-to-home) cuyos reproductores realmente viven a través del túnel generalmente presenta un adaptador sin puerta de enlace predeterminada, por lo que el modo automático también lo elimina. Esos usuarios fijan la dirección VPN en su lugar, lo que tiene prioridad sobre el filtro de puerta de enlace.

Contenido