MusicBee UPnP Plugin Hjälp

Nyheter

2.0.2 - 2026-07-20

  • En mall kan inte längre raderas medan någon nod följer den – raderingsknappen förblir helt enkelt inaktiverad, så en nod kan aldrig lämnas föräldralös. Varje mallrad visar nu en live (n) räkning av dess följare, vilket förklarar en inaktiverad radering vid en snabb blick; att radera en oanvänd mall kvarstår nu också, istället för att de levererade standardinställningarna tyst återkommer vid nästa laddning.
  • En ny tratt-knapp bredvid vyn-trädet visar exakt vilka noder som följer en mall: den filtrerar trädet till bara följarna av den valda mallen, omfiltrerar när du väljer andra mallar och återställer hela trädet när den stängs av.
  • Noderna Radio och Podcasts är nu permanent kopplade till sin kategoris mall – omforma mallen på fliken Sökvägar och noden följer av sig själv; det finns inget att tillämpa, så knappen Tillämpa är inaktiverad för dem. Mallistan återspeglar detta med två band, Standard (där alla nya mallar skapas) och Reserverad (Radio + Podcasts).

2.0.1 - 2026-07-20

  • Sökvägsmallar är nu direktlänkade till de noder som använder dem. När du tillämpar en mall får noden den att följa den: redigera mallen senare och varje nod som följer den omformas omedelbart – du behöver inte leta upp den och tillämpa den nod för nod. Vyträdet visar vilken mall varje nod följer direkt efter dess namn, en namnändring visas där omedelbart, och när du tar bort en mall får du först veta hur många noder som följer den (de behåller sin nuvarande layout och slutar helt enkelt att följa något).
  • Att tillämpa en mall på en dold nod gör den också synlig igen – att tillämpa är gesten "visa mig detta, format som det", medan dölja stannar kvar med kryssrutan Synlig. Den reserverade "Dold"-mallen som detta ersätter är borta.
  • Vyträdet tappar inte längre din plats: bockar, expanderade mappar och rullningsposition överlever alla tillämpning av mallar och andra uppdateringar.

2.0.0 - 2026-06-16

Detta är den första offentliga utgåvan av den öppen källkodsbaserade yaiol-förgreningen av MusicBee UPnP-pluginet. Den presenteras i två delar: allt som är nytt i denna förgrening, sedan de fixar och förbättringar som gjorts i det ursprungliga pluginet. Varje post behåller Vad / Varför-formen från projektets interna funktionskatalog så att resonemanget bakom varje ändring finns på sidan, inte bara ändringen.

Nytt i denna förgrening

MediaRenderer - spela till MusicBee

N01 - MusicBee som en spela-till-renderare

Vad: normalt fungerar detta plugin åt ett håll: en telefon eller annan enhet bläddrar i MusicBees bibliotek och spelar musiken på sig själv. Denna funktion lägger till den motsatta riktningen - den låter MusicBee vara spelaren. Från en kontrollapp på din telefon (som BubbleUPnP) kan du välja din stationära MusicBee som den enhet som spelar, och sedan styra den från din hand: spela, pausa, stoppa, hoppa framåt eller bakåt, hoppa till en punkt i spåret och ändra volymen eller stänga av ljudet.

Varför: det förvandlar din telefon till en fjärrkontroll för musiken som redan finns på din PC. Sitt i soffan, bläddra i ditt bibliotek på telefonen, tryck på ett spår, och det kommer ut ur högtalarna som är anslutna till din stationära dator - med full kontroll från där du sitter. Det ursprungliga pluginet levererade aldrig detta som en fungerande funktion.

Aktivera det: det är avstängt som standard, eftersom att slå på det låter vad som helst på ditt hemnätverk starta uppspelning på din PC. Du aktiverar det med en kryssruta på inställningsdialogens flik "Allmänt". Pluginets tre roller har var sin kryssruta där - dela mitt bibliotek (Server), låt andra spela till mig (Renderare) och spela till andra enheter (Kontrollpunkt) - och dialogen visar bara de inställningsflikar som de roller du aktiverat faktiskt behöver, så du möts aldrig av alternativ som inte gäller dig.

Skilja dina maskiner åt: du kan ge renderaren vilket namn du vill (det börjar som "MusicBee (yaiol)"). Det namnet är vad som visas i din telefons lista över spela-till-mål, så när mer än en PC kör MusicBee kan du se vilken som är vilken. En namnändring träder i kraft omedelbart, utan omstart.

Bästa möjliga ljud när den spelar till sig själv: när du bläddrar i MusicBees eget bibliotek från din telefon och skickar ett spår tillbaka till samma MusicBee, känner pluginet igen att den ombeds spela en av sina egna filer och spelar den helt enkelt direkt från din disk. Resultatet är exakt och omedelbart - bit-perfekt, med MusicBees egen equalizer och volymutjämning tillämpad - istället för att meningslöst skicka ut ljudet på nätverket och rakt tillbaka till sig själv.

Köra det privat: de tre rollerna fungerar oberoende, så du kan slå på renderaren samtidigt som du lämnar biblioteksdelning avstängd. I den "endast renderare"-inställningen förblir ditt bibliotek helt dolt från nätverket - endast spela-till-målet annonseras - och MusicBee kommer aldrig att erbjuda att spela till sig själv.


Uppspelningsbeteende

N02 - 5.1 FLAC inte auto-nedmixad

Vad: kanalsräkningsbegränsningen i MediaServerDevice.GetEncodedFile var If StereoOnly OrElse Not isPcmData Then channelCount = 2. Klausulen Not isPcmData nedmixade tyst varje icke-PCM-transkodning (FLAC, MP3, AAC, Ogg) till stereo oavsett källans kanalsräkning, vilket gjorde 5.1-kompatibla renderare oanvändbara när källan var 5.1 FLAC. Nu exkluderar den andra klausulen FLAC: Not isPcmData AndAlso encoder.Codec <> FileCodec.Flac. FLAC 5.1 passerar igenom; MP3/AAC/Ogg tvingas fortfarande till stereo eftersom MusicBees kommandorads-encoders för dessa format förväntar sig 2-kanalsingång.

Varför: hela poängen med att transkoda en 5.1 FLAC-källa till FLAC-utgång är att bevara flerkanalsmixen. Tyst nedmixning gjorde FLAC-transkodningsalternativet värdelöst för surroundlyssning. Med N02 gör det rätt.


Arkitektur

Den strukturella förändringen som gör förgreningen livskraftig på ett stort bibliotek - frånvarande i det ursprungliga pluginet.

N03 - Lat (on-demand) bläddringsträd

Vad: det ursprungliga pluginet byggde hela bläddringsträdet vid MusicBees start - räkna upp varje spår, full Library_GetFileTags per fil, montera hela containerhierarkin - innan HTTP-porten öppnades. På ett verkligt bibliotek (50k+ spår, 5400 poddavsnitt, hundratals stationer) är det minuter av kallstart, och trädet stannar i RAM för alltid inklusive grenar som ingen klient någonsin öppnar. Denna förgrening bygger ingenting i förväg: roten exponerar en L:-prefixad platshållare per slutpunkt (L:music, L:podcast, L:filter:…); varje nivå beräknas endast när en klient bläddrar in i den (LazyBrowseEnsureLazyEndpointInMemory → cachar per nivå), och biblioteksändringsmeddelanden rensar cacharna (SetLibraryDirty).

Varför: kallstart är i princip omedelbar - HTTP-porten är öppen när MusicBee avslutar plugin-init - och minnet förblir proportionellt mot vad som har bläddrats, inte mot biblioteksstorleken. Kompromiss: den första bläddringen in i en slutpunkt betalar dess laddningskostnad; återinträde cachas tills nästa biblioteksändring. Detta är grunden som allt annat beror på. Fullständiga anteckningar: FIXES.md.


Nätverk och robusthet

Förstärkning av HTTP-serverns bindningsväg. Det ursprungliga pluginet dör tyst när dess port är otillgänglig.

N04 - Självläkande HTTP-portbindning

Vad: pluginets HTTP-server dör inte längre när dess konfigurerade port är otillgänglig. Tre länkade ändringar:

  1. Automatisk återgång vid bindningsfel. HttpServer.Start försöker den konfigurerade porten, och vid SocketException skannar den uppåt upp till 20 portar efter den första lediga. Den faktiska bundna porten registreras i en ny Plugin.boundServerPort, och allt som annonserar servern - SSDP LOCATION-URL:er (NOTIFY + M-SEARCH-svar), enhets-URL:en (PrimaryHostUrl), routerns portvidarebefordran och SSDP/kontrollpunkts-självfiltren - läser nu boundServerPort istället för Settings.ServerPort. UPnP-klienter upptäcker den verkliga porten via SSDP, så en flyttad port är transparent för renderare.
  2. Användarmeddelande. När en återgång sker (den sparade porten är inte den som används), berättar en lokaliserad MessageBox (WarnPortInUse) för användaren vilken port som faktiskt tjänar och att enheter fortfarande kommer att hitta den - eftersom pluginet körs huvudlöst och ett meddelande i dialogen endast skulle ses av någon som redan misstänkte ett problem.
  3. Omstartsåterställning. RestartServer (omstartsbanan för inställningssparande) brukade avreferera Plugin.controller / Plugin.server blint. Om den initiala Initialise kastade ett undantag innan de skapades (precis vad ett misslyckat bindningsförsök orsakade), träffade nästa inställningssparande en NullReferenceException - vilket lämnade ett halvdött plugin. Den återskapar och startar dem nu när Nothing, så att spara en fungerande port återupplivar pluginet utan en fullständig MusicBee-omstart.

