Novedades
2.0.2 - 2026-07-20
- Ya no se puede eliminar una plantilla mientras algún nodo la siga; el botón de eliminar simplemente permanece deshabilitado, por lo que un nodo nunca puede quedar huérfano. Cada fila de plantilla ahora muestra un recuento en vivo (n) de sus seguidores, lo que explica una eliminación deshabilitada de un vistazo; la eliminación de una plantilla no utilizada ahora también es permanente, en lugar de que las plantillas predeterminadas de fábrica reaparezcan silenciosamente en la siguiente carga.
- Un nuevo botón de embudo junto al árbol de Vista muestra exactamente qué nodos siguen una plantilla: filtra el árbol para mostrar solo los seguidores de la plantilla seleccionada, vuelve a filtrar a medida que seleccionas otras plantillas y restaura el árbol completo cuando se desactiva.
- Los nodos de Radio y Podcasts ahora están permanentemente emparejados con la plantilla de su categoría: remodela la plantilla en la pestaña Rutas y el nodo la seguirá por sí mismo; no hay nada que aplicar, por lo que el botón Aplicar está deshabilitado para ellos. La lista de plantillas refleja esto con dos bandas, Estándar (donde se crean todas las nuevas plantillas) y Reservado (Radio + Podcasts).
2.0.1 - 2026-07-20
- Las plantillas de ruta ahora están vinculadas en vivo a los nodos que las usan. Al aplicar una plantilla, el nodo la sigue: edita la plantilla más tarde y cada nodo que la sigue se reformará inmediatamente, sin tener que buscarla y volver a aplicarla nodo por nodo. El árbol de vista muestra qué plantilla sigue cada nodo justo después de su nombre, un cambio de nombre aparece allí instantáneamente, y al eliminar una plantilla primero te dice cuántos nodos la siguen (mantienen su diseño actual y simplemente dejan de seguir cualquier cosa).
- Aplicar una plantilla a un nodo oculto también lo hace visible de nuevo; aplicar es el gesto de "muéstrame esto, con esta forma", mientras que ocultar permanece con la casilla de verificación Visible. La plantilla reservada "Hidden" que esto reemplaza ha desaparecido.
- El árbol de vista ya no pierde tu lugar: las marcas, las carpetas expandidas y la posición de desplazamiento sobreviven a la aplicación de plantillas y otras actualizaciones.
2.0.0 - 2026-06-16
Esta es la primera versión pública de la bifurcación de código abierto yaiol del plugin MusicBee UPnP. Se presenta en dos partes: todo lo nuevo de esta bifurcación, luego las correcciones y mejoras realizadas al plugin original. Cada elemento mantiene el formato Qué / Por qué del catálogo de características interno del proyecto para que el razonamiento detrás de cada cambio esté en la página, no solo el cambio.
Novedades de esta bifurcación
MediaRenderer - reproducir en MusicBee
N01 - MusicBee como renderizador de reproducción
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 controlador 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, avanzar o retroceder, saltar 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 diálogo de configuración. Los tres roles del plugin tienen cada uno su propia casilla de verificación allí — compartir mi biblioteca (Servidor), permitir que otros reproduzcan en mí (Renderizador) y reproducir en otros dispositivos (Punto de control) — y el 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 renderizador 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 distinguir cuál es cuál. Un cambio de nombre surte efecto inmediatamente, sin necesidad de reiniciar.
Mejor sonido posible cuando se reproduce a sí mismo: cuando navegas por la propia biblioteca 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 propio ecualizador y nivelación de volumen 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 renderizador mientras dejas la compartición de la biblioteca desactivada. En esa configuración de "solo renderizador", tu biblioteca permanece completamente oculta de la red — solo se anuncia el destino de reproducción — y MusicBee nunca ofrecerá reproducir a 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 renderizadores compatibles con 5.1 cuando la fuente era FLAC 5.1. Ahora la segunda cláusula excluye FLAC: Not isPcmData AndAlso encoder.Codec <> FileCodec.Flac. FLAC 5.1 pasa directamente; 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 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 la bifurcación 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 iniciar MusicBee — enumeraba cada pista, Library_GetFileTags completo por archivo, ensamblaba toda la jerarquía de contenedores — antes de abrir el puerto HTTP. En una biblioteca real (más de 50.000 pistas, 5.400 episodios de podcast, cientos de estaciones) eso significa minutos de arranque en frío, y el árbol permanece en la RAM para siempre, incluyendo ramas que ningún cliente abre jamás. Esta bifurcación 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 (LazyBrowse → EnsureLazyEndpointInMemory → 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. Compensación: la primera navegación a un punto final paga su coste de carga; la reentrada se almacena en caché hasta el siguiente cambio de biblioteca. Esta es la base de la que depende 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 se detiene cuando su puerto configurado no está disponible. Tres cambios relacionados:
- Respaldo automático en caso de fallo de enlace.
HttpServer.Startintenta el puerto configurado y, en caso deSocketException, escanea hasta 20 puertos hacia arriba en busca del primero libre. El puerto realmente enlazado se registra en un nuevoPlugin.boundServerPort, y todo lo que anuncia el servidor — URLs SSDPLOCATION(respuesta NOTIFY + M-SEARCH), la URL del dispositivo (PrimaryHostUrl), el reenvío de puertos del router y los autofiltros SSDP/punto de control — ahora leeboundServerPorten lugar deSettings.ServerPort. Los clientes UPnP descubren el puerto real a través de SSDP, por lo que un puerto movido es transparente para los renderizadores. - Notificación al usuario. Cuando ocurre un respaldo (el puerto guardado no es el que está en uso), un
MessageBoxlocalizado (WarnPortInUse) le informa al usuario qué puerto está realmente en servicio 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. - Recuperación de reinicio.
RestartServer(la ruta de reinicio al guardar la configuración) solía desreferenciarPlugin.controller/Plugin.serverciegamente. Si laInitialiseinicial lanzaba una excepción antes de crearlos (exactamente lo que causaba un enlace fallido), el siguiente guardado de configuración provocaba unaNullReferenceException— dejando un plugin medio muerto. Ahora los recrea y los inicia cuando sonNothing, 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 que ya estaba en el nuevo puerto), fallando con WSAEADDRINUSE. Cada fallo era absorbido en Initialise, dejando el plugin silenciosamente inactivo y luego provocando una NRE en el siguiente guardado de configuración. Después de N04, una colisión de puertos se autorrepara — el servidor sigue funcionando 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
49382→9779(por debajo del rango dinámico, por lo que Windows nunca lo reserva automáticamente; no es un valor predeterminado conocido de servidor multimedia) en las tres declaraciones deServerPort+ el respaldo en caso de fallo de análisis de configuración. Plugin.boundServerPort(nuevo campo compartido) contiene el puerto de escucha activo;activeServerPortpermanece como la instantánea configurada para que la lógica de la insignia "Reiniciar requerido" no se active falsamente en caso de respaldo.HttpServer.PortScanRange = 20; el escaneo se detiene en el primerTcpListener.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 el momento de la 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 "cannot access a disposed object" 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 invá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 de la biblioteca
Estos se incluyeron en esta bifurcación y no están en el plugin original. Surgieron de la navegación real de la propia salida del 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 son luego navegables en una jerarquía de AlbumArtistSort → Album → Tracks.
Por qué: los usuarios con filtros de MusicBee curados (por ejemplo, "Pistas de 5 estrellas", "Añadidos recientemente", "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 - Conexión del campo SortAlbumArtist
Qué: el plugin ahora lee el MetaDataType 165 de MusicBee (Artista del álbum de ordenación) 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 ordenació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 único 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 todo navegador de alta fidelidad moderno.
N10 - Ordenación de pistas dentro de álbumes filtrados
Qué: las pistas dentro de un álbum expuesto por filtro ahora se ordenan por Número de disco, luego por Número de pista.
Por qué: orden estándar del álbum. Sin una ordenación explícita, las pistas se devolvían en el orden en que el filtro las había devuelto — generalmente con un aspecto aleatorio.
N11 - Corrección del árbol de carpetas de listas de reproducción
Qué: la función LoadLibraryPlaylists (originalmente de Steven Mayall, ~2014) no lograba descender a las carpetas de listas de reproducción recién creadas. La primera lista de reproducción de cada carpeta, además de cualquier subcarpeta, terminaba huérfana en el nivel raíz.
Por qué: presente en el plugin original durante once años. Visible a los 30 segundos de abrir BubbleUPnP y hacer clic en Listas de reproducción. Corregido en yaiol recursando correctamente en las carpetas recién creadas durante la construcción del árbol.
N12 - Saneamiento de caracteres de control ilegales en XML
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 → truncando 0x99 de nuevo a 0x19) hacía que toda la respuesta de Browse fallara con Action Failed una vez que la pista defectuosa 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 cualquiera. Presente en el plugin original. Corregido eliminando caracteres inválidos en cada punto de salida de Library_GetFileTags a través de XmlConvert.IsXmlChar.
N13 - Listado de radio determinista en navegación paginada
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 otras en ninguna (faltantes) — pareciendo aleatorio 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 al cargar 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 - La búsqueda UPnP de clase álbum devuelve contenedores de álbumes
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 álbumes, 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 navegación
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. Esta bifurcación anuncia las propiedades de búsqueda reales, implementa la búsqueda de 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 de álbumes sean clicables a través de IDs sintéticos Ssrch_alb_* que una rama temprana de Browse mapea de nuevo a las pistas del álbum. (La parte de 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 conformes con la especificación (BubbleUPnP) trataban la biblioteca como inmutable: 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". Esta bifurcación inicializa SystemUpdateID a partir de segundos de época al cargar (para que cada reinicio sea estrictamente posterior al último) y lo actualiza en cada mutación de la biblioteca y cambio de configuración (SetLibraryDirty / ResetCache → BumpSystemUpdateId).
Por qué: los clientes detectan 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átulas de suscripciones de podcast
Qué: las miniaturas de podcast no mostraban imágenes — cada solicitud a /PodcastThumbnail/ devolvía un 404. Dos errores apilados: la cadena de resolución nunca comprobaba la caché de carátulas real de MusicBee (%LocalAppData%\MusicBee\InternalCache\Subscriptions\<name>.jpg, de donde carga la interfaz de usuario de escritorio), y el unescape+minúsculas de la capa HTTP corrompía la clave de ruta de la URL del feed hasta su último segmento de ruta. Esta bifurcación resuelve las carátulas desde la 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é: las carátulas de suscripción ahora se muestran en las vistas de navegación (las 22 solicitudes que antes daban 404 se resuelven).
N18 - Navegación de etiquetas jerárquica (delimitada)
Qué: cualquier campo puede marcarse como jerárquico en la pestaña Opciones de la biblioteca y asignársele 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). Establece Agrupación en / y un valor como Jazz/Cool Jazz y luego 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 cómo se nombran los grupos fusionados del primer campo.
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 — "Género / Artista del álbum de ordenació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 deben estar — bajo su campo compartido.
N21 - Rutas de navegación tipadas por categoría (Estándar / Radio / Podcast)
Qué: cada ruta de navegación se tipifica 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 ofrece solo los campos que los datos de esa categoría pueden realmente proporcionar, y una plantilla solo se puede aplicar a nodos coincidentes en el árbol de Vista (los nodos incompatibles se atenúan y no se pueden seleccionar). 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 silenciosamente resultara 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 destinos 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 de 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 único 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 inútil 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 simple, y el alias de campo de año codificado está eliminado, por lo que cada campo de agrupación ahora se resuelve genéricamente a partir de la definición de ruta.
Por qué: para bibliotecas cuya etiqueta de Año contiene una fecha completa, agrupar o buscar 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 encuentren las pistas de nuevo.
N25 - Campos de agrupación separados "Año" y "Año (aaaa)"
Qué: las rutas de agrupación y navegación de álbumes ahora exponen ambos 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 acumulen en un solo grupo al final de la raíz. Cada acceso directo anclado se sitúa con los de su tipo.
Por qué: a medida que anclas más accesos directos, un único 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.
Diálogo de configuración y empaquetado
N26 - 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 — bien para el desarrollador que lo construyó, desconcertante para todos los demás. La seccionamiento agrupa opciones relacionadas y hace que el 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 en tiempo de ejecución (mecanismo detrás de F40)
Qué: un patrón de interfaz de usuario genérico para mostrar condiciones importantes en tiempo de ejecución como insignias de colores visibles en el diálogo de Configuración. Instancias actuales:
- ⚠ Conexiones Máx. (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 deWaitOnSendBarriercuando no hay ningún espacio 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 encuentra con "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 abre el plugin — descubrible sin leer nada.
Reutilizable para el futuro:
- Desajuste de perfil detectado (el user-agent del dispositivo nunca coincidió con ningún perfil, se recurrió a Genérico).
- Retroceso de NextURI activado (F13 - reproducción sin pausas deshabilitada para la sesión en un dispositivo inestable).
- Escaneo de biblioteca fallido / parcial.
- Conexión del renderizador perdida a mitad de sesión.
- Cualquier otra condición donde "sucedió una vez, el usuario debería saberlo" supera a "registrado silenciosamente entre otras 1000 líneas".
Convenciones de implementación:
- Las etiquetas de las insignias residen 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 en la fila inferior cerca de Guardar/Cancelar (actual: y=410 apiladas horizontalmente).
- Cada insignia tiene una bandera de sesión persistente correspondiente en
Pluginque se activa a True cuando ocurre la condición y se reinicia solo al reiniciar MusicBee. - Recursos:
<Condition>Badge(texto de la etiqueta, prefijo con ⚠) +<Condition>BadgeTip(tooltip que explica la causa + remedio). - Para las insignias de "la configuración guardada necesita reinicio", toma una instantánea en tiempo de ejecución en
Plugin.Initialise()y compárala conSettings.*después deSettings.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 diálogo de configuración ahora se descartan cuando el usuario hace clic en Cancelar, en lugar de permanecer aplicadas silenciosamente, y cualquier plantilla reservada eliminada durante la sesión se vuelve a crear. (Las plantillas, por lo demás, 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 manualmente.
N28 - Ampersands literales en el menú selector de campos
Qué: un campo cuyo nombre contiene "&" — por ejemplo, "Estado de ánimo & Contexto" — renderiza el ampersand literalmente en el menú selector de campos en lugar de absorberlo 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 la 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 la redacción por idioma era una superficie traducible sin beneficio — el título es una marca. Fijarlo lo mantiene estable y consistente en todas partes.
Localización
El plugin original solo está en inglés. Esta bifurcación es totalmente localizable — cada cadena visible para el usuario fluye a través de un paquete de recursos, y el plugin detecta automáticamente el idioma de la interfaz de usuario de MusicBee.
N30 - Interfaz de usuario multi-idioma (traducciones pendientes)
Qué: la maquinaria de localización está completa y en producción. 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 visible para el usuario está conectado a una clave de recurso (controles de diseñador a través de ApplyDesignerExtras + sync-en-locale.js; cadenas en tiempo de ejecución como WarnPortInUse añadidas manualmente). Lo que no está hecho todavía es la traducción real: solo existe el paquete de recursos fuente 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 características (traducir por partes mientras las cadenas aún cambian es un esfuerzo desperdiciado).
Idiomas de destino (el conjunto que ofrece el propio MusicBee, emparejado 1:1 por endonymToCulture para que el plugin siga el idioma de MusicBee automáticamente):
Á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 localización 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 único paquete — "English(US)" (en-US) de MusicBee recurre a en a través de la cadena de culturas de .NET, por lo que no se produce un paquete US separado. ES y FR son igualmente de una sola localización.
Por qué: la configuración de un plugin UPnP ("no usar PCM sin procesar", "forzar PCM little-endian", advertencias de respaldo de puerto) es bastante críptica en el idioma nativo de uno. 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é: abrir el enlace de Ayuda desde el plugin respeta el idioma completo de la interfaz del usuario (por ejemplo, pt-BR, zh-CN) en lugar de colapsar al idioma base, y 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 brasileño, 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 banderas 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 estaban congelados alrededor de 2014. PS4/Xbox/BubbleUPnP han ganado soporte de audio de alta resolución desde entonces. De fábrica, una nueva instalación reproduce con 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 renderizador 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 está marcada, 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 a byte (salvo el enmarcado HTTP).
Por qué: según testimonios en foros, esta es la mayor mejora en la calidad de reproducción. Los usuarios de alta fidelidad que compran renderizadores 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ían ser opcionales. 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 inverso 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 la opción ForceNativeStream por perfil (F03). Caso real: el dispositivo A es un DAC de alta fidelidad que desea flujos nativos bit-perfectos; el dispositivo B es un antiguo receptor AV que se atasca con FLAC. Con un interruptor global, el usuario tendría que elegir — a costa del otro dispositivo. Con la configuración por perfil, cada dispositivo obtiene la respuesta correcta.
Implementación:
StreamingProfile.ForceTranscoding As Boolean = False.- Esquema de persistencia actualizado a v9. Los archivos pre-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 bidireccional (
CheckedChangeden cada uno desuscribe al otro antes de cambiar, para evitar un bucle infinito). - Punto de decisión:
Settings.ForceTranscoding→streamingProfile.ForceTranscodingenWriteAudioFileDIDL.
F05 - "Forzar PCM little-endian" por perfil
Qué: los flujos PCM (tipos mime L16/L24) son big-endian según la especificación. Algunos dispositivos esperan erróneamente little-endian y reproducen ruido blanco cuando se les entregan 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 soporta PCM sin procesar, el plugin lo usa. 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-sobre-Wave independientemente de lo que anuncie el dispositivo.
Por qué: modelos específicos de Marantz — anuncian PCM sin procesar pero solo WAVE funciona. Sin esto, los flujos PCM sin procesar salen distorsionados, sin ningún mensaje de error que lo indique.
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 bloques).
- 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, a algunos les molesta en los flujos, algunos necesitan un valor centinela "realmente grande" para mantener el 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 (notablemente 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 pista cuando la cola se vacía. Con F08 marcado, el dispositivo mantiene el NextURI obsoleto en 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: 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.Flac ↔ SelectedIndex = 5. El tipo Mime, DLNA y la característica de codificación ya estaban conectados en ItemManager.GetMimes / GetDlnaType / GetEncodeFeature de trabajos anteriores (F21, F26).
Sin pausas (SetNextAVTransportURI)
F10 - Núcleo de SetNextAVTransportURI / NextURI
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 solo 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 (grabaciones 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 esta bifurcación.
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 interviene en la pista en cola. Compensación: 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 recurrir 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 incompletas, cuelgues). En lugar de hacer ingeniería inversa en 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 ponerse en cola para la 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 ha cambiado (o borra la cola si MusicBee dice que no hay siguiente pista).
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
nextPlaySourceUrlalmacena la URL de la biblioteca de MusicBee de lo que está en cola (la URL de streaming con sufijo de identificador no es comparable a una ruta de biblioteca). - Nueva
Public Sub RefreshQueuedNextUri()enMediaRendererDevice. Tres resultados: ningún NextURI en cola → no-op; en cola coincide con el nuevo "siguiente" → no-op; en cola difiere → llamar aQueueNextcon la nueva URL (oQueueNext("")para borrar — lo que respeta F08 DoNotClearNextUri). - Conectado en
Plugin.ReceiveNotificationbajoNotificationType.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, problemas de red), el plugin seguiría reintentando en cada pista. F13 detiene el ruido y recurre 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 "de envoltura" correcta (pista 1 al final de la lista) a
Plugin.QueueNextdirectamente. No se necesita lógica especial del plugin — el dispositivo transiciona a ella y el detector F15 llama aPlayer_PlayNextTrackcomo de costumbre, lo que devuelve el índice NPL de MusicBee a 0. - Repetir uno: MusicBee pasa la MISMA URL de pista a
Plugin.QueueNext. El dispositivo transiciona a ella (nuevo identificador de flujo, misma fuente). El detector F15 ahora consultaPlayer_GetRepeat()— si esRepeatMode.One, omite la llamada aPlayer_PlayNextTrackpara 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 sobre el 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 detectarlo 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 el URI reportado coincide con el 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 y por renderizador porque cada marca de renderizador tiene sus propias peculiaridades en cuándo informa el cambio de URI (algunos informan TRANSITIONING primero, algunos saltan directamente a PLAYING con el nuevo URI, algunos tienen un breve STOPPED entre medias).
Notas: nuestra primera versión funciona en el renderizador 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, forzando al DAC del dispositivo a volver a sincronizarse en la transición. F16 añade un diagnóstico NextUri:FormatChange que se activa en el momento de la cola siempre que los formatos difieren, nombrando ambos lados — para que los usuarios que escuchan chasquidos puedan correlacionar.
El diagnóstico también apunta a la mitigación: marca Forzar transcodificación 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 en reproducción) 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 añadirí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
lastSourceUrlrastrea la URL de origen que se está reproduciendo actualmente. QueueNextleeFilePropertyType.SampleRate/Channels/Kindpara las pistas actuales y en cola y registraNextUri:FormatChangeen caso de desajuste.
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 de 1 segundo de RelTime de UPnP: cuando la posición reportada por el dispositivo se redondea a menos de 1 segundo del objetivo solicitado por el usuario, el plugin ahora confía en el valor sub-segundo preciso del usuario en lugar de la truncación del dispositivo. Solo cuando el dispositivo informa algo drásticamente diferente (más de 1s de diferencia) usamos su valor (la búsqueda aterrizó en un lugar diferente al solicitado, por ejemplo, ajuste a fotograma clave en algunos códecs).
Por qué: sin esto, buscar a 2:30.500 anclado en el informe "2:30" del dispositivo haría que la barra de progreso mostrara ~500ms de retraso con respecto a 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é: dos interbloqueos ahora en su lugar:
- Tiempo de ejecución:
QueueNextdevuelveFalseanticipadamente cuandoSettings.ContinuousOutputestá activado. El flujo continuo es su propio mecanismo sin pausas (un flujo largo concatenado); enviarSetNextAVTransportURIademás confunde al dispositivo sobre si cada pista es un URI discreto o parte del flujo continuo. - Interfaz de usuario: cuando el usuario marca la casilla de flujo continuo global, el
forceNativeStreamdel perfil actualmente mostrado se desmarca automáticamente. El flujo continuo siempre transcodifica, por lo que forzar 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 un URI de flujo continuo COMO un NextURI para cada pista subsiguiente, con un comportamiento indefinido dependiendo del renderizador.
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 — registrados pero no propagados como errores.
Por qué: la condición de "no hay siguiente pista" es normal, no un error. Tratarlo como fatal contamina el registro y (en algunos flujos) desencadena tormentas de reintentos.
Tipos MIME y metadatos DLNA
F20 - Mime MP3 → 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 renderizadores más estrictos lo rechazan.
Por qué: corrige silenciosamente la reproducción en dispositivos más estrictos que siguen el estándar. La base de código de yaiol ya tenía esto correcto; no se necesitaba ningún cambio.
F21 - Orden de tipos MIME: variante sin x- primero
Qué: cuando un dispositivo anuncia tanto audio/flac como audio/x-flac, el plugin devuelve primero la variante sin x-. Lo mismo para cualquier códec con mimes estándar y experimentales.
Por qué: el prefijo x- marca mimes experimentales/no oficiales. Algunos renderizadores se comportan mejor con el formato estándar. Pequeña reordenación, impacto real.
F22 - Soporte de tipo MIME Opus
Qué: reconoce Opus como un códec de audio transmisible; envía el 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 streaming/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 - Respaldo de tipo MIME AAC / ALAC
Qué: si un dispositivo soporta AAC o ALAC pero no los anuncia explícitamente en su descripción de servicio UPnP, el plugin los ofrece de todos modos como respaldo.
Por qué: varios dispositivos que manejan AAC correctamente olvidaron listarlo en su XML de capacidades. Sin F24, el plugin ni siquiera lo intentará, forzando la transcodificación. Con F24, el plugin lo intenta y permite que el dispositivo lo maneje de forma nativa si puede.
F25 - Bandera de tipo DLNA para flujos WAV nativos + codificados
Qué: la bandera de tipo DLNA (un identificador de perfil como LPCM, WAVE, MP3) debe coincidir con lo que recibe el dispositivo. F25 asegura 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 soportan FLAC no reconocen el flujo como tal.
F27 - Corrección del cálculo de la tasa de bits en los metadatos
Qué: el 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 divide por 8 en lugar de 1000.
Por qué: visualización de tasa de bits incorrecta en el dispositivo — cosmético en la mayoría de los renderizadores, 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 tenía esto correcto ((bitrate_kbps * 1000) \ 8 = bytes/seg); solo la ruta del flujo continuo estaba mal.
F28 - Corrección del formato de hora de 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 formateado 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 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 dispositivos que muestran el campo. Ahora usa HH.
F29 - Soporte de búsqueda en MP3 codificado (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 transcodificado ahora pueden manejar su barra de progreso/interfaz 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 bytes 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 eran enviados al inicio de la pista.
Implementación: reestructurado 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. Sin cambios para AAC/FLAC/etc. — esos necesitarían verificación específica del códec de la naturaleza CBR 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" — confuso para el usuario.
Comportamiento de reproducción
F31 - Los flujos 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 reporta "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: "Flujo continuo", id="continuousstream", salida PCM/Wave fija); el dispositivo ve un único flujo de estilo infinito.
Por qué: los flujos de radio no tienen límites de pista, ni longitud fija, ni búsqueda. Tratarlos 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 un flujo" elimina un problema en el que el usuario no debería tener que pensar.
Alcance: solo se aplica cuando MusicBee está controlando la reproducción (musicBeePlayToMode). La ruta de obtención de biblioteca (navegación de cliente UPnP) no cambia — las URLs de radio allí son raras y el comportamiento visible para el usuario no debería cambiar sin pruebas explícitas.
F32 - Respaldo 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 un XML de capacidades incompleto o ilegible pero en realidad manejan el códec correctamente. F32 cambia un pequeño "mejor intento y prueba" por un rechazo total.
F33 - Mejora de la sincronización de la barra de progreso
Qué: la posición entre sondeos ya se extrapola por reloj de pared desde un único ancla (currentPlayStartTicks), por lo que la barra de progreso se actualiza suavemente a una velocidad sub-segundo. La fuente de fluctuación restante era el ancla inicial para una pista recién iniciada: el código anterior asumía position=0 en el momento en que el temporizador de estado notó por primera vez que el estado pasó a Reproduciendo, pero para entonces el dispositivo podría haber estado reproduciendo durante 100-500ms (un intervalo de sondeo). La barra de progreso de MusicBee comenzaría en 0, luego saltaría hacia adelante 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 actual real del dispositivo, luego ancla contra eso. UPnP solo informa una resolución de 1 segundo, por lo que el ancla sigue cuantificada, pero está mucho más cerca de la verdad que asumir 0.
Por qué: visualización de progreso más suave + más precisa, especialmente justo después del cambio de pista. No hay forma de evitar la resolución de informe de 1 segundo de UPnP en sí — 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 con sus valores de la pista anterior durante la ventana de ~100ms entre SOAP-Play y el primer sondeo 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é: forzar la transcodificación 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:
- Precedencia con ForceNativeStream. Cuando ambos eran True (lo que puede ocurrir durante una migración de esquema o un archivo de configuración parcial), ForceTranscoding ahora prevalece 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 haya cargado de forma inconsistente desde el disco. - Lógica de bypassTranscodeDecision. Anteriormente:
streamingProfile.ForceNativeStream AndAlso Not Settings.ForceTranscoding. Ahora:streamingProfile.ForceNativeStream AndAlso Not streamingProfile.ForceTranscoding— 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 recurrir silenciosamente a la transmisión nativa, independientemente de cómo se combinen otras banderas.
F36 - Excepción de renderizador cerrado
Qué: 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 vuelta 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 renderizador a través de SOAP. Los sitios de llamada individuales ya tenían Try/Catch alrededor de sus llamadas SOAP, pero un caso de temporización suficientemente extraño (por ejemplo, el renderizador muriendo entre dos llamadas SOAP en el mismo controlador de notificación) aún podría escapar. El envoltorio de nivel superior es la última red de seguridad para que el usuario nunca vea una ventana emergente genérica de "TargetInvocationException" de MusicBee.
Implementación: renombró el cuerpo existente a ReceiveNotificationInternal y 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 en pista larga que activa una transición falsa
Qué: buscar dentro de una pista larga puede producir un breve ciclo Detenido→Reproduciendo en algunos renderizadores. Sin discriminación, ProcessNewPlayState.Stopped lo trata como el final natural de la pista 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 parada de usuario ya existente.
F38 - Manejo mejorado de la búsqueda para códecs propensos a fallos
Qué: el bloqueo de BubbleUPnP al buscar en 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 HTTP Range correctamente (206, Content-Range, AcceptRanges), la ruta codificada anuncia X-AvailableSeekRange y analiza los encabezados entrantes timeSeekRange.dlna.org / npt, las banderas DLNA.ORG_OP reflejan las capacidades reales del flujo (con DisablePcmTimeSeek para dispositivos Platinum problemáticos). Probado por usuarios en BubbleUPnP 4.6.4 actual: no se observaron bloqueos.
Por qué: el bloqueo de BubbleUPnP al buscar en MP3 se informó alrededor de 2024 y la aplicación ha tenido ~16 meses de correcciones desde entonces. F29 (MP3 codificado OP=11) era 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 por perfil de "búsqueda limitada" que fuerza DLNA.ORG_OP=10 (solo por byte) en códecs marcados — reflejando cómo DisablePcmTimeSeek ya funciona para PCM. Añadir entonces, no de forma preventiva.
Interfaz de usuario y registro
F39 - El botón "Añadir" selecciona el nuevo perfil
Qué: hacer clic en "Añadir" en la lista de perfiles de dispositivo crea un nuevo perfil Y lo 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 Añadir directo como la ruta desde plantilla terminan con Me.activeStreamingProfiles.SelectedIndex = Me.activeStreamingProfiles.Items.Count - 1. Una verificación confirmó que nuestra bifurcación ya maneja esto — nada que cambiar.
Por qué: pequeño problema de UX que resultó no ser un problema aquí.
F40 - Mayor número de conexiones máximas + registro de advertencia
Qué: el límite de flujos concurrentes del plugin (SemaphoreSlim alrededor de Sockets_Stream_File / Sockets_Encoder_Start) estaba codificado a 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 espacio, Y muestra una insignia roja ⚠ Conexiones Máx. 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 escaneos de carátulas, sondeos 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 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 descubrible para cualquiera que abra las preferencias del plugin.
Implementación:
- Centralizó la espera en
WaitOnSendBarrier(logTag)enMusicBeeUpnp.vb; ambos sitios de llamada (MediaServerDevice.GetFile,Encoder.StartEncode) lo usan. Settings.MaxConnectionspersistido en la v8 del esquema de configuración.Plugin.MaxConnectionsHites una bandera de sesión persistente establecida dentro deWaitOnSendBarrier; se reinicia solo al reiniciar MusicBee.SettingsDialog.maxConnectionsBadgees una etiqueta roja en negrita en(16, 410)que solo se muestra cuandoPlugin.MaxConnectionsHites True. Tiene una descripción emergente que explica la causa y el remedio.- El semáforo se inicializa una vez al cargar el tipo, por lo que cambiar la configuración requiere un reinicio de MusicBee (indicado en la etiqueta del campo).
F41 - Registrar "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 más detalles.
F42 - Registrar "el renderizador no soporta 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 cada condición que activó 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 pueden querer que se active el respaldo de F32.
Implementación: cadena acumuladora única construida incrementalmente a través de la cadena de decisión; registrada una vez al final. Controlado 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 biblioteca de MusicBee> junto con stream=<URL de streaming HTTP>. Mismo cambio aplicado 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 streaming (/encode/aabbccdd0.flac) es opaca por sí misma — la misma para cada pista. La URL de origen es la ruta de biblioteca legible por humanos 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 malformada en la respuestaGetProtocolInfodel dispositivo, nombrando qué entrada no pudo ser analizada (para que el usuario pueda ver, por ejemplo, "el Marantz devolvióhttp-get:*::*para algún códec — la capacidad no está verificada, el respaldo de F32 adivinará").Activate:NoSinkInfo- se activa una vez si el dispositivo no devolvió ningún elemento<Sink>. Significa queSupportedMimeTypespermanece en Nothing yIsCodecSupportedse degrada a "asumir que todo funciona" — contexto útil cuando aparecen errores posteriores de "dispositivo rechazó el flujo".
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 había enriquecido en trabajos anteriores de yaiol (la sesión de errores de Alia Vox) con ObjectID y el seguimiento de la pila. F45 lo expande 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 — indica qué tan avanzada estaba la pista defectuosa en el lote).
Por qué: cuando algo sale mal a mitad de DIDL, el valor de longitud parcial te dice si el fallo fue en la primera pista del lote (parcial=0) o a mitad de camino (parcial=N) — combinado con el startingIndex del lote, puedes identificar el índice de la pista infractora. 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 se anuncia en adaptadores de red real
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 transmites descubría el server dos o tres veces y mostraba la biblioteca como copias duplicadas. El modo automático ahora conserva solo los adaptadores que tienen una puerta de enlace IPv4 predeterminada real (HasIPv4Gateway) - que los adaptadores de túnel y de conmutador virtual no tienen - de modo que esos se eliminan de la lista de anuncios. Una dirección fijada por el usuario sigue ganando siempre (anuncio solo en esa interfaz), y si ningún adaptador informa de una puerta de enlace, el selector recurre a todos los adaptadores, por lo que la lista de direcciones anunciadas nunca queda vacía y el plugin no puede volverse invisible.
Por qué: el duplicado no lo causa "estar en una VPN" - lo causa anunciarse en el adaptador LAN y en el adaptador de túnel/virtual al mismo tiempo, de modo que un mismo punto de control ve el mismo server en dos direcciones. Una VPN de consumo (NordVPN/NordLynx) solo tuneliza el tráfico destinado a internet; el renderizador DLNA vive en la LAN y el tráfico de la subred local evita el túnel, así que el adaptador del túnel nunca llega a un renderizador de todos modos - eliminarlo quita una copia fantasma, nunca una ruta funcional. La prueba de puerta de enlace es la señal barata y fiable que separa un adaptador LAN/Wi-Fi real de un túnel o un conmutador virtual. Complementa a N05 (que corrigió cómo se envían los anuncios en tales enlaces - multicast en lugar de broadcast); F46 gobierna en qué adaptadores se anuncia siquiera.
Límite conocido: una VPN de malla / acceso remoto (Tailscale, ZeroTier, WireGuard hacia casa) cuyos renderizadores viven realmente al otro lado del túnel suele presentar un adaptador sin puerta de enlace predeterminada, por lo que el modo automático también lo descarta. Esos usuarios fijan en su lugar la dirección VPN, que tiene prioridad sobre el filtro de puerta de enlace.