MusicBee UPnP Plugin Ajuda

Novidades

2.0.2 - 2026-07-20

  • Um modelo já não pode ser eliminado enquanto qualquer nó o seguir — o botão de eliminação simplesmente permanece desativado, para que um nó nunca fique órfão. Cada linha de modelo agora mostra uma contagem (n) em tempo real dos seus seguidores, o que explica uma eliminação desativada à primeira vista; a eliminação de um modelo não utilizado também permanece agora, em vez de as predefinições enviadas reaparecerem silenciosamente no próximo carregamento.
  • Um novo botão de funil ao lado da árvore de Visualização mostra exatamente quais nós seguem um modelo: filtra a árvore apenas para os seguidores do modelo selecionado, refiltra à medida que seleciona outros modelos e restaura a árvore completa quando desativado.
  • Os nós Rádio e Podcasts estão agora permanentemente emparelhados com o modelo da sua categoria — reformule o modelo no separador Caminhos e o nó segue por si só; não há nada para aplicar, por isso o botão Aplicar está desativado para eles. A lista de modelos reflete isto 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 estão agora ligados 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 é imediatamente remodelado — sem ter de o procurar e reaplicá-lo nó a nó. A árvore de visualização mostra qual o modelo que cada nó segue logo após o seu nome, uma renomeação aparece lá instantaneamente, e apagar um modelo primeiro informa quantos nós o seguem (eles mantêm o seu layout atual e simplesmente deixam de seguir qualquer coisa).
  • Aplicar um modelo a um nó oculto também o torna visível novamente — aplicar é o gesto "mostra-me isto, com esta forma", enquanto ocultar permanece com a caixa de seleção Visível. O modelo reservado "Oculto" que isto substitui desapareceu.
  • A árvore de visualização já não perde o seu lugar: as seleções, as pastas expandidas e a posição de deslocamento 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. É apresentada em duas partes: tudo o que é novo neste fork, e depois as correções e melhorias feitas ao plugin original. Cada item mantém a forma O quê / Porquê do catálogo de funcionalidades internas do projeto, para que o raciocínio por trás de cada alteração esteja na página, e não apenas a alteração.

Novidades neste fork

MediaRenderer - reproduzir no MusicBee

N01 - MusicBee como renderer de reprodução

O quê: normalmente, este plugin funciona num sentido: um telefone ou outro dispositivo navega na biblioteca do MusicBee e reproduz a música em si mesmo. Esta funcionalidade adiciona a direção oposta - permite que o MusicBee seja o reprodutor. A partir de uma aplicação de controlo no seu telefone (como o BubbleUPnP), pode escolher o seu MusicBee de ambiente de trabalho como o dispositivo de reprodução e controlá-lo a partir da sua mão: reproduzir, pausar, parar, avançar ou retroceder, saltar para um ponto na faixa e alterar o volume ou silenciar.

Porquê: transforma o seu telefone num controlo remoto para a música que já está no seu PC. Sente-se no sofá, navegue na sua biblioteca no telefone, toque numa faixa e ela sairá dos altifalantes ligados ao seu ambiente de trabalho - com controlo total de onde está sentado. O plugin original nunca lançou isto como uma funcionalidade em funcionamento.

Como ativar: está desativado por predefinição, porque ativá-lo permite que qualquer dispositivo na sua rede doméstica inicie a reprodução no seu PC. Ativa-o com uma caixa de seleção no separador Geral da caixa de diálogo de definições. As três funções do plugin têm cada uma a sua própria caixa de seleção lá - partilhar a minha biblioteca (Servidor), permitir que outros reproduzam para mim (Renderer) e reproduzir para outros dispositivos (Ponto de Controlo) - e a caixa de diálogo só mostra os separadores de definições que as funções que ativou realmente precisam, para que nunca se depare com opções que não se aplicam a si.

Como distinguir as suas máquinas: pode dar ao renderer o nome que quiser (começa como "MusicBee (yaiol)"). Esse nome é o que aparece na lista de destinos de reprodução do seu telefone, para que, quando mais de um PC estiver a executar o MusicBee, possa distinguir qual é qual. Uma alteração de nome tem efeito imediato, sem necessidade de reiniciar.

Melhor som possível quando reproduz para si mesmo: quando navega na própria biblioteca do MusicBee a partir do seu telefone e envia uma faixa de volta para esse mesmo MusicBee, o plugin reconhece que lhe está a ser pedido para reproduzir um dos seus próprios ficheiros e simplesmente 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 inutilmente o áudio para a rede e de volta para si mesmo.

Execução privada: as três funções funcionam independentemente, pelo que pode ativar o renderer enquanto mantém a partilha da biblioteca desativada. Nessa configuração "apenas renderer", a sua biblioteca permanece completamente oculta da rede - apenas o destino de reprodução é anunciado - e o MusicBee nunca se oferecerá para reproduzir para si mesmo.


Comportamento de reprodução

N02 - FLAC 5.1 não é automaticamente downmixed

O quê: a restrição de contagem de canais em MediaServerDevice.GetEncodedFile era If StereoOnly OrElse Not isPcmData Then channelCount = 2. A cláusula Not isPcmData fazia silenciosamente o downmix de cada transcodificação não-PCM (FLAC, MP3, AAC, Ogg) para estéreo, independentemente da contagem de canais da fonte, impedindo renderers compatíveis com 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.

Porquê: o objetivo de transcodificar uma fonte FLAC 5.1 para saída FLAC é preservar a mistura multicanal. O downmix silencioso tornava a opção de transcodificação FLAC inútil para audição surround. Com N02, faz a coisa certa.


Arquitetura

A alteração estrutural que torna o fork viável numa grande biblioteca - ausente do plugin original.

N03 - Árvore de navegação preguiçosa (sob demanda)

O quê: o plugin original construía toda a árvore de navegação no arranque do MusicBee - enumerava cada faixa, Library_GetFileTags completo por ficheiro, montava toda a hierarquia de contentores - antes de abrir a porta HTTP. Numa biblioteca real (mais de 50 mil faixas, 5400 episódios de podcast, centenas de estações), isso são minutos de arranque a frio, e a árvore permanece na RAM para sempre, incluindo ramos que nenhum cliente alguma vez abre. Este fork não constrói nada antecipadamente: a raiz expõe um marcador de posição prefixado com L: por ponto final (L:music, L:podcast, L:filter:…); cada nível é calculado apenas quando um cliente navega nele (LazyBrowseEnsureLazyEndpointInMemorycaches por nível), e as notificações de alteração da biblioteca limpam as caches (SetLibraryDirty).

Porquê: o arranque a frio é essencialmente instantâneo - 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. Compromisso: a primeira navegação num ponto final paga o seu custo de carregamento; a reentrada é armazenada em cache até à próxima alteração da biblioteca. Esta é a base de tudo o resto. Notas completas: FIXES.md.


Rede e robustez

Reforço do caminho de ligação do servidor HTTP. O plugin original morre silenciosamente quando a sua porta está indisponível.

N04 - Ligação da porta HTTP com autocorreção

O quê: o servidor HTTP do plugin já não morre quando a sua porta configurada está indisponível. Três alterações interligadas:

  1. Recuo automático em caso de falha de ligação. HttpServer.Start tenta a porta configurada e, em caso de SocketException, verifica até 20 portas para encontrar a primeira livre. A porta realmente ligada é registada num novo Plugin.boundServerPort, e tudo o que anuncia o servidor - URLs SSDP LOCATION (NOTIFY + resposta M-SEARCH), o URL do dispositivo (PrimaryHostUrl), o encaminhamento de porta do router e os autofiltros SSDP/ponto de controlo - agora lê boundServerPort em vez de Settings.ServerPort. Os clientes UPnP descobrem a porta real via SSDP, pelo que uma porta movida é transparente para os renderers.
  2. Notificação ao utilizador. Quando ocorre um recuo (a porta guardada não é a que está em uso), uma MessageBox localizada (WarnPortInUse) informa o utilizador qual a porta que está realmente a servir e que os dispositivos ainda a encontrarão - porque o plugin é executado sem interface gráfica e uma mensagem na caixa de diálogo só seria vista por alguém que já suspeitasse de um problema.
  3. Recuperação de reinício. RestartServer (o caminho de reinício de guardar definições) costumava desreferenciar Plugin.controller / Plugin.server cegamente. Se o Initialise inicial lançasse uma exceção antes de os criar (exatamente o que uma ligação falhada causava), a próxima gravação de definições atingia uma NullReferenceException - deixando um plugin meio morto. Agora, recria-os e inicia-os quando Nothing, de modo que guardar uma porta funcional revive o plugin sem um reinício completo do MusicBee.

Porquê: o gatilho foi um incidente real de utilizador. A antiga porta predefinida 49382 reside no intervalo dinâmico do Windows (49152-65535), onde Hyper-V/WSL2/Docker/WinNAT reservam grandes blocos que mudam a cada arranque - então a ligação falhou com WSAEACCES ("acesso proibido") numa máquina onde tinha funcionado por meses. Mudar a predefinição para uma porta livre colidiu então com o Serviio (um servidor DLNA separado já na nova porta), falhando com WSAEADDRINUSE. Cada falha foi engolida em Initialise, deixando o plugin silenciosamente morto e depois NRE-ing na próxima gravação de definições. Após N04, uma colisão de porta autocorrige-se - o servidor continua a funcionar na próxima porta livre, o utilizador é informado e os clientes redescobertam-no - em vez de derrubar todo o plugin.

Implementação:

  • A porta predefinida mudou de 49382 para 9779 (abaixo do intervalo dinâmico, para que o Windows nunca a reserve automaticamente; não é uma predefinição de servidor de mídia conhecida) em todas as três declarações de ServerPort + o recuo de falha de análise de definições.
  • Plugin.boundServerPort (novo campo partilhado) contém a porta de escuta ativa; activeServerPort permanece o instantâneo configurado para que a lógica do emblema "Reinício Necessário" não dispare falsamente num recuo.
  • HttpServer.PortScanRange = 20; a verificação para na primeira TcpListener.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 transmissão IP. O erro inofensivo "não é possível aceder a um objeto descartado" registado quando uma resposta de pesquisa SSDP compete com um reinício do servidor também é suprimido.

Porquê: em adaptadores de rede ponto a ponto / VPN, a transmissão IP não se aplica - o antigo envio de transmissão falhava com "argumento inválido" e os anúncios eram perdidos, pelo que o plugin era invisível para os clientes nessas ligações. Anunciar para o grupo multicast adequado corrige a descoberta exatamente nesses adaptadores.

Navegação na biblioteca

Estas funcionalidades foram lançadas neste fork e não estão no plugin original. Resultaram da navegação real na saída do próprio plugin a partir de clientes UPnP reais.

N06 - Exposição da biblioteca baseada em filtros

O quê: os separadores de filtro do MusicBee (ficheiros .xautopf na pasta do MusicBee do utilizador) tornam-se contentores raiz UPnP na biblioteca do plugin. As faixas de cada filtro são então navegáveis numa hierarquia de AlbumArtistSort → Album → Tracks.

Porquê: os utilizadores com filtros MusicBee curados (por exemplo, "Faixas 5 estrelas", "Adicionadas recentemente", "Clássica → Barroca") esperam encontrá-los ao navegar no plugin a partir de um cliente UPnP. O plugin original apenas expunha a árvore de biblioteca bruta.


N07 - Ligação do campo SortAlbumArtist

O quê: o plugin agora lê o MetaDataType 165 do MusicBee (Sort Album Artist) e usa-o para agrupar/ordenar artistas nas vistas de navegação.

Porquê: navegadores de alta fidelidade e audiófilos usam nomes de artistas para ordenar ("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 - Tratamento 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 vistas de navegação, e não sob um único artista Frankenstein que combina os nomes.

Porquê: álbuns colaborativos e compilações precisam de aparecer sob cada colaborador. Sem isto, metade dos caminhos de pesquisa para encontrar o álbum estão quebrados.


N09 - Arte de capa do contentor do álbum (upnp:albumArtURI)

O quê: os nós do contentor do álbum nas respostas de navegação DIDL agora incluem um elemento upnp:albumArtURI que aponta para a capa do álbum.

Porquê: sem isto, cada álbum na vista de navegação de um cliente UPnP mostra um ícone genérico em vez da capa do álbum. Pista visual para navegação; esperada por todos os navegadores de alta fidelidade modernos.


N10 - Ordenação de faixas dentro de álbuns filtrados

O quê: as faixas dentro de um álbum exposto por filtro são agora ordenadas por Número de Disco, e depois por Número de Faixa.

Porquê: ordem padrão do álbum. Sem ordenação explícita, as faixas eram devolvidas na ordem em que o filtro as retornava - geralmente com aparência aleatória.


N11 - Correção da árvore de pastas de listas de reprodução

O quê: a função LoadLibraryPlaylists (originalmente de Steven Mayall, ~2014) não conseguia descer para pastas de listas de reprodução recém-criadas. A primeira lista de reprodução em cada pasta, mais quaisquer subpastas, acabava órfã no nível raiz.

Porquê: presente no plugin original por onze anos. Visível em 30 segundos após abrir o BubbleUPnP e clicar em Listas de Reprodução. Corrigido em yaiol recursando corretamente em pastas recém-criadas durante a construção da árvore.


N12 - Sanitização de caracteres de controlo XML-ilegais

O quê: qualquer faixa com uma etiqueta contendo um caractere de controlo C0 (por exemplo, 0x19 de uma passagem de codificação incorreta - UTF-8 → Latin-1 → truncagem de 0x99 para 0x19) fazia com que toda a resposta de Navegação falhasse com Action Failed assim que a faixa incorreta entrava num lote paginado.

Porquê: o XML 1.0 proíbe a maioria dos caracteres de controlo C0, e o 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 Navegação paginada

O quê: a navegação para o contentor de Rádio caía no ramo genérico de lista de ficheiros, que chamava files.Sort(AlbumFileComparer) em cada chamada. As entradas de rádio têm etiquetas de Álbum/Disco/Faixa vazias, pelo que cada comparação retornava 0 - List(Of T).Sort é instável, produzindo uma ordem diferente a cada invocação. Os pontos de controlo UPnP paginam (BubbleUPnP busca 0..15 e depois 16..fim); entre as duas chamadas, a lista era reordenada, de modo que algumas estações apareciam em ambas as páginas (duplicados) e algumas em nenhuma (em falta) - parecendo aleatório a cada atualização.

Porquê: presente no plugin original (o seu autor nunca navega rádio via UPnP). Corrigido aqui com um ramo ContainerCategory.Radio dedicado em Browse, sem ordenação por chamada; radioFiles é ordenado uma vez no momento do 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 contentores 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 contentores de álbum, de modo que o cliente mostrava zero álbuns. O manipulador original apenas analisava critérios entre parênteses, e depois descartava todas as faixas, independentemente da classe solicitada.

Porquê: corrigido aqui - as consultas de classe de álbum agora enumeram álbuns distintos (agrupados por AlbumArtist+Album) e emitem cada um como um contentor musicAlbum adequado com arte de 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 click-through

O quê: o original não anunciava capacidades de pesquisa (GetSearchCapabilities retornava vazio), então os clientes recusavam-se até 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), restringe a consulta ao ramo atual do cliente quando um ID de contentor 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 dos resultados de classe de álbum como contentores é N14.)

Porquê: 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 da cache UPnP (SystemUpdateID)

O quê: o original retornava um SystemUpdateID=0 constante - o contrato de invalidação de cache do ContentDirectory UPnP - de modo que os clientes compatíveis com a especificação (BubbleUPnP) tratavam a biblioteca como nunca mudando: resultados de navegação desatualizados, miniaturas 404 após uma alteração do esquema de URL, e a dança de "reiniciar o MusicBee duas vezes para ver as alterações". 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 alteração de configurações (SetLibraryDirty / ResetCacheBumpSystemUpdateId).

Porquê: os clientes detetam de forma fiável edições, novos ficheiros e alterações de configurações na sua próxima navegação. Limite conhecido: os clientes subscritos não recebem ativamente o novo valor via GENA (estacionado como trabalho futuro); eles ainda o veem na sua próxima navegação.


N17 - Arte de capa de subscrição de podcast

O quê: os mosaicos de podcast não mostravam imagens - cada pedido /PodcastThumbnail/ resultava em 404. Dois erros empilhados: a cadeia de resolução nunca verificava a cache de arte de capa real do MusicBee (%LocalAppData%\MusicBee\InternalCache\Subscriptions\<name>.jpg, de onde a UI do ambiente de trabalho carrega), e o unescape+minúsculas da camada HTTP corrompia a chave de rota do URL do feed até ao seu último segmento de caminho. Este fork resolve a arte de capa da InternalCache do MB e encaminha as pesquisas através de um slug seguro para URL que sobrevive intacto à camada HTTP (PodcastSlug / podcastSubIdBySlug).

Porquê: a arte de capa da subscrição agora é renderizada nas vistas de navegação (todos os 22 pedidos que anteriormente resultavam em 404 são resolvidos).


N18 - Navegação hierárquica (delimitada) por etiquetas

O quê: qualquer campo pode ser marcado como hierárquico no separador Opções da Biblioteca e receber um delimitador de um caractere (um seletor de campo + caixa de delimitador com adicionar/remover, persistido nas definiçõ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. As faixas etiquetadas exatamente num ramo (apenas Jazz) obtêm o seu próprio nó [Jazz] para que nada seja ocultado, um ramo com um único filho colapsa por si só, e ; é recusado como delimitador porque é o próprio separador de múltiplos valores do MusicBee.

Porquê: taxonomias de etiquetas profundas que um utilizador já codificou num único campo (árvores de género, hierarquias de humor, "Clássica/Barroca/Concerto") finalmente navegam como a árvore que a etiqueta descreve, em vez de uma parede plana de strings separadas por barras que o utilizador tem que ler de ponta a ponta.


N19 - Caminho de raiz único rotulado pelo seu campo de agrupamento

O quê: um único caminho de navegação na raiz é rotulado pelo seu campo de agrupamento (por exemplo, "Género") em vez do seu caminho curto completo, correspondendo à forma como os grupos do primeiro campo mesclados são nomeados.

Porquê: a árvore de navegação é lida de forma consistente - uma regra de nomenclatura, quer uma entrada raiz esteja sozinha ou tenha sido mesclada com irmãos (N20) - em vez de uma entrada raiz solitária mostrar um caminho interno verboso enquanto os seus vizinhos mesclados mostram um nome de campo limpo.


N20 - Mesclar caminhos de navegação que partilham um primeiro campo

O quê: dois caminhos de navegação que partilham o mesmo primeiro campo - "Género / Artista do Álbum para Ordenar" e "Género / Pessoas do Podcast" - colapsam numa única pasta raiz Género que lista os valores do género primeiro e depois se divide nas duas vistas, em vez de duas entradas "Género / ..." quase duplicadas lado a lado na raiz.

Porquê: um utilizador com várias vistas relacionadas aninhadas sob um campo comum via a raiz desorganizada com entradas de nível superior quase idênticas. A fusão delas mantém a raiz legível e agrupa as vistas relacionadas onde elas pertencem - sob o seu campo partilhado.


N21 - Caminhos de navegação categorizados (Padrão / Rádio / Podcast)

O quê: cada caminho de navegação é tipificado por categoria - Padrão, Rádio ou Podcast. A lista de modelos é agrupada nessas três secçõ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 cinzentos e não podem ser selecionados). Os modelos reservados de Rádio e Podcasts não podem ser eliminados, para que a sua secção de categoria nunca desapareça.

Porquê: sem a tipagem, um utilizador 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ó o descobriria navegando para uma pasta morta a partir de um cliente UPnP. Restringir o menu de campos e os destinos 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 os colapsar sob um único "Desconhecido".

Porquê: grandes subscrições de podcast tornam-se navegáveis por ano como o resto da biblioteca, em vez de cada episódio cair num único monte sem data porque o plugin nunca olhou para a data de publicação por episódio.


N23 - Colapsar 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 Registo mostrando apenas "LP" para um artista que só fez LPs, ou um nível de letra com uma única letra - é automaticamente ignorado, levando o utilizador diretamente para o seu conteúdo.

Porquê: navegar por uma pasta que contém exatamente uma pasta é pura fricção. Colapsar o nível de escolha única remove o clique morto sem alterar o que o utilizador pode alcançar.


N24 - Agrupamento/pesquisa por ano em relação ao campo de data do MusicBee

O quê: a condição de ano já não consulta o campo "Ano" de data completa do MusicBee com um valor de quatro dígitos, e o aliasing do campo de ano codificado está ausente, de modo que cada campo de agrupamento agora resolve genericamente a partir da definição do caminho.

Porquê: para bibliotecas cuja etiqueta de 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 etiqueta de data completa) e Ano (aaaa) (apenas o ano de quatro dígitos) - para que o utilizador possa escolher qualquer um ao definir um agrupamento de álbum ou um caminho de navegação.

Porquê: os dois campos significam coisas diferentes no MusicBee, e colapsá-los perdia essa distinção. Expor ambos permite que um utilizador reúna todos os lançamentos de um ano (aaaa) ou mantenha a ordenação por data exata (etiqueta de Ano completa), conforme pretendido.


N32 - Filtros e listas de reprodução afixados agrupados por tipo na raiz

O quê: um filtro afixado aparece agora diretamente abaixo da pasta Filtros na raiz de navegação, e uma lista de reprodução afixada diretamente abaixo da pasta Listas de reprodução, em vez de todos os afixados se acumularem num único conjunto no final da raiz. Cada atalho afixado fica junto dos do seu tipo.

Porquê: à medida que afixa mais atalhos, um único conjunto final de filtros e listas de reprodução misturados torna-se mais difícil de analisar e separa cada atalho da pasta a que pertence. Agrupar os itens afixados sob a sua própria categoria mantém a raiz legível e cada atalho junto das coisas a que pertence.

Caixa de diálogo de definições e empacotamento

N26 - Caixa de diálogo de definições seccionada

O quê: a página de Preferências ganhou um layout de navegação à esquerda com secções: Geral / Reprodução / Biblioteca / Perfis de Dispositivo / Diagnóstico.

Porquê: o original era uma única lista longa e plana de todas as definições - bom para o desenvolvedor que o construiu, confuso para todos os outros. O seccionamento agrupa opções relacionadas e faz com que a caixa de diálogo pareça mais com as definições de aplicações modernas.


Renomeação de assembly + plugin (sem F-id - nota de empacotamento)

O quê: a DLL compilada é nomeada mb_UPnP_yaiol.dll e o plugin reporta-se como "MusicBee UPnP (yaiol)". Distinto do original mb_Upnp.dll.

Porquê: os utilizadores podem instalar o yaiol juntamente com o plugin original e comparar o comportamento lado a lado.


Sistema de emblemas - exibição do estado 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 emblemas coloridos visíveis na caixa de diálogo de Definiçõ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 iniciou. Sinalizador de sessão persistente Plugin.MaxConnectionsHit. Definido dentro de WaitOnSendBarrier quando não há slot livre.
  • ⚠ Reinício Necessário - dispara quando uma definição guardada precisa de um reinício do MusicBee para ter efeito. Sinalizador de sessão persistente Plugin.RestartRequired. Definido no manipulador de Guardar 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 definições que exigem reinício são limitadas àquelas que genuinamente 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.

Porquê: o ficheiro de registo do plugin é bom para utilizadores técnicos a depurar, mas um utilizador não técnico a olhar para "o dispositivo soa mal" ou "a reprodução está lenta" nunca abrirá Diagnóstico → Ver registo. Os emblemas capturam os casos em que o utilizador precisa de saber que algo aconteceu e os exibem na próxima vez que abrirem o plugin - descobertos sem ler nada.

Reutilizável para o futuro:

  • Incompatibilidade de perfil detetada (o user-agent do dispositivo nunca correspondeu a nenhum perfil, voltou para Genérico).
  • Recuo de NextURI acionado (F13 - gapless desativado para a sessão num dispositivo instável).
  • Análise da biblioteca falhou / parcial.
  • Conexão do renderer perdida a meio da sessão.
  • Qualquer outra condição em que "aconteceu uma vez, o utilizador deve saber" supera "registado silenciosamente entre 1000 outras linhas".

Convenções de implementação:

  • Os rótulos dos emblemas ficam ao nível da caixa de diálogo (não dentro de nenhum painel) para que sejam visíveis independentemente da secção em que o utilizador se encontra.
  • Posicionados na linha inferior perto de Guardar/Cancelar (atual: y=410 empilhados horizontalmente).
  • Cada emblema tem um sinalizador de sessão persistente correspondente em Plugin que muda para Verdadeiro quando a condição ocorre e só é redefinido no reinício do MusicBee.
  • Recursos: <Condition>Badge (texto do rótulo, prefixado com ⚠) + <Condition>BadgeTip (dica de ferramenta explicando a causa + remédio).
  • Para emblemas "definição guardada precisa de reinício", tire um instantâneo de tempo de execução em Plugin.Initialise() e compare com Settings.* após Settings.SaveSettings() no manipulador de Guardar da caixa de 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 definições são agora descartadas quando o utilizador 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 guardados em tempo real à medida que são editados - não há um botão Guardar separado no separador Caminhos.)

Porquê: Cancelar deve significar cancelar. Anteriormente, um utilizador que experimentava alterações de caminho/modelo e recuava encontrava as alterações já confirmadas, sem forma de as desfazer a não ser refazendo cada uma manualmente.


N28 - E comercial literal 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 o engolir como um prefixo mnemónico Alt.

Porquê: nomes de campos com um e comercial eram exibidos incorretamente (o caractere desaparecia e a próxima letra tornava-se um acelerador), tornando a entrada do menu difícil de reconhecer.


N29 - Título da janela de definições estável e não traduzido

O quê: o título da janela de definições é fixado à string da marca "MusicBee UPnP Plugin" e já não varia com o idioma da interface; a string DialogTitle por idioma foi removida de todos os pacotes de localização.

Porquê: um título de janela que mudava de redação por idioma era uma superfície traduzível sem benefício - o título é uma marca. Fixá-lo mantém-no estável e consistente em todo o lado.

Localização

O plugin original é apenas em inglês. Este fork é totalmente localizável - cada string virada para o utilizador passa por um pacote de recursos, e o plugin deteta automaticamente o idioma da UI do próprio MusicBee.

N30 - UI multi-idioma (traduções pendentes)

O quê: o mecanismo de localização está completo e a ser lançado. Localisation.vb lê o idioma selecionado do MusicBee de MusicBee3Settings.ini (endónimo <SystemLanguage>) e aplica a cultura .NET correspondente ao thread, de modo que My.Resources.Resources.* retorna a string localizada. Cada rótulo/botão/mensagem virado para o utilizador está ligado 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élite para os outros idiomas são produzidos numa única passagem em lote quando o plugin estiver completo em termos de funcionalidades (traduzir por partes enquanto as strings ainda estão a mudar desperdiça esforço).

Idiomas alvo (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) Checo (cs) Alemão (de) Grego (el)
Espanhol (es) Francês (fr) Húngaro (hu) Italiano (it)
Coreano (ko) Holandês (nl) Norueguês (nb) Polaco (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 (de acordo com a regra de localidade do espaço de trabalho): PT e ZH são divididos em pacotes distintos porque o vocabulário/escrita diverge genuinamente (pt-BR/pt-PT, zh-CN/zh-TW). EN é um único pacote - "English(US)" (en-US) do MusicBee volta para en através da cadeia de cultura do .NET, então nenhum pacote US separado é produzido. ES e FR são igualmente de localidade única.

Porquê: as definições de um plugin UPnP ("não usar PCM bruto", "forçar PCM little-endian", avisos de fallback de porta) são suficientemente crípticas na língua nativa. Seguir o próprio idioma da UI do MusicBee - em vez de forçar o inglês - é a diferença entre uma ferramenta que um utilizador não-inglês pode configurar e uma que não pode. Nenhuma das upstreams tentou isto.


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 utilizador (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.

Porquê: um utilizador a executar o MusicBee numa variante regional (Português do Brasil, Chinês Simplificado) era enviado para a página de ajuda do idioma base. Transportar a cultura completa leva-o para a página de ajuda no idioma exato que utiliza.

Correções e melhorias ao plugin original

Protocolo principal e reprodução

F01 - Perfis de dispositivo DLNA predefinidos atualizados

O quê: inclui novos perfis predefinidos 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.

Porquê: os padrões do plugin original estavam congelados por volta de 2014. PS4/Xbox/BubbleUPnP ganharam desde então suporte a áudio de alta resolução. Fora da caixa, uma nova instalação reproduz o melhor da classe nestes dispositivos sem que o utilizador precise de tocar nas definiçõ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 renderer para decidir se o MusicBee pode controlá-lo. O original apenas correspondia a urn:schemas-upnp-org:device:MediaRenderer:1. Dispositivos modernos anunciam :2 ou :3. F02 alarga a correspondência.

Porquê: sem isto, 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 stream nativo" por perfil (predefinição LIGADO)

O quê: quando selecionado, o plugin envia os bytes do ficheiro original para o dispositivo sem transcodificação, sem DSP, sem processamento ReplayGain aplicado. Apenas o ficheiro bruto que o utilizador escolheu, byte a byte (módulo de enquadramento HTTP).

Porquê: por testemunho em fóruns, esta é a maior vitória em qualidade de reprodução. Utilizadores de alta fidelidade que compram renderers caros querem explicitamente uma saída bit-perfect; qualquer toque de DSP anula o objetivo. Predefinição LIGADO porque a maioria dos dispositivos modernos lida com qualquer codec que o utilizador lhes envie, e ReplayGain/EQ deve ser opcional. É por perfil para que possa manter a transcodificação para uma Xbox antiga 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 stream para este dispositivo através do transcodificador, independentemente do suporte de codec nativo. O inverso de F03 (ForceNativeStream). Mutuamente exclusivo com F03 - a UI desmarca automaticamente o outro quando um é ativado.

Porquê: um único toggle global seria contraditório com o ForceNativeStream por perfil (F03). Caso real: o dispositivo A é um DAC de alta fidelidade que quer streams nativos bit-perfect; o dispositivo B é um recetor AV antigo que engasga com FLAC. Com um toggle global, o utilizador tem de escolher - à custa 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. Ficheiros pré-v9 carregam o valor global legado uma vez e copiam-no para todos os perfis, preservando o comportamento antigo através da atualização.
  • UI: removido do painel de Diagnóstico, adicionado à secção Perfis de Dispositivo ao lado de ForceNativeStream. Manipuladores de exclusão mútua bidirecionais (CheckedChanged em cada um desinscreve o outro antes de inverter, para evitar um ciclo infinito).
  • Local de decisão: Settings.ForceTranscodingstreamingProfile.ForceTranscoding em WriteAudioFileDIDL.

F05 - "Forçar PCM little-endian" por perfil

O quê: os streams 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.

Porquê: sem isto, certos dispositivos produzem uma parede de estática. O sintoma é dramático e a causa invisível sem conhecimento da codificação PCM - o toggle dá aos utilizadores uma correção por tentativa e erro.


F06 - "Não usar PCM bruto" por perfil

O quê: quando o dispositivo afirma suportar PCM bruto, o plugin usa isso. Alguns dispositivos mentem - aceitam o handshake SOAP, mas corrompem os dados PCM brutos reais, enquanto lidam corretamente com PCM encapsulado num contentor WAVE. F06 força PCM-over-Wave, independentemente do que o dispositivo anuncia.

Porquê: certos modelos Marantz especificamente - anunciam PCM bruto, mas apenas WAVE funciona. Sem isto, os streams PCM brutos saem distorcidos, sem uma 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:

  • Predefinição - contagem real de bytes quando conhecida, omitir quando desconhecida.
  • Nenhum - nunca enviar o cabeçalho (apenas codificação em blocos).
  • Apenas PCM - enviar apenas para PCM bruto; omitir para tudo o resto.
  • Fixo - enviar UInt32.MaxValue - 8192 (um sentinela para "comprimento desconhecido enorme").

Porquê: os dispositivos UPnP/DLNA variam muito na forma como reagem ao Content-Length. Alguns precisam de um número exato, alguns odeiam-no em streams, alguns precisam de um valor sentinela "muito grande" para manter o buffering. Isto foi posteriormente alargado de apenas PCM para todos os formatos de saída porque os mesmos problemas apareceram em streams MP3/AAC transcodificados.


F08 - "Não limpar NextURI" por perfil

O quê: normalmente, o plugin limpa o NextURI em fila 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.

Porquê: sem isto, os proprietários de Denon experimentam o dispositivo a cortar a meio da faixa quando a fila esvazia. Com F08 selecionado, o dispositivo mantém o NextURI obsoleto na memória (inofensivo - apenas é sobrescrito na próxima vez que algo é colocado em fila).


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 juntamente com PCM 16/24, MP3, AAC, Ogg. Escolhê-lo encaminha 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).

Porquê: para dispositivos que lidam bem com FLAC, mas não conseguem descodificar o codec de origem (por exemplo, um Eversolo a receber a biblioteca WMA do MusicBee convertida para FLAC), isto preserva a qualidade sem perdas onde MP3/AAC descartariam dados de áudio. Desbloqueia N02 (controlo de downmix 5.1), que não podia 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 junta-se a MP3/AAC/Ogg no ramo acionado por linha de comando. O menu suspenso da UI obtém "FLAC" como 6ª opção. O mapeamento de carregamento/gravação em SettingsDialog estende-se para reconhecer FileCodec.FlacSelectedIndex = 5. Mime, tipo DLNA e funcionalidade de codificação já estavam ligados em ItemManager.GetMimes / GetDlnaType / GetEncodeFeature de trabalho anterior (F21, F26).


Gapless (SetNextAVTransportURI)

F10 - SetNextAVTransportURI / NextURI principal

O quê: reprodução gapless verdadeira. Quando o dispositivo anuncia suporte para SetNextAVTransportURI na 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 se ouve num leitor de CD. Isto não é o hack de "fluxo contínuo" (que concatena tudo num único fluxo longo e perde os metadados por faixa).

Porquê: a funcionalidade principal de Nível 2. Álbuns gravados como uma performance ao vivo contínua (gravações ao vivo, movimentos clássicos, sets de DJ) soam errados quando há um silêncio de meio segundo entre as faixas. Resolver isto corretamente é uma funcionalidade principal, agora neste fork.

Notas: o áudio em fila é servido através do servidor HTTP do plugin usando streamHandle=0 (modo de busca da biblioteca), o que significa que o motor de áudio do MusicBee não está envolvido na faixa em fila. Compromisso: os efeitos 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 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 a voltar para a reprodução de uma faixa de cada vez.

Porquê: alguns dispositivos anunciam NextURI, mas têm uma implementação com erros (falhas, transições incompletas, bloqueios). Em vez de fazer engenharia inversa de cada dispositivo avariado, o utilizador obtém um toggle "basta desligá-lo 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 gapless. Ele pede ao MusicBee a nova faixa "seguinte" 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 tiver mudado (ou limpa a fila se o MusicBee disser que não há próxima faixa).

Porquê: sem F12, o dispositivo continuava a reproduzir um NextURI obsoleto quando o utilizador removia/reordenava a faixa em fila. Isto historicamente levou várias iterações porque cada mutação de lista precisa de um tratamento diferente - nós simplificamos confiando em NowPlayingList_GetNextIndex (que já respeita a ordem aleatória e o loop de repetição de tudo), então todas as variantes são canalizadas através da mesma comparação.

Implementação:

  • Novo campo nextPlaySourceUrl armazena o URL da biblioteca do MusicBee do que está em fila (o URL de streaming com sufixo de identificador não é comparável a um caminho de biblioteca).
  • Nova Public Sub RefreshQueuedNextUri() em MediaRendererDevice. Três resultados: nenhum NextURI em fila → sem operação; em fila corresponde ao novo "próximo" → sem operação; em fila difere → chamar QueueNext com o novo URL (ou QueueNext("") para limpar - o que respeita F08 DoNotClearNextUri).
  • Ligado em Plugin.ReceiveNotification sob NotificationType.NowPlayingListChanged.

F13 - Recuo em caso de falha do NextURI

O quê: após 4 falhas consecutivas de SetNextAVTransportURI no mesmo dispositivo, o plugin desativa o gapless para esse dispositivo até que o MusicBee seja reiniciado.

Porquê: se um dispositivo estiver realmente avariado para NextURI (falhas SOAP intermitentes, falhas de rede), o plugin continuaria a tentar em cada faixa. F13 para o ruído e volta para a reprodução de uma faixa de cada vez silenciosamente.


F14 - Integração do modo de repetição + NextURI

O quê: F14 divide-se em dois casos tratados no detetor de transição F15 em OnAvTransportStatusCheck:

  • Repetir Tudo: o MusicBee passa o URL "wrap" correto (faixa 1 no fim da lista) para Plugin.QueueNext. Nenhuma lógica especial do plugin é necessária - o dispositivo transita para ele e o detetor F15 chama Player_PlayNextTrack como 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 transita para ele (novo identificador de stream, mesma fonte). O detetor F15 agora consulta Player_GetRepeat() - se for RepeatMode.One, ele ignora a chamada Player_PlayNextTrack para que o MusicBee não avance o índice NPL para longe da faixa em loop.

Porquê: sem o salto de Repetir Um, chamar Player_PlayNextTrack na transição gapless 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 reprodutor - Próxima Faixa sempre avança), contradizendo o que Repetir Um significa.

Advertência de contagem de reproduções: em Repetir Um, o incremento da contagem de reproduções depende do MusicBee 3.7.9563+ notar a reprodução em loop. Versões mais antigas do MusicBee reproduzem a repetição gapless corretamente, mas perdem o aumento da contagem de reproduções. Documentado; não bloqueador.


F15 - Máquina de estados de deteção de transição de faixa

O quê: quando o dispositivo transita internamente da faixa atual para o NextURI, o plugin precisa de notar e dizer ao MusicBee para avançar o 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: consulta GetPositionInfo.TrackURI a cada tick do temporizador de estado. Quando o URI reportado 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 gapless).

Porquê: 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 deteção de transição de faixa precisa de uma iteração longa, por renderer, porque cada marca de renderer tem as suas próprias peculiaridades em quando reporta a alteração do URI (alguns reportam TRANSITIONING primeiro, alguns saltam diretamente para PLAYING com novo URI, alguns têm um breve STOPPED entre eles).

Notas: a nossa primeira versão funciona no renderer BubbleUPnP. Casos extremos por dispositivo permanecem em B6.


F16 - Correção de pop na transição gapless

O quê: o pop ocorre quando o formato da fonte (taxa de amostragem / canais / codec) da faixa em fila difere da faixa atualmente em reprodução, forçando o DAC do dispositivo a rebloquear na transição. F16 adiciona um diagnóstico NextUri:FormatChange que dispara no momento da fila sempre que os formatos diferem, nomeando ambos os lados - para que os utilizadores que ouvem pops possam correlacionar.

O diagnóstico também aponta para a mitigação: selecione 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.

Porquê adiado para a correção real de transcodificação para correspondência: a correção estrutural (transcodificar a faixa em fila 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 ficheiro em fila nativamente. Uma futura v2 de F16 adicionaria rotas /encode/{id}0_{rate}_{depth}.{ext} por formato e as ligaria através do codificador. Essa é uma mudança arquitetónica maior que vale a pena fazer se um dispositivo real exibir o pop depois que o ForceTranscoding não for suficiente.

Implementação hoje:

  • O campo lastSourceUrl rastreia o URL da fonte atualmente em reprodução.
  • QueueNextFilePropertyType.SampleRate/Channels/Kind para as faixas atual e em fila e regista NextUri:FormatChange em 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 Seek SOAP 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 reportada do dispositivo arredonda para dentro de 1 segundo do alvo solicitado pelo utilizador, o plugin agora confia no valor sub-segundo preciso do utilizador em vez do truncamento do dispositivo. Apenas quando o dispositivo reporta algo dramaticamente diferente (mais de 1s de diferença) usamos o seu valor (a busca caiu noutro lugar do que o pedido, por exemplo, snap-to-keyframe em alguns codecs).

Porquê: sem isto, procurar 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 utilizador para o caso comum de scrub dentro da faixa, e ainda respeita o relatório do dispositivo para o snap-to-keyframe atípico.


F18 - Interbloqueio de stream contínuo / NextURI

O quê: dois interbloqueios agora em vigor:

  1. Tempo de execução: QueueNext retorna False antecipadamente quando Settings.ContinuousOutput está ativado. O stream contínuo é o seu próprio mecanismo gapless (um longo stream concatenado); enviar SetNextAVTransportURI por cima dele confunde o dispositivo sobre se cada faixa é um URI discreto ou parte do fluxo contínuo.
  2. UI: quando o utilizador marca a caixa de seleção global de stream contínuo, o forceNativeStream do perfil atualmente exibido é desmarcado automaticamente. O stream contínuo sempre transcodifica, então o force-native é sem sentido em combinação.

Porquê: impede que o utilizador ative dois mecanismos gapless conflitantes simultaneamente. Sem F18, o dispositivo receberia um URI de stream contínuo E um NextURI para cada faixa subsequente, com comportamento indefinido dependendo do renderer.


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 engole-os silenciosamente - registados, mas não propagados como erros.

Porquê: a condição "sem próxima faixa" é normal, não um erro. Tratá-la como fatal polui o registo e (em alguns fluxos) aciona tempestades de repetição.


Tipos MIME e metadados DLNA

F20 - Tipo MIME MP3 → 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 renderers mais rigorosos o rejeitam.

Porquê: 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 alteração necessária.


F21 - Ordem dos tipos 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 ambos os tipos MIME padrão e experimentais.

Porquê: o prefixo x- marca tipos MIME experimentais/não oficiais. Alguns renderers comportam-se melhor com a forma padrão. Pequena reordenação, impacto no mundo real.


F22 - Suporte para tipo MIME Opus

O quê: reconhece Opus como um codec de áudio streamable; envia o tipo MIME audio/opus ao servir faixas Opus.

Porquê: Opus é agora comum (codec de compromisso moderno para fala/música). Sem F22, o plugin recusaria o streaming de ficheiros Opus mesmo para dispositivos que os suportam.


F23 - Suporte a ficheiros de origem Monkey Audio (APE)

O quê: reconhece ficheiros .ape como um codec de origem válido para streaming/transcodificação.

Porquê: APE é um formato sem perdas com uma base de utilizadores de nicho, mas leal. Adicioná-lo custa pouco e desbloqueia a biblioteca para esses utilizadores.


F24 - Fallback de tipo MIME AAC / ALAC

O quê: se um dispositivo suporta AAC ou ALAC, mas não os anuncia explicitamente na sua descrição de serviço UPnP, o plugin oferece-os de qualquer forma como um fallback.

Porquê: vários dispositivos que lidam bem com AAC esqueceram-se de o listar no seu XML de capacidades. Sem F24, o plugin nem sequer tentaria, forçando a transcodificação. Com F24, o plugin tenta e permite que o dispositivo o trate nativamente, se puder.


F25 - Sinalizador de tipo DLNA para streams WAV nativos + codificados

O quê: o sinalizador de tipo DLNA (um identificador de perfil como LPCM, WAVE, MP3) precisa de corresponder ao que o dispositivo recebe. F25 garante que os streams nativos e os streams WAV codificados são sinalizados corretamente.

Porquê: o tipo DLNA incompatível faz com que alguns dispositivos recusem a reprodução completamente ou apliquem o descodificador errado.


F26 - Cabeçalho DLNA para ficheiros FLAC

O quê: os streams FLAC recebem o identificador de perfil DLNA adequado nos seus cabeçalhos.

Porquê: sem ele, alguns dispositivos que suportam FLAC não reconhecem o stream como tal.


F27 - Correção do cálculo da taxa de bits nos metadados

O quê: o res@bitrate do stream contínuo estava a ser 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.

Porquê: exibição de taxa de bits errada no dispositivo - cosmético na maioria dos renderers, mas alguns alocam buffers de stream a partir do valor e gaguejam em streams que parecem ~125× menores do que são. O caminho do ficheiro de origem não contínuo já tinha isso correto ((bitrate_kbps * 1000) \ 8 = bytes/seg); apenas o caminho do stream 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 a forma nua como inválida e deixam a sua exibição de duração em branco. Agora formatado como H:MM:SS.fff (por exemplo, 0:03:42.000).

Porquê: problema de exibição específico de uma marca; o formato ISO-style em conformidade com segundos fracionários corrige-o sem afetar qualquer outro dispositivo. Aplicado em ambos os locais de emissão DIDL (caminho do ficheiro de origem + caminho do stream codificado em WriteAudioFileDIDL).

Correção bónus na mesma passagem: pv:addedTime e pv:lastPlayedTime estavam a usar hh (relógio de 12 horas) nas 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 de MP3 codificado (CBR)

O quê: os streams MP3 transcodificados agora anunciam DLNA.ORG_OP=11 (busca por byte e por tempo) em vez de DLNA.ORG_OP=10 (apenas busca por byte). Dispositivos que anteriormente recusavam a busca por tempo em MP3 transcodificado agora podem controlar a sua barra de progresso/UI de busca normalmente.

Porquê: o transcodificador do MusicBee produz MP3 de taxa de bits constante na predefinição HighQuality, então o mapeamento byte ↔ tempo é linear - o dispositivo pode converter um pedido de busca por tempo para uma busca por byte de intervalo HTTP sem qualquer suporte do lado do codificador. Anunciar OP=11 desbloqueia essa UI no dispositivo. Sem F29, os utilizadores que procuravam 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 em linha numa cadeia If/ElseIf/Else legível. MP3 obtém OP=11 explicitamente; outros codecs não-PCM mantêm OP=10. Nenhuma alteração 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 ficheiro .mpeg tratada

O quê: ficheiros com extensão .mpeg (e o ainda mais raro .mpe) são agora reconhecidos como FileCodec.Mp3 em GetCodec. Antes de F30, retornavam FileCodec.Unknown e eram silenciosamente rejeitados da biblioteca / incapazes de serem fontes de transcodificação.

Porquê: arquivos antigos MPEG-1 Layer 3 às vezes usavam .mpeg em vez de .mp3 (a especificação permite ambos). Um punhado de ficheiros numa biblioteca de 300k é suficiente para sentir "o MusicBee mostra-os, mas o plugin não" - confuso para o utilizador.


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 ficheiro cuja Kind termine em "Stream" (o MusicBee reporta "MP3 Stream", "Internet Stream", etc. para rádio) como contínuo, independentemente do toggle global Settings.ContinuousOutput. O ramo DIDL de stream contínuo (Título: "Continuous Stream", id="continuousstream", saída PCM/Wave fixa) é usado; o dispositivo vê um único stream de estilo infinito.

Porquê: os streams de rádio não têm limites de faixa, comprimento fixo, nem busca. Tratá-los como ficheiros discretos no DIDL fazia com que o plugin anunciasse intervalos de bytes e durações que não existem. A mudança automática quando o MusicBee já nos disse "isto é um Stream" remove um erro que o utilizador não deveria ter de pensar.

Âmbito: aplica-se apenas quando o MusicBee está a controlar a reprodução (musicBeePlayToMode). O caminho de busca da biblioteca (navegação do cliente UPnP) permanece inalterado - os URLs de rádio são raros lá e o comportamento voltado para o utilizador não deve mudar sem testes explícitos.


F32 - Fallback de anúncio de codec

O quê: se um dispositivo não anuncia certos codecs (ou o plugin não consegue analisar o XML de capacidade do dispositivo), o plugin não rejeita imediatamente o stream. Em vez disso, tenta servi-lo e deixa o dispositivo decidir.

Porquê: muitos dispositivos têm XML de capacidade incompleto ou ilegível, mas na verdade lidam bem com o codec. F32 troca um pequeno "tentar e adivinhar" por uma recusa total.


F33 - Melhoria da sincronização da barra de progresso

O quê: a posição entre as sondagens já é extrapolada por relógio de parede a partir de uma única âncora (currentPlayStartTicks), de modo que a barra de progresso se atualiza suavemente a uma 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 estado notou pela primeira vez que o estado passou para Reprodução, mas nessa altura o dispositivo poderia estar a reproduzir há 100-500ms (um intervalo de sondagem). A barra de progresso do MusicBee começaria em 0, depois saltaria para a frente quando a realidade se atualizasse.

Correção F33: ao fazer a transição para Reprodução pela primeira vez numa nova faixa (currentPlayStartTimeEstimated=True), chame GetPlayPositionInformation() para obter a posição atual real do dispositivo, depois ancore-se nisso. O UPnP apenas reporta resolução de 1 segundo, então a âncora ainda é quantizada, mas está muito mais próxima da verdade do que assumir 0.

Porquê: 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 nos seus valores da faixa anterior durante a janela de ~100ms entre o SOAP-Play e a primeira sondagem do temporizador de estado que detetava o novo estado de Reprodução. A barra de progresso do MusicBee mostraria brevemente o fim da faixa anterior, depois voltaria para 0, depois subiria. F34 zera ambos na entrada de PlayToDevice - no momento em que sabemos que uma mudança de faixa está a acontecer, antes de qualquer trabalho SOAP.

Porquê: falha visual em casos de uso de salto rápido (próximo manual ou transição gapless). Agora, a primeira consulta de PlayPositionMs do MusicBee pós-Play retorna 0 de forma limpa, depois o GetPlayPositionInformation do F33 refina-o 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 - Erro de "Forçar transcodificação"

O quê: a transcodificação forçada ainda podia ignorar a transcodificação em certas combinações. Após a reformulação F04 por perfil, duas lacunas específicas foram seladas:

  1. Precedência com ForceNativeStream. Quando ambos eram Verdadeiros (o que pode acontecer numa migração de esquema ou num ficheiro de definições parcial), ForceTranscoding agora vence (If streamingProfile.ForceTranscoding Then forceEncode = True ElseIf streamingProfile.ForceNativeStream Then forceEncode = False). A exclusão mútua da UI impede que o utilizador marque ambos, mas a guarda de tempo de execução lida com qualquer estado que tenha carregado inconsistente do disco.
  2. 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.

Porquê: "forçar" deve significar forçar. Se o utilizador ativou explicitamente ForceTranscoding para um dispositivo, o plugin nunca deve passar silenciosamente para o streaming nativo, independentemente de como outras sinalizações se combinem.


F36 - Exceção de renderer fechado

O quê: envolveu Plugin.ReceiveNotification num Try/Catch de nível superior que regista qualquer exceção não capturada em vez de a deixar propagar de volta para o pump de notificações do MusicBee.

Porquê: as notificações do MusicBee (PlayStateChanged, VolumeMuteChanged, etc.) são despachadas para ControlPointManager que comunica com o renderer via SOAP. Os locais de chamada individuais já tinham Try/Catch em torno das suas chamadas SOAP, mas um caso de timing suficientemente estranho (por exemplo, o renderer a morrer entre duas chamadas SOAP no mesmo manipulador de notificação) ainda podia escapar. O wrapper de nível superior é a rede de segurança final para que o utilizador 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 de faixa longa aciona transição falsa

O quê: procurar dentro de uma faixa longa pode produzir um breve ciclo Parado→Reprodução em alguns renderers. Sem discriminação, ProcessNewPlayState.Stopped trata isso como fim natural da faixa e chama Player_PlayNextTrack, avançando o MusicBee quando o utilizador apenas queria avançar. F37 carimba lastUserInitiatedSeek em Seek() e adiciona uma guarda de 5 segundos no manipulador Parado (espelhando a janela lastUserInitiatedStop existente).

Porquê: o salto silencioso para a próxima faixa durante uma busca é um daqueles erros cuja causa ninguém consegue adivinhar - o utilizador pensa "estranho, tentei avançar e agora está a tocar a próxima música". A correção é mecânica: o mesmo padrão da discriminação de paragem do utilizador já existente.


F38 - Melhoria do tratamento de busca para codecs propensos a falhas

O quê: o BubbleUPnP a falhar 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 stream (com DisablePcmTimeSeek opcional para dispositivos Platinum problemáticos). Testado pelo utilizador no BubbleUPnP 4.6.4 atual: nenhuma falha observada.

Porquê: a falha de busca de MP3 do BubbleUPnP foi relatada por volta de 2024 e a aplicação teve cerca de 16 meses de correções desde então. F29 (MP3 codificado OP=11) foi a nova variável que poderia tê-la reexposto; não o faz, nas versões testadas.

Se uma falha retornar: a forma da correção seria um toggle "busca limitada" por perfil que força DLNA.ORG_OP=10 (apenas por byte) em codecs sinalizados - espelhando como DisablePcmTimeSeek já funciona para PCM. Adicionar então, não preventivamente.


UI e registo

F39 - Botão "Adicionar" seleciona o novo perfil

O quê: clicar em "Adicionar" na lista de perfis de dispositivo cria um novo perfil E seleciona-o automaticamente para que o utilizador possa editar os campos imediatamente. A nossa refatoração da caixa de diálogo seccionada já faz isso - tanto o caminho de Adicionar direto quanto o caminho de modelo terminam com Me.activeStreamingProfiles.SelectedIndex = Me.activeStreamingProfiles.Items.Count - 1. Uma verificação confirmou que o nosso fork já lida com isso - nada a mudar.

Porquê: pequena falha de UX que acabou por já não ser uma falha aqui.


F40 - Mais conexões máximas + registo de aviso

O quê: o limite de streams concorrentes do plugin (SemaphoreSlim em torno de Sockets_Stream_File / Sockets_Encoder_Start) era um valor fixo de 4. F40 torna-o configurável pelo utilizador na página de definições Gerais (predefinição 16, intervalo 1-256), adiciona uma linha de registo MaxConnections quando um pedido tem de esperar por um slot, E exibe um emblema vermelho ⚠ Max Conn no canto inferior esquerdo da caixa de diálogo de definições se o limite foi atingido pelo menos uma vez desde que o MusicBee iniciou.

Porquê: quando um dispositivo dispara pedidos paralelos (alguns Marantz/Linn durante verificações de arte de capa, sondas de metadados do BubbleUPnP juntamente com reprodução ativa), pedidos adicionais bloqueavam silenciosamente atrás do semáforo - o utilizador via "dispositivo lento" sem causa visível. A linha de registo é boa para depuração técnica, mas utilizadores não técnicos nunca leem registos. O emblema visível na caixa de diálogo de definições torna a condição de limite atingido descoberta por qualquer pessoa que abra as preferências do plugin.

Implementação:

  • Centralizou a espera em WaitOnSendBarrier(logTag) em MusicBeeUpnp.vb; ambos os locais de chamada (MediaServerDevice.GetFile, Encoder.StartEncode) o usam.
  • Settings.MaxConnections persistido na v8 do esquema de definições.
  • Plugin.MaxConnectionsHit é um sinalizador de sessão persistente definido dentro de WaitOnSendBarrier; reinicia apenas no reinício do MusicBee.
  • SettingsDialog.maxConnectionsBadge é um rótulo em negrito vermelho em (16, 410) que só aparece quando Plugin.MaxConnectionsHit é Verdadeiro. Tem uma dica de ferramenta explicando a causa e o remédio.
  • O semáforo é inicializado uma vez no carregamento do tipo, então alterar a definição requer um reinício do MusicBee (observado no rótulo do campo).

F41 - Registar "codificação devido a ReplayGain/DSP"

O quê: em vez de linhas de registo separadas "codificação para RG" / "codificação para DSP", a única linha StreamDecision de F42 inclui MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain como razões acumuladas. Mesmo valor de diagnóstico, menos ruído.

Porquê: os utilizadores veem todas as razões pelas quais a transcodificação está a acontecer para uma determinada faixa numa única linha de registo, não espalhadas. Consulte F42 para detalhes completos.


F42 - Registar "o renderer não suporta o codec de origem"

O quê: adicionada uma linha de registo StreamDecision por faixa reproduzida para o dispositivo que diz "CODEC nativo" ou "transcodificar CODEC→CODEC razão=…". O campo de razão 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.

Porquê: os utilizadores estavam confusos com picos inesperados de CPU em ficheiros que esperavam transmitir nativamente. Uma linha de registo por faixa diz-lhes exatamente qual a condição que causou a transcodificação - e se o campo mostrar DeviceLacksCodec(Flac), eles sabem imediatamente que a informação de protocolo do dispositivo estava incompleta e podem querer que o fallback do F32 seja ativado.

Implementação: string acumuladora única construída incrementalmente através da cadeia de decisão; registada uma vez no final. Controlada por Settings.LogDebugInfo para evitar ruído de registo em produção.


F43 - O registo SetNextAVTransport mostra o URL de origem

O quê: as entradas de registo QueueNext agora incluem source=<caminho da biblioteca MusicBee> juntamente com stream=<URL de *streaming* HTTP>. A mesma alteração aplicada ao caminho de sucesso e ao caminho de falha (QueueNext:Failed).

Porquê: ao depurar um problema de faixa em fila, o URL de streaming (/encode/aabbccdd0.flac) é opaco por si só - o mesmo para cada faixa. O URL de origem é o caminho da biblioteca pesquisável por humanos que lhe diz exatamente qual ficheiro o MusicBee tentou colocar em fila.


F44 - Melhor registo de erros de tipo MIME

O quê: duas novas entradas de registo durante Activate:

  • Activate:MimeUnverified - dispara por entrada malformada na resposta GetProtocolInfo do dispositivo, nomeando qual entrada não pôde ser analisada (para que o utilizador possa ver, por exemplo, "o Marantz retornou http-get:*::* para algum codec - a capacidade não é verificada, o fallback do F32 adivinhará").
  • Activate:NoSinkInfo - dispara uma vez se o dispositivo não retornou nenhum elemento <Sink>. Significa que SupportedMimeTypes permanece Nulo e IsCodecSupported degrada para "assumir que tudo funciona" - contexto útil quando erros posteriores de "dispositivo recusou stream" aparecem.

Porquê: antes de F44, estas falhas silenciosas de capacidade deixavam os utilizadores a adivinhar porque as suas faixas eram transcodificadas contra as expectativas ou recusadas pelo dispositivo. Agora, uma única pesquisa por Activate: mostra se a informação de capacidade do dispositivo era utilizável.


F45 - Melhor registo de erros de metadados

O quê: o registo 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 stack trace. F45 expande-o 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 problemática se encontra).

Porquê: quando algo corre mal a meio do DIDL, o valor do comprimento parcial diz-lhe se a falha ocorreu na primeira faixa do lote (parcial=0) ou a meio (parcial=N) - combinado com o startingIndex do lote, pode identificar o índice da faixa ofensora. Filter e BrowseFlag explicam que tipo de navegação o cliente queria; por 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 anunciava-se (SSDP) em cada adaptador IPv4 operacional. Numa máquina que também executa um túnel VPN (NordLynx) ou um comutador virtual (Hyper-V / WSL / Docker), a mesma biblioteca era anunciada também em cada um desses adaptadores, pelo que o ponto de controlo a partir do qual transmite descobria o server duas ou três vezes e listava a biblioteca como cópias duplicadas. O modo automático mantém agora apenas os adaptadores que têm um gateway IPv4 predefinido real (HasIPv4Gateway) - que os adaptadores de túnel e de comutador virtual não têm - pelo que esses são retirados da lista de anúncios. Um endereço fixado pelo utilizador continua a vencer sempre (anúncio apenas nessa interface), e se nenhum adaptador reportar um gateway, o seletor recorre a todos os adaptadores, pelo que a lista de endereços anunciados nunca fica vazia e o plugin não pode tornar-se invisível.

Porquê: o duplicado não é causado por "estar numa VPN" - é causado por anunciar no adaptador LAN e no adaptador de túnel/virtual ao mesmo tempo, pelo que um ponto de controlo vê o mesmo server em dois endereços. Uma VPN de consumo (NordVPN/NordLynx) só encaminha pelo túnel o tráfego destinado à internet; o renderer DLNA vive na LAN e o tráfego da sub-rede local contorna o túnel, pelo que o adaptador do túnel nunca alcança um renderer de qualquer forma - removê-lo elimina uma cópia fantasma, nunca um caminho funcional. O teste de gateway é o sinal barato e fiável que separa um adaptador LAN/Wi-Fi real de um túnel ou comutador virtual. Complementa o N05 (que corrigiu como os anúncios são enviados em tais ligações - multicast em vez de broadcast); o F46 governa em que adaptadores se anuncia, à partida.

Limitação conhecida: uma VPN mesh / de acesso remoto (Tailscale, ZeroTier, WireGuard para casa) cujos renderers vivem realmente do outro lado do túnel apresenta normalmente um adaptador sem gateway predefinido, pelo que o modo automático também o descarta. Esses utilizadores fixam antes o endereço VPN, que tem precedência sobre o filtro de gateway.