MusicBee UPnP Plugin Ajuda

Novidades

2.0.9 - 2026-08-23

O aviso de atualização abre suas páginas no seu idioma

O quê: os links Novidades e Baixar no aviso de atualização agora abrem as páginas do plugin no idioma do próprio MusicBee, em vez de em inglês.

Porquê: esses dois links restringiam o idioma a um de quatro — inglês, francês, espanhol ou alemão — antes de enviá-lo para o site, então todos os outros recebiam a página em inglês, mesmo quando existia uma tradução dela. Agora, eles passam o idioma do MusicBee inalterado e deixam o site decidir o que servir, o que o botão Ajuda ao lado deles sempre fez.

2.0.8 - 2026-08-22

O plugin agora se apresenta corretamente aos aplicativos e dispositivos que o descobrem em sua rede, e as configurações de Perfis de Dispositivo estão novamente alinhadas.

Seus dispositivos mostram o fabricante, modelo e versão corretos

O quê: quando um aplicativo de controle, um telefone ou uma TV encontra o MusicBee em sua rede, ele agora apresenta este plugin como feito por yaiol, aponta para o site do próprio plugin, o descreve como cobrindo todas as três funções — servidor, reprodutor e renderizador — e informa a versão que você realmente tem instalada.

Por quê: todo dispositivo UPnP anuncia quem o fez e o que ele é, e os aplicativos de controle mostram isso como a identidade do dispositivo. Este plugin ainda estava anunciando os detalhes do plugin original do qual foi bifurcado: o nome de outro autor, o site do MusicBee em vez do seu próprio, e um número de modelo congelado em "1.0" desde o primeiro lançamento. Do seu telefone, não havia como saber com qual plugin você estava falando, muito menos qual versão dele. Esses detalhes agora vêm do próprio plugin, então a versão mostrada ao lado do dispositivo permanece correta a cada atualização.

As configurações de Perfis de Dispositivo estão novamente alinhadas

O quê: na aba Perfis de Dispositivo, os rótulos e suas caixas compartilham uma borda esquerda e ficam com espaçamento uniforme, e o intervalo de taxa de amostragem é lido como uma única linha.

Por quê: os campos haviam se afastado à medida que as opções eram adicionadas à aba ao longo do tempo, e o rótulo "para" do intervalo de taxa de amostragem havia acabado ficando em cima da caixa ao lado dele — legível depois que você sabia o que dizia, intrigante na primeira vez que você olhava.

2.0.7 - 2026-08-08

Faixas maiores mantêm seu título e seu controle deslizante de posição

O quê: uma faixa maior enviada de um telefone — um arquivo FLAC longo, de alta resolução ou DSD — agora mostra seu título correto e pode ser movida, como uma pequena. Anteriormente, algumas delas eram reproduzidas pela rede, com um endereço da web no lugar do título e um controle deslizante que não fazia nada.

Por quê: o plugin espera por sua cópia local antes de iniciar, mas costumava decidir antecipadamente se valia a pena esperar por um arquivo, com base em seu tamanho. Isso era realmente um palpite sobre a velocidade da sua rede, que ele não tem como saber: uma faixa de 65 MB era julgada muito grande, então terminava de baixar um segundo depois — confortavelmente dentro do tempo de espera que ele já havia desistido. Agora ele simplesmente observa o download. Enquanto ainda está chegando, o plugin continua esperando, não importa quanto tempo leve; ele desiste apenas quando a transferência realmente trava, o que ele agora percebe mais rápido do que o antigo atraso fixo.

2.0.6 - 2026-08-03

Álbuns enviados de um telefone agora são reproduzidos sem interrupção entre as faixas, e o rádio da internet é reconhecido como rádio em vez de ser tratado como uma música excepcionalmente longa.

Álbuns são reproduzidos sem interrupções

O quê: quando você envia um álbum inteiro do seu telefone, o MusicBee agora passa de uma faixa para a próxima sem pausa — assim, gravações ao vivo, sets de DJ e obras clássicas contínuas permanecem conectadas.

Porquê: o padrão permite que um aplicativo controlador diga "aqui está o que vem a seguir", o que torna possível uma junção perfeita. Essa instrução não era aceita de forma alguma, então o aplicativo não tinha onde colocar a próxima faixa e a anunciava como se fosse a atual — a causa do problema do álbum corrigido na versão anterior. Agora é aceita corretamente: a próxima faixa é buscada enquanto a atual ainda está tocando, e o próprio player do MusicBee cruza o limite.

Rádio da internet é reconhecido como rádio

O quê: uma estação ao vivo enviada para o MusicBee é reproduzida como um stream e nunca baixada.

Porquê: baixar uma transmissão não faz sentido — não tem fim, e não há nada para pular — mas o plugin anteriormente não tinha como distinguir uma de um arquivo de música, então ele começava a buscar e parava assim que o download ultrapassava um tamanho fixo. O aplicativo controlador declara qual dos dois está enviando, e isso agora é lido diretamente. Sem configuração, sem adivinhação.

Faixas longas de alta resolução mantêm sua cópia

O quê: arquivos DSD e gravações longas de 24 bits agora podem ser movidos como qualquer outra faixa.

Porquê: eles eram pegos pelo limite de tamanho acima — um movimento de alta resolução de 20 minutos ou uma faixa DSD de 10 minutos excediam-no — então sua cópia era abandonada e o controle deslizante de posição parava de funcionar exatamente para o material com maior probabilidade de valer a pena ser escrutinado. Com o rádio identificado corretamente, nenhum limite de tamanho é necessário.

2.0.5 - 2026-08-03

Duas correções para controlar o MusicBee a partir de um telefone: o volume agora significa a mesma coisa em ambas as extremidades, e a transmissão de um álbum inteiro continua funcionando após a primeira faixa.

O volume do seu telefone corresponde ao volume do MusicBee

O quê: aumentar o volume ao máximo no seu telefone agora atinge o máximo no MusicBee, e a própria configuração do MusicBee é lida corretamente no telefone.

Por quê: o plugin nunca informou ao aplicativo controlador qual era seu volume mais alto, então cada aplicativo tinha que adivinhar. Um deles definiu 69, o que significava que seus 100% só atingiam 69% no MusicBee, enquanto os 100% do MusicBee voltavam como 144% no telefone — e os botões de volume do telefone nunca conseguiam atingir o máximo. O renderizador agora declara o intervalo claramente, então ambas as extremidades estão falando sobre a mesma escala.

A transmissão de um álbum mantém seus títulos e seu controle deslizante de posição

O quê: cada faixa de um álbum enviado de um telefone agora mostra seu título correto e pode ser movida, não apenas a primeira.

Por quê: um aplicativo controlador anuncia a próxima faixa uma fração de segundo após a atual, e esse anúncio estava cancelando a cópia que estava sendo buscada para a faixa prestes a tocar — então a maioria das faixas silenciosamente voltava a tocar pela rede, o que perde tanto o título quanto a capacidade de pular. Cópias para várias faixas agora são mantidas lado a lado, então um anúncio não pode mais cancelar a que está em uso.

2.0.4 - 2026-08-02

Músicas enviadas para o MusicBee de um telefone ou outro servidor agora se comportam como uma faixa real: você pode navegar por ela e ela mostra seu título correto imediatamente. Além de uma correção para streamers de alta fidelidade que se apresentam como um dispositivo combinado.

Navegue por uma faixa enviada de outro lugar

O quê: arrastar o controle deslizante de posição agora funciona para uma faixa enviada do seu telefone, um NAS ou outro servidor de mídia. Para tornar isso possível, o MusicBee baixa uma cópia da faixa para uma pasta temporária enquanto ela começa a tocar e reproduz essa cópia. Leva cerca de um segundo em uma rede doméstica, a cópia é excluída assim que você envia outra faixa, e quaisquer sobras são limpas na próxima vez que o MusicBee iniciar.

Por quê: o MusicBee pode iniciar e parar algo que está ouvindo pela rede, mas não pode navegar por ele — então o controle deslizante parecia pular e depois deslizava de volta para onde estava, sem explicação. Tocar um arquivo comum em seu próprio disco remove a limitação inteiramente, em vez de contorná-la.

Título e duração corretos desde a primeira nota

O quê: uma faixa enviada por um aplicativo que não dá aos seus arquivos uma extensão de arquivo comum agora mostra seu título e duração reais assim que começa, em vez de aparecer como um longo endereço da web.

Por quê: o MusicBee identifica uma faixa — e encontra suas tags — a partir da extensão do arquivo, e alguns players distribuem endereços sem extensão alguma. A cópia local sempre carrega a correta, então a faixa é reconhecida, independentemente do que o aplicativo remetente a chame.

Um salto que não pode ser feito agora é informado

O quê: se o renderizador realmente não puder se mover para o ponto solicitado, o aplicativo controlador é informado e relata isso.

Por quê: ele anteriormente respondia "feito" independentemente, então o controle deslizante deslizava de volta um segundo depois sem nada para explicar o porquê. Uma recusa honesta é mais fácil de agir do que uma silenciosa.

Streamers de alta fidelidade combinados são lidos corretamente

O quê: quando o MusicBee reproduz para um dispositivo que se apresenta como uma unidade combinada — um streamer Marantz ou Denon, onde o player fica dentro de um invólucro do fabricante junto com um servidor de mídia — o plugin agora lê os detalhes do próprio player em vez dos do servidor de mídia.

Por quê: ele estava perguntando à metade errada do dispositivo quais formatos de áudio ele poderia lidar, não obtendo nenhuma resposta utilizável e continuando sem nunca verificar — exatamente o hardware onde o tratamento de formato mais precisa estar correto. A descrição do modelo de um dispositivo também é agora levada em consideração ao combiná-lo com um perfil de dispositivo; ela estava sendo lida do lugar errado e descartada.

2.0.3 - 2026-08-02

O papel de "tocar para" amadurece: o MusicBee agora pode receber músicas que não estão em sua biblioteca — um arquivo no seu telefone, em um NAS, em outro servidor — em vez de apenas faixas que já possui.

Tocar músicas enviadas do seu telefone, não apenas da sua própria biblioteca

O quê: quando você usa um aplicativo de controle como Symfonium ou BubbleUPnP para enviar música para o MusicBee, a faixa não precisa mais vir da própria biblioteca do MusicBee. Um arquivo armazenado no próprio telefone, em um NAS ou em outro servidor de mídia agora é reproduzido. O título e a duração vêm do aplicativo que o enviou, então a faixa aparece corretamente, mesmo que o MusicBee nunca tenha visto o arquivo.

Por quê: a função de "tocar para" foi criada para o caso em que você navega pela biblioteca deste PC a partir do seu telefone e toca em uma música — a faixa já estava no PC, então o MusicBee simplesmente reproduzia seu próprio arquivo. Qualquer coisa que chegasse de outro lugar era silenciosamente descartada, o que tornava o recurso inútil para o caso igualmente natural de enviar música do telefone para os bons alto-falantes.

Uma faixa que não pode ser reproduzida informa isso

O quê: se o renderizador realmente não conseguir reproduzir o que foi enviado, ele agora reporta isso de volta ao aplicativo que o enviou.

Por quê: ele anteriormente respondia "entendido" a qualquer coisa, então o aplicativo de controle prosseguia e pressionava reproduzir. Com nada realmente carregado, o MusicBee reiniciava qualquer faixa que tivesse sobrado de antes — e se esse arquivo tivesse sumido, reclamava que sua fonte não podia ser encontrada. O erro nomeava uma faixa não relacionada e não apontava para o problema real.

Enviar uma nova faixa enquanto pausado agora reproduz essa faixa

O quê: se o MusicBee estiver pausado e seu aplicativo de controle enviar algo novo, a nova faixa começa.

Por quê: a retomada tinha prioridade sobre o carregamento, então a faixa pausada continuava de onde parou e a faixa que você tinha acabado de escolher era descartada sem uma palavra.

2.0.2 - 2026-08-01

O tema é a pesquisa: agora funciona por artista, retorna os resultados certos e é rápida em uma biblioteca grande. Além de uma nova maneira de direcionar a reprodução aleatória do seu aplicativo de controle e uma correção para três botões que não levavam a lugar nenhum.

A pesquisa por artista realmente funciona

O quê: pesquisar por um artista no seu aplicativo de controle agora retorna a música desse artista. A pesquisa corresponde tanto ao Artista da faixa quanto ao Artista do Álbum do álbum, então uma compilação é encontrada quer você digite o nome do intérprete ou o nome sob o qual o álbum está arquivado.

Por quê: uma pesquisa de artista era anteriormente confundida com "me dê tudo desse tipo" — o artista que você digitou era descartado e a biblioteca inteira retornava, então uma pesquisa que deveria ter correspondido a algumas centenas de faixas retornava dezenas de milhares. Corresponder apenas a um dos dois campos de artista teria silenciosamente perdido metade dos resultados, então ambos são verificados.

A página de resultados da pesquisa é exibida corretamente

O quê: rolar por uma longa lista de resultados de pesquisa agora a percorre. Cada página para a qual você rola é a página que você obtém.

Por quê: o servidor costumava responder a cada solicitação com o primeiro punhado de resultados, enquanto relatava a contagem total de correspondências, então um aplicativo que rolava para mais continuava recebendo os mesmos itens e nunca chegava ao fim.

A pesquisa é muito mais rápida em uma biblioteca grande

O quê: uma pesquisa agora executa uma única consulta para todo o conjunto de resultados e lê apenas as tags da página que você está visualizando.

Por quê: cada página anteriormente reexecutava a consulta contra a biblioteca e depois carregava as tags de cada correspondência — milhares delas — para mostrar uma dúzia. Em uma coleção grande, isso fazia com que cada rolagem pausasse. Os resultados também são descartados sempre que a biblioteca é atualizada, então uma edição nunca é servida desatualizada.

Direcione sua reprodução aleatória para um filtro

O quê: uma nova configuração Reproduções aleatórias de na guia Opções da Biblioteca. Deixe-a em Todas as Músicas e as pastas Faixas Aleatórias / Álbuns Aleatórios do seu aplicativo de controle se comportarão como antes; escolha um dos seus filtros do MusicBee e cada solicitação aleatória será extraída desse filtro. Filtros ocultos também são oferecidos.

Por quê: uma pasta de reprodução aleatória pede uma fatia de "tudo", e é a única solicitação que não carrega nenhuma pista sobre o que você quis dizer — então sempre extraía da biblioteca inteira, palavra falada e tudo mais. É aqui que você diz o que "tudo" significa. Abrir uma pasta de reprodução aleatória de dentro de um filtro no dispositivo ainda reproduz aleatoriamente esse filtro: uma escolha que você faz enquanto navega prevalece sobre a configuração.

Ajuda, GitHub e verificação de atualização alcançam páginas reais

O quê: os botões Ajuda e GitHub na caixa de diálogo de configurações, e a verificação automática de uma nova versão, agora abrem as páginas que nomeiam.

Por quê: todos os três foram construídos a partir de uma forma abreviada do nome do plugin que nunca existiu em nenhuma página, então cada um falhou silenciosamente — os botões pareciam não fazer nada e a verificação de atualização nunca relatou nada, por mais tempo que uma nova versão estivesse disponível.

2.0.1 - 2026-07-26

Uma rodada de correções de reprodução e navegação, focada em podcasts e nos aplicativos controladores (como BubbleUPnP) que gerenciam a reprodução e a ordem aleatória.

Podcasts começam a tocar sem pausa

O quê: um episódio de podcast baixado agora informa seu comprimento real e tamanho de arquivo antecipadamente, diretamente do arquivo no disco para os metadados da mídia.

Por quê: sem uma duração declarada, um controlador como o BubbleUPnP verifica novamente todo o fluxo de áudio toda vez que você pressiona reproduzir, apenas para descobrir quanto tempo dura o episódio — então a reprodução começava apenas após uma pausa perceptível. Com o comprimento agora anunciado, ela começa de forma limpa.

Podcasts aparecem quando você navega por artista

O quê: o nome do programa de um podcast agora é arquivado como seu artista — espelhado nos campos Artista e Artista do Álbum (e suas variantes de classificação), exatamente como já preenche o campo Álbum.

Por quê: um caminho de navegação que agrupa por um campo de artista costumava encontrar o artista de cada podcast em branco e sem saída em um nível vazio. Tratar o próprio programa como o artista — consistente com tratá-lo como o álbum — significa que esses caminhos agora alcançam os episódios em vez de nada.

"Reproduzidos Recentemente" e transmissão funcionam corretamente após uma reinicialização

O quê: reproduzir um podcast, audiolivro, caixa de entrada ou faixa de rádio diretamente — sem primeiro navegar até ele, como fazem a lista "Reproduzidos Recentemente" do BubbleUPnP e os destinos de transmissão — não falha mais. O plugin agora carrega a faixa sob demanda quando é solicitada por id.

Por quê: essas listas solicitam uma faixa logo após o MusicBee reiniciar, antes que qualquer coisa tenha sido navegada, então o plugin nunca tinha visto o id e respondia "Bad id". Ele agora força o carregamento das fontes relevantes nessa primeira solicitação direta e encontra a faixa.

"Faixas Aleatórias" e "Álbuns Aleatórios" retornam resultados

O quê: as pastas de reprodução aleatória "Faixas Aleatórias" e "Álbuns Aleatórios" do BubbleUPnP, que solicitam uma fatia aleatória de toda a biblioteca, agora voltam preenchidas.

Por quê: estas são pesquisas sem título para corresponder, e o plugin anteriormente as respondia de uma lista interna vazia, então elas sempre mostravam nada. Elas agora são servidas — corretamente paginadas — do mesmo caminho de consulta sob demanda que o resto da navegação usa.

Álbuns com uma tag em branco listam suas faixas

O quê: abrir um álbum que foi agrupado em um valor vazio — por exemplo, faixas da caixa de entrada que não possuem ano — agora mostra suas faixas.

Por quê: a correspondência que reúne as faixas de um álbum tratava "esta tag está vazia" como "nenhuma correspondência", então qualquer álbum formado a partir de um campo em branco não mostrava nada. Um valor de grupo vazio agora corresponde corretamente às faixas que o compartilham.


2.0.0 - 2026-07-22

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, em seguida, as correções e melhorias feitas no plugin original. Cada item mantém o formato O quê / Por quê do catálogo de recursos internos 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 nele mesmo. Este recurso adiciona a direção oposta - ele permite que o MusicBee seja o player. 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, em seguida, controlá-lo com a mão: reproduzir, pausar, parar, pular para frente ou para trás, 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 já existente em 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: ele 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. Os três papéis 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 os papéis 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, sem sentido, enviar o áudio para a rede e direto de volta para si mesmo.

Executando-o privadamente: os três papéis 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 mixado para baixo

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 mixava silenciosamente para baixo toda transcodificação não-PCM (FLAC, MP3, AAC, Ogg) para estéreo, independentemente da contagem de canais da fonte, anulando 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. A mixagem silenciosa para baixo tornava a opção de transcodificação FLAC inútil para audição surround. Com N02, ela 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 (LazyBrowseEnsureLazyEndpointInMemory → 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. Desvantagem: 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 com autocorreção

O quê: o servidor HTTP do plugin não morre mais quando sua porta configurada está indisponível. Três mudanças interligadas:

  1. Retorno 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 real ligada é registrada em um 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 roteador e os autofiltros SSDP/ponto de controle - agora lê boundServerPort em vez de Settings.ServerPort. Os clientes UPnP descobrem a porta real via SSDP, então uma porta movida é transparente para os renderizadores.
  2. Notificação ao usuário. Quando ocorre um fallback (a porta salva não é a que está em uso), uma MessageBox localizada (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.
  3. Recuperação de reinício. RestartServer (o caminho de reinício de salvamento de configurações) costumava desreferenciar Plugin.controller / Plugin.server cegamente. Se o Initialise inicial lançasse uma exceção antes de criá-los (exatamente o que uma ligação falha causava), o próximo salvamento de configurações resultaria em um NullReferenceException - deixando um plugin meio morto. Agora ele os recria e inicia quando Nothing, 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 está 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 foi 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 autocorrige - 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 493829779 (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 de ServerPort + o fallback de falha de análise de configurações.
  • Plugin.boundServerPort (novo campo compartilhado) mantém a porta de escuta ativa; activeServerPort permanece o snapshot configurado para que a lógica do selo "Reinicialização Necessária" não dispare falsamente em um fallback.
  • HttpServer.PortScanRange = 20; a varredura 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 broadcast IP. O erro inofensivo "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 ficava 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 guias 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", "Adicionados recentemente", "Clássica → Barroca") esperam encontrá-los ao navegar pelo plugin a partir de um cliente UPnP. O plugin original expunha apenas a árvore de 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 hi-fi 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 do contêiner do álbum (upnp:albumArtURI)

O quê: os nós do contêiner do álbum nas respostas de navegação DIDL 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 hi-fi moderno.


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

O quê: as faixas dentro de um álbum exposto por filtro agora são classificadas por Número do Disco e, em seguida, 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, além de 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 ilegais em XML

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 → truncamento de 0x99 em 0x19) fazia com que toda a resposta de Navegação 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 Navegação paginada

O quê: a Navegação para o contêiner de Rádio caía na ramificação genérica 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 cada 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 era reorganizada, 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 uma ramificação dedicada ContainerCategory.Radio em Browse, sem classificação por chamada; radioFiles é classificado 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 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 via 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 nenhuma capacidade 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 a ramificação 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 mapeamento de ramificação inicial de Browse 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 ContentDirectory UPnP - 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 de biblioteca e mudança de configurações (SetLibraryDirty / ResetCacheBumpSystemUpdateId).

Por quê: os clientes captam de forma confiável edições, novos arquivos e mudanças 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 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 o unescape+lowercase da camada HTTP corrompia a chave de rota do 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 anteriormente 404 agora são resolvidas).


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

O quê: qualquer campo pode ser marcado como hierárquico na guia 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 como / e um valor como Jazz/Cool Jazz então navega como Jazz › Cool Jazz em vez de uma única entrada plana. As faixas marcadas exatamente em uma ramificação (apenas Jazz) obtêm seu próprio nó [Jazz] para que nada seja ocultado, uma ramificação com um único filho se recolhe por conta própria, 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 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 a 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, quer uma entrada raiz esteja sozinha ou tenha sido 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 de 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 desordenada com entradas de nível superior quase idênticas. Mesclá-las 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 uma única pilha sem data porque o plugin nunca olhou 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 se resolve em 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 ao 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 contra o 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 simples, e o aliasing de campo de ano codificado foi removido, então cada campo de agrupamento agora é 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 - Campos de agrupamento "Ano" e "Ano (aaaa)" separados

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 álbum 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 pretendido.


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 agruparem em um único aglomerado no final da raiz. Cada atalho fixado fica com seu próprio tipo.

Por quê: à medida que um usuário fixa mais atalhos, um único aglomerado final de filtros e playlists misturados fica mais difícil de escanear e separa cada atalho da pasta a que pertence. Agrupar itens fixados sob sua própria categoria mantém a raiz legível e mantém cada atalho próximo às coisas a que pertence.

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

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

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 a caixa de 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 mb_Upnp.dll original.

Por quê: os usuários podem instalar o yaiol junto com o plugin original e comparar o comportamento lado a lado.


Sistema de selos - 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 selos 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 de WaitOnSendBarrier quando 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 snapshot 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 em Initialise.

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 selos capturam os casos em que o usuário precisa saber que algo aconteceu e os exibem na próxima vez que abrirem o plugin - detectáveis 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 outras 1000 linhas".

Convenções de implementação:

  • Os rótulos dos selos ficam no nível da caixa de 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 selo tem um sinalizador de sessão persistente correspondente em Plugin que muda para True quando a condição ocorre e é redefinido apenas na reinicialização do MusicBee.
  • Recursos: <Condition>Badge (texto do rótulo, prefixo com ⚠) + <Condition>BadgeTip (tooltip explicando a causa + solução).
  • Para selos "configuração salva precisa de reinicialização", tire um snapshot de tempo de execução em Plugin.Initialise() e compare com Settings.* após Settings.SaveSettings() no manipulador de Salvamento 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 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 enquanto são editados - não há um botão Salvar separado na guia Caminhos.)

Por quê: Cancelar deve significar cancelar. Anteriormente, um usuário que experimentava mudanças de caminho/modelo e desistia encontrava as mudanças 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 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 por partes enquanto as strings ainda estão em mudança 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 interface do próprio 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. Levar 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 principal e reprodução

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 estavam 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 de 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 ReplayGain aplicado. Apenas o arquivo bruto que o usuário escolheu, byte por 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 desejam 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 lhes forneça, 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 hi-fi que deseja streams 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 (CheckedChanged em cada um desinscreve o outro antes de alternar, para evitar um loop infinito).
  • Local de decisão: Settings.ForceTranscodingstreamingProfile.ForceTranscoding em WriteAudioFileDIDL.

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 produzem 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 usa isso. 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 streams, 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 streams 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 na ramificação orientada por linha de comando. O menu suspenso da UI obtém "FLAC" como uma 6ª opção. O mapeamento de carregamento/salvamento em SettingsDialog se estende para reconhecer FileCodec.FlacSelectedIndex = 5. Mime, tipo DLNA e recurso de codificação já estavam conectados em ItemManager.GetMimes / GetDlnaType / GetEncodeFeature de trabalhos anteriores (F21, F26).


Sem interrupções (SetNextAVTransportURI)

F10 - SetNextAVTransportURI / NextURI core

O quê: reprodução verdadeiramente sem interrupções. 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 uma lacuna 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 único fluxo longo 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. Desvantagem: 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 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 de 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 nextPlaySourceUrl armazena o URL da biblioteca do MusicBee do que está enfileirado (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 enfileirado → sem operação; enfileirado corresponde ao novo "próximo" → sem operação; enfileirado difere → chamar QueueNext com o novo URL (ou QueueNext("") para limpar - o que respeita F08 DoNotClearNextUri).
  • Conectado em Plugin.ReceiveNotification sob NotificationType.NowPlayingListChanged.

F13 - Recuo 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.QueueNext ele mesmo. Nenhuma lógica de plugin especial é necessária - o dispositivo faz a transição para ele e o detector 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 faz a transição para ele (novo identificador de fluxo, mesma fonte). O detector 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.

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 interface 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 estado de detecção de transição de faixa

O quê: quando o dispositivo transita internamente 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 fora de sincronia.

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 interface 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 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 pop na transição sem interrupções

O quê: o pop acontece 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 pops possam correlacionar.

O diagnóstico também aponta para a mitigação: marque ForceTranscoding no perfil do dispositivo. Isso homogeneíza 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 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 enfileirada e registra 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 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 ao 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 busca dentro da faixa, e ainda respeita o relatório do dispositivo para o caso atípico de ajuste ao keyframe.


F18 - Intertravamento de fluxo contínuo / NextURI

O quê: dois intertravamentos agora em vigor:

  1. Tempo de execução: QueueNext retorna False antecipadamente quando Settings.ContinuousOutput está ativado. O fluxo contínuo é seu próprio mecanismo sem interrupções (um longo fluxo concatenado); enviar SetNextAVTransportURI em cima dele confunde o dispositivo sobre se cada faixa é um URI discreto ou parte do fluxo contínuo.
  2. UI: quando o usuário marca a caixa de seleção global de fluxo contínuo, o forceNativeStream do 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 os engole silenciosamente - registrados, mas não propagados como erros.

Por quê: a condição "sem 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 impróprio 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 mime audio/opus ao servir faixas Opus.

Por quê: Opus é agora comum (codec de compromisso moderno para fala/música). Sem F22, o plugin se recusaria a transmitir arquivos Opus mesmo para dispositivos que os manipulam.


F23 - Suporte a arquivos 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 de formato de tempo de 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 conforme 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ê: os fluxos MP3 transcodificados agora anunciam DLNA.ORG_OP=11 (busca por byte e por tempo) em vez de DLNA.ORG_OP=10 (somente 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 obtém 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 300 mil é o 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" (o MusicBee relata "MP3 Stream", "Internet Stream", etc. para rádio) como contínuo, independentemente do botão global Settings.ContinuousOutput. A ramificação DIDL de fluxo contínuo (Título: "Continuous Stream", id="continuousstream", saída PCM/Wave fixa) é usada; o dispositivo vê um único fluxo de estilo infinito.

Por quê: streams de rádio não têm limites de faixa, comprimento fixo, busca. Tratá-los como arquivos discretos no DIDL fazia com que o plugin anunciasse intervalos de bytes e durações que não existem. A troca automática quando o MusicBee já nos disse "isso é um Stream" remove uma armadilha 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 (navegação do cliente UPnP) permanece inalterado - URLs de rádio são raros 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 anuncia certos codecs (ou o plugin não consegue 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 "tentar e adivinhar" por uma recusa total.


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

O quê: a posição entre as pesquisas já era extrapolada por relógio de parede a partir de uma única âncora (currentPlayStartTicks), então a barra de progresso atualiza 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 notou 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 e, em seguida, 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 UPnP de 1 segundo - 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 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 de PlayPositionMs do MusicBee pós-Play retorna 0 de forma limpa, então o GetPlayPositionInformation do F33 a 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 F04, duas lacunas específicas foram seladas:

  1. 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.
  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.

Por quê: "forçar" deve significar forçar. Se o usuário ativou explicitamente ForceTranscoding para um dispositivo, o plugin nunca deve cair silenciosamente para o streaming nativo, independentemente de como outras sinalizações 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, 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 "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 acionando falsa transição

O quê: buscar dentro de uma faixa longa pode produzir um breve ciclo Parado→Reproduzindo 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 buscar. F37 marca lastUserInitiatedSeek em Seek() e adiciona uma guarda de 5 segundos no manipulador Parado (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 buscar 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 - Melhoria no tratamento de busca para codecs propensos a travamentos

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 de busca de MP3 do BubbleUPnP foi relatado por volta de 2024 e o aplicativo teve cerca de 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 por já não ser uma falha aqui.


F40 - Conexões máximas maiores + 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 selo 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 selo 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) em MusicBeeUpnp.vb; ambos os locais de chamada (MediaServerDevice.GetFile, Encoder.StartEncode) o usam.
  • Settings.MaxConnections persistido na v8 do esquema de configurações.
  • Plugin.MaxConnectionsHit é um sinalizador de sessão persistente definido dentro de WaitOnSendBarrier; reinicia apenas na reinicialização do MusicBee.
  • SettingsDialog.maxConnectionsBadge é um rótulo em negrito vermelho em (16, 410) que só aparece quando Plugin.MaxConnectionsHit é True. Tem uma dica de ferramenta explicando a causa e a solução.
  • 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 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.

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 reproduzida para o 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 estavam 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 mostra DeviceLacksCodec(Flac), eles sabem imediatamente que as informações de protocolo do dispositivo estavam incompletas e podem querer que o fallback do F32 entre em ação.

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 na produção.


F43 - Log SetNextAVTransport mostra URL de origem

O quê: as entradas de log QueueNext agora incluem source=<caminho da biblioteca do 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 legível por humanos 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 resposta GetProtocolInfo do dispositivo, nomeando qual entrada não pôde ser analisada (para que o usuário possa ver, por exemplo, "o Marantz retornou http-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 que SupportedMimeTypes permanece Nothing e IsCodecSupported degrada 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 busca 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ção 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 - Modo automático anuncia apenas em adaptadores de rede reais

O quê: no modo de interface Automático, o plugin costumava se anunciar (SSDP) em todos os adaptadores IPv4 operacionais. 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 em cada um desses adaptadores também, então o ponto de controle de onde você transmitia descobria o servidor duas ou três vezes e listava a biblioteca como cópias duplicadas. O modo automático agora mantém apenas adaptadores que têm um gateway IPv4 padrão real (HasIPv4Gateway) - o que os adaptadores de túnel e switch virtual não têm - então eles são removidos da lista de anúncios. Um endereço fixado pelo usuário ainda prevalece (anuncia apenas nessa interface), e se nenhum adaptador relatar um gateway, o seletor volta para todos os adaptadores, então a lista de endereços anunciados nunca está vazia e o plugin não pode se tornar invisível.

Por quê: a duplicação 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 servidor em dois endereços. Uma VPN de consumidor (NordVPN/NordLynx) tunela apenas o tráfego de internet; o renderizador DLNA vive na LAN e o tráfego da sub-rede local ignora o túnel, então o adaptador de túnel nunca alcança um renderizador de qualquer forma - removê-lo remove uma cópia fantasma, nunca um caminho de trabalho. 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 N05 (que corrigiu como os anúncios são enviados nesses links - multicast em vez de broadcast); F46 governa quais adaptadores são anunciados.

Limite conhecido: uma VPN de malha / acesso remoto (Tailscale, ZeroTier, WireGuard-to-home) cujos renderizadores realmente vivem através 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, que tem precedência sobre o filtro de gateway.

Conteúdo