Varför: utlösaren var en verklig användarincident. Den gamla standardporten 49382 ligger i Windows dynamiska intervall (49152-65535), där Hyper-V/WSL2/Docker/WinNAT reserverar stora block som flyttas vid varje uppstart - så bindningen misslyckades med WSAEACCES ("åtkomst nekad") på en maskin där den hade fungerat i månader. Att ändra standardporten till en ledig port kolliderade sedan med Serviio (en separat DLNA-server som redan fanns på den nya porten), vilket misslyckades med WSAEADDRINUSE. Varje fel svaldes i Initialise, vilket lämnade pluginet tyst dött och sedan NRE:ade vid nästa inställningssparande. Efter N04 självläker en portkollision - servern fortsätter att köras på nästa lediga port, användaren informeras, och klienter återupptäcker den - istället för att stänga ner hela pluginet.

Implementering:

  • Standardporten flyttades 493829779 (under det dynamiska intervallet, så Windows reserverar den aldrig automatiskt; inte en känd mediaserverstandard) i alla tre ServerPort-deklarationer + återgången vid inställningsparsfel.
  • Plugin.boundServerPort (nytt delat fält) håller den aktiva lyssningsporten; activeServerPort förblir den konfigurerade ögonblicksbilden så att logiken för "Omstart krävs"-märket inte falskt utlöses vid en återgång.
  • HttpServer.PortScanRange = 20; skanningen stoppas vid den första framgångsrika TcpListener.Start() och kastar det sista undantaget endast om alla försök misslyckas.
  • Ny EN-resursnyckel WarnPortInUse (översättningar följer den lokala passningen vid publicering).

N05 - SSDP-annonseringar över multicast-gruppen (VPN / punkt-till-punkt)

Vad: SSDP-annonseringar skickas till UPnP multicast-gruppen (239.255.255.250) istället för en IP-broadcastadress. Det ofarliga felet "kan inte komma åt ett borttaget objekt" som loggas när ett SSDP-sökresultat tävlar med en serveromstart undertrycks också.

Varför: på punkt-till-punkt / VPN-nätverksadaptrar gäller inte IP-broadcast - den gamla broadcast-sändningen misslyckades med "ogiltigt argument" och annonseringar missades, så pluginet var osynligt för klienter på dessa länkar. Att annonsera till den korrekta multicast-gruppen fixar upptäckten på just dessa adaptrar.

Biblioteksnavigering

Dessa levererades i denna förgrening och finns inte i det ursprungliga pluginet. De kom från att faktiskt bläddra i pluginets egen utdata från verkliga UPnP-klienter.

N06 - Filterbaserad biblioteksexponering

Vad: MusicBees filterflikar (.xautopf-filer i användarens MusicBee-mapp) blir UPnP-rotcontainrar i pluginets bibliotek. Varje filters spår kan sedan bläddras i en hierarki av AlbumArtistSort → Album → Tracks.

Varför: användare med kurerade MusicBee-filter (t.ex. "5-stjärniga spår", "Nyligen tillagda", "Klassisk → Barock") förväntar sig att hitta dem när de bläddrar i pluginet från en UPnP-klient. Originalpluginet exponerade endast det råa biblioteksträdet.


N07 - SortAlbumArtist-fältkoppling

Vad: pluginet läser nu MusicBees MetaDataType 165 (Sort Album Artist) och använder det för att gruppera/sortera artister i bläddringsvyer.

Varför: hi-fi-webbläsare och audiofiler använder sorteringsartistnamn ("Beethoven, Ludwig van" istället för "Ludwig van Beethoven") för att organisera bibliotek. Standardförväntning för seriösa lyssnare. Saknas i båda uppströms.


N08 - Hantering av flera värden för AlbumArtist

Vad: när ett albums AlbumArtist-fält innehåller flera artister separerade med "; " (t.ex. "yaiol; Ars Ricercata"), visas spåret nu under varje artist i bläddringsvyer, inte under en enda Frankenstein-artist som kombinerar namnen.

Varför: samarbeten och samlingsalbum behöver visas under varje samarbetspartner. Utan detta är hälften av sökvägarna för att hitta albumet trasiga.


N09 - Album-container konstverk (upnp:albumArtURI)

Vad: albumcontainernoder i DIDL Browse-svar inkluderar nu ett upnp:albumArtURI-element som pekar på albumomslaget.

Varför: utan detta visar varje album i en UPnP-klients bläddringsvy en generisk ikon istället för albumomslaget. Visuell ledtråd för navigering; förväntas av varje modern hi-fi-webbläsare.


N10 - Spårordning inom filteralbum

Vad: spår inom ett filterexponerat album sorteras nu efter skivnummer, sedan spårnummer.

Varför: standardalbumordning. Utan explicit sortering kom spåren tillbaka i den ordning som filtret råkade returnera dem - oftast slumpmässigt.


N11 - Spellista mappträd fix

Vad: funktionen LoadLibraryPlaylists (ursprungligen av Steven Mayall, ~2014) misslyckades med att gå ner i nyskapade spellistmappar. Den första spellistan i varje mapp, plus eventuella undermappar, hamnade föräldralösa på rotnivån.

Varför: fanns i det ursprungliga pluginet i elva år. Synlig inom 30 sekunder efter att ha öppnat BubbleUPnP och klickat på Spellistor. Fixat i yaiol genom att korrekt rekursivt gå in i nyskapade mappar under trädkonstruktionen.


N12 - Sanering av XML-olagliga kontrolltecken

Vad: varje spår med en tagg som innehåller ett C0-kontrolltecken (t.ex. 0x19 från en dålig kodningspass - UTF-8 → Latin-1 → tillbaka trunkerande 0x99 till 0x19) orsakade att hela Browse-svaret misslyckades med Action Failed när det dåliga spåret kom in i en paginerad batch.

Varför: XML 1.0 förbjuder de flesta C0-kontrolltecken, och XmlWriter kastar ett undantag när den ombeds skriva något. Fanns i det ursprungliga pluginet. Fixat genom att strippa ogiltiga tecken vid varje Library_GetFileTags-utgångspunkt via XmlConvert.IsXmlChar.


N13 - Radiolista deterministisk över paginerad bläddring

Vad: Bläddring för radiocontainern hamnade i den generiska fillistgrenen, som anropade files.Sort(AlbumFileComparer) vid varje anrop. Radioinlägg har tomma Album/Disc/Track-taggar, så varje jämförelse returnerade 0 - List(Of T).Sort är instabil och producerar en annan ordning vid varje anrop. UPnP-kontrollpunkter paginerar (BubbleUPnP hämtar 0..15 sedan 16..slut); mellan de två anropen omorganiserades listan, så vissa stationer dök upp på båda sidorna (dubletter) och vissa på ingen (saknade) - vilket såg slumpmässigt ut vid varje uppdatering.

Varför: fanns i det ursprungliga pluginet (dess författare bläddrar aldrig radio via UPnP). Fixat här med en dedikerad ContainerCategory.Radio-gren i Browse, ingen sortering per anrop; radioFiles sorteras en gång vid laddningstid efter titel (stabil). Paginerad bläddring ser nu en deterministisk ordning; sida 1 och sida 2 är åtskilda.


N14 - UPnP Sök albumklass returnerar albumcontainrar

Vad: UPnP Sök efter albumklassfrågor (upnp:class = "object.container.album.musicAlbum", t.ex. BubbleUPnPs "Slumpmässiga album") returnerade hela spårlistan istället för albumcontainrar, så klienten visade noll album. Den ursprungliga hanteraren analyserade endast parentesiserade kriterier, och dumpade sedan alla spår oavsett den begärda klassen.

Varför: fixat här - albumklassfrågor räknar nu upp distinkta album (grupperade efter AlbumArtist+Album) och avger varje som en korrekt musicAlbum-container med omslagsbilder, adresserbar via det virtuella ID-utrymmet Salb<idx> så att klienten kan borra ner i ett resultat och spela det.


N15 - Fungerande, omfångsmedveten UPnP-sökning med klick-genomgång

Vad: originalet annonserade inga sökfunktioner (GetSearchCapabilities returnerade tomt), så klienter vägrade ens att skicka en sökning; och den gamla backend läste från musicFiles, permanent tom i den lata träd-eran. Denna förgrening annonserar de verkliga sökbara egenskaperna, implementerar spår-efter-titel och album-efter-titel mot det lata biblioteket (HandleLazySearch), begränsar frågan till klientens aktuella gren när ett verkligt container-ID skickas (annars ersätter L:music så att sökningar i toppfältet inte drar in podcast/radio/ljudboksbrus), och gör albumresultat klickbara via syntetiska Ssrch_alb_*-ID:n som en Browse-tidig-gren mappar tillbaka till albumets spår. (Albumklass-resultat-som-containrar-delen är N14.)

Varför: sökning i BubbleUPnP gick från "Biblioteket stöder inte sökning" till att returnera användbara, omfångsmedvetna, spelbara resultat. Fullständig design + avvisade metoder: SEARCH.md.


N16 - UPnP-cacheinvalidering (SystemUpdateID)

Vad: originalet returnerade ett konstant SystemUpdateID=0 - UPnP ContentDirectory cache-invalideringskontraktet - så spec-kompatibla klienter (BubbleUPnP) behandlade biblioteket som aldrig föränderligt: inaktuella bläddringsresultat, 404-miniatyrer efter en URL-schemaändring, och "starta om MusicBee två gånger för att se ändringar"-dansen. Denna förgrening sätter SystemUpdateID från epok-sekunder vid laddning (så varje omstart är strikt före den senaste) och höjer den vid varje biblioteksändring och inställningsändring (SetLibraryDirty / ResetCacheBumpSystemUpdateId).

Varför: klienter plockar pålitligt upp redigeringar, nya filer och inställningsändringar vid nästa bläddring. Känd begränsning: prenumererade klienter får inte aktivt den nya värdet via GENA (parkerat som framtida arbete); de ser det fortfarande vid nästa bläddring.


N17 - Podcastprenumerationskonstverk

Vad: podcastrutor visade inga bilder - varje /PodcastThumbnail/-förfrågan 404:ade. Två staplade buggar: upplösningskedjan kontrollerade aldrig MusicBees faktiska konstverkscache (%LocalAppData%\MusicBee\InternalCache\Subscriptions\<name>.jpg, där skrivbordsgränssnittet laddar från), och HTTP-lagrets unescape+lowercase förstörde feed-URL-nyckeln ner till dess sista sökvägssegment. Denna förgrening löser konstverk från MB:s InternalCache och dirigerar uppslagningar via en URL-säker slug som överlever HTTP-lagret intakt (PodcastSlug / podcastSubIdBySlug).

Varför: prenumerationskonstverk renderas nu i bläddringsvyer (alla 22 tidigare 404-förfrågningar löses).


N18 - Hierarkisk (avgränsad) taggbläddring

Vad: vilket fält som helst kan markeras som hierarkiskt på fliken Biblioteksinställningar och ges en en-teckens avgränsare (en fältväljare + avgränsarruta med lägg till/ta bort, sparas i plugininställningarna). Ställ in Gruppering till / och ett värde som Jazz/Cool Jazz bläddrar sedan som Jazz › Cool Jazz istället för en platt post. Spår taggade exakt vid en gren (bara Jazz) får sin egen [Jazz]-nod så att inget döljs, en gren med ett enda barn kollapsar av sig själv, och ; nekas som avgränsare eftersom det är MusicBees egen flervärdesavgränsare.

Varför: djupa tagg-taxonomier som en användare redan har kodat i ett enda fält (genreträd, stämninghierarkier, "Klassisk/Barock/Konsert") bläddras äntligen som det träd taggen beskriver, istället för en platt vägg av snedstrecksseparerade strängar som användaren måste läsa från början till slut.


N19 - Enkel rotväg märkt med sitt grupperingsfält

Vad: en enda bläddringsväg vid roten märks med sitt grupperingsfält (t.ex. "Genre") snarare än dess fullständiga korta väg, vilket matchar hur sammanslagna första fältgrupper namnges.

Varför: bläddringsträdet läses konsekvent - en namngivningsregel oavsett om en rotpost står ensam eller slogs samman med syskon (N20) - istället för att en ensam rotpost visar en utförlig intern väg medan dess sammanslagna grannar visar ett rent fältnamn.


N20 - Slå samman bläddringsvägar som delar ett första fält

Vad: två bläddringsvägar som delar samma första fält - "Genre / Sortera Albumartist" och "Genre / Podcastpersoner" - kollapsar till en enda Genre-rotmapp som först listar genrevärdena och sedan delar upp sig i de två vyerna, istället för två nästan identiska "Genre / …"-poster sida vid sida vid roten.

Varför: en användare med flera relaterade vyer kapslade under ett gemensamt fält såg roten belamrad med nästan identiska toppnivåposter. Att slå samman dem håller roten läsbar och grupperar de relaterade vyerna där de hör hemma - under deras delade fält.


N21 - Kategoritypade bläddringsvägar (Standard / Radio / Podcast)

Vad: varje bläddringsväg är typad efter kategori - Standard, Radio eller Podcast. Mallistan är grupperad i dessa tre sektioner, varje malls fältväljare erbjuder endast de fält som den kategorins data faktiskt kan leverera, och en mall kan endast tillämpas på matchande noder i vyträdet (inkompatibla noder blir gråa och kan inte markeras). De reserverade mallarna för Radio och Podcasts kan inte raderas, så deras kategorisektion försvinner aldrig.

Varför: utan typningen kunde en användare bygga en layout som tyst kom upp tom - en radiostation har inget "album", ett poddavsnitt har ingen "albumartist" - och bara upptäcka det genom att bläddra till en död mapp från en UPnP-klient. Att begränsa fältmenyn och tillämpningsmålen till kategorins verkliga data gör tomma layouter omöjliga att bygga.


N22 - Gruppera podcaster efter publiceringsår

Vad: varje poddavsnitts publiceringsdatum läses in, så en poddbbläddringsväg med en årsnivå grupperar avsnitt efter år istället för att kollapsa dem under en enda "Okänd".

Varför: stora podcastprenumerationer blir navigerbara efter år som resten av biblioteket, istället för att varje avsnitt hamnar i en odaterad hög eftersom pluginet aldrig tittade på publiceringsdatumet per avsnitt.


N23 - Kollapsa grupperingsnivåer med ett enda resultat

Vad: en grupperingsnivå som resulterar i ett enda värde - en posttypsnivå som endast visar "LP" för en artist som bara gjorde LP-skivor, eller en bokstavsnivå med en enda bokstav - hoppas över automatiskt, vilket leder användaren direkt till dess innehåll.

Varför: att bläddra igenom en mapp som innehåller exakt en mapp är ren friktion. Att kollapsa nivån med ett enda val tar bort det döda klicket utan att ändra vad användaren kan nå.


N24 - Årsgruppering/sökning mot MusicBees datumfält

Vad: årsvillkoret frågar inte längre MusicBees fullständiga datumfält "År" med ett rent fyrsiffrigt värde, och den hårdkodade år-fältaliasingen är borta så att varje grupp-efter-fält nu löses generiskt från sökvägsdefinitionen.

Varför: för bibliotek vars årstagg innehåller ett komplett datum, returnerade gruppering eller sökning efter år tidigare ingenting - den fyrsiffriga frågan matchade aldrig det fullständiga datumfältet. Att fråga rätt fält gör att årsgruppering och sökning hittar spåren igen.


N25 - Separata "År" och "År (åååå)" grupp-efter-fält

Vad: albumgruppering och bläddringsvägar exponerar nu båda MusicBees egna årsfält - År (hela datumtaggen) och År (åååå) (bara det fyrsiffriga året) - så att användaren kan välja antingen när de definierar en albumgruppering eller en bläddringsväg.

Varför: de två fälten betyder olika saker i MusicBee, och att slå samman dem förlorade den distinktionen. Att visa båda låter en användare samla varje utgåva från ett år tillsammans (åååå) eller behålla exakt datumordning (fullständig årstagg), som de avser.


N32 - Fästa filter och spellistor grupperade efter slag i roten

Vad: ett fäst filter visas nu direkt under mappen Filter i bläddringsroten, och en fäst spellista direkt under mappen Spellistor, istället för att alla fästa objekt samlas i en klump i slutet av roten. Varje fäst genväg står med sitt eget slag.

Varför: ju fler genvägar du fäster, desto svårare blir en enda avslutande klump av blandade filter och spellistor att skanna, och den skiljer varje genväg från mappen den hör till. Att gruppera fästa objekt under sin egen kategori håller roten läsbar och varje genväg bredvid de saker den hör ihop med.

Inställningsdialog och paketering

N26 - Sektionerad inställningsdialog

Vad: inställningssidan fick en vänsternavigeringslayout med sektioner: Allmänt / Uppspelning / Bibliotek / Enhetsprofiler / Diagnostik.

Varför: originalet var en enda lång platt lista över alla inställningar - bra för utvecklaren som byggde det, förvirrande för alla andra. Sektionering grupperar relaterade alternativ och får dialogen att kännas mer som moderna appinställningar.


Assembly + plugin-omdöpning (ingen F-id - paketeringsanteckning)

Vad: den kompilerade DLL:en heter mb_UPnP_yaiol.dll och pluginet rapporterar sig själv som "MusicBee UPnP (yaiol)". Skiljer sig från originalet mb_Upnp.dll.

Varför: användare kan installera yaiol bredvid det ursprungliga pluginet och jämföra beteende sida vid sida.


Märkessystem - synliggör körningsstatus (mekanism bakom F40)

Vad: ett generiskt UI-mönster för att synliggöra viktiga körningsförhållanden som synliga färgade märken i inställningsdialogen. Aktuella instanser:

  • ⚠ Max Conn (N04) - utlöses när max-anslutningsgränsen nåddes minst en gång sedan MusicBee startade. Klibbig sessionsflagga Plugin.MaxConnectionsHit. Ställs in i WaitOnSendBarrier när ingen plats är ledig.
  • ⚠ Omstart krävs - utlöses när en sparad inställning kräver en MusicBee-omstart för att träda i kraft. Klibbig sessionsflagga Plugin.RestartRequired. Ställs in i dialogens Spara-hanterare när det nya sparade värdet skiljer sig från körningsögonblicksbilden (Plugin.activeMaxConnections, Plugin.activeServerPort, Plugin.activeIpAddress). Inställningar som kräver omstart är begränsade till de som verkligen inte kan hot-reloadas - HTTP-serverns bindningsparametrar och SemaphoreSlim som byggs en gång vid Initialise.

Varför: pluginets loggfil är bra för tekniska användare som felsöker, men en icke-teknisk användare som stirrar på "enheten låter fel" eller "uppspelningen är långsam" kommer aldrig att öppna Diagnostik → Visa logg. Märken fångar upp de fall där användaren behöver veta att något hände och synliggör det nästa gång de öppnar pluginet - upptäckbart utan att läsa något.

Återanvändbart för framtiden:

  • Profilmatchning upptäckt (enhetens user-agent matchade aldrig någon profil, återgick till Generisk).
  • NextURI backoff utlöst (F13 - gapless inaktiverad för sessionen på en opålitlig enhet).
  • Biblioteksskanning misslyckades / delvis.
  • Renderaranslutning förlorad mitt i sessionen.
  • Alla andra förhållanden där "hände en gång, användaren borde veta" slår "loggades tyst bland 1000 andra rader".

Implementeringskonventioner:

  • Märkesetiketter finns på dialognivå (inte inuti någon panel) så att de är synliga oavsett vilken sektion användaren befinner sig i.
  • Placerade längs den nedre raden nära Spara/Avbryt (aktuell: y=410 horisontellt staplade).
  • Varje märke har en motsvarande klibbig sessionsflagga i Plugin som blir True när tillståndet inträffar och återställs endast vid MusicBee-omstart.
  • Resurser: <Condition>Badge (etiketttext, prefix med ⚠) + <Condition>BadgeTip (verktygstips som förklarar orsak + åtgärd).
  • För märken som "sparad inställning kräver omstart", ta en körningsögonblicksbild vid Plugin.Initialise() och jämför med Settings.* efter Settings.SaveSettings() i dialogens Spara-hanterare.

N27 - Avbryt kastar bort sökvägs-/mallredigeringar

Vad: redigeringar som gjorts i sökvägar och mallar i inställningsdialogen kastas nu bort när användaren klickar på Avbryt, istället för att tyst förbli tillämpade, och alla reserverade mallar som tagits bort under sessionen återskapas. (Mallar sparas annars live när de redigeras - det finns ingen separat Spara-knapp på fliken Sökvägar.)

Varför: Avbryt ska betyda avbryt. Tidigare fann en användare som experimenterade med sökvägs-/malländringar och ångrade sig att ändringarna redan var genomförda, utan möjlighet att ångra dem annat än att göra om varje för hand.


N28 - Bokstavliga et-tecken i fältväljarmenyn

Vad: ett fält vars namn innehåller "&" - t.ex. "Stämning & Kontext" - renderar et-tecknet bokstavligt i fältväljarmenyn istället för att svälja det som ett Alt-mnemonic-prefix.

Varför: fältnamn med ett et-tecken visades fel (tecknet försvann och nästa bokstav blev en accelerator), vilket gjorde menyalternativet svårt att känna igen.


N29 - Stabil, oöversatt inställningsfönstertitel

Vad: inställningsfönstrets titel är fastställd till varumärkessträngen "MusicBee UPnP Plugin" och varierar inte längre med gränssnittsspråket; den språkspecifika DialogTitle-strängen togs bort från varje språkpaket.

Varför: en fönstertitel som ändrade formulering per språk var en översättningsbar yta utan fördel - titeln är ett varumärke. Att fästa den håller den stabil och konsekvent överallt.

Lokalisering

Det ursprungliga pluginet är endast på engelska. Denna förgrening är fullt lokaliserbar - varje användarvänd sträng flödar genom ett resursbundle, och pluginet upptäcker automatiskt MusicBees eget UI-språk.

N30 - Flerspråkigt UI (översättningar väntar)

Vad: lokaliseringsmekanismen är komplett och levereras. Localisation.vb läser MusicBees valda språk från MusicBee3Settings.ini (<SystemLanguage> endonym) och tillämpar den matchande .NET-kulturen på tråden, så My.Resources.Resources.* returnerar den lokaliserade strängen. Varje användarvänd etikett/knapp/meddelande är kopplad till en resursnyckel (designer-kontroller via ApplyDesignerExtras + sync-en-locale.js; körningssträngar som WarnPortInUse handläggs). Vad som inte är klart ännu är den faktiska översättningen: endast det engelska källpaketet (Resources.resx) existerar - satellitpaketen för de andra språken produceras i en batchpass när pluginet är funktionskomplett (att översätta bitvis medan strängar fortfarande ändras slösar bort ansträngning).

Målspråk (den uppsättning MusicBee själv erbjuder, matchad 1:1 av endonymToCulture så att pluginet följer MusicBees språk automatiskt):

Arabiska (ar) Tjeckiska (cs) Tyska (de) Grekiska (el)
Spanska (es) Franska (fr) Ungerska (hu) Italienska (it)
Koreanska (ko) Nederländska (nl) Norska (nb) Polska (pl)
Portugisiska BR (pt-BR) Portugisiska PT (pt-PT) Svenska (sv) Turkiska (tr)
Ukrainska (uk) Ryska (ru) Japanska (ja) Förenklad kinesiska (zh-CN)
Traditionell kinesiska (zh-TW) Engelska (en, källa)

Variantpolicy (enligt arbetsytans lokalregel): PT och ZH är uppdelade i distinkta paket eftersom ordförråd/skript verkligen skiljer sig (pt-BR/pt-PT, zh-CN/zh-TW). EN är ett enda paket - MusicBees "English(US)" (en-US) faller tillbaka till en via .NETs kulturkedja, så inget separat US-paket produceras. ES och FR är likaså enskilda lokaler.

Varför: ett UPnP-plugins inställningar ("använd inte rå PCM", "tvinga little-endian PCM", port-fallback-varningar) är kryptiska nog på ens modersmål. Att följa MusicBees eget UI-språk - istället för att tvinga engelska - är skillnaden mellan ett verktyg som en icke-engelsk användare kan konfigurera och ett de inte kan. Ingen av uppströms försökte detta.


N31 - Hjälplänk öppnas i det fullständiga gränssnittsspråket

Vad: att öppna hjälplänken från pluginet respekterar användarens fullständiga gränssnittsspråk (t.ex. pt-BR, zh-CN) istället för att kollapsa till grundspråket, och skickar en tydligare uppdateringskontrollidentifierare.

Varför: en användare som kör MusicBee i en regional variant (brasiliansk portugisiska, förenklad kinesiska) skickades till hjälpsidan på grundspråket. Att bära med sig hela kulturen landar dem på hjälpsidan på exakt det språk de använder.

Fixar och förbättringar av det ursprungliga pluginet

Kärnprotokoll och uppspelning

F01 - Uppdaterade standard DLNA-enhetsprofiler

Vad: levererar nya standardprofiler för PlayStation 4, Xbox 360/One och moderna BubbleUPnP, med kapacitetsflaggor (samplingsfrekvenser, bitdjup, codecs) som återspeglar vad dessa enheter faktiskt stöder idag.

Varför: det ursprungliga pluginets standardinställningar var frysta runt 2014. PS4/Xbox/BubbleUPnP har sedan dess fått stöd för högupplöst ljud. Direkt ur lådan spelar en ny installation bäst i klassen på dessa enheter utan att användaren behöver ändra enhetsprofilinställningarna.


F02 - Kontrollera enheter som annonserar MediaRenderer:3

Vad: pluginet undersöker en renderarens UPnP-tjänstbeskrivning för att avgöra om MusicBee kan styra den. Originalet matchade endast urn:schemas-upnp-org:device:MediaRenderer:1. Moderna enheter annonserar :2 eller :3. F02 breddar matchningen.

Varför: utan detta visas inte nya Sonos / WiiM / Eversolo-enheter som mål i MusicBees "Spela till"-enhetslista - även om de talar samma protokoll. En enkel strängprefixmatchningsfix låser upp hela den moderna enhetsgenerationen.


F03 - "Tvinga nativ ström" per-profilalternativ (standard PÅ)

Vad: när kryssat skickar pluginet de ursprungliga filbytes till enheten med ingen transkodning, ingen DSP, ingen ReplayGain-bearbetning tillämpad. Bara den råa filen användaren valde, byte för byte (med undantag för HTTP-ramning).

Varför: enligt forumvittnesmål är detta den enskilt största vinsten i uppspelningskvalitet. Hi-fi-användare som köper dyra renderare vill uttryckligen ha bit-perfekt utdata; varje DSP-beröring motverkar syftet. Standard PÅ eftersom de flesta moderna enheter hanterar vilken codec användaren än kastade på dem, och ReplayGain/EQ bör vara opt-in. Det är per-profil så att du kan behålla transkodning för en gammal Xbox samtidigt som du skickar nativt till en hi-fi DAC.


F04 - "Tvinga transkodning" per-profil

Vad: per-profil-åsidosättning som tvingar varje ström till denna enhet genom transkodaren, oavsett stöd för nativ codec. Det omvända av F03 (ForceNativeStream). Ömsesidigt exklusivt med F03 - UI:et avmarkerar automatiskt den andra när någon av dem slås på.

Varför: en enda global växling skulle vara motsägelsefull med den per-profilerade ForceNativeStream (F03). Verkligt fall: enhet A är en hi-fi DAC som vill ha bit-perfekta nativa strömmar; enhet B är en gammal AV-mottagare som kvävs av FLAC. Med en global växling måste användaren välja - på bekostnad av den andra enheten. Med per-profil får varje enhet rätt svar.

Implementering:

  • StreamingProfile.ForceTranscoding As Boolean = False.
  • Beständighetsschema uppdaterat till v9. Filer före v9 laddar det äldre globala värdet en gång och kopierar det till alla profiler, vilket bevarar det gamla beteendet genom uppgraderingen.
  • UI: borttagen från diagnostikpanelen, tillagd i avsnittet Enhetsprofiler bredvid ForceNativeStream. Tvåvägs ömsesidigt exklusiva hanterare (CheckedChanged på varje avregistrerar den andra innan den växlar, för att undvika en oändlig loop).
  • Beslutsplats: Settings.ForceTranscodingstreamingProfile.ForceTranscoding i WriteAudioFileDIDL.

F05 - "Tvinga little-endian PCM" per-profil

Vad: PCM-strömmar (L16/L24 mime-typer) är big-endian enligt specifikationen. Vissa enheter förväntar sig felaktigt little-endian och spelar upp vitt brus när de får korrekt big-endian-data. F05 växlar byteordningen per profil.

Varför: utan detta ger vissa enheter ut en vägg av statiskt brus. Symptomet är dramatiskt och orsaken osynlig utan kunskap om PCM-kodning - växlingen ger användarna en gissa-och-kontroll-fix.


F06 - "Använd inte rå PCM" per-profil

Vad: när enheten hävdar att den stöder rå PCM, använder pluginet det. Vissa enheter ljuger - de accepterar SOAP-handskakningen men förvränger faktiska råa PCM-data, samtidigt som de korrekt hanterar PCM insvept i en WAVE-container. F06 tvingar PCM-över-Wave oavsett vad enheten annonserar.

Varför: vissa Marantz-modeller specifikt - de annonserar rå PCM men endast WAVE fungerar. Utan detta kommer råa PCM-strömmar ut förvrängda, utan något felmeddelande som pekar på orsaken.


F07 - "Innehållslängd" per-profil

Vad: vilket värde som ska skickas i HTTP Content-Length-huvudet. Fyra alternativ:

  • Standard - faktiskt byteantal när känt, utelämna när okänt.
  • Ingen - skicka aldrig huvudet (endast chunked encoding).
  • Endast PCM - skicka endast för rå PCM; utelämna för allt annat.
  • Fast - skicka UInt32.MaxValue - 8192 (en sentinel för "enorm okänd längd").

Varför: UPnP/DLNA-enheter varierar vilt i hur de reagerar på Content-Length. Vissa behöver ett exakt nummer, vissa hatar det på strömmar, vissa behöver ett sentinel "riktigt stort" värde för att fortsätta buffra. Detta breddades senare från endast PCM till alla utdataformat eftersom samma problem dök upp i transkodade MP3/AAC-strömmar.


F08 - "Rensa inte NextURI" per-profil

Vad: normalt rensar pluginet enhetens köade NextURI när kön töms (skickar SetNextAVTransportURI med en tom URL). Vissa enheter (särskilt Denon) tolkar en tom NextURI som "stoppa allt" och stoppar uppspelningen omedelbart. F08 förhindrar pluginet från att någonsin rensa den.

Varför: utan detta upplever Denon-ägare att enheten avbryter mitt i spåret när kön töms. Med F08 ikryssat behåller enheten den inaktuella NextURI i minnet (ofarligt - den skrivs bara över nästa gång något köas).


F09 - FLAC som transkodningsutdataformat

Vad: rullgardinsmenyn för transkodningsformat i enhetsprofilerna erbjuder nu FLAC bredvid PCM 16/24, MP3, AAC, Ogg. Att välja det dirigerar BASS-kodaren genom MusicBees standard FLAC-konverteringskommandorad (samma mekanism som MP3/AAC/Ogg redan använder).

Varför: för enheter som hanterar FLAC bra men inte kan avkoda källcodecen (t.ex. en Eversolo som tar emot MusicBees WMA-bibliotek konverterat till FLAC), bevarar detta förlustfri kvalitet där MP3/AAC skulle kasta bort ljuddata. Låser upp N02 (5.1 nedmixningskontroll), som inte kunde åtgärdas utan ett förlustfritt transkodningsalternativ.

Implementering: enradig tillägg till Encoder.StartEncodes Select Case Codec-block - FLAC ansluter sig till MP3/AAC/Ogg i den kommandoradsdrivna grenen. UI-rullgardinsmenyn får "FLAC" som ett 6:e alternativ. Laddnings-/sparingsmappning i SettingsDialog utökas för att känna igen FileCodec.FlacSelectedIndex = 5. Mime, DLNA-typ och kodningsfunktion var redan kopplade i ItemManager.GetMimes / GetDlnaType / GetEncodeFeature från tidigare arbete (F21, F26).


Gapless (SetNextAVTransportURI)

F10 - SetNextAVTransportURI / NextURI kärna

Vad: verklig gapless uppspelning. När enheten annonserar stöd för SetNextAVTransportURI i sin UPnP-tjänstbeskrivning, förköar pluginet nästa spår på enheten innan det aktuella spåret slutar. Enheten övergår internt utan hörbart mellanrum mellan spåren - vad du hör på en CD-spelare. Detta är inte "kontinuerlig ström"-hacket (som sammanfogar allt till en lång ström och förlorar metadata per spår).

Varför: flaggskeppsfunktionen i nivå 2. Album inspelade som en kontinuerlig liveframförande (liveinspelningar, klassiska satser, DJ-set) låter fel när det är en halv sekunds tystnad mellan spåren. Att lösa detta korrekt är en flaggskeppsfunktion, nu i denna förgrening.

Anmärkningar: det köade ljudet serveras via pluginets HTTP-server med streamHandle=0 (biblioteks-hämtningsläge), vilket innebär att MusicBees ljudmotor inte är inblandad för det köade spåret. Kompromiss: ReplayGain/DSP/EQ-effekter tillämpas inte på nästa spår. Acceptabelt när "tvinga nativ ström" är på (standard).


F11 - "Inaktivera NextURI-stöd" per-profil

Vad: även om en enhet annonserar SetNextAVTransportURI, tvingar denna kryssruta pluginet att ignorera den annonseringen och falla tillbaka till uppspelning ett spår i taget.

Varför: vissa enheter annonserar NextURI men har en buggig implementering (kraschar, halva övergångar, hänger sig). Istället för att omvänt konstruera varje trasig enhet får användaren en "stäng bara av det här"-växling.


F12 - NextURI livscykel på nu-spelar-listan

Vad: när MusicBee utlöser NowPlayingListChanged, omvärderar pluginet vad som ska köas för gapless övergång. Den frågar MusicBee efter det nya "nästa" spåret via NowPlayingList_GetNextIndex(1) + NowPlayingList_GetListFileUrl, jämför med vad som för närvarande är köat på enheten (spåras via det nya fältet nextPlaySourceUrl), och köar om det om det ändrats (eller rensar kön om MusicBee säger att det inte finns något nästa spår).

Varför: utan F12 fortsatte enheten att spela en inaktuell NextURI när användaren tog bort/omordnade det köade spåret. Detta tog historiskt flera iterationer eftersom varje listmutation behöver olika hantering - vi förenklade genom att lita på NowPlayingList_GetNextIndex (som redan respekterar shuffle och repeat-all wrap-around), så alla varianter kanaliseras genom samma jämförelse.

Implementering:

  • Nytt fält nextPlaySourceUrl lagrar MusicBee-biblioteks-URL:en för det som är köat (ström-URL:en med handtagssuffix är inte jämförbar med en bibliotekssökväg).
  • Ny Public Sub RefreshQueuedNextUri()MediaRendererDevice. Tre utfall: ingen NextURI köad → ingen åtgärd; köad matchar ny "nästa" → ingen åtgärd; köad skiljer sig → anropa QueueNext med den nya URL:en (eller QueueNext("") för att rensa - vilket respekterar F08 DoNotClearNextUri).
  • Kopplad i Plugin.ReceiveNotification under NotificationType.NowPlayingListChanged.

F13 - NextURI felåterställning

Vad: efter 4 på varandra följande SetNextAVTransportURI-fel på samma enhet, inaktiverar pluginet gapless för den enheten tills MusicBee startas om.

Varför: om en enhet är genuint trasig för NextURI (intermittenta SOAP-fel, nätverksglitchar), skulle pluginet annars fortsätta att försöka igen vid varje spår. F13 stoppar bruset och faller tillbaka till en-spår-i-taget tyst.


F14 - Upprepa-läge + NextURI-integration

Vad: F14 delas upp i två fall som hanteras vid F15-övergångsdetektorn i OnAvTransportStatusCheck:

  • Upprepa alla: MusicBee skickar den korrekta "wrap"-URL:en (spår 1 vid slutet av listan) till Plugin.QueueNext själv. Ingen speciell plugin-logik behövs - enheten övergår till den och F15-detektorn anropar Player_PlayNextTrack som vanligt, vilket omsluter MusicBees NPL-index tillbaka till 0.
  • Upprepa en: MusicBee skickar SAMMA spår-URL till Plugin.QueueNext. Enheten övergår till den (ny strömhanterare, samma källa). F15-detektorn frågar nu Player_GetRepeat() - om det är RepeatMode.One, hoppar den över Player_PlayNextTrack-anropet så att MusicBee inte flyttar NPL-indexet bort från det loopande spåret.

Varför: utan Repeat-One-hoppet skulle anropet Player_PlayNextTrack vid gapless övergång flytta MusicBee till nästa spår i listan (Repeat-One påverkar endast automatisk framåtrörelse vid slutet av spåret i spelarens UI - Nästa spår flyttar alltid framåt), vilket motsäger vad Repeat-One betyder.

Spelräkningsvarning: i Repeat-One beror ökningen av spelräkningen på att MusicBee 3.7.9563+ märker den loopade uppspelningen. Äldre MusicBee-versioner spelar den gapless upprepningen korrekt men missar ökningen av spelräkningen. Dokumenterat; inte blockerande.


F15 - Spårövergångsdetekteringsmaskin

Vad: när enheten internt övergår från aktuellt spår till NextURI, måste pluginet märka det och berätta för MusicBee att flytta fram sitt nuvarande uppspelningsindex. Annars tror MusicBee att det fortfarande är på föregående spår och spelräkningar / UI / scrobbling hamnar i otakt.

Implementering: frågar GetPositionInfo.TrackURI vid varje status-timer-tick. När den rapporterade URI:n matchar den vi köade via NextURI, anropar vi Player_PlayNextTrack på MusicBee och sätter suppressNextSoapCall så att den resulterande PlayToDevice inte skickar SetAVTransportURI igen (vilket skulle avbryta den gapless uppspelningen).

Varför: utan F15 spelar enheten nästa spår men MusicBees UI säger att det fortfarande är på föregående. Förvirrande, bryter scrobbling, bryter spårning av spelräkning. Spårövergångsdetektering kräver lång, per-renderare-iteration eftersom varje renderarmärke har sina egna egenheter i när det rapporterar URI-ändringen (vissa rapporterar TRANSITIONING först, vissa hoppar direkt till PLAYING med ny URI, vissa har en kort STOPPED däremellan).

Anmärkningar: vår första version fungerar på BubbleUPnP-renderaren. Per-enhetens kantfall finns kvar i B6.


F16 - Pop-on-gapless-transition fix

Vad: poppen uppstår när källformatet (samplingsfrekvens / kanaler / codec) för det köade spåret skiljer sig från det för närvarande spelande spåret, vilket tvingar enhetens DAC att återlåsa vid övergången. F16 lägger till en NextUri:FormatChange-diagnostik som utlöses vid kötid när formaten skiljer sig, och namnger båda sidor - så att användare som hör poppar kan korrelera.

Diagnostiken pekar också på åtgärden: kryssa i Tvinga transkodning på enhetsprofilen. Det homogeniserar varje spår till en enda transkodningscodec/samplingsfrekvens/bitdjup, vilket eliminerar skillnaden i källformat helt.

Varför uppskjuten för den faktiska transkodnings-till-matchningsfixen: den strukturella fixen (transkoda det köade spåret för att matcha det spelande spårets format) kräver ändringar i pluginets HTTP-server-URL-schema - för närvarande serverar /encode/{id}0.{ext} den köade filen nativt. En framtida v2 av F16 skulle lägga till per-format /encode/{id}0_{rate}_{depth}.{ext}-rutter och koppla dem genom kodaren. Det är en större arkitektonisk förändring värd att göra om en verklig enhet uppvisar poppen efter att ForceTranscoding inte räcker.

Implementering idag:

  • lastSourceUrl-fältet spårar den för närvarande spelande käll-URL:en.
  • QueueNext läser FilePropertyType.SampleRate/Channels/Kind för både de aktuella och köade spåren och loggar NextUri:FormatChange vid matchningsfel.

F17 - Förloppsindikator resynkronisering efter sökning

Vad: funktionen Seek() anropade redan GetPlayPositionInformation() efter en lyckad Seek SOAP, vilket fixar fallet "ingen resynkronisering alls". F17 stänger den återstående upp till 1s drift som orsakas av UPnP:s 1-sekunds RelTime-kvantisering: när enhetens rapporterade position avrundas till inom 1 sekund från användarens begärda mål, litar pluginet nu på användarens sub-sekund-noggranna värde istället för enhetens trunkering. Endast när enheten rapporterar något dramatiskt annorlunda (>1s avvikelse) använder vi dess värde (sökningen landade någon annanstans än begärt, t.ex. snap-to-keyframe på vissa codecs).

Varför: utan detta skulle sökning till 2:30.500 förankrad mot enhetens "2:30"-rapport göra att förloppsindikatorn visade ~500ms efter verkligheten. Efter F17 matchar indikatorn användarens avsikt för det vanliga fallet med skrubbning i spåret, och respekterar fortfarande enhetens rapport för snap-to-keyframe-undantaget.


F18 - Kontinuerlig ström / NextURI förregling

Vad: två förreglingar är nu på plats:

  1. Körning: QueueNext returnerar tidigt False när Settings.ContinuousOutput är på. Kontinuerlig ström är dess egen gapless-mekanism (en lång sammanfogad ström); att skicka SetNextAVTransportURI ovanpå den förvirrar enheten om huruvida varje spår är en diskret URI eller en del av det kontinuerliga flödet.
  2. UI: när användaren kryssar i den globala kryssrutan för kontinuerlig ström, avmarkeras den för närvarande visade profilens forceNativeStream automatiskt. Kontinuerlig ström transkodar alltid, så force-native är meningslöst i kombination.

Varför: förhindrar användaren från att aktivera två motstridiga gapless-mekanismer samtidigt. Utan F18 skulle enheten ta emot både en kontinuerlig ström-URI OCH en NextURI för varje efterföljande spår, med odefinierat beteende beroende på renderaren.


F19 - Tomma NextURI-fel ignoreras

Vad: när SetNextAVTransportURI anropas med en tom URL (t.ex. sista spåret i listan), returnerar vissa enheter ett SOAP-fel. F19 sväljer dessa tyst - loggas men sprids inte som fel.

Varför: tillståndet "inget nästa spår" är normalt, inte ett fel. Att behandla det som fatalt förorenar loggen och (i vissa flöden) utlöser omförsöksstormar.


Mime-typer och DLNA-metadata

F20 - MP3 mime → audio/mpeg

Vad: den standardkonforma MP3 mime-typen är audio/mpeg, inte audio/mp3. Den senare är en vanlig felaktig benämning som de flesta enheter tolererar, men striktare renderare avvisar den.

Varför: fixar tyst uppspelning på striktare enheter som följer standarden. Yaiol-kodbasen hade detta redan korrekt; ingen ändring behövdes.


F21 - Mime-typordning: icke-x- variant först

Vad: när en enhet annonserar både audio/flac och audio/x-flac, returnerar pluginet den icke-x- varianten först. Samma för alla codecs med både standard- och experimentella mimes.

Varför: x- prefixet markerar experimentella/inofficiella mimes. Vissa renderare fungerar bättre med standardformen. Liten omordning, verklig påverkan.


F22 - Opus mime-typstöd

Vad: känner igen Opus som en strömbar ljudcodec; skickar audio/opus mime när Opus-spår serveras.

Varför: Opus är nu vanligt (modern kompromisscodec för tal/musik). Utan F22 skulle pluginet vägra att strömma Opus-filer även till enheter som hanterar dem.


F23 - Monkey Audio (APE) källfilstöd

Vad: känner igen .ape-filer som en giltig källcodec för strömning/transkodning.

Varför: APE är ett förlustfritt format med en nischad men lojal användarbas. Att lägga till det kostar lite och låser upp biblioteket för dessa användare.


F24 - AAC / ALAC mime-återgång

Vad: om en enhet stöder AAC eller ALAC men inte uttryckligen annonserar dem i sin UPnP-tjänstbeskrivning, erbjuder pluginet dem ändå som en återgång.

Varför: flera enheter som hanterar AAC bra glömde att lista det i sin kapacitets-XML. Utan F24 kommer pluginet inte ens att försöka, vilket tvingar transkodning. Med F24 försöker pluginet och låter enheten hantera det nativt om den kan.


F25 - DLNA-typflagga för nativa + kodade WAV-strömmar

Vad: DLNA-typflaggan (en profilidentifierare som LPCM, WAVE, MP3) måste matcha vad enheten tar emot. F25 säkerställer att nativa strömmar och kodade WAV-strömmar flaggas korrekt.

Varför: felmatchad DLNA-typ gör att vissa enheter helt vägrar uppspelning eller tillämpar fel avkodare.


F26 - DLNA-huvud för FLAC-filer

Vad: FLAC-strömmar får rätt DLNA-profilidentifierare i sina huvuden.

Varför: utan det känner vissa enheter som stöder FLAC inte igen strömmen som sådan.


F27 - Bitrate-beräkningsfix i metadata

Vad: den kontinuerliga strömmens res@bitrate beräknades som (sampleRate * channels * bitsPerSample) / 1000 - kbps, fel med en faktor på ~125 från UPnP DIDL-specifikationen som definierar attributet som bytes per sekund. Delar nu med 8 istället för 1000.

Varför: felaktig bitrate-visning på enheten - kosmetiskt på de flesta renderare, men vissa allokerar strömbuffrar från värdet och stammar på strömmar som ser ~125× mindre ut än de är. Den icke-kontinuerliga källfilssökvägen hade detta redan rätt ((bitrate_kbps * 1000) \ 8 = bytes/sek); endast den kontinuerliga strömsökvägen var fel.


F28 - Metadata tidsformatfix (Marantz)

Vad: res@duration i DIDL formaterades som H:MM:SS (t.ex. 0:03:42). UPnP DIDL-specifikationen definierar formatet som H+:MM:SS[.F+] - strikt med bråksekunder valfritt men rekommenderat; vissa Marantz-enheter behandlar den bara formen som ogiltig och lämnar sin varaktighetsvisning tom. Nu formateras som H:MM:SS.fff (t.ex. 0:03:42.000).

Varför: visningsproblem specifikt för ett märke; konformt ISO-stilformat med bråksekunder fixar det utan att påverka någon annan enhet. Tillämpas på båda DIDL-emissionsplatserna (källfilssökväg + kodad strömsökväg i WriteAudioFileDIDL).

Bonusfix i samma pass: pv:addedTime och pv:lastPlayedTime använde hh (12-timmarsklocka) i sina DateTime-formatsträngar istället för HH (24-timmars). Varje spår som lades till eller spelades mellan 13:00 och 23:59 skulle renderas med en felaktig timme (t.ex. 17:42 → "05:42") på enheter som visar fältet. Använder nu HH.


F29 - Kodad MP3-sökning (CBR)

Vad: transkodade MP3-strömmar annonserar nu DLNA.ORG_OP=11 (både byte- och tidsökning) istället för DLNA.ORG_OP=10 (endast byte). Enheter som tidigare vägrade tidsökning på transkodade MP3 kan nu styra sin förloppsindikator/sök-UI normalt.

Varför: MusicBees transkodare producerar MP3 med konstant bithastighet vid HighQuality-förinställningen, så byte ↔ tidsmappning är linjär - enheten kan konvertera en tidsökningsförfrågan till en HTTP Range byte-sökning själv utan något kodarsidigt stöd. Att annonsera OP=11 låser upp det UI:et på enheten. Utan F29 ignorerades användare som sökte i en transkodad MP3 antingen tyst eller släpptes till spårstart.

Implementering: omstrukturerade GetEncodeFeature i ItemManager.vb för att bryta den inbyggda If till en läsbar If/ElseIf/Else-kedja. MP3 får OP=11 explicit; andra icke-PCM-codecs behåller OP=10. Ingen ändring för AAC/FLAC/etc. - dessa skulle behöva codecspecifik verifiering av CBR-egenskap som MusicBee inte garanterar.


F30 - .mpeg-filändelse hanteras

Vad: filer med .mpeg (och den ännu mer sällsynta .mpe) filändelse känns nu igen som FileCodec.Mp3 i GetCodec. Före F30 returnerade de FileCodec.Unknown och avvisades tyst från biblioteket / kunde inte vara transkodningskällor.

Varför: gamla MPEG-1 Layer 3-arkiv använde ibland .mpeg istället för .mp3 (specifikationen tillåter båda). Ett fåtal filer i ett 300k-bibliotek är tillräckligt för att känna "MusicBee visar dem men pluginet gör det inte" - förvirrande för användaren.


Uppspelningsbeteende

F31 - Radioströmmar använder automatiskt kontinuerligt läge

Vad: WriteAudioFileDIDL undersöker nu käll-URL:ens Kind-egenskap via Library_GetFileProperty och behandlar alla filer vars Kind slutar på "Stream" (MusicBee rapporterar "MP3 Stream", "Internet Stream", etc. för radio) som kontinuerliga oavsett den globala Settings.ContinuousOutput-växlingen. Den kontinuerliga strömmens DIDL-gren (Titel: "Continuous Stream", id="continuousstream", fast PCM/Wave-utgång) används; enheten ser en enda oändlig ström.

Varför: radioströmmar har inga spårgränser, ingen fast längd, ingen sökning. Att behandla dem som diskreta filer i DIDL gjorde att pluginet annonserade byteintervall och varaktigheter som inte existerar. Att automatiskt växla när MusicBee redan berättade för oss "detta är en ström" tar bort en fotpistol som användaren inte borde behöva tänka på.

Omfattning: gäller endast när MusicBee driver uppspelning (musicBeePlayToMode). Biblioteks-hämtningsvägen (UPnP-klient som bläddrar) är oförändrad - radio-URL:er där är sällsynta och det användarvänliga beteendet bör inte ändras utan explicit testning.


F32 - Codec-annonseringsåtergång

Vad: om en enhet inte annonserar vissa codecs (eller om pluginet inte kan tolka enhetens kapacitets-XML), avvisar pluginet inte omedelbart strömmen. Istället försöker den servera den och låter enheten bestämma.

Varför: många enheter har ofullständig eller oläsbar kapacitets-XML men hanterar faktiskt codecen bra. F32 byter en liten "bästa gissning och försök" mot ett direkt avslag.


F33 - Förbättring av förloppsindikatorns synkronisering

Vad: positionen mellan omröstningarna extrapoleras redan med väggklocka från ett enda ankare (currentPlayStartTicks), så förloppsindikatorn uppdateras smidigt i sub-sekundhastighet. Den återstående jitterkällan var det initiala ankaret för ett nystartat spår: den tidigare koden antog position=0 i det ögonblick status-timern först märkte att tillståndet gick till Spelar, men då kan enheten ha spelat i 100-500ms (ett omröstningsintervall). MusicBees förloppsindikator skulle starta på 0, sedan hoppa framåt när verkligheten kom ikapp.

F33-fix: när man övergår till Spelar för första gången på ett nytt spår (currentPlayStartTimeEstimated=True), anropa GetPlayPositionInformation() för att få enhetens faktiska aktuella position, och förankra sedan mot det. UPnP rapporterar endast 1-sekunds upplösning så ankaret är fortfarande kvantiserat, men det är mycket närmare sanningen än att anta 0.

Varför: smidigare + mer exakt förloppsvisning, särskilt direkt efter spårbyte. Ingen väg runt UPnP:s 1-sekunds rapporteringsupplösning i sig - det är specifikation.


F34 - Förloppsindikatorns jitter efter spårbyte

Vad: när PlayToDevice anropas för ett nytt spår, brukade pluginet lämna currentPlayPositionMs och currentPlayStartTicks vid deras tidigare spårvärden under ~100ms-fönstret mellan SOAP-Play och den första status-timer-omröstningen som upptäckte det nya Playing-tillståndet. MusicBees förloppsindikator skulle kort visa slutet av föregående spår, sedan hoppa tillbaka till 0, sedan klättra. F34 nollställer båda vid PlayToDevice-inträde - i det ögonblick vi vet att ett spårbyte sker, innan något av SOAP-arbetet.

Varför: visuell glitch vid snabbhoppningsfall (manuell nästa eller gapless övergång). Nu returnerar MusicBees första PlayPositionMs-fråga efter Play rent 0, sedan förfinar F33:s GetPlayPositionInformation det till enhetens faktiska position vid den första tillståndsändrings-tick.

Implementering: fyra rader högst upp i PlayToDevice, parat med F33:s övergångstidsnoggranna förankring.


F35 - "Tvinga transkodning" bugg

Vad: tvingad transkodning kunde fortfarande hoppa över transkodning i vissa kombinationer. Efter F04 per-profil-omstruktureringen förseglades två specifika luckor:

  1. Prioritet med ForceNativeStream. När båda var True (vilket kan hända vid en schema-migrering eller en partiell inställningsfil), vinner ForceTranscoding nu direkt (If streamingProfile.ForceTranscoding Then forceEncode = True ElseIf streamingProfile.ForceNativeStream Then forceEncode = False). UI:s ömsesidiga exkludering förhindrar användaren från att kryssa i båda, men körningsskyddet hanterar alla tillstånd som laddades in inkonsekvent från disk.
  2. bypassTranscodeDecision-logik. Tidigare: streamingProfile.ForceNativeStream AndAlso Not Settings.ForceTranscoding. Nu: streamingProfile.ForceNativeStream AndAlso Not streamingProfile.ForceTranscoding - samma prioritetregel men på samma per-profil-omfång.

Varför: "tvinga" ska betyda tvinga. Om användaren uttryckligen aktiverade ForceTranscoding för en enhet, får pluginet aldrig tyst falla igenom till nativ strömning, oavsett hur andra flaggor råkar kombineras.


F36 - Renderare-stängt undantag

Vad: omslöt Plugin.ReceiveNotification i en toppnivå Try/Catch som loggar alla okontrollerade undantag istället för att låta det spridas tillbaka till MusicBees meddelandepump.

Varför: meddelanden från MusicBee (PlayStateChanged, VolumeMuteChanged, etc.) skickas till ControlPointManager som kommunicerar med renderaren över SOAP. Individuella anropsplatser hade redan Try/Catch runt sina SOAP-anrop, men ett tillräckligt konstigt tidsscenario (t.ex. att renderaren dör mellan två SOAP-anrop i samma meddelandehanterare) kunde fortfarande undkomma. Toppnivåomslaget är det slutliga säkerhetsnätet så att användaren aldrig ser en generisk "TargetInvocationException"-popup från MusicBee.

Implementering: döpte om den befintliga kroppen till ReceiveNotificationInternal och lade till en tunn omslag ReceiveNotification som gör Try { ReceiveNotificationInternal(...) } Catch { LogError(...) }. Den befintliga per-metod Try/Catch-infrastrukturen inuti ControlPointManager (runt varje PostSoapRequest-anrop) kvarstår - F36 är både hängslen och livrem.


F37 - Långspårsökning utlöser falsk övergång

Vad: att söka i ett långt spår kan producera en kort Stopped→Playing-cykel på vissa renderare. Utan diskriminering behandlar ProcessNewPlayState.Stopped det som naturligt slut på spåret och anropar Player_PlayNextTrack, vilket flyttar MusicBee framåt när användaren bara ville skrubba. F37 stämplar lastUserInitiatedSeek i Seek() och lägger till en 5-sekunders spärr i Stopped-hanteraren (speglar det befintliga lastUserInitiatedStop-fönstret).

Varför: tyst hopp till nästa spår under en sökning är en av de buggar som ingen kan gissa orsaken till - användaren tänker "konstigt, jag försökte skrubba framåt och nu spelar den nästa låt". Fixen är mekanisk: samma mönster som den användarstoppsdiskriminering som redan finns på plats.


F38 - Förbättrad sökhantering för kraschbenägna codecs

Vad: BubbleUPnP som kraschar vid MP3-sökning var det kanoniska symptomet. Efter granskning gör den nuvarande yaiol-sökkoden redan rätt saker - nativ sökväg hanterar HTTP Range korrekt (206, Content-Range, AcceptRanges), kodad sökväg annonserar X-AvailableSeekRange och tolkar inkommande timeSeekRange.dlna.org / npt-huvuden, DLNA.ORG_OP-flaggor återspeglar de faktiska strömkapaciteterna (med DisablePcmTimeSeek opt-out för problematiska Platinum-enheter). Användartestad på nuvarande BubbleUPnP 4.6.4: inga krascher observerades.

Varför: BubbleUPnP MP3-sök-kraschen rapporterades runt 2024 och appen har haft ~16 månaders fixar sedan dess. F29 (kodad MP3 OP=11) var den nya variabeln som kunde ha återexponerat den; gör det inte, på testade versioner.

Om en krasch återkommer: fix-formen skulle vara en per-profil "begränsad sökning"-växling som tvingar DLNA.ORG_OP=10 (endast byte) på flaggade codecs - speglar hur DisablePcmTimeSeek redan fungerar för PCM. Lägg till då, inte i förväg.


UI och loggning

F39 - "Lägg till"-knappen väljer den nya profilen

Vad: att klicka på "Lägg till" i enhetsprofillistan skapar en ny profil OCH väljer den automatiskt så att användaren omedelbart kan redigera fält. Vår sektionerade dialog-refaktorering gör redan detta - både den direkta Lägg till-vägen och från-mall-vägen slutar med Me.activeStreamingProfiles.SelectedIndex = Me.activeStreamingProfiles.Items.Count - 1. En kontroll bekräftade att vår förgrening redan hanterar detta - inget att ändra.

Varför: liten UX-irritation som visade sig redan vara fixad här.


F40 - Större max-anslutningar + varningslogg

Vad: pluginets samtidiga strömgräns (SemaphoreSlim runt Sockets_Stream_File / Sockets_Encoder_Start) var hårdkodad till 4. F40 gör den användarkonfigurerbar på inställningssidan Allmänt (standard 16, intervall 1-256), lägger till en MaxConnections-loggrad när en begäran måste vänta på en plats, OCH visar en röd ⚠ Max Conn-ikon längst ner till vänster i inställningsdialogen om gränsen nåddes minst en gång sedan MusicBee startade.

Varför: när en enhet skickar parallella förfrågningar (vissa Marantz/Linn under konstverkskanningar, BubbleUPnP:s metadata-sonderingar tillsammans med aktiv uppspelning), blockerades ytterligare förfrågningar tyst bakom semaforen - användaren såg "enheten långsam" utan synlig orsak. Loggraden är bra för teknisk felsökning men icke-tekniska användare läser aldrig loggar. Den synliga ikonen i inställningsdialogen gör att tillståndet med nådd gräns kan upptäckas av alla som öppnar pluginets inställningar.

Implementering:

  • Centraliserade väntan i WaitOnSendBarrier(logTag) i MusicBeeUpnp.vb; båda anropsplatserna (MediaServerDevice.GetFile, Encoder.StartEncode) använder den.
  • Settings.MaxConnections sparas i v8 av inställningsschemat.
  • Plugin.MaxConnectionsHit är en klibbig sessionsflagga som sätts i WaitOnSendBarrier; återställs endast vid MusicBee-omstart.
  • SettingsDialog.maxConnectionsBadge är en röd fet etikett vid (16, 410) som endast visas när Plugin.MaxConnectionsHit är True. Har ett verktygstips som förklarar orsaken och åtgärden.
  • Semaforen initieras en gång vid typ-laddning, så att ändra inställningen kräver en MusicBee-omstart (noteras i fältetiketten).

F41 - Logga "kodning på grund av ReplayGain/DSP"

Vad: istället för separata "kodning för RG" / "kodning för DSP" loggrader, inkluderar den enskilda StreamDecision-raden från F42 MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain som ackumulerade orsaker. Samma diagnostiska värde, mindre brus.

Varför: användare ser alla orsaker till att transkodning sker för ett givet spår på en loggrad, inte utspridda. Se F42 för fullständiga detaljer.


F42 - Logga "renderaren stöder inte källcodecen"

Vad: lade till en StreamDecision-loggrad per spela-till-enhet-spår som säger antingen "nativ CODEC" eller "transkoda CODEC→CODEC reason=…". Fältet för anledning ackumulerar varje villkor som utlöste transkodning: MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain, WebFile, VirtualFile, ForceTranscoding(global), SampleRate<min/SampleRate>max, DownmixToStereo, DeviceLacksCodec(X), BandwidthConstrained.

Varför: användare var förvirrade av oväntade CPU-toppar på filer de förväntade sig att strömma nativt. En loggrad per spår berättar exakt vilket villkor som orsakade transkodning - och om fältet visar DeviceLacksCodec(Flac) vet de omedelbart att enhetens protokollinformation var ofullständig och kanske vill att F32:s återgång ska träda i kraft.

Implementering: en enda ackumulatorsträng byggd inkrementellt genom beslutskedjan; loggas en gång i slutet. Begränsad av Settings.LogDebugInfo för att undvika loggbrus i produktion.


F43 - SetNextAVTransport-loggen visar käll-URL

Vad: QueueNext-loggposter inkluderar nu source=<MusicBee library path> tillsammans med stream=<HTTP streaming URL>. Samma ändring tillämpas på framgångsvägen och felvägen (QueueNext:Failed).

Varför: vid felsökning av ett köat spårproblem är strömmande URL (/encode/aabbccdd0.flac) ogenomskinlig i sig - samma för varje spår. Käll-URL:en är den mänskligt sökbara bibliotekssökvägen som berättar exakt vilken fil MusicBee försökte köa.


F44 - Bättre loggning av mime-typfel

Vad: två nya loggposter under Activate:

  • Activate:MimeUnverified - utlöses per felaktig post i enhetens GetProtocolInfo-svar, och namnger vilken post som inte kunde parsas (så att användaren kan se t.ex. "Marantz returnerade http-get:*::* för någon codec - kapaciteten är overifierad, F32:s återgång kommer att gissa").
  • Activate:NoSinkInfo - utlöses en gång om enheten inte returnerade något <Sink>-element alls. Betyder att SupportedMimeTypes förblir Nothing och IsCodecSupported degraderas till "anta att allt fungerar" - användbar kontext när senare "enheten vägrade ström" -fel uppstår.

Varför: före F44 lämnade dessa tysta kapacitets-fallbacks användare att gissa varför deras spår antingen transkodades mot förväntningarna eller vägrades av enheten. Nu visar en enda sökning efter Activate: om enhetens kapacitetsinformation var användbar.


F45 - Bättre loggning av metadatafel

Vad: Browse-undantagsloggen i ContentDirectoryService.vb berikades redan i tidigare yaiol-arbete (Alia Vox-buggsessionen) med ObjectID och stackspårning. F45 utökar den ytterligare med BrowseFlag (metadata vs barn), Filter (vilka attribut klienten begärde), sortCriteria och partialResultLength (hur många byte DIDL som producerades före felet - pekar på hur långt in i batchen det dåliga spåret sitter).

Varför: när något går fel mitt i DIDL, berättar partial-length-värdet om felet var på det första spåret i batchen (partial=0) eller en bit in (partial=N) - kombinerat med batchens startingIndex kan du identifiera det felande spårindexet. Filter och BrowseFlag förklarar vilken typ av bläddring klienten ville ha; ibland misslyckas en metadata-endast-bläddring där en barnbläddring för samma ID lyckas.


Nätverk

F46 - Automatiskt läge annonserar endast på riktiga nätverkskort

Vad: i gränssnittsläget Automatisk annonserade insticksprogrammet sig tidigare (SSDP) på varje fungerande IPv4-adapter. På en maskin som också kör en VPN-tunnel (NordLynx) eller en virtuell växel (Hyper-V / WSL / Docker) annonserades samma bibliotek även på vart och ett av dessa kort, så kontrollpunkten du castar från upptäckte servern två eller tre gånger och listade biblioteket som dubbletter. Automatiskt läge behåller nu bara adaptrar som har en riktig IPv4-standardgateway (HasIPv4Gateway) - vilket tunnel- och virtuella växelkort inte har - så de tas bort från annonseringslistan. En adress som användaren fäst vinner fortfarande alltid (annonsering endast på det gränssnittet), och om ingen adapter rapporterar en gateway faller väljaren tillbaka på alla adaptrar, så listan över annonserade adresser är aldrig tom och insticksprogrammet kan inte bli osynligt.

Varför: dubbletten orsakas inte av att "vara på VPN" - den orsakas av att annonsera på LAN-kortet och tunnel-/virtuella kortet samtidigt, så en kontrollpunkt ser samma server på två adresser. En konsument-VPN (NordVPN/NordLynx) tunnlar bara internetriktad trafik; DLNA-renderaren bor på LAN-et och lokal subnätstrafik går förbi tunneln, så tunnelkortet når ändå aldrig en renderare - att ta bort det tar bort en fantomkopia, aldrig en fungerande väg. Gateway-testet är den billiga, pålitliga signalen som skiljer ett riktigt LAN/Wi-Fi-kort från en tunnel eller virtuell växel. Kompletterar N05 (som fixade hur annonseringar skickas på sådana länkar - multicast istället för broadcast); F46 styr vilka kort det överhuvudtaget annonseras på.

Känd begränsning: en mesh- / fjärråtkomst-VPN (Tailscale, ZeroTier, WireGuard hem) vars renderare verkligen bor på andra sidan tunneln visar vanligtvis en adapter utan standardgateway, så Automatiskt läge kastar den också. De användarna fäster istället VPN-adressen, som har företräde framför gateway-filtret.