Novidades
2.0.2 - 2026-07-20
- Um modelo não pode mais ser excluído enquanto qualquer nó o segue — o botão de exclusão simplesmente permanece desabilitado, para que um nó nunca seja deixado órfão. Cada linha de modelo agora mostra uma contagem (n) em tempo real de seus seguidores, o que explica uma exclusão desabilitada rapidamente; a exclusão de um modelo não utilizado agora também permanece, em vez dos padrões enviados reaparecerem silenciosamente na próxima carga.
- Um novo botão de funil ao lado da árvore de Visualização mostra exatamente quais nós seguem um modelo: ele filtra a árvore apenas para os seguidores do modelo selecionado, refiltra conforme você seleciona outros modelos e restaura a árvore completa quando desativado.
- Os nós de Rádio e Podcasts agora estão permanentemente emparelhados com o modelo de sua categoria — remodele o modelo na guia Caminhos e o nó segue por si só; não há nada para aplicar, então o botão Aplicar está desabilitado para eles. A lista de modelos reflete isso com duas bandas, Padrão (onde todos os novos modelos são criados) e Reservado (Rádio + Podcasts).
2.0.1 - 2026-07-20
- Os modelos de caminho agora estão vinculados dinamicamente aos nós que os utilizam. Aplicar um modelo faz com que o nó o siga: edite o modelo mais tarde e cada nó que o segue se reformula imediatamente — sem precisar procurá-lo e reaplicá-lo nó por nó. A árvore de visualização mostra qual modelo cada nó segue logo após seu nome, uma renomeação aparece lá instantaneamente, e a exclusão de um modelo primeiro informa quantos nós o seguem (eles mantêm seu layout atual e simplesmente param de seguir qualquer coisa).
- Aplicar um modelo a um nó oculto também o torna visível novamente — aplicar é o gesto "mostre-me isso, formatado assim", enquanto ocultar permanece com a caixa de seleção Visível. O modelo reservado "Oculto" que isso substitui foi removido.
- A árvore de visualização não perde mais seu lugar: as marcas, pastas expandidas e posição de rolagem sobrevivem à aplicação de modelos e outras atualizações.
2.0.0 - 2026-06-16
Esta é a primeira versão pública do fork de código aberto yaiol do plugin UPnP do MusicBee. Ele é apresentado em duas partes: tudo o que é novo neste fork, e depois as correções e melhorias feitas no plugin original. Cada item mantém o formato O quê / Por quê do catálogo de recursos interno do projeto, para que o raciocínio por trás de cada mudança esteja na página, e não apenas a mudança.
Novo neste fork
MediaRenderer - reproduzir no MusicBee
N01 - MusicBee como um renderizador de reprodução
O quê: normalmente este plugin funciona de uma maneira: um telefone ou outro dispositivo navega pela biblioteca do MusicBee e reproduz a música em si. Este recurso adiciona a direção oposta - ele permite que o MusicBee seja o reprodutor. A partir de um aplicativo controlador em seu telefone (como o BubbleUPnP), você pode escolher o MusicBee do seu desktop como o dispositivo de reprodução e controlá-lo com a mão: reproduzir, pausar, parar, avançar ou retroceder, pular para um ponto na faixa e alterar o volume ou silenciar.
Por quê: ele transforma seu telefone em um controle remoto para a música que já está no seu PC. Sente-se no sofá, navegue pela sua biblioteca no telefone, toque em uma faixa e ela sairá dos alto-falantes conectados ao seu desktop - com controle total de onde você está sentado. O plugin original nunca entregou isso como um recurso funcional.
Ativando: está desativado por padrão, porque ativá-lo permite que qualquer dispositivo em sua rede doméstica inicie a reprodução em seu PC. Você o habilita com uma caixa de seleção na guia Geral da caixa de diálogo de configurações. As três funções do plugin têm suas próprias caixas de seleção lá - compartilhar minha biblioteca (Servidor), permitir que outros reproduzam para mim (Renderizador) e reproduzir para outros dispositivos (Ponto de Controle) - e a caixa de diálogo mostra apenas as guias de configurações que as funções que você ativou realmente precisam, para que você nunca se depare com opções que não se aplicam a você.
Distinguindo suas máquinas: você pode dar ao renderizador o nome que quiser (ele começa como "MusicBee (yaiol)"). Esse nome é o que aparece na lista de destinos de reprodução do seu telefone, então, quando mais de um PC estiver executando o MusicBee, você poderá saber qual é qual. Uma mudança de nome entra em vigor imediatamente, sem reiniciar.
Melhor som possível ao reproduzir para si mesmo: quando você navega pela própria biblioteca do MusicBee a partir do seu telefone e envia uma faixa de volta para o mesmo MusicBee, o plugin reconhece que está sendo solicitado a reproduzir um de seus próprios arquivos e simplesmente o reproduz diretamente do seu disco. O resultado é exato e instantâneo - bit-perfect, com o próprio equalizador e nivelamento de volume do MusicBee aplicados - em vez de enviar o áudio desnecessariamente para a rede e de volta para si mesmo.
Executando-o de forma privada: as três funções funcionam independentemente, então você pode ativar o renderizador enquanto mantém o compartilhamento da biblioteca desativado. Nessa configuração "somente renderizador", sua biblioteca permanece completamente oculta da rede - apenas o destino de reprodução é anunciado - e o MusicBee nunca oferecerá a reprodução para si mesmo.
Comportamento de reprodução
N02 - FLAC 5.1 não é automaticamente downmixado
O quê: o limite de contagem de canais em MediaServerDevice.GetEncodedFile era If StereoOnly OrElse Not isPcmData Then channelCount = 2. A cláusula Not isPcmData silenciosamente downmixava toda transcodificação não-PCM (FLAC, MP3, AAC, Ogg) para estéreo, independentemente da contagem de canais da fonte, desativando renderizadores capazes de 5.1 quando a fonte era FLAC 5.1. Agora a segunda cláusula exclui FLAC: Not isPcmData AndAlso encoder.Codec <> FileCodec.Flac. FLAC 5.1 passa; MP3/AAC/Ogg ainda forçam estéreo porque os codificadores de linha de comando do MusicBee para esses formatos esperam entrada de 2 canais.
Por quê: o objetivo de transcodificar uma fonte FLAC 5.1 para saída FLAC é preservar a mixagem multicanal. O downmix silencioso tornava a opção de transcodificação FLAC inútil para audição surround. Com N02, ele faz a coisa certa.
Arquitetura
A mudança estrutural que torna o fork viável em uma grande biblioteca - ausente no plugin original.
N03 - Árvore de navegação preguiçosa (sob demanda)
O quê: o plugin original construía toda a árvore de navegação na inicialização do MusicBee - enumerava cada faixa, Library_GetFileTags completo por arquivo, montava toda a hierarquia de contêineres - antes de abrir a porta HTTP. Em uma biblioteca real (mais de 50 mil faixas, 5400 episódios de podcast, centenas de estações), isso significa minutos de inicialização a frio, e a árvore permanece na RAM para sempre, incluindo ramificações que nenhum cliente jamais abre. Este fork não constrói nada antecipadamente: a raiz expõe um placeholder prefixado com L: por endpoint (L:music, L:podcast, L:filter:…); cada nível é calculado apenas quando um cliente navega nele (LazyBrowse → EnsureLazyEndpointInMemory → caches por nível), e as notificações de mudança de biblioteca limpam os caches (SetLibraryDirty).
Por quê: a inicialização a frio é essencialmente instantânea - a porta HTTP está aberta quando o MusicBee termina a inicialização do plugin - e a memória permanece proporcional ao que foi navegado, não ao tamanho da biblioteca. Compensação: a primeira navegação em um endpoint paga seu custo de carregamento; a reentrada é armazenada em cache até a próxima mudança da biblioteca. Esta é a base da qual todo o resto depende. Notas completas: FIXES.md.
Rede e robustez
Reforçando o caminho de ligação do servidor HTTP. O plugin original morre silenciosamente quando sua porta está indisponível.
N04 - Ligação de porta HTTP auto-reparável
O quê: o servidor HTTP do plugin não morre mais quando sua porta configurada está indisponível. Três mudanças interligadas:
- Retorno automático em caso de falha de ligação.
HttpServer.Starttenta a porta configurada e, em caso deSocketException, verifica até 20 portas para a primeira livre. A porta real ligada é registrada em um novoPlugin.boundServerPort, e tudo o que anuncia o servidor - URLs SSDPLOCATION(NOTIFY + resposta M-SEARCH), o URL do dispositivo (PrimaryHostUrl), o encaminhamento de porta do roteador e os auto-filtros SSDP/ponto de controle - agora lêboundServerPortem vez deSettings.ServerPort. Clientes UPnP descobrem a porta real via SSDP, então uma porta movida é transparente para os renderizadores. - Notificação ao usuário. Quando ocorre um fallback (a porta salva não é a que está em uso), uma
MessageBoxlocalizada (WarnPortInUse) informa ao usuário qual porta está realmente servindo e que os dispositivos ainda a encontrarão - porque o plugin é executado sem interface e uma mensagem na caixa de diálogo só seria vista por alguém que já suspeitava de um problema. - Recuperação de reinício.
RestartServer(o caminho de reinício de salvamento de configurações) costumava desreferenciarPlugin.controller/Plugin.servercegamente. Se aInitialiseinicial lançasse uma exceção antes de criá-los (exatamente o que uma ligação falha causava), o próximo salvamento de configurações atingiria umaNullReferenceException- deixando um plugin meio morto. Agora ele os recria e inicia quandoNothing, então salvar uma porta funcional revive o plugin sem uma reinicialização completa do MusicBee.
Por quê: o gatilho foi um incidente real com um usuário. A antiga porta padrão 49382 reside no intervalo dinâmico do Windows (49152-65535), onde Hyper-V/WSL2/Docker/WinNAT reservam grandes blocos que mudam a cada inicialização - então a ligação falhou com WSAEACCES ("acesso proibido") em uma máquina onde havia funcionado por meses. Mudar o padrão para uma porta livre então colidiu com o Serviio (um servidor DLNA separado já na nova porta), falhando com WSAEADDRINUSE. Cada falha era engolida em Initialise, deixando o plugin silenciosamente morto e depois NRE-ing no próximo salvamento de configurações. Após N04, uma colisão de porta se auto-cura - o servidor continua funcionando na próxima porta livre, o usuário é informado e os clientes o redescobrem - em vez de derrubar todo o plugin.
Implementação:
- Porta padrão movida
49382→9779(abaixo do intervalo dinâmico, então o Windows nunca a reserva automaticamente; não é um padrão conhecido de servidor de mídia) em todas as três declarações deServerPort+ o fallback de falha de análise de configurações. Plugin.boundServerPort(novo campo compartilhado) mantém a porta de escuta ativa;activeServerPortpermanece o instantâneo configurado para que a lógica do distintivo "Reinicialização Necessária" não dispare falsamente em um fallback.HttpServer.PortScanRange = 20; a varredura para na primeiraTcpListener.Start()bem-sucedida e lança a última exceção apenas se todas as tentativas falharem.- Nova chave de recurso EN
WarnPortInUse(as traduções seguem a passagem de localidade no momento da publicação).
N05 - Anúncios SSDP sobre o grupo multicast (VPN / ponto a ponto)
O quê: os anúncios SSDP são enviados para o grupo multicast UPnP (239.255.255.250) em vez de um endereço de broadcast IP. O inofensivo erro "não é possível acessar um objeto descartado" registrado quando uma resposta de pesquisa SSDP compete com uma reinicialização do servidor também é suprimido.
Por quê: em adaptadores de rede ponto a ponto / VPN, o broadcast IP não se aplica - o antigo envio de broadcast falhava com "argumento inválido" e os anúncios eram perdidos, então o plugin era invisível para os clientes nessas conexões. Anunciar para o grupo multicast correto corrige a descoberta exatamente nesses adaptadores.
Navegação na biblioteca
Estes foram enviados neste fork e não estão no plugin original. Eles vieram da navegação real da própria saída do plugin a partir de clientes UPnP reais.
N06 - Exposição da biblioteca baseada em filtro
O quê: as abas de filtro do MusicBee (arquivos .xautopf na pasta do MusicBee do usuário) tornam-se contêineres raiz UPnP na biblioteca do plugin. As faixas de cada filtro são então navegáveis em uma hierarquia de AlbumArtistSort → Album → Tracks.
Por quê: usuários com filtros MusicBee curados (por exemplo, "Faixas 5 estrelas", "Adicionadas recentemente", "Clássica → Barroca") esperam encontrá-los ao navegar pelo plugin a partir de um cliente UPnP. O plugin original expunha apenas a árvore da biblioteca bruta.
N07 - Fiação do campo SortAlbumArtist
O quê: o plugin agora lê o MetaDataType 165 do MusicBee (Sort Album Artist) e o usa para agrupar/ordenar artistas em visualizações de navegação.
Por quê: navegadores de alta fidelidade e audiófilos usam nomes de artistas para classificação ("Beethoven, Ludwig van" em vez de "Ludwig van Beethoven") para organizar bibliotecas. Expectativa padrão para ouvintes sérios. Ausente em ambos os upstreams.
N08 - Manipulação de AlbumArtist com múltiplos valores
O quê: quando o campo AlbumArtist de um álbum contém vários artistas separados por "; " (por exemplo, "yaiol; Ars Ricercata"), a faixa agora aparece sob cada artista nas visualizações de navegação, e não sob um único artista Frankenstein combinando os nomes.
Por quê: álbuns colaborativos e compilações precisam aparecer sob cada colaborador. Sem isso, metade dos caminhos de busca para encontrar o álbum estão quebrados.
N09 - Arte da capa do contêiner do álbum (upnp:albumArtURI)
O quê: os nós do contêiner do álbum nas respostas do DIDL Browse agora incluem um elemento upnp:albumArtURI apontando para a capa do álbum.
Por quê: sem isso, cada álbum na visualização de navegação de um cliente UPnP mostra um ícone genérico em vez da capa do álbum. Pista visual para navegação; esperado por todo navegador de alta fidelidade moderno.
N10 - Ordem das faixas dentro dos álbuns filtrados
O quê: as faixas dentro de um álbum exposto por filtro agora são classificadas por Número do Disco, depois por Número da Faixa.
Por quê: ordem padrão do álbum. Sem classificação explícita, as faixas retornavam na ordem em que o filtro as retornava - geralmente parecendo aleatórias.
N11 - Correção da árvore de pastas de playlists
O quê: a função LoadLibraryPlaylists (originalmente de Steven Mayall, ~2014) falhava ao descer em pastas de playlists recém-criadas. A primeira playlist em cada pasta, mais quaisquer subpastas, acabava órfã no nível raiz.
Por quê: presente no plugin original por onze anos. Visível em 30 segundos após abrir o BubbleUPnP e clicar em Playlists. Corrigido em yaiol recursando corretamente em pastas recém-criadas durante a construção da árvore.
N12 - Sanitização de caracteres de controle XML-ilegais
O quê: qualquer faixa com uma tag contendo um caractere de controle C0 (por exemplo, 0x19 de uma passagem de codificação ruim - UTF-8 → Latin-1 → truncando 0x99 em 0x19) fazia com que toda a resposta do Browse falhasse com Action Failed assim que a faixa ruim entrava em um lote paginado.
Por quê: XML 1.0 proíbe a maioria dos caracteres de controle C0, e XmlWriter lança uma exceção quando solicitado a escrever qualquer um. Presente no plugin original. Corrigido removendo caracteres inválidos em cada ponto de saída de Library_GetFileTags via XmlConvert.IsXmlChar.
N13 - Listagem de rádio determinística em Browse paginado
O quê: a navegação para o contêiner de Rádio caía no ramo genérico de lista de arquivos, que chamava files.Sort(AlbumFileComparer) em cada chamada. As entradas de rádio têm tags de Álbum/Disco/Faixa vazias, então toda comparação retornava 0 - List(Of T).Sort é instável, produzindo uma ordem diferente a cada invocação. Os pontos de controle UPnP paginam (BubbleUPnP busca 0..15 e depois 16..fim); entre as duas chamadas, a lista se reorganizava, então algumas estações apareciam em ambas as páginas (duplicatas) e algumas em nenhuma (ausentes) - parecendo aleatórias a cada atualização.
Por quê: presente no plugin original (seu autor nunca navega em rádio via UPnP). Corrigido aqui com um ramo ContainerCategory.Radio dedicado em Browse, sem classificação por chamada; radioFiles é classificado uma vez no tempo de carregamento por Título (estável). A navegação paginada agora vê uma ordem determinística; a página 1 e a página 2 são disjuntas.
N14 - Pesquisa UPnP de classe de álbum retorna contêineres de álbum
O quê: a pesquisa UPnP para consultas de classe de álbum (upnp:class = "object.container.album.musicAlbum", por exemplo, "Álbuns Aleatórios" do BubbleUPnP) retornava a lista completa de faixas em vez de contêineres de álbum, então o cliente mostrava zero álbuns. O manipulador original apenas analisava critérios entre parênteses, então descartava todas as faixas, independentemente da classe solicitada.
Por quê: corrigido aqui - as consultas de classe de álbum agora enumeram álbuns distintos (agrupados por AlbumArtist+Album) e emitem cada um como um contêiner musicAlbum adequado com arte da capa, endereçável através do espaço de ID virtual Salb<idx> para que o cliente possa detalhar um resultado e reproduzi-lo.
N15 - Pesquisa UPnP funcional e com reconhecimento de escopo, com clique
O quê: o original não anunciava recursos de pesquisa (GetSearchCapabilities retornava vazio), então os clientes se recusavam até mesmo a enviar uma pesquisa; e o antigo backend lia de musicFiles, permanentemente vazio na era da árvore preguiçosa. Este fork anuncia as propriedades pesquisáveis reais, implementa faixas por título e álbuns por título contra a biblioteca preguiçosa (HandleLazySearch), define o escopo da consulta para o ramo atual do cliente quando um ID de contêiner real é enviado (caso contrário, substitui L:music para que as pesquisas da barra superior não arrastem ruído de podcast/rádio/audiolivro), e torna os resultados do álbum clicáveis via IDs sintéticos Ssrch_alb_* que um ramo inicial de Browse mapeia de volta para as faixas do álbum. (A parte de resultados de classe de álbum como contêineres é N14.)
Por quê: a pesquisa no BubbleUPnP passou de "A biblioteca não suporta pesquisa" para retornar resultados úteis, com escopo e reproduzíveis. Design completo + abordagens rejeitadas: SEARCH.md.
N16 - Invalidação de cache UPnP (SystemUpdateID)
O quê: o original retornava um SystemUpdateID=0 constante - o contrato de invalidação de cache do UPnP ContentDirectory - então clientes compatíveis com a especificação (BubbleUPnP) tratavam a biblioteca como nunca mudando: resultados de navegação desatualizados, miniaturas 404 após uma mudança de esquema de URL e a dança de "reiniciar o MusicBee duas vezes para ver as mudanças". Este fork semeia SystemUpdateID a partir de segundos de época no carregamento (para que cada reinício esteja estritamente à frente do último) e o incrementa em cada mutação da biblioteca e mudança de configurações (SetLibraryDirty / ResetCache → BumpSystemUpdateId).
Por quê: os clientes captam de forma confiável edições, novos arquivos e alterações de configuração em sua próxima navegação. Limite conhecido: clientes inscritos não recebem ativamente o novo valor via GENA (estacionado como trabalho futuro); eles ainda o veem em sua próxima navegação.
N17 - Arte da capa de assinatura de podcast
O quê: os blocos de podcast não mostravam imagens - cada solicitação /PodcastThumbnail/ resultava em 404. Dois bugs empilhados: a cadeia de resolução nunca verificava o cache de arte real do MusicBee (%LocalAppData%\MusicBee\InternalCache\Subscriptions\<name>.jpg, de onde a interface do usuário da área de trabalho carrega), e a camada HTTP de unescape+lowercase corrompia a chave de rota da URL do feed até seu último segmento de caminho. Este fork resolve a arte do InternalCache do MB e roteia as pesquisas via um slug seguro para URL que sobrevive intacto à camada HTTP (PodcastSlug / podcastSubIdBySlug).
Por quê: a arte da assinatura agora é renderizada nas visualizações de navegação (todas as 22 solicitações que anteriormente resultavam em 404 são resolvidas).
N18 - Navegação hierárquica (delimitada) por tags
O quê: qualquer campo pode ser marcado como hierárquico na aba Opções da Biblioteca e receber um delimitador de um caractere (um seletor de campo + caixa de delimitador com adicionar/remover, persistido nas configurações do plugin). Defina Agrupamento para / e um valor como Jazz/Cool Jazz então navega como Jazz › Cool Jazz em vez de uma única entrada plana. Faixas marcadas exatamente em um ramo (apenas Jazz) recebem seu próprio nó [Jazz] para que nada seja ocultado, um ramo com um único filho se recolhe sozinho, e ; é recusado como delimitador porque é o próprio separador de múltiplos valores do MusicBee.
Por quê: taxonomias de tags profundas que um usuário já codificou em um único campo (árvores de gênero, hierarquias de humor, "Clássica/Barroca/Concerto") finalmente navegam como a árvore que a tag descreve, em vez de uma parede plana de strings separadas por barras que o usuário precisa ler de ponta a ponta.
N19 - Caminho de raiz único rotulado por seu campo de agrupamento
O quê: um único caminho de navegação na raiz é rotulado por seu campo de agrupamento (por exemplo, "Gênero") em vez de seu caminho curto completo, correspondendo à forma como os grupos de primeiro campo mesclados são nomeados.
Por quê: a árvore de navegação é lida de forma consistente - uma regra de nomenclatura, seja uma entrada raiz sozinha ou mesclada com irmãos (N20) - em vez de uma entrada raiz solitária mostrando um caminho interno verboso enquanto seus vizinhos mesclados mostram um nome de campo limpo.
N20 - Mesclar caminhos de navegação que compartilham um primeiro campo
O quê: dois caminhos de navegação que compartilham o mesmo primeiro campo - "Gênero / Artista do Álbum para Classificação" e "Gênero / Pessoas do Podcast" - se colapsam em uma única pasta raiz Gênero que lista os valores de gênero primeiro e depois se divide nas duas visualizações, em vez de duas entradas "Gênero / ..." quase duplicadas lado a lado na raiz.
Por quê: um usuário com várias visualizações relacionadas aninhadas sob um campo comum via a raiz poluída com entradas de nível superior quase idênticas. A mesclagem as mantém a raiz legível e agrupa as visualizações relacionadas onde elas pertencem - sob seu campo compartilhado.
N21 - Caminhos de navegação categorizados (Padrão / Rádio / Podcast)
O quê: cada caminho de navegação é tipado por categoria - Padrão, Rádio ou Podcast. A lista de modelos é agrupada nessas três seções, o seletor de campo de cada modelo oferece apenas os campos que os dados dessa categoria podem realmente fornecer, e um modelo só pode ser aplicado a nós correspondentes na árvore de Visualização (nós incompatíveis ficam cinza e não podem ser marcados). Os modelos reservados de Rádio e Podcasts não podem ser excluídos, então sua seção de categoria nunca desaparece.
Por quê: sem a tipagem, um usuário poderia construir um layout que silenciosamente aparecesse vazio - uma estação de rádio não tem "álbum", um episódio de podcast não tem "artista do álbum" - e só descobrir isso navegando para uma pasta morta a partir de um cliente UPnP. Restringir o menu de campos e os alvos de aplicação aos dados reais da categoria torna os layouts vazios impossíveis de construir.
N22 - Agrupar podcasts por ano de publicação
O quê: a data de publicação de cada episódio de podcast é lida, de modo que um caminho de navegação de podcast com um nível de Ano agrupa os episódios por ano em vez de colapsá-los sob um único "Desconhecido".
Por quê: grandes assinaturas de podcast tornam-se navegáveis por ano como o resto da biblioteca, em vez de cada episódio cair em um único monte sem data porque o plugin nunca olhou para a data de publicação por episódio.
N23 - Recolher níveis de agrupamento de resultado único
O quê: um nível de agrupamento que resolve para um único valor - um nível de Tipo de Registro mostrando apenas "LP" para um artista que só fez LPs, ou um nível de letra com uma única letra - é ignorado automaticamente, levando o usuário diretamente para seu conteúdo.
Por quê: navegar por uma pasta que contém exatamente uma pasta é pura fricção. Recolher o nível de escolha única remove o clique morto sem alterar o que o usuário pode alcançar.
N24 - Agrupamento/pesquisa por ano em relação ao campo de data do MusicBee
O quê: a condição de ano não consulta mais o campo "Ano" de data completa do MusicBee com um valor de quatro dígitos, e o aliasing de campo de ano codificado foi removido para que cada campo de agrupamento agora seja resolvido genericamente a partir da definição do caminho.
Por quê: para bibliotecas cuja tag Ano contém uma data completa, agrupar ou pesquisar por ano anteriormente não retornava nada - a consulta de quatro dígitos nunca correspondia ao campo de data completa. Consultar o campo correto faz com que o agrupamento e a pesquisa por ano encontrem as faixas novamente.
N25 - Separar campos de agrupamento "Ano" e "Ano (aaaa)"
O quê: os caminhos de agrupamento e navegação de álbuns agora expõem ambos os campos de ano do próprio MusicBee - Ano (a tag de data completa) e Ano (aaaa) (apenas o ano de quatro dígitos) - para que o usuário possa escolher qualquer um ao definir um agrupamento de álbuns ou um caminho de navegação.
Por quê: os dois campos significam coisas diferentes no MusicBee, e colapsá-los perdia essa distinção. Expor ambos permite que um usuário reúna todos os lançamentos de um ano (aaaa) ou mantenha a ordenação por data exata (tag de Ano completa), conforme sua intenção.
N32 - Filtros e playlists fixados agrupados por tipo na raiz
O quê: um filtro fixado agora aparece diretamente abaixo da pasta Filtros na raiz de navegação, e uma playlist fixada diretamente abaixo da pasta Playlists, em vez de todos os fixados se acumularem em um único bloco no final da raiz. Cada atalho fixado fica com os de seu tipo.
Por quê: à medida que você fixa mais atalhos, um único bloco final de filtros e playlists misturados fica mais difícil de escanear e separa cada atalho da pasta a que pertence. Agrupar os itens fixados sob sua própria categoria mantém a raiz legível e cada atalho ao lado das coisas a que pertence.
Diálogo de configurações e empacotamento
N26 - Diálogo de configurações seccionado
O quê: a página de Preferências ganhou um layout de navegação à esquerda com seções: Geral / Reprodução / Biblioteca / Perfis de Dispositivo / Diagnóstico.
Por quê: o original era uma única lista alta e plana de todas as configurações - bom para o desenvolvedor que o construiu, confuso para todos os outros. O seccionamento agrupa opções relacionadas e faz com que o diálogo pareça mais com as configurações de aplicativos modernos.
Renomeação de Assembly + plugin (sem F-id - nota de empacotamento)
O quê: a DLL compilada é nomeada mb_UPnP_yaiol.dll e o plugin se reporta como "MusicBee UPnP (yaiol)". Distinto do original mb_Upnp.dll.
Por quê: os usuários podem instalar o yaiol junto com o plugin original e comparar o comportamento lado a lado.
Sistema de distintivos - exibindo o estado de tempo de execução (mecanismo por trás de F40)
O quê: um padrão de UI genérico para exibir condições importantes de tempo de execução como distintivos coloridos visíveis na caixa de diálogo de Configurações. Instâncias atuais:
- ⚠ Max Conn (N04) - dispara quando o limite máximo de conexões foi atingido pelo menos uma vez desde que o MusicBee foi iniciado. Sinalizador de sessão persistente
Plugin.MaxConnectionsHit. Definido dentro deWaitOnSendBarrierquando nenhum slot está livre. - ⚠ Reinicialização Necessária - dispara quando uma configuração salva precisa de uma reinicialização do MusicBee para entrar em vigor. Sinalizador de sessão persistente
Plugin.RestartRequired. Definido no manipulador de Salvamento da caixa de diálogo quando o novo valor persistido difere do instantâneo de tempo de execução (Plugin.activeMaxConnections,Plugin.activeServerPort,Plugin.activeIpAddress). As configurações que exigem reinicialização são limitadas àquelas que realmente não podem ser recarregadas a quente - parâmetros de ligação do servidor HTTP e o SemaphoreSlim construído uma vez na Inicialização.
Por quê: o arquivo de log do plugin é bom para usuários técnicos depurando, mas um usuário não técnico olhando para "o dispositivo soa errado" ou "a reprodução está lenta" nunca abrirá Diagnóstico → Ver log. Os distintivos capturam os casos em que o usuário precisa saber que algo aconteceu e o exibem na próxima vez que abrirem o plugin - detectável sem ler nada.
Reutilizável para o futuro:
- Incompatibilidade de perfil detectada (o user-agent do dispositivo nunca correspondeu a nenhum perfil, voltou para Genérico).
- Recuo de NextURI acionado (F13 - sem interrupções desativado para a sessão em um dispositivo instável).
- Verificação da biblioteca falhou / parcial.
- Conexão do renderizador perdida no meio da sessão.
- Qualquer outra condição em que "aconteceu uma vez, o usuário deve saber" supera "registrado silenciosamente entre 1000 outras linhas".
Convenções de implementação:
- Os rótulos dos distintivos ficam no nível do diálogo (não dentro de nenhum painel) para que sejam visíveis independentemente da seção em que o usuário esteja.
- Posicionados na linha inferior perto de Salvar/Cancelar (atual: y=410 empilhados horizontalmente).
- Cada distintivo tem um sinalizador de sessão persistente correspondente em
Pluginque muda para True quando a condição ocorre e é redefinido apenas na reinicialização do MusicBee. - Recursos:
<Condition>Badge(texto do rótulo, prefixado com ⚠) +<Condition>BadgeTip(tooltip explicando a causa + remédio). - Para distintivos "configuração salva precisa de reinicialização", tire um instantâneo de tempo de execução em
Plugin.Initialise()e compare comSettings.*apósSettings.SaveSettings()no manipulador de Salvamento do diálogo.
N27 - Cancelar descarta edições de caminho/modelo
O quê: as edições feitas em caminhos e modelos dentro da caixa de diálogo de configurações agora são descartadas quando o usuário clica em Cancelar, em vez de permanecerem aplicadas silenciosamente, e qualquer modelo reservado removido durante a sessão é recriado. (Os modelos, de outra forma, são salvos ao vivo conforme são editados - não há um botão Salvar separado na aba Caminhos.)
Por quê: Cancelar deve significar cancelar. Anteriormente, um usuário que experimentava alterações de caminho/modelo e desistia encontrava as alterações já confirmadas, sem como desfazê-las a não ser refazendo cada uma manualmente.
N28 - E comerciais literais no menu do seletor de campo
O quê: um campo cujo nome contém "&" - por exemplo, "Humor & Contexto" - renderiza o e comercial literalmente no menu do seletor de campo em vez de engoli-lo como um prefixo de mnemônico Alt.
Por quê: nomes de campos com um e comercial eram exibidos incorretamente (o caractere desaparecia e a próxima letra se tornava um acelerador), tornando a entrada do menu difícil de reconhecer.
N29 - Título da janela de configurações estável e não traduzido
O quê: o título da janela de configurações é fixado na string da marca "MusicBee UPnP Plugin" e não varia mais com o idioma da interface; a string DialogTitle por idioma foi removida de todos os pacotes de localidade.
Por quê: um título de janela que mudava a redação por idioma era uma superfície traduzível sem benefício - o título é uma marca. Fixá-lo o mantém estável e consistente em todos os lugares.
Localização
O plugin original é apenas em inglês. Este fork é totalmente localizável - cada string voltada para o usuário flui através de um pacote de recursos, e o plugin detecta automaticamente o idioma da interface do próprio MusicBee.
N30 - UI multi-idioma (traduções pendentes)
O quê: o mecanismo de localização está completo e sendo enviado. Localisation.vb lê o idioma selecionado do MusicBee de MusicBee3Settings.ini (endônimo <SystemLanguage>) e aplica a cultura .NET correspondente ao thread, então My.Resources.Resources.* retorna a string localizada. Cada rótulo/botão/mensagem voltado para o usuário está conectado a uma chave de recurso (controles de designer via ApplyDesignerExtras + sync-en-locale.js; strings de tempo de execução como WarnPortInUse adicionadas manualmente). O que não está feito ainda é a tradução real: apenas o pacote de origem em inglês (Resources.resx) existe - os pacotes satélites para os outros idiomas são produzidos em uma única passagem em lote quando o plugin estiver completo em recursos (traduzir aos poucos enquanto as strings ainda mudam desperdiça esforço).
Idiomas de destino (o conjunto que o próprio MusicBee oferece, correspondido 1:1 por endonymToCulture para que o plugin siga o idioma do MusicBee automaticamente):
Árabe (ar) |
Tcheco (cs) |
Alemão (de) |
Grego (el) |
Espanhol (es) |
Francês (fr) |
Húngaro (hu) |
Italiano (it) |
Coreano (ko) |
Holandês (nl) |
Norueguês (nb) |
Polonês (pl) |
Português BR (pt-BR) |
Português PT (pt-PT) |
Sueco (sv) |
Turco (tr) |
Ucraniano (uk) |
Russo (ru) |
Japonês (ja) |
Chinês Simplificado (zh-CN) |
Chinês Tradicional (zh-TW) |
Inglês (en, fonte) |
Política de variantes (conforme a regra de localidade do espaço de trabalho): PT e ZH são divididos em pacotes distintos porque o vocabulário/script realmente diverge (pt-BR/pt-PT, zh-CN/zh-TW). EN é um único pacote - "English(US)" (en-US) do MusicBee retorna para en via cadeia de cultura do .NET, então nenhum pacote US separado é produzido. ES e FR são igualmente de localidade única.
Por quê: as configurações de um plugin UPnP ("não usar PCM bruto", "forçar PCM little-endian", avisos de fallback de porta) são criptográficas o suficiente no idioma nativo. Seguir o idioma da própria interface do MusicBee - em vez de forçar o inglês - é a diferença entre uma ferramenta que um usuário não-inglês pode configurar e uma que não pode. Nenhum upstream tentou isso.
N31 - Link de ajuda abre no idioma completo da interface
O quê: abrir o link de Ajuda do plugin respeita o idioma completo da interface do usuário (por exemplo, pt-BR, zh-CN) em vez de colapsar para o idioma base, e envia um identificador de verificação de atualização mais claro.
Por quê: um usuário executando o MusicBee em uma variante regional (Português do Brasil, Chinês Simplificado) era enviado para a página de ajuda do idioma base. Carregar a cultura completa os leva para a página de ajuda no idioma exato que eles usam.
Correções e melhorias no plugin original
Protocolo e reprodução principais
F01 - Perfis de dispositivo DLNA padrão atualizados
O quê: envia novos perfis padrão para PlayStation 4, Xbox 360/One e BubbleUPnP moderno, com sinalizadores de capacidade (taxas de amostragem, profundidades de bits, codecs) que refletem o que esses dispositivos realmente suportam hoje.
Por quê: os padrões do plugin original foram congelados por volta de 2014. PS4/Xbox/BubbleUPnP ganharam suporte a áudio de alta resolução desde então. Fora da caixa, uma nova instalação reproduz o melhor da categoria nesses dispositivos sem que o usuário precise tocar nas configurações do perfil do dispositivo.
F02 - Controlar dispositivos que anunciam MediaRenderer:3
O quê: o plugin sonda a descrição do serviço UPnP de um renderizador para decidir se o MusicBee pode controlá-lo. O original correspondia apenas a urn:schemas-upnp-org:device:MediaRenderer:1. Dispositivos modernos anunciam :2 ou :3. F02 amplia a correspondência.
Por quê: sem isso, unidades Sonos / WiiM / Eversolo recentes simplesmente não aparecem como alvos na lista de dispositivos "Reproduzir para" do MusicBee - mesmo que falem o mesmo protocolo. Uma única correção de correspondência de prefixo de string desbloqueia toda a geração de dispositivos modernos.
F03 - Opção "Forçar fluxo nativo" por perfil (padrão LIGADO)
O quê: quando marcada, o plugin envia os bytes do arquivo original para o dispositivo sem transcodificação, sem DSP, sem processamento de ReplayGain aplicado. Apenas o arquivo bruto que o usuário escolheu, byte a byte (módulo de enquadramento HTTP).
Por quê: por testemunho do fórum, esta é a maior vitória em qualidade de reprodução. Usuários de alta fidelidade que compram renderizadores caros querem explicitamente saída bit-perfect; qualquer toque de DSP anula o objetivo. Padrão LIGADO porque a maioria dos dispositivos modernos lida com qualquer codec que o usuário jogue neles, e ReplayGain/EQ deve ser opcional. É por perfil para que você possa manter a transcodificação para um Xbox antigo enquanto envia nativo para um DAC de alta fidelidade.
F04 - "Forçar transcodificação" por perfil
O quê: substituição por perfil que força cada fluxo para este dispositivo através do transcodificador, independentemente do suporte a codec nativo. O inverso de F03 (ForceNativeStream). Mutuamente exclusivo com F03 - a UI desmarca automaticamente o outro quando um é ativado.
Por quê: um único botão global seria contraditório com o ForceNativeStream por perfil (F03). Caso real: dispositivo A é um DAC de alta fidelidade que deseja fluxos nativos bit-perfect; dispositivo B é um receptor AV antigo que engasga com FLAC. Com um botão global, o usuário tem que escolher - ao custo do outro dispositivo. Com por perfil, cada dispositivo obtém a resposta certa.
Implementação:
StreamingProfile.ForceTranscoding As Boolean = False.- Esquema de persistência atualizado para v9. Arquivos pré-v9 carregam o valor global legado uma vez e o copiam para todos os perfis, preservando o comportamento antigo através da atualização.
- UI: removido do painel de Diagnóstico, adicionado à seção Perfis de Dispositivo ao lado de ForceNativeStream. Manipuladores de exclusão mútua bidirecional (
CheckedChangedem cada um desinscreve o outro antes de alternar, para evitar um loop infinito). - Local de decisão:
Settings.ForceTranscoding→streamingProfile.ForceTranscodingemWriteAudioFileDIDL.
F05 - "Forçar PCM little-endian" por perfil
O quê: fluxos PCM (tipos mime L16/L24) são big-endian por especificação. Alguns dispositivos esperam erroneamente little-endian e reproduzem ruído branco quando recebem dados big-endian corretos. F05 alterna a ordem dos bytes por perfil.
Por quê: sem isso, certos dispositivos emitem uma parede de estática. O sintoma é dramático e a causa invisível sem conhecimento da codificação PCM - o botão oferece aos usuários uma correção de tentativa e erro.
F06 - "Não usar PCM bruto" por perfil
O quê: quando o dispositivo afirma que suporta PCM bruto, o plugin o utiliza. Alguns dispositivos mentem - eles aceitam o handshake SOAP, mas corrompem os dados PCM brutos reais, enquanto lidam corretamente com PCM encapsulado em um contêiner WAVE. F06 força PCM-over-Wave, independentemente do que o dispositivo anuncia.
Por quê: certos modelos Marantz especificamente - eles anunciam PCM bruto, mas apenas WAVE funciona. Sem isso, os fluxos PCM brutos saem distorcidos, sem nenhuma mensagem de erro para apontar.
F07 - "Comprimento do conteúdo" por perfil
O quê: qual valor enviar no cabeçalho HTTP Content-Length. Quatro opções:
- Padrão - contagem real de bytes quando conhecida, omitir quando desconhecida.
- Nenhum - nunca enviar o cabeçalho (apenas codificação em blocos).
- Somente PCM - enviar apenas para PCM bruto; omitir para todo o resto.
- Fixo - enviar
UInt32.MaxValue - 8192(um sentinela para "comprimento desconhecido enorme").
Por quê: dispositivos UPnP/DLNA variam muito em como reagem ao Content-Length. Alguns precisam de um número exato, alguns odeiam em fluxos, alguns precisam de um valor sentinela "muito grande" para manter o buffer. Isso foi posteriormente ampliado de apenas PCM para todos os formatos de saída porque os mesmos problemas apareceram em fluxos MP3/AAC transcodificados.
F08 - "Não limpar NextURI" por perfil
O quê: normalmente o plugin limpa o NextURI enfileirado do dispositivo quando a fila esvazia (enviando SetNextAVTransportURI com um URL em branco). Alguns dispositivos (notavelmente Denon) interpretam um NextURI em branco como "parar tudo" e interrompem a reprodução imediatamente. F08 impede que o plugin o limpe.
Por quê: sem isso, os proprietários de Denon experimentam o dispositivo cortando no meio da faixa quando a fila esvazia. Com F08 marcado, o dispositivo mantém o NextURI obsoleto na memória (inofensivo - ele apenas é sobrescrito na próxima vez que algo é enfileirado).
F09 - FLAC como formato de saída de transcodificação
O quê: o menu suspenso de formato de transcodificação em Perfis de Dispositivo agora oferece FLAC junto com PCM 16/24, MP3, AAC, Ogg. Escolhê-lo roteia o codificador BASS através da linha de comando de conversão FLAC padrão do MusicBee (o mesmo mecanismo que MP3/AAC/Ogg já usam).
Por quê: para dispositivos que lidam bem com FLAC, mas não conseguem decodificar o codec de origem (por exemplo, um Eversolo recebendo a biblioteca WMA do MusicBee convertida para FLAC), isso preserva a qualidade sem perdas onde MP3/AAC descartaria dados de áudio. Desbloqueia N02 (controle de downmix 5.1), que não poderia ser abordado sem uma opção de transcodificação sem perdas.
Implementação: adição de uma linha ao bloco Select Case Codec de Encoder.StartEncode - FLAC se junta a MP3/AAC/Ogg no ramo acionado por linha de comando. O menu suspenso da UI recebe "FLAC" como uma 6ª opção. O mapeamento de carregamento/salvamento em SettingsDialog se estende para reconhecer FileCodec.Flac ↔ SelectedIndex = 5. Mime, tipo DLNA e recurso de codificação já estavam conectados em ItemManager.GetMimes / GetDlnaType / GetEncodeFeature de trabalho anterior (F21, F26).
Sem interrupções (SetNextAVTransportURI)
F10 - SetNextAVTransportURI / NextURI core
O quê: reprodução sem interrupções verdadeira. Quando o dispositivo anuncia suporte para SetNextAVTransportURI em sua descrição de serviço UPnP, o plugin pré-enfileira a próxima faixa no dispositivo antes que a atual termine. O dispositivo faz a transição internamente sem interrupção audível entre as faixas - o que você ouve em um CD player. Isso não é o hack de "fluxo contínuo" (que concatena tudo em um longo fluxo e perde metadados por faixa).
Por quê: o recurso principal de Nível 2. Álbuns gravados como uma performance ao vivo contínua (discos ao vivo, movimentos clássicos, sets de DJ) soam errados quando há um silêncio de meio segundo entre as faixas. Resolver isso corretamente é um recurso principal, agora neste fork.
Notas: o áudio enfileirado é servido via servidor HTTP do plugin usando streamHandle=0 (modo de busca de biblioteca), o que significa que o motor de áudio do MusicBee não está no loop para a faixa enfileirada. Compensação: efeitos de ReplayGain/DSP/EQ não se aplicam à próxima faixa. Aceitável quando "forçar fluxo nativo" está ativado (o padrão).
F11 - "Desativar suporte a NextURI" por perfil
O quê: mesmo que um dispositivo anuncie SetNextAVTransportURI, esta caixa de seleção força o plugin a ignorar esse anúncio e voltar para a reprodução de uma faixa por vez.
Por quê: alguns dispositivos anunciam NextURI, mas têm uma implementação com bugs (travamentos, transições incompletas, congelamentos). Em vez de fazer engenharia reversa em cada dispositivo quebrado, o usuário obtém um botão "apenas desative aqui".
F12 - Ciclo de vida do NextURI na lista de reprodução atual
O quê: quando o MusicBee dispara NowPlayingListChanged, o plugin reavalia o que deve ser enfileirado para transição sem interrupções. Ele pede ao MusicBee a nova faixa "próxima" via NowPlayingList_GetNextIndex(1) + NowPlayingList_GetListFileUrl, compara com o que está atualmente enfileirado no dispositivo (rastreado via o novo campo nextPlaySourceUrl) e re-enfileira se mudou (ou limpa a fila se o MusicBee disser que não há próxima faixa).
Por quê: sem F12, o dispositivo continuava reproduzindo um NextURI obsoleto quando o usuário removia/reorganizava a faixa enfileirada. Isso historicamente levou várias iterações porque cada mutação de lista precisa de tratamento diferente - nós simplificamos confiando em NowPlayingList_GetNextIndex (que já respeita a reprodução aleatória e o loop de todas as faixas), então todas as variantes são canalizadas pela mesma comparação.
Implementação:
- Novo campo
nextPlaySourceUrlarmazena o URL da biblioteca do MusicBee do que está enfileirado (o URL de streaming com sufixo de handle não é comparável a um caminho de biblioteca). - Novo
Public Sub RefreshQueuedNextUri()emMediaRendererDevice. Três resultados: nenhum NextURI enfileirado → sem operação; enfileirado corresponde ao novo "próximo" → sem operação; enfileirado difere → chamarQueueNextcom o novo URL (ouQueueNext("")para limpar - o que respeita F08 DoNotClearNextUri). - Conectado em
Plugin.ReceiveNotificationsobNotificationType.NowPlayingListChanged.
F13 - Recuo em caso de falha do NextURI
O quê: após 4 falhas consecutivas de SetNextAVTransportURI no mesmo dispositivo, o plugin desativa a reprodução sem interrupções para esse dispositivo até que o MusicBee seja reiniciado.
Por quê: se um dispositivo estiver realmente quebrado para NextURI (falhas SOAP intermitentes, falhas de rede), o plugin continuaria tentando em cada faixa. F13 interrompe o ruído e volta para a reprodução de uma faixa por vez silenciosamente.
F14 - Modo de repetição + integração NextURI
O quê: F14 se divide em dois casos tratados no detector de transição F15 em OnAvTransportStatusCheck:
- Repetir Tudo: o MusicBee passa o URL "wrap" correto (faixa 1 no final da lista) para
Plugin.QueueNextele mesmo. Nenhuma lógica especial do plugin é necessária - o dispositivo faz a transição para ele e o detector F15 chamaPlayer_PlayNextTrackcomo de costume, que envolve o índice NPL do MusicBee de volta para 0. - Repetir Um: o MusicBee passa o MESMO URL da faixa para
Plugin.QueueNext. O dispositivo faz a transição para ele (novo handle de fluxo, mesma fonte). O detector F15 agora consultaPlayer_GetRepeat()- se forRepeatMode.One, ele ignora a chamadaPlayer_PlayNextTrackpara que o MusicBee não avance o índice NPL para longe da faixa em loop.
Por quê: sem a omissão de Repetir Um, chamar Player_PlayNextTrack na transição sem interrupções avançaria o MusicBee para a próxima faixa na lista (Repetir Um afeta apenas o comportamento de avanço automático no final da faixa na UI do player - Próxima Faixa sempre avança), contradizendo o que Repetir Um significa.
Ressalva da contagem de reproduções: em Repetir Um, o incremento da contagem de reproduções depende do MusicBee 3.7.9563+ perceber a reprodução em loop. Versões mais antigas do MusicBee reproduzem a repetição sem interrupções corretamente, mas perdem o aumento da contagem de reproduções. Documentado; não bloqueador.
F15 - Máquina de estados de detecção de transição de faixa
O quê: quando o dispositivo faz a transição interna da faixa atual para o NextURI, o plugin precisa perceber e informar ao MusicBee para avançar seu índice de reprodução atual. Caso contrário, o MusicBee pensa que ainda está na faixa anterior e as contagens de reprodução / UI / scrobbling ficam dessincronizadas.
Implementação: pesquisa GetPositionInfo.TrackURI a cada tick do temporizador de status. Quando o URI relatado corresponde ao que enfileiramos via NextURI, chamamos Player_PlayNextTrack no MusicBee e definimos suppressNextSoapCall para que o PlayToDevice resultante não reenvie SetAVTransportURI (o que interromperia a reprodução sem interrupções).
Por quê: sem F15, o dispositivo reproduz a próxima faixa, mas a UI do MusicBee diz que ainda está na anterior. Confuso, quebra o scrobbling, quebra o rastreamento da contagem de reproduções. A detecção de transição de faixa precisa de uma iteração longa e por renderizador porque cada marca de renderizador tem suas próprias peculiaridades em quando ele relata a mudança de URI (alguns relatam TRANSITIONING primeiro, alguns pulam direto para PLAYING com novo URI, alguns têm um breve STOPPED no meio).
Notas: nosso primeiro corte funciona no renderizador BubbleUPnP. Casos extremos por dispositivo permanecem em B6.
F16 - Correção de estalo na transição sem interrupções
O quê: o estalo ocorre quando o formato da fonte (taxa de amostragem / canais / codec) da faixa enfileirada difere da faixa atualmente em reprodução, forçando o DAC do dispositivo a re-travar na transição. F16 adiciona um diagnóstico NextUri:FormatChange que dispara no momento do enfileiramento sempre que os formatos diferem, nomeando ambos os lados - para que os usuários que ouvem estalos possam correlacionar.
O diagnóstico também aponta para a mitigação: marque ForceTranscoding no perfil do dispositivo. Isso homogeneiza cada faixa para um único codec/taxa de amostragem/profundidade de bits de transcodificação, eliminando completamente a diferença de formato da fonte.
Por que adiado para a correção real de transcodificação para correspondência: a correção estrutural (transcodificar a faixa enfileirada para corresponder ao formato da faixa em reprodução) requer alterações no esquema de URL do servidor HTTP do plugin - atualmente /encode/{id}0.{ext} serve o arquivo enfileirado nativamente. Uma futura v2 de F16 adicionaria rotas /encode/{id}0_{rate}_{depth}.{ext} por formato e as conectaria através do codificador. Essa é uma mudança arquitetônica maior que vale a pena fazer se um dispositivo real exibir o estalo depois que o ForceTranscoding não for suficiente.
Implementação hoje:
- O campo
lastSourceUrlrastreia o URL da fonte atualmente em reprodução. QueueNextlêFilePropertyType.SampleRate/Channels/Kindpara as faixas atual e enfileirada e registraNextUri:FormatChangeem caso de incompatibilidade.
F17 - Ressincronização da barra de progresso após busca
O quê: a função Seek() já chamava GetPlayPositionInformation() após um SOAP de busca bem-sucedido, o que corrige o caso de "nenhuma ressincronização". F17 fecha o desvio restante de até 1s causado pela quantização de 1 segundo de RelTime do UPnP: quando a posição relatada do dispositivo arredonda para dentro de 1 segundo do alvo solicitado pelo usuário, o plugin agora confia no valor sub-segundo preciso do usuário em vez do truncamento do dispositivo. Somente quando o dispositivo relata algo dramaticamente diferente (>1s de diferença) usamos seu valor (a busca caiu em outro lugar do que o solicitado, por exemplo, ajuste para keyframe em alguns codecs).
Por quê: sem isso, buscar para 2:30.500 ancorado contra o relatório "2:30" do dispositivo faria com que a barra de progresso mostrasse ~500ms atrás da realidade. Após F17, a barra corresponde à intenção do usuário para o caso comum de arrastar na faixa, e ainda respeita o relatório do dispositivo para o caso atípico de ajuste para keyframe.
F18 - Intertravamento de fluxo contínuo / NextURI
O quê: dois intertravamentos agora em vigor:
- Tempo de execução:
QueueNextretornaFalseantecipadamente quandoSettings.ContinuousOutputestá ativado. O fluxo contínuo é seu próprio mecanismo sem interrupções (um longo fluxo concatenado); enviarSetNextAVTransportURIem cima dele confunde o dispositivo sobre se cada faixa é um URI discreto ou parte do fluxo contínuo. - UI: quando o usuário marca a caixa de seleção global de fluxo contínuo, o
forceNativeStreamdo perfil atualmente exibido é desmarcado automaticamente. O fluxo contínuo sempre transcodifica, então o fluxo nativo forçado é sem sentido em combinação.
Por quê: impede que o usuário habilite dois mecanismos sem interrupções conflitantes simultaneamente. Sem F18, o dispositivo receberia um URI de fluxo contínuo E um NextURI para cada faixa subsequente, com comportamento indefinido dependendo do renderizador.
F19 - Erros de NextURI em branco ignorados
O quê: quando SetNextAVTransportURI é chamado com um URL em branco (por exemplo, última faixa na lista), alguns dispositivos retornam uma falha SOAP. F19 as engole silenciosamente - registradas, mas não propagadas como erros.
Por quê: a condição "nenhuma próxima faixa" é normal, não um erro. Tratá-la como fatal polui o log e (em alguns fluxos) aciona tempestades de repetição.
Tipos MIME e metadados DLNA
F20 - MP3 mime → audio/mpeg
O quê: o tipo mime MP3 em conformidade com os padrões é audio/mpeg, não audio/mp3. Este último é um nome incorreto comum que a maioria dos dispositivos tolera, mas renderizadores mais rigorosos o rejeitam.
Por quê: corrige silenciosamente a reprodução em dispositivos mais rigorosos que seguem o padrão. A base de código yaiol já tinha isso correto; nenhuma mudança necessária.
F21 - Ordem do tipo MIME: variante não-x- primeiro
O quê: quando um dispositivo anuncia audio/flac e audio/x-flac, o plugin retorna a variante não-x- primeiro. O mesmo para qualquer codec com mimes padrão e experimentais.
Por quê: o prefixo x- marca mimes experimentais/não oficiais. Alguns renderizadores se comportam melhor com o formato padrão. Pequena reordenação, impacto no mundo real.
F22 - Suporte a tipo MIME Opus
O quê: reconhece Opus como um codec de áudio transmitível; envia o tipo MIME audio/opus ao servir faixas Opus.
Por quê: Opus agora é comum (codec de compromisso moderno para fala/música). Sem F22, o plugin recusaria a transmissão de arquivos Opus mesmo para dispositivos que os manipulam.
F23 - Suporte a arquivo de origem Monkey Audio (APE)
O quê: reconhece arquivos .ape como um codec de origem válido para streaming/transcodificação.
Por quê: APE é um formato sem perdas com uma base de usuários de nicho, mas leal. Adicioná-lo custa pouco e desbloqueia a biblioteca para esses usuários.
F24 - Fallback de MIME AAC / ALAC
O quê: se um dispositivo suporta AAC ou ALAC, mas não os anuncia explicitamente em sua descrição de serviço UPnP, o plugin os oferece de qualquer forma como um fallback.
Por quê: vários dispositivos que lidam bem com AAC esqueceram de listá-lo em seu XML de capacidades. Sem F24, o plugin nem tentaria, forçando a transcodificação. Com F24, o plugin tenta e permite que o dispositivo o manipule nativamente, se puder.
F25 - Sinalizador de tipo DLNA para fluxos WAV nativos + codificados
O quê: o sinalizador de tipo DLNA (um identificador de perfil como LPCM, WAVE, MP3) precisa corresponder ao que o dispositivo recebe. F25 garante que os fluxos nativos e os fluxos WAV codificados sejam sinalizados corretamente.
Por quê: o tipo DLNA incompatível faz com que alguns dispositivos recusem a reprodução completamente ou apliquem o decodificador errado.
F26 - Cabeçalho DLNA para arquivos FLAC
O quê: os fluxos FLAC recebem o identificador de perfil DLNA adequado em seus cabeçalhos.
Por quê: sem ele, alguns dispositivos que suportam FLAC não reconhecem o fluxo como tal.
F27 - Correção de cálculo de bitrate em metadados
O quê: o res@bitrate do fluxo contínuo estava sendo calculado como (sampleRate * channels * bitsPerSample) / 1000 - kbps, com um erro de um fator de ~125 em relação à especificação UPnP DIDL que define o atributo como bytes por segundo. Agora divide por 8 em vez de 1000.
Por quê: exibição de bitrate errada no dispositivo - cosmética na maioria dos renderizadores, mas alguns alocam buffers de fluxo a partir do valor e gaguejam em fluxos que parecem ~125× menores do que são. O caminho do arquivo de origem não contínuo já estava correto ((bitrate_kbps * 1000) \ 8 = bytes/seg); apenas o caminho do fluxo contínuo estava errado.
F28 - Correção do formato de hora dos metadados (Marantz)
O quê: res@duration em DIDL era formatado como H:MM:SS (por exemplo, 0:03:42). A especificação UPnP DIDL define o formato como H+:MM:SS[.F+] - estritamente com segundos fracionários opcionais, mas recomendados; alguns dispositivos Marantz tratam o formato simples como inválido e deixam sua exibição de duração em branco. Agora formatado como H:MM:SS.fff (por exemplo, 0:03:42.000).
Por quê: problema de exibição específico de uma marca; o formato ISO-style em conformidade com segundos fracionários o corrige sem afetar nenhum outro dispositivo. Aplicado em ambos os locais de emissão DIDL (caminho do arquivo de origem + caminho do fluxo codificado em WriteAudioFileDIDL).
Correção bônus na mesma passagem: pv:addedTime e pv:lastPlayedTime estavam usando hh (relógio de 12 horas) em suas strings de formato DateTime em vez de HH (24 horas). Qualquer faixa adicionada ou reproduzida entre 13:00 e 23:59 seria renderizada com uma hora errada (por exemplo, 17:42 → "05:42") em dispositivos que exibem o campo. Agora usa HH.
F29 - Suporte a busca em MP3 codificado (CBR)
O quê: fluxos MP3 transcodificados agora anunciam DLNA.ORG_OP=11 (busca por byte e tempo) em vez de DLNA.ORG_OP=10 (somente busca por byte). Dispositivos que anteriormente recusavam a busca por tempo em MP3 transcodificado agora podem controlar sua barra de progresso/UI de busca normalmente.
Por quê: o transcodificador do MusicBee produz MP3 de taxa de bits constante no preset HighQuality, então o mapeamento byte ↔ tempo é linear - o dispositivo pode converter uma solicitação de busca por tempo em uma busca por byte de HTTP Range sem nenhum suporte do lado do codificador. Anunciar OP=11 desbloqueia essa UI no dispositivo. Sem F29, os usuários que buscavam dentro de um MP3 transcodificado tinham a busca silenciosamente ignorada ou eram levados para o início da faixa.
Implementação: reestruturado GetEncodeFeature em ItemManager.vb para quebrar o If inline em uma cadeia If/ElseIf/Else legível. MP3 recebe OP=11 explicitamente; outros codecs não-PCM mantêm OP=10. Nenhuma mudança para AAC/FLAC/etc. - esses precisariam de verificação específica do codec de CBR-ness que o MusicBee não garante.
F30 - Extensão de arquivo .mpeg tratada
O quê: arquivos com extensão .mpeg (e o ainda mais raro .mpe) agora são reconhecidos como FileCodec.Mp3 em GetCodec. Antes de F30, eles retornavam FileCodec.Unknown e eram silenciosamente rejeitados da biblioteca / incapazes de serem fontes de transcodificação.
Por quê: arquivos antigos MPEG-1 Layer 3 às vezes usavam .mpeg em vez de .mp3 (a especificação permite ambos). Um punhado de arquivos em uma biblioteca de 300k é suficiente para sentir "o MusicBee os mostra, mas o plugin não" - confuso para o usuário.
Comportamento de reprodução
F31 - Streams de rádio usam automaticamente o modo contínuo
O quê: WriteAudioFileDIDL agora sonda a propriedade Kind do URL de origem via Library_GetFileProperty e trata qualquer arquivo cuja Kind termine em "Stream" (MusicBee relata "MP3 Stream", "Internet Stream", etc. para rádio) como contínuo, independentemente do botão global Settings.ContinuousOutput. O ramo DIDL de fluxo contínuo (Título: "Continuous Stream", id="continuousstream", saída PCM/Wave fixa) é usado; o dispositivo vê um único fluxo de estilo infinito.
Por quê: streams de rádio não têm limites de faixa, comprimento fixo, nem busca. Tratando-os como arquivos discretos no DIDL, o plugin anunciava intervalos de bytes e durações que não existem. A troca automática quando o MusicBee já nos disse "isso é um Stream" remove um problema que o usuário não deveria ter que pensar.
Escopo: aplica-se apenas quando o MusicBee está controlando a reprodução (musicBeePlayToMode). O caminho de busca da biblioteca (cliente UPnP navegando) permanece inalterado - URLs de rádio são raras lá e o comportamento voltado para o usuário não deve mudar sem testes explícitos.
F32 - Fallback de anúncio de codec
O quê: se um dispositivo não anunciar certos codecs (ou o plugin não conseguir analisar o XML de capacidade do dispositivo), o plugin não rejeita imediatamente o fluxo. Em vez disso, ele tenta servi-lo e deixa o dispositivo decidir.
Por quê: muitos dispositivos têm XML de capacidade incompleto ou ilegível, mas na verdade lidam bem com o codec. F32 troca um pequeno "melhor palpite e tentativa" por uma recusa total.
F33 - Melhoria na sincronização da barra de progresso
O quê: a posição entre as pesquisas já é extrapolada pelo relógio a partir de uma única âncora (currentPlayStartTicks), então a barra de progresso é atualizada suavemente em taxa sub-segundo. A fonte de jitter restante era a âncora inicial para uma faixa recém-iniciada: o código anterior assumia position=0 no momento em que o temporizador de status notava pela primeira vez que o estado mudou para Reproduzindo, mas então o dispositivo poderia estar reproduzindo por 100-500ms (um intervalo de pesquisa). A barra de progresso do MusicBee começaria em 0, então pularia para frente quando a realidade alcançasse.
Correção F33: ao fazer a transição para Reproduzindo pela primeira vez em uma nova faixa (currentPlayStartTimeEstimated=True), chame GetPlayPositionInformation() para obter a posição real atual do dispositivo, então ancore-se nela. O UPnP relata apenas resolução de 1 segundo, então a âncora ainda é quantizada, mas está muito mais próxima da verdade do que assumir 0.
Por quê: exibição de progresso mais suave + mais precisa, especialmente logo após a mudança de faixa. Não há como contornar a própria resolução de relatório de 1 segundo do UPnP - isso é especificação.
F34 - Jitter da barra de progresso após mudança de faixa
O quê: quando PlayToDevice é chamado para uma nova faixa, o plugin costumava deixar currentPlayPositionMs e currentPlayStartTicks em seus valores da faixa anterior para a janela de ~100ms entre o SOAP-Play e a primeira pesquisa do temporizador de status detectando o novo estado de Reprodução. A barra de progresso do MusicBee mostraria brevemente o final da faixa anterior, então voltaria para 0, então subiria. F34 zera ambos na entrada de PlayToDevice - no momento em que sabemos que uma mudança de faixa está acontecendo, antes de qualquer trabalho SOAP.
Por quê: falha visual em casos de uso de salto rápido (próximo manual ou transição sem interrupções). Agora, a primeira consulta PlayPositionMs do MusicBee pós-Play retorna 0 limpo, então o GetPlayPositionInformation do F33 o refina para a posição real do dispositivo no primeiro tick de mudança de estado.
Implementação: quatro linhas no topo de PlayToDevice, emparelhadas com a ancoragem precisa do tempo de transição do F33.
F35 - Bug de "Forçar transcodificação"
O quê: a transcodificação forçada ainda podia pular a transcodificação em certas combinações. Após a reformulação por perfil do F04, duas lacunas específicas foram seladas:
- Precedência com ForceNativeStream. Quando ambos eram True (o que pode acontecer em uma migração de esquema ou um arquivo de configurações parcial), ForceTranscoding agora vence totalmente (
If streamingProfile.ForceTranscoding Then forceEncode = True ElseIf streamingProfile.ForceNativeStream Then forceEncode = False). A exclusão mútua da UI impede que o usuário marque ambos, mas a guarda de tempo de execução lida com qualquer estado que carregou inconsistente do disco. - Lógica bypassTranscodeDecision. Anteriormente:
streamingProfile.ForceNativeStream AndAlso Not Settings.ForceTranscoding. Agora:streamingProfile.ForceNativeStream AndAlso Not streamingProfile.ForceTranscoding- mesma regra de precedência, mas no mesmo escopo por perfil.
Por quê: "forçar" deve significar forçar. Se o usuário ativou explicitamente ForceTranscoding para um dispositivo, o plugin nunca deve passar silenciosamente para o streaming nativo, independentemente de como outras flags se combinam.
F36 - Exceção de renderizador fechado
O quê: envolveu Plugin.ReceiveNotification em um Try/Catch de nível superior que registra qualquer exceção não capturada em vez de deixá-la propagar de volta para o pump de notificação do MusicBee.
Por quê: notificações do MusicBee (PlayStateChanged, VolumeMuteChanged, etc.) são despachadas para ControlPointManager que se comunica com o renderizador via SOAP. Locais de chamada individuais já tinham Try/Catch em torno de suas chamadas SOAP, mas um caso de tempo suficientemente estranho (por exemplo, o renderizador morrendo entre duas chamadas SOAP no mesmo manipulador de notificação) ainda poderia escapar. O wrapper de nível superior é a rede de segurança final para que o usuário nunca veja um popup genérico de "TargetInvocationException" do MusicBee.
Implementação: renomeou o corpo existente para ReceiveNotificationInternal e adicionou um wrapper fino ReceiveNotification que faz Try { ReceiveNotificationInternal(...) } Catch { LogError(...) }. A infraestrutura Try/Catch pré-existente por método dentro de ControlPointManager (em torno de cada chamada PostSoapRequest) permanece - F36 é cinto + suspensórios.
F37 - Busca em faixa longa acionando transição falsa
O quê: buscar dentro de uma faixa longa pode produzir um breve ciclo Stopped→Playing em alguns renderizadores. Sem discriminação, ProcessNewPlayState.Stopped trata isso como fim natural da faixa e chama Player_PlayNextTrack, avançando o MusicBee quando o usuário apenas queria arrastar. F37 carimba lastUserInitiatedSeek em Seek() e adiciona uma guarda de 5 segundos no manipulador Stopped (espelhando a janela lastUserInitiatedStop existente).
Por quê: pular silenciosamente para a próxima faixa durante uma busca é um daqueles bugs cuja causa ninguém consegue adivinhar - o usuário pensa "estranho, tentei arrastar para frente e agora está tocando a próxima música". A correção é mecânica: o mesmo padrão da discriminação de parada do usuário já existente.
F38 - Manipulação aprimorada de busca para codecs propensos a falhas
O quê: o travamento do BubbleUPnP na busca de MP3 era o sintoma canônico. Após auditoria, o código de busca atual do yaiol já faz as coisas certas - o caminho nativo lida corretamente com HTTP Range (206, Content-Range, AcceptRanges), o caminho codificado anuncia X-AvailableSeekRange e analisa os cabeçalhos timeSeekRange.dlna.org / npt de entrada, os sinalizadores DLNA.ORG_OP refletem as capacidades reais do fluxo (com DisablePcmTimeSeek opt-out para dispositivos Platinum problemáticos). Testado pelo usuário no BubbleUPnP 4.6.4 atual: nenhum travamento observado.
Por quê: o travamento na busca de MP3 do BubbleUPnP foi relatado por volta de 2024 e o aplicativo teve ~16 meses de correções desde então. F29 (MP3 codificado OP=11) foi a nova variável que poderia tê-lo reexposto; não o faz, nas versões testadas.
Se um travamento retornar: a forma da correção seria um botão "busca limitada" por perfil que força DLNA.ORG_OP=10 (somente por byte) em codecs sinalizados - espelhando como DisablePcmTimeSeek já funciona para PCM. Adicionar então, não preventivamente.
UI e registro
F39 - Botão "Adicionar" seleciona o novo perfil
O quê: clicar em "Adicionar" na lista de perfis de dispositivo cria um novo perfil E o seleciona automaticamente para que o usuário possa editar os campos imediatamente. Nossa refatoração de diálogo seccionado já faz isso - tanto o caminho de Adição direta quanto o caminho de modelo terminam com Me.activeStreamingProfiles.SelectedIndex = Me.activeStreamingProfiles.Items.Count - 1. Uma verificação confirmou que nosso fork já lida com isso - nada a mudar.
Por quê: pequena falha de UX que acabou não sendo uma falha aqui.
F40 - Mais conexões máximas + log de aviso
O quê: o limite de fluxo simultâneo do plugin (SemaphoreSlim em torno de Sockets_Stream_File / Sockets_Encoder_Start) era um valor fixo de 4. F40 o torna configurável pelo usuário na página de configurações Gerais (padrão 16, intervalo 1-256), adiciona uma linha de log MaxConnections quando uma solicitação precisa esperar por um slot, E exibe um distintivo vermelho ⚠ Max Conn no canto inferior esquerdo da caixa de diálogo de configurações se o limite foi atingido pelo menos uma vez desde que o MusicBee foi iniciado.
Por quê: quando um dispositivo dispara solicitações paralelas (alguns Marantz/Linn durante varreduras de arte, sondagens de metadados do BubbleUPnP junto com a reprodução ativa), solicitações adicionais eram bloqueadas silenciosamente atrás do semáforo - o usuário via "dispositivo lento" sem causa visível. A linha de log é boa para depuração técnica, mas usuários não técnicos nunca leem logs. O distintivo visível na caixa de diálogo de configurações torna a condição de limite atingido detectável para qualquer pessoa que abra as preferências do plugin.
Implementação:
- Centralizou a espera em
WaitOnSendBarrier(logTag)emMusicBeeUpnp.vb; ambos os locais de chamada (MediaServerDevice.GetFile,Encoder.StartEncode) o utilizam. Settings.MaxConnectionspersistido na v8 do esquema de configurações.Plugin.MaxConnectionsHité um sinalizador de sessão persistente definido dentro deWaitOnSendBarrier; reinicia apenas na reinicialização do MusicBee.SettingsDialog.maxConnectionsBadgeé um rótulo vermelho em negrito em(16, 410)que só aparece quandoPlugin.MaxConnectionsHité True. Tem uma dica de ferramenta explicando a causa e o remédio.- O semáforo é inicializado uma vez no carregamento do tipo, então a alteração da configuração requer uma reinicialização do MusicBee (observado no rótulo do campo).
F41 - Log "codificação devido a ReplayGain/DSP"
O quê: em vez de linhas de log separadas "codificando para RG" / "codificando para DSP", a única linha StreamDecision do F42 inclui MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain como razões acumuladas. Mesmo valor diagnóstico, menos ruído.
Por quê: os usuários veem todas as razões pelas quais a transcodificação está acontecendo para uma determinada faixa em uma linha de log, não espalhadas. Veja F42 para detalhes completos.
F42 - Log "renderizador não suporta codec de origem"
O quê: adicionada uma linha de log StreamDecision por faixa de reprodução para dispositivo que diz "codec NATIVO" ou "transcodificar CODEC→CODEC motivo=…". O campo de motivo acumula todas as condições que acionaram a transcodificação: MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain, WebFile, VirtualFile, ForceTranscoding(global), SampleRate<min/SampleRate>max, DownmixToStereo, DeviceLacksCodec(X), BandwidthConstrained.
Por quê: os usuários ficavam confusos com picos inesperados de CPU em arquivos que esperavam transmitir nativamente. Uma linha de log por faixa informa exatamente qual condição causou a transcodificação - e se o campo mostrar DeviceLacksCodec(Flac), eles sabem imediatamente que as informações de protocolo do dispositivo estavam incompletas e podem querer que o fallback do F32 seja acionado.
Implementação: string acumuladora única construída incrementalmente através da cadeia de decisão; registrada uma vez no final. Protegida por Settings.LogDebugInfo para evitar ruído de log em produção.
F43 - Log SetNextAVTransport mostra URL de origem
O quê: as entradas de log QueueNext agora incluem source=<caminho da biblioteca MusicBee> junto com stream=<URL de streaming HTTP>. A mesma alteração aplicada ao caminho de sucesso e ao caminho de falha (QueueNext:Failed).
Por quê: ao depurar um problema de faixa enfileirada, o URL de streaming (/encode/aabbccdd0.flac) é opaco por si só - o mesmo para cada faixa. O URL de origem é o caminho da biblioteca que pode ser pesquisado por humanos e que informa exatamente qual arquivo o MusicBee tentou enfileirar.
F44 - Melhor registro de erros de tipo MIME
O quê: duas novas entradas de log durante Activate:
Activate:MimeUnverified- dispara por entrada malformada na respostaGetProtocolInfodo dispositivo, nomeando qual entrada não pôde ser analisada (para que o usuário possa ver, por exemplo, "o Marantz retornouhttp-get:*::*para algum codec - a capacidade não foi verificada, o fallback do F32 adivinhará").Activate:NoSinkInfo- dispara uma vez se o dispositivo não retornou nenhum elemento<Sink>. Significa queSupportedMimeTypespermanece Nothing eIsCodecSupporteddegrada para "assumir que tudo funciona" - contexto útil quando erros posteriores de "dispositivo recusou fluxo" aparecem.
Por quê: antes de F44, essas falhas silenciosas de capacidade deixavam os usuários adivinhando por que suas faixas eram transcodificadas contra as expectativas ou recusadas pelo dispositivo. Agora, uma única pesquisa por Activate: mostra se as informações de capacidade do dispositivo eram utilizáveis.
F45 - Melhor registro de erros de metadados
O quê: o log de exceções Browse em ContentDirectoryService.vb já foi enriquecido em trabalhos anteriores do yaiol (a sessão de bug do Alia Vox) com ObjectID e rastreamento de pilha. F45 o expande ainda mais com BrowseFlag (metadados vs filhos), Filter (quais atributos o cliente solicitou), sortCriteria e partialResultLength (quantos bytes de DIDL foram produzidos antes da falha - aponta para quão longe no lote a faixa ruim está).
Por quê: quando algo dá errado no meio do DIDL, o valor de comprimento parcial informa se a falha ocorreu na primeira faixa do lote (parcial=0) ou no meio (parcial=N) - combinado com o startingIndex do lote, você pode identificar o índice da faixa ofensora. Filter e BrowseFlag explicam que tipo de navegação o cliente queria; às vezes, uma navegação apenas de metadados falha onde uma navegação de filhos para o mesmo ID é bem-sucedida.
Rede
F46 - O modo automático só se anuncia em adaptadores de rede real
O quê: no modo de interface Automático, o plugin costumava se anunciar (SSDP) em cada adaptador IPv4 operacional. Em uma máquina que também executa um túnel VPN (NordLynx) ou um switch virtual (Hyper-V / WSL / Docker), a mesma biblioteca era anunciada também em cada um desses adaptadores, então o ponto de controle de onde você transmite descobria o server duas ou três vezes e listava a biblioteca como cópias duplicadas. O modo automático agora mantém apenas os adaptadores que têm um gateway IPv4 padrão real (HasIPv4Gateway) - que os adaptadores de túnel e switch virtual não têm - então esses são removidos da lista de anúncios. Um endereço fixado pelo usuário ainda vence sempre (anúncio apenas naquela interface), e se nenhum adaptador informar um gateway, o seletor recorre a todos os adaptadores, de modo que a lista de endereços anunciados nunca fica vazia e o plugin não pode ficar invisível.
Por quê: a duplicata não é causada por "estar em uma VPN" - é causada por anunciar no adaptador LAN e no adaptador de túnel/virtual ao mesmo tempo, então um ponto de controle vê o mesmo server em dois endereços. Uma VPN de consumo (NordVPN/NordLynx) tunela apenas o tráfego destinado à internet; o renderizador DLNA vive na LAN e o tráfego da sub-rede local contorna o túnel, então o adaptador do túnel nunca alcança um renderizador de qualquer forma - removê-lo elimina uma cópia fantasma, nunca um caminho funcional. O teste de gateway é o sinal barato e confiável que separa um adaptador LAN/Wi-Fi real de um túnel ou switch virtual. Complementa o N05 (que corrigiu como os anúncios são enviados em tais links - multicast em vez de broadcast); o F46 governa em quais adaptadores se anuncia, para começar.
Limitação conhecida: uma VPN mesh / de acesso remoto (Tailscale, ZeroTier, WireGuard para casa) cujos renderizadores realmente vivem do outro lado do túnel geralmente apresenta um adaptador sem gateway padrão, então o modo automático também o descarta. Esses usuários fixam o endereço VPN em vez disso, que tem precedência sobre o filtro de gateway.