MusicBee UPnP Plugin Hilfe

Neuerungen

2.0.2 - 2026-07-20

  • Eine Vorlage kann nicht mehr gelöscht werden, solange ein Knoten ihr folgt – die Schaltfläche zum Löschen bleibt einfach deaktiviert, sodass ein Knoten niemals verwaist zurückbleiben kann. Jede Vorlagenzeile zeigt jetzt eine Live-Anzahl (n) ihrer Follower an, was ein deaktiviertes Löschen auf einen Blick erklärt; das Löschen einer unbenutzten Vorlage bleibt jetzt auch erhalten, anstatt dass die ausgelieferten Standardeinstellungen beim nächsten Laden stillschweigend wieder erscheinen.
  • Eine neue Trichter-Schaltfläche neben dem Ansichtsbaum zeigt genau an, welche Knoten einer Vorlage folgen: Sie filtert den Baum auf nur die Follower der ausgewählten Vorlage, filtert neu, wenn Sie andere Vorlagen auswählen, und stellt den vollständigen Baum wieder her, wenn sie ausgeschaltet wird.
  • Die Knoten Radio und Podcasts sind jetzt dauerhaft mit der Vorlage ihrer Kategorie gekoppelt – formen Sie die Vorlage auf der Registerkarte „Pfade“ um, und der Knoten folgt von selbst; es gibt nichts anzuwenden, daher ist die Schaltfläche „Anwenden“ für sie deaktiviert. Die Vorlagenliste spiegelt dies mit zwei Bändern wider: Standard (wo alle neuen Vorlagen erstellt werden) und Reserviert (Radio + Podcasts).

2.0.1 - 2026-07-20

  • Pfadvorlagen sind jetzt live verknüpft mit den Knoten, die sie verwenden. Das Anwenden einer Vorlage bewirkt, dass der Knoten ihr folgt: Bearbeiten Sie die Vorlage später, und jeder Knoten, der ihr folgt, wird sofort neu geformt – kein Suchen und erneutes Anwenden Knoten für Knoten. Der Ansichtsbaum zeigt direkt nach dem Namen an, welcher Vorlage jeder Knoten folgt, eine Umbenennung wird dort sofort angezeigt, und das Löschen einer Vorlage teilt Ihnen zuerst mit, wie viele Knoten ihr folgen (sie behalten ihr aktuelles Layout und folgen einfach nichts mehr).
  • Das Anwenden einer Vorlage auf einen ausgeblendeten Knoten macht ihn auch wieder sichtbar – das Anwenden ist die Geste „Zeige mir dies, so geformt“, während das Ausblenden bei der Sichtbar-Checkbox bleibt. Die reservierte „Versteckt“-Vorlage, die dies ersetzt, ist verschwunden.
  • Der Ansichtsbaum verliert Ihren Platz nicht mehr: Häkchen, erweiterte Ordner und Scrollposition bleiben alle nach dem Anwenden von Vorlagen und anderen Aktualisierungen erhalten.

2.0.0 - 2026-06-16

Dies ist die erste öffentliche Veröffentlichung des Open-Source-Forks yaiol des MusicBee UPnP-Plugins. Sie wird in zwei Teilen präsentiert: alles, was neu in diesem Fork ist, und dann die Korrekturen und Verbesserungen, die am ursprünglichen Plugin vorgenommen wurden. Jeder Punkt behält die Was / Warum-Form des internen Feature-Katalogs des Projekts bei, sodass die Begründung für jede Änderung auf der Seite steht und nicht nur die Änderung selbst.

Neu in diesem Fork

MediaRenderer – Wiedergabe an MusicBee

N01 – MusicBee als Wiedergabe-Renderer

Was: Normalerweise funktioniert dieses Plugin in eine Richtung: Ein Telefon oder ein anderes Gerät durchsucht die Bibliothek von MusicBee und spielt die Musik auf sich selbst ab. Diese Funktion fügt die entgegengesetzte Richtung hinzu – sie lässt MusicBee der Player sein. Von einer Controller-App auf Ihrem Telefon (wie BubbleUPnP) können Sie Ihr Desktop-MusicBee als Wiedergabegerät auswählen und es dann von Hand steuern: Wiedergabe, Pause, Stopp, Vor- oder Zurückspringen, zu einem bestimmten Punkt im Titel springen und die Lautstärke ändern oder stummschalten.

Warum: Es verwandelt Ihr Telefon in eine Fernbedienung für die Musik, die sich bereits auf Ihrem PC befindet. Setzen Sie sich auf die Couch, durchsuchen Sie Ihre Bibliothek auf dem Telefon, tippen Sie auf einen Titel, und er kommt aus den an Ihrem Desktop angeschlossenen Lautsprechern – mit voller Kontrolle von Ihrem Sitzplatz aus. Das ursprüngliche Plugin hat dies nie als funktionierende Funktion ausgeliefert.

Einschalten: Es ist standardmäßig deaktiviert, da das Einschalten es jedem Gerät in Ihrem Heimnetzwerk ermöglicht, die Wiedergabe auf Ihrem PC zu starten. Sie aktivieren es mit einem Kontrollkästchen auf der Registerkarte „Allgemein“ des Einstellungsdialogs. Die drei Rollen des Plugins haben jeweils ihr eigenes Kontrollkästchen dort – meine Bibliothek teilen (Server), andere an mich abspielen lassen (Renderer) und auf andere Geräte abspielen (Kontrollpunkt) – und der Dialog zeigt nur die Einstellungsregisterkarten an, die die von Ihnen aktivierten Rollen tatsächlich benötigen, sodass Sie nie mit Optionen konfrontiert werden, die für Sie nicht relevant sind.

Ihre Geräte unterscheiden: Sie können dem Renderer einen beliebigen Namen geben (er beginnt als „MusicBee (yaiol)“). Dieser Name erscheint in der Liste der Wiedergabeziele Ihres Telefons, sodass Sie, wenn mehr als ein PC MusicBee ausführt, erkennen können, welches welches ist. Eine Namensänderung wird sofort wirksam, ohne Neustart.

Bestmöglicher Klang bei der Wiedergabe an sich selbst: Wenn Sie MusicBees eigene Bibliothek von Ihrem Telefon aus durchsuchen und einen Titel an dasselbe MusicBee zurücksenden, erkennt das Plugin, dass es aufgefordert wird, eine seiner eigenen Dateien abzuspielen, und spielt sie einfach direkt von Ihrer Festplatte ab. Das Ergebnis ist exakt und sofort – bitgenau, mit MusicBees eigenem Equalizer und Lautstärkeausgleich – anstatt das Audio sinnlos ins Netzwerk und direkt wieder zurückzuschieben.

Privater Betrieb: Die drei Rollen arbeiten unabhängig voneinander, sodass Sie den Renderer einschalten können, während die Bibliotheksfreigabe deaktiviert bleibt. In dieser „Nur-Renderer“-Konfiguration bleibt Ihre Bibliothek vollständig vor dem Netzwerk verborgen – nur das Wiedergabeziel wird angekündigt – und MusicBee wird niemals anbieten, an sich selbst abzuspielen.


Wiedergabeverhalten

N02 – 5.1 FLAC wird nicht automatisch heruntergemischt

Was: Die Kanalanzahlbegrenzung in MediaServerDevice.GetEncodedFile war If StereoOnly OrElse Not isPcmData Then channelCount = 2. Die Klausel Not isPcmData mischte jede Nicht-PCM-Transkodierung (FLAC, MP3, AAC, Ogg) stillschweigend auf Stereo herunter, unabhängig von der Kanalanzahl der Quelle, was 5.1-fähige Renderer bei 5.1 FLAC-Quellen nutzlos machte. Jetzt schließt die zweite Klausel FLAC aus: Not isPcmData AndAlso encoder.Codec <> FileCodec.Flac. FLAC 5.1 wird durchgeleitet; MP3/AAC/Ogg erzwingen weiterhin Stereo, da MusicBees Kommandozeilen-Encoder für diese Formate eine 2-Kanal-Eingabe erwarten.

Warum: Der ganze Sinn der Transkodierung einer 5.1 FLAC-Quelle in FLAC-Ausgabe besteht darin, den Mehrkanal-Mix zu erhalten. Eine stille Heruntermischung machte die FLAC-Transkodierungsoption für Surround-Hören nutzlos. Mit N02 wird dies korrekt gehandhabt.


Architektur

Die strukturelle Änderung, die den Fork für eine große Bibliothek nutzbar macht – im ursprünglichen Plugin nicht vorhanden.

N03 – Lazy (On-Demand) Browse-Baum

Was: Das ursprüngliche Plugin baute den gesamten Browse-Baum beim Start von MusicBee auf – es enumerierte jeden Titel, führte für jede Datei Library_GetFileTags vollständig aus und setzte die gesamte Container-Hierarchie zusammen – bevor der HTTP-Port geöffnet wurde. Bei einer echten Bibliothek (über 50.000 Titel, 5.400 Podcast-Episoden, Hunderte von Sendern) bedeutet das Minuten an Kaltstart, und der Baum bleibt für immer im RAM, einschließlich Zweigen, die kein Client jemals öffnet. Dieser Fork baut nichts im Voraus auf: Der Root-Knoten exponiert einen L:-präfigierten Platzhalter pro Endpunkt (L:music, L:podcast, L:filter:…); jede Ebene wird nur berechnet, wenn ein Client hineinbrowsed (LazyBrowseEnsureLazyEndpointInMemory → Caches pro Ebene), und Bibliotheksänderungsbenachrichtigungen leeren die Caches (SetLibraryDirty).

Warum: Der Kaltstart ist im Wesentlichen sofort – der HTTP-Port ist geöffnet, sobald MusicBee die Plugin-Initialisierung abgeschlossen hat – und der Speicherverbrauch bleibt proportional zu dem, was durchsucht wurde, nicht zur Bibliotheksgröße. Kompromiss: Das erste Browsen in einen Endpunkt zahlt dessen Ladekosten; der erneute Zugriff wird bis zur nächsten Bibliotheksänderung zwischengespeichert. Dies ist die Grundlage, auf der alles andere aufbaut. Vollständige Hinweise: FIXES.md.


Netzwerk & Robustheit

Härtung des Bindungspfads des HTTP-Servers. Das ursprüngliche Plugin stirbt stillschweigend, wenn sein Port nicht verfügbar ist.

N04 – Selbstheilende HTTP-Portbindung

Was: Der HTTP-Server des Plugins stirbt nicht mehr, wenn sein konfigurierter Port nicht verfügbar ist. Drei miteinander verbundene Änderungen:

  1. Automatischer Fallback bei Bindungsfehler. HttpServer.Start versucht den konfigurierten Port und scannt bei einer SocketException bis zu 20 Ports aufwärts nach dem ersten freien. Der tatsächlich gebundene Port wird in einem neuen Plugin.boundServerPort aufgezeichnet, und alles, was den Server bewirbt – SSDP LOCATION URLs (NOTIFY + M-SEARCH-Antwort), die Geräte-URL (PrimaryHostUrl), die Router-Portweiterleitung und die SSDP/Kontrollpunkt-Selbstfilter – liest nun boundServerPort anstelle von Settings.ServerPort. UPnP-Clients entdecken den echten Port über SSDP, sodass ein verschobener Port für Renderer transparent ist.
  2. Benutzerbenachrichtigung. Wenn ein Fallback auftritt (der gespeicherte Port ist nicht der verwendete), informiert eine lokalisierte MessageBox (WarnPortInUse) den Benutzer darüber, welcher Port tatsächlich verwendet wird und dass Geräte ihn trotzdem finden werden – da das Plugin headless läuft und eine Nachricht im Dialog nur von jemandem gesehen würde, der bereits ein Problem vermutete.
  3. Neustart-Wiederherstellung. RestartServer (der Neustartpfad beim Speichern der Einstellungen) dereferenzierte Plugin.controller / Plugin.server früher blind. Wenn die anfängliche Initialise vor deren Erstellung einen Fehler auslöste (genau das, was ein fehlgeschlagenes Binden verursachte), führte das nächste Speichern der Einstellungen zu einer NullReferenceException – was ein halb totes Plugin hinterließ. Es erstellt und startet sie nun neu, wenn Nothing, sodass das Speichern eines funktionierenden Ports das Plugin ohne vollständigen MusicBee-Neustart wiederbelebt.

Warum: Der Auslöser war ein echter Benutzerzwischenfall. Der alte Standardport 49382 liegt im dynamischen Bereich von Windows (49152-65535), wo Hyper-V/WSL2/Docker/WinNAT große Blöcke reservieren, die sich bei jedem Booten verschieben – so schlug die Bindung mit WSAEACCES („Zugriff verboten“) auf einem Rechner fehl, auf dem sie monatelang funktioniert hatte. Die Änderung des Standards auf einen freien Port kollidierte dann mit Serviio (einem separaten DLNA-Server, der bereits auf dem neuen Port lief) und schlug mit WSAEADDRINUSE fehl. Jeder Fehler wurde in Initialise verschluckt, was das Plugin stillschweigend sterben ließ und dann beim nächsten Speichern der Einstellungen eine NRE auslöste. Nach N04 heilt sich eine Portkollision selbst – der Server läuft auf dem nächsten freien Port weiter, der Benutzer wird informiert und Clients entdecken ihn neu – anstatt das gesamte Plugin zum Absturz zu bringen.

Implementierung:

  • Standardport von 493829779 verschoben (unterhalb des dynamischen Bereichs, sodass Windows ihn nie automatisch reserviert; kein bekannter Standard für Medienserver) in allen drei ServerPort-Deklarationen + dem Fallback bei Einstellungs-Parse-Fehlern.
  • Plugin.boundServerPort (neues gemeinsames Feld) enthält den aktiven Listening-Port; activeServerPort bleibt der konfigurierte Snapshot, damit die Logik des „Neustart erforderlich“-Badges bei einem Fallback nicht fälschlicherweise auslöst.
  • HttpServer.PortScanRange = 20; der Scan stoppt beim ersten erfolgreichen TcpListener.Start() und wirft die letzte Ausnahme nur, wenn alle Versuche fehlschlagen.
  • Neuer EN-Ressourcenschlüssel WarnPortInUse (Übersetzungen folgen dem Locale-Durchlauf zur Veröffentlichungszeit).

N05 – SSDP-Ankündigungen über die Multicast-Gruppe (VPN / Punkt-zu-Punkt)

Was: SSDP-Ankündigungen werden an die UPnP-Multicast-Gruppe (239.255.255.250) gesendet, anstatt an eine IP-Broadcast-Adresse. Der harmlose Fehler „cannot access a disposed object“, der protokolliert wird, wenn eine SSDP-Suchantwort mit einem Serverneustart kollidiert, wird ebenfalls unterdrückt.

Warum: Bei Punkt-zu-Punkt-/VPN-Netzwerkadaptern ist IP-Broadcast nicht anwendbar – der alte Broadcast-Versand schlug mit „invalid argument“ fehl und Ankündigungen wurden verpasst, sodass das Plugin für Clients auf diesen Verbindungen unsichtbar war. Die Ankündigung an die richtige Multicast-Gruppe behebt die Erkennung genau auf diesen Adaptern.

Bibliotheksnavigation

Diese wurden in diesem Fork ausgeliefert und sind nicht im ursprünglichen Plugin enthalten. Sie entstanden durch das tatsächliche Browsen der eigenen Ausgabe des Plugins von echten UPnP-Clients.

N06 – Filterbasierte Bibliotheksfreigabe

Was: MusicBees Filter-Registerkarten (.xautopf-Dateien im MusicBee-Ordner des Benutzers) werden zu UPnP-Root-Containern in der Bibliothek des Plugins. Die Titel jedes Filters können dann in einer Hierarchie von AlbumArtistSort → Album → Tracks durchsucht werden.

Warum: Benutzer mit kuratierten MusicBee-Filtern (z. B. „5-Sterne-Titel“, „Zuletzt hinzugefügt“, „Klassik → Barock“) erwarten, diese beim Browsen des Plugins von einem UPnP-Client aus zu finden. Das ursprüngliche Plugin exponierte nur den rohen Bibliotheksbaum.


N07 – SortAlbumArtist-Feldverdrahtung

Was: Das Plugin liest nun MusicBees MetaDataType 165 (Sort Album Artist) und verwendet es zum Gruppieren/Sortieren von Künstlern in Browse-Ansichten.

Warum: Hi-Fi-Browser und Audiophile verwenden Sortier-Künstlernamen („Beethoven, Ludwig van“ anstelle von „Ludwig van Beethoven“), um Bibliotheken zu organisieren. Standarderwartung für ernsthafte Hörer. In beiden Upstreams fehlend.


N08 – Umgang mit mehrwertigen AlbumArtisten

Was: Wenn das AlbumArtist-Feld eines Albums mehrere Künstler enthält, die durch "; " getrennt sind (z. B. „yaiol; Ars Ricercata“), erscheint der Titel nun unter jedem Künstler in den Browse-Ansichten, nicht unter einem einzigen Frankenstein-Künstler, der die Namen kombiniert.

Warum: Kollaborative Alben und Kompilationen müssen unter jedem Kollaborateur erscheinen. Ohne dies wären die Hälfte der Suchpfade zum Auffinden des Albums unterbrochen.


N09 – Album-Container-Artwork (upnp:albumArtURI)

Was: Album-Container-Knoten in DIDL-Browse-Antworten enthalten nun ein upnp:albumArtURI-Element, das auf das Albumcover verweist.

Warum: Ohne dies zeigt jedes Album in der Browse-Ansicht eines UPnP-Clients ein generisches Symbol anstelle des Albumcovers. Visueller Hinweis zur Navigation; von jedem modernen Hi-Fi-Browser erwartet.


N10 – Titelreihenfolge innerhalb von Filteralben

Was: Titel innerhalb eines über Filter exponierten Albums werden nun nach Disc-Nummer und dann nach Titelnummer sortiert.

Warum: Standard-Albumreihenfolge. Ohne explizite Sortierung wurden die Titel in der Reihenfolge zurückgegeben, in der der Filter sie zufällig zurückgab – meist zufällig aussehend.


N11 – Korrektur des Wiedergabelisten-Ordnerbaums

Was: Die Funktion LoadLibraryPlaylists (ursprünglich von Steven Mayall, ca. 2014) konnte nicht in neu erstellte Wiedergabelistenordner absteigen. Die erste Wiedergabeliste in jedem Ordner sowie alle Unterordner landeten verwaist auf der Root-Ebene.

Warum: Elf Jahre lang im ursprünglichen Plugin vorhanden. Innerhalb von 30 Sekunden nach dem Öffnen von BubbleUPnP und dem Klicken auf Wiedergabelisten sichtbar. In yaiol behoben durch korrektes Rekursieren in neu erstellte Ordner während des Baumaufbaus.


N12 – Bereinigung von XML-illegalen Steuerzeichen

Was: Jeder Titel mit einem Tag, der ein C0-Steuerzeichen enthielt (z. B. 0x19 von einem fehlerhaften Kodierungsdurchlauf – UTF-8 → Latin-1 → Zurückschneiden von 0x99 zu 0x19), führte dazu, dass die gesamte Browse-Antwort mit Action Failed fehlschlug, sobald der fehlerhafte Titel in einen paginierten Batch gelangte.

Warum: XML 1.0 verbietet die meisten C0-Steuerzeichen, und XmlWriter wirft eine Ausnahme, wenn es aufgefordert wird, solche zu schreiben. Im ursprünglichen Plugin vorhanden. Behoben durch Entfernen ungültiger Zeichen an jedem Library_GetFileTags-Exit-Punkt über XmlConvert.IsXmlChar.


N13 – Deterministische Radiosenderliste über paginiertes Browsen hinweg

Was: Das Browsen für den Radio-Container fiel in den generischen Dateilisten-Zweig, der bei jedem Aufruf files.Sort(AlbumFileComparer) aufrief. Radio-Einträge haben leere Album-/Disc-/Track-Tags, sodass jeder Vergleich 0 zurückgab – List(Of T).Sort ist instabil und erzeugt bei jedem Aufruf eine andere Reihenfolge. UPnP-Kontrollpunkte paginieren (BubbleUPnP ruft 0..15 und dann 16..Ende ab); zwischen den beiden Aufrufen wurde die Liste neu gemischt, sodass einige Sender auf beiden Seiten erschienen (Duplikate) und einige auf keiner (fehlend) – was bei jeder Aktualisierung zufällig aussah.

Warum: Im ursprünglichen Plugin vorhanden (dessen Autor browsed Radio nie über UPnP). Hier behoben mit einem dedizierten ContainerCategory.Radio-Zweig in Browse, keine Sortierung pro Aufruf; radioFiles wird einmal beim Laden nach Titel sortiert (stabil). Paginiertes Browsen zeigt nun eine deterministische Reihenfolge; Seite 1 und Seite 2 sind disjunkt.


N14 – UPnP-Suche nach Album-Klasse gibt Album-Container zurück

Was: Die UPnP-Suche nach Album-Klasse-Abfragen (upnp:class = "object.container.album.musicAlbum", z. B. BubbleUPnPs „Zufällige Alben“) gab die vollständige Titelliste anstelle von Album-Containern zurück, sodass der Client null Alben anzeigte. Der ursprüngliche Handler analysierte nur geklammerte Kriterien und gab dann alle Titel aus, unabhängig von der angeforderten Klasse.

Warum: Hier behoben – Album-Klasse-Abfragen enumerieren nun unterschiedliche Alben (gruppiert nach AlbumArtist+Album) und geben jedes als ordnungsgemäßen musicAlbum-Container mit Cover-Art aus, adressierbar über den virtuellen ID-Raum Salb<idx>, sodass der Client in ein Ergebnis eintauchen und es abspielen kann.


N15 – Funktionierende, bereichsbewusste UPnP-Suche mit Klick-Durchgang

Was: Das Original bewarb keine Suchfunktionen (GetSearchCapabilities gab leer zurück), sodass Clients sich weigerten, überhaupt eine Suche zu senden; und das alte Backend las aus musicFiles, das in der Lazy-Tree-Ära dauerhaft leer war. Dieser Fork bewirbt die tatsächlich durchsuchbaren Eigenschaften, implementiert Titel-nach-Titel- und Alben-nach-Titel-Suchen gegen die Lazy-Bibliothek (HandleLazySearch), begrenzt die Abfrage auf den aktuellen Zweig des Clients, wenn eine echte Container-ID gesendet wird (ersetzt andernfalls L:music, damit Top-Bar-Suchen keine Podcast-/Radio-/Hörbuch-Geräusche mit sich ziehen), und macht Album-Ergebnisse über synthetische Ssrch_alb_*-IDs anklickbar, die ein Browse-Frühzweig auf die Titel des Albums zurückführt. (Der Teil „Album-Klasse-Ergebnisse als Container“ ist N14.)

Warum: Die Suche in BubbleUPnP entwickelte sich von „Bibliothek unterstützt keine Suche“ zu nützlichen, bereichsbezogenen, abspielbaren Ergebnissen. Vollständiges Design + abgelehnte Ansätze: SEARCH.md.


N16 – UPnP-Cache-Invalidierung (SystemUpdateID)

Was: Das Original gab eine konstante SystemUpdateID=0 zurück – den UPnP ContentDirectory-Cache-Invalidierungsvertrag – sodass spezifikationskonforme Clients (BubbleUPnP) die Bibliothek als unveränderlich behandelten: veraltete Browse-Ergebnisse, 404-Thumbnails nach einer URL-Schemaänderung und das „MusicBee zweimal neu starten, um Änderungen zu sehen“-Spiel. Dieser Fork initialisiert SystemUpdateID beim Laden aus Epochensekunden (sodass jeder Neustart streng vor dem letzten liegt) und erhöht sie bei jeder Bibliotheksmutation und Einstellungsänderung (SetLibraryDirty / ResetCacheBumpSystemUpdateId).

Warum: Clients übernehmen Bearbeitungen, neue Dateien und Einstellungsänderungen zuverlässig bei ihrem nächsten Browse. Bekannte Einschränkung: Abonnierte Clients erhalten den neuen Wert nicht aktiv über GENA erneut zugestellt (als zukünftige Arbeit geparkt); sie sehen ihn immer noch bei ihrem nächsten Browse.


N17 – Podcast-Abonnement-Artwork

Was: Podcast-Kacheln zeigten keine Bilder – jede /PodcastThumbnail/-Anfrage führte zu einem 404-Fehler. Zwei gestapelte Fehler: Die Auflösungskette überprüfte nie MusicBees tatsächlichen Artwork-Cache (%LocalAppData%\MusicBee\InternalCache\Subscriptions\<name>.jpg, von wo die Desktop-Benutzeroberfläche lädt), und die Unescape+Lowercase-Funktion der HTTP-Schicht veränderte den Feed-URL-Route-Schlüssel bis zu seinem letzten Pfadsegment. Dieser Fork löst Artwork aus MBs InternalCache auf und leitet Suchanfragen über einen URL-sicheren Slug weiter, der die HTTP-Schicht intakt übersteht (PodcastSlug / podcastSubIdBySlug).

Warum: Abonnement-Artwork wird nun in Browse-Ansichten gerendert (alle 22 zuvor 404-fehlerhaften Anfragen werden aufgelöst).


N18 – Hierarchisches (begrenztes) Tag-Browsing

Was: Jedes Feld kann auf der Registerkarte „Bibliotheksoptionen“ als hierarchisch markiert und mit einem einstelligen Trennzeichen versehen werden (ein Feldauswähler + Trennzeichenfeld mit Hinzufügen/Entfernen, das in den Plugin-Einstellungen gespeichert wird). Setzen Sie Gruppierung auf / und einen Wert wie Jazz/Cool Jazz, dann wird es als Jazz › Cool Jazz durchsucht, anstatt als ein flacher Eintrag. Titel, die genau an einem Zweig getaggt sind (nur Jazz), erhalten ihren eigenen [Jazz]-Knoten, sodass nichts verborgen bleibt, ein Zweig mit einem einzelnen Kind kollabiert von selbst, und ; wird als Trennzeichen abgelehnt, da es MusicBees eigener Mehrwert-Separator ist.

Warum: Tiefe Tag-Taxonomien, die ein Benutzer bereits in einem einzigen Feld kodiert hat (Genre-Bäume, Stimmungs-Hierarchien, „Klassik/Barock/Konzert“), werden endlich als der Baum durchsucht, den der Tag beschreibt, anstatt als eine flache Wand von schrägstrichgetrennten Zeichenketten, die der Benutzer von Anfang bis Ende lesen muss.


N19 – Einzelner Root-Pfad, beschriftet nach seinem Gruppierungsfeld

Was: Ein einzelner Browse-Pfad im Root-Verzeichnis wird nach seinem Gruppierungsfeld (z. B. „Genre“) und nicht nach seinem vollständigen Kurzpfad benannt, passend zur Benennung zusammengeführter Erstfeldgruppen.

Warum: Der Browse-Baum liest sich konsistent – eine Benennungsregel, ob ein Root-Eintrag allein steht oder mit Geschwistern (N20) zusammengeführt wurde – anstatt dass ein einzelner Root-Eintrag einen ausführlichen internen Pfad anzeigt, während seine zusammengeführten Nachbarn einen sauberen Feldnamen zeigen.


N20 – Zusammenführen von Browse-Pfaden, die ein erstes Feld teilen

Was: Zwei Browse-Pfade, die dasselbe erste Feld teilen – „Genre / Sort Album Artist“ und „Genre / Podcast People“ – werden zu einem einzigen Genre-Root-Ordner zusammengefasst, der zuerst die Genre-Werte auflistet und sich dann in die beiden Ansichten aufteilt, anstatt zwei nahezu duplizierte „Genre / …“-Einträge nebeneinander im Root-Verzeichnis anzuzeigen.

Warum: Ein Benutzer mit mehreren verwandten Ansichten, die unter einem gemeinsamen Feld verschachtelt waren, sah das Root-Verzeichnis mit fast identischen Top-Level-Einträgen überladen. Das Zusammenführen hält das Root-Verzeichnis lesbar und gruppiert die verwandten Ansichten dort, wo sie hingehören – unter ihrem gemeinsamen Feld.


N21 – Kategorisierte Browse-Pfade (Standard / Radio / Podcast)

Was: Jeder Browse-Pfad ist nach Kategorie typisiert – Standard, Radio oder Podcast. Die Vorlagenliste ist in diese drei Abschnitte gruppiert, der Feldauswähler jeder Vorlage bietet nur die Felder an, die die Daten dieser Kategorie tatsächlich liefern können, und eine Vorlage kann nur auf passende Knoten im Ansichtsbaum angewendet werden (inkompatible Knoten werden ausgegraut und können nicht ausgewählt werden). Die reservierten Radio- und Podcast-Vorlagen können nicht gelöscht werden, sodass ihr Kategorieabschnitt nie verschwindet.

Warum: Ohne die Typisierung könnte ein Benutzer ein Layout erstellen, das stillschweigend leer bleibt – ein Radiosender hat kein „Album“, eine Podcast-Episode hat keinen „Albumkünstler“ – und dies erst entdecken, indem er von einem UPnP-Client aus zu einem leeren Ordner navigiert. Die Beschränkung des Feldmenüs und der Anwendungsziele auf die tatsächlichen Daten der Kategorie macht leere Layouts unbaubar.


N22 – Podcasts nach Veröffentlichungsjahr gruppieren

Was: Das Veröffentlichungsdatum jeder Podcast-Episode wird eingelesen, sodass ein Podcast-Browse-Pfad mit einer Jahres-Ebene Episoden nach Jahr gruppiert, anstatt sie unter einem einzigen „Unbekannt“ zusammenzufassen.

Warum: Große Podcast-Abonnements werden wie der Rest der Bibliothek nach Jahr navigierbar, anstatt dass jede Episode in einem undatierten Haufen landet, weil das Plugin das Veröffentlichungsdatum pro Episode nie beachtet hat.


N23 – Gruppierungsebenen mit Einzelergebnissen zusammenführen

Was: Eine Gruppierungsebene, die sich auf einen einzelnen Wert auflöst – eine Ebene für den Aufnahmetyp, die nur „LP“ für einen Künstler anzeigt, der nur LPs gemacht hat, oder eine Buchstabenebene mit einem einzelnen Buchstaben – wird automatisch übersprungen, wodurch der Benutzer direkt zu deren Inhalt gelangt.

Warum: Das Browsen durch einen Ordner, der genau einen Ordner enthält, ist reine Reibung. Das Zusammenführen der Einzelauswahl-Ebene entfernt den unnötigen Klick, ohne zu ändern, was der Benutzer erreichen kann.


N24 – Jahresgruppierung/-suche anhand des Datumsfelds von MusicBee

Was: Die Jahresbedingung fragt MusicBees vollständiges Datumsfeld „Jahr“ nicht mehr mit einem bloßen vierstelligen Wert ab, und das fest codierte Jahresfeld-Aliasing ist verschwunden, sodass jedes Gruppierungsfeld nun generisch aus der Pfaddefinition aufgelöst wird.

Warum: Bei Bibliotheken, deren Jahres-Tag ein vollständiges Datum enthält, lieferte die Gruppierung oder Suche nach Jahr zuvor keine Ergebnisse – die vierstellige Abfrage passte nie zum vollständigen Datumsfeld. Das Abfragen des richtigen Feldes lässt die Jahresgruppierung und -suche die Titel wieder finden.


N25 – Separate Gruppierungsfelder „Jahr“ und „Jahr (jjjj)“

Was: Album-Gruppierungs- und Browse-Pfade exponieren nun beide eigenen Jahresfelder von MusicBee – Jahr (das vollständige Datums-Tag) und Jahr (jjjj) (nur das vierstellige Jahr) – sodass der Benutzer bei der Definition einer Album-Gruppierung oder eines Browse-Pfads eines davon auswählen kann.

Warum: Die beiden Felder bedeuten in MusicBee unterschiedliche Dinge, und deren Zusammenfassung ließ diese Unterscheidung verloren gehen. Das Anzeigen beider ermöglicht es einem Benutzer, alle Veröffentlichungen eines Jahres zusammenzufassen (jjjj) oder die genaue Datumsreihenfolge beizubehalten (vollständiges Jahres-Tag), je nach Absicht.


N32 - Angeheftete Filter und Wiedergabelisten nach Art in der Wurzel gruppiert

Was: ein angehefteter Filter erscheint jetzt direkt unter dem Ordner Filter in der Navigationswurzel und eine angeheftete Wiedergabeliste direkt unter dem Ordner Wiedergabelisten, anstatt dass sich alle angehefteten Elemente in einem Klumpen am Ende der Wurzel sammeln. Jede angeheftete Verknüpfung steht bei ihrer eigenen Art.

Warum: je mehr Verknüpfungen ein Benutzer anheftet, desto schwerer lässt sich ein einzelner Klumpen aus gemischten Filtern und Wiedergabelisten am Ende überblicken, und er trennt jede Verknüpfung von dem Ordner, zu dem sie gehört. Das Gruppieren angehefteter Elemente unter ihrer eigenen Kategorie hält die Wurzel lesbar und jede Verknüpfung neben den Dingen, zu denen sie gehört.

Einstellungsdialog & Paketierung

N26 – Gliederter Einstellungsdialog

Was: Die Seite „Einstellungen“ erhielt ein Layout mit linker Navigation und Abschnitten: Allgemein / Wiedergabe / Bibliothek / Geräteprofile / Diagnose.

Warum: Das Original war eine einzige lange, flache Liste aller Einstellungen – für den Entwickler, der es gebaut hat, in Ordnung, für alle anderen verwirrend. Die Gliederung gruppiert verwandte Optionen und lässt den Dialog eher wie moderne App-Einstellungen wirken.


Assembly + Plugin-Umbenennung (keine F-ID – Verpackungshinweis)

Was: Die kompilierte DLL heißt mb_UPnP_yaiol.dll und das Plugin meldet sich als „MusicBee UPnP (yaiol)“. Unterscheidet sich von der ursprünglichen mb_Upnp.dll.

Warum: Benutzer können yaiol neben dem ursprünglichen Plugin installieren und das Verhalten nebeneinander vergleichen.


Badge-System – Anzeige des Laufzeitstatus (Mechanismus hinter F40)

Was: Ein generisches UI-Muster zur Anzeige wichtiger Laufzeitbedingungen als sichtbare farbige Badges im Einstellungsdialog. Aktuelle Instanzen:

  • ⚠ Max. Verbindungen (N04) – wird ausgelöst, wenn die Obergrenze für maximale Verbindungen mindestens einmal seit dem Start von MusicBee erreicht wurde. Sticky Session Flag Plugin.MaxConnectionsHit. Wird in WaitOnSendBarrier gesetzt, wenn kein Slot frei ist.
  • ⚠ Neustart erforderlich – wird ausgelöst, wenn eine gespeicherte Einstellung einen MusicBee-Neustart erfordert, um wirksam zu werden. Sticky Session Flag Plugin.RestartRequired. Wird im Save-Handler des Dialogs gesetzt, wenn der neue persistierte Wert vom Laufzeit-Snapshot abweicht (Plugin.activeMaxConnections, Plugin.activeServerPort, Plugin.activeIpAddress). Neustart-erforderliche Einstellungen sind auf diejenigen beschränkt, die sich wirklich nicht im laufenden Betrieb neu laden lassen – HTTP-Server-Bindungsparameter und das einmal bei Initialise erstellte SemaphoreSlim.

Warum: Die Protokolldatei des Plugins ist für technische Benutzer, die debuggen, in Ordnung, aber ein nicht-technischer Benutzer, der auf „Gerät klingt falsch“ oder „Wiedergabe ist langsam“ starrt, wird niemals Diagnose → Protokoll anzeigen öffnen. Badges erfassen die Fälle, in denen der Benutzer wissen muss, dass etwas passiert ist, und zeigen es beim nächsten Öffnen des Plugins an – auffindbar, ohne etwas lesen zu müssen.

Wiederverwendbar für die Zukunft:

  • Profil-Fehlübereinstimmung erkannt (Geräte-User-Agent passte nie zu einem Profil, Fallback auf Generisch).
  • NextURI-Backoff ausgelöst (F13 – lückenlose Wiedergabe für die Sitzung auf einem fehlerhaften Gerät deaktiviert).
  • Bibliotheks-Scan fehlgeschlagen / teilweise.
  • Renderer-Verbindung während der Sitzung verloren.
  • Jede andere Bedingung, bei der „einmal passiert, Benutzer sollte es wissen“ besser ist als „still unter 1000 anderen Zeilen protokolliert“.

Implementierungskonventionen:

  • Badge-Beschriftungen befinden sich auf Dialogebene (nicht innerhalb eines Panels), sodass sie unabhängig davon sichtbar sind, in welchem Abschnitt sich der Benutzer befindet.
  • Positioniert entlang der unteren Reihe in der Nähe von Speichern/Abbrechen (aktuell: y=410 horizontal gestapelt).
  • Jedes Badge hat ein entsprechendes Sticky Session Flag in Plugin, das auf True wechselt, wenn die Bedingung eintritt, und nur bei einem MusicBee-Neustart zurückgesetzt wird.
  • Ressourcen: <Condition>Badge (Beschriftungstext, mit ⚠ präfixiert) + <Condition>BadgeTip (Tooltip, der Ursache + Abhilfe erklärt).
  • Für „gespeicherte Einstellung benötigt Neustart“-Badges wird ein Laufzeit-Snapshot bei Plugin.Initialise() erstellt und nach Settings.SaveSettings() im Dialog-Speicher-Handler mit Settings.* verglichen.

N27 – Abbrechen verwirft Pfad-/Vorlagenbearbeitungen

Was: Änderungen an Pfaden und Vorlagen im Einstellungsdialog werden nun verworfen, wenn der Benutzer auf „Abbrechen“ klickt, anstatt stillschweigend angewendet zu bleiben, und jede während der Sitzung entfernte reservierte Vorlage wird neu erstellt. (Vorlagen werden ansonsten live gespeichert, während sie bearbeitet werden – es gibt keinen separaten Speichern-Button auf der Registerkarte „Pfade“.)

Warum: Abbrechen sollte Abbrechen bedeuten. Zuvor fand ein Benutzer, der mit Pfad-/Vorlagenänderungen experimentierte und diese rückgängig machte, die Änderungen bereits übernommen vor, ohne eine Möglichkeit, sie rückgängig zu machen, außer jede manuell neu zu erstellen.


N28 – Literale Ampersands im Feldauswahlmenü

Was: Ein Feld, dessen Name „&“ enthält – z. B. „Stimmung & Kontext“ – rendert das Ampersand im Feldauswahlmenü wörtlich, anstatt es als Alt-Mnemonic-Präfix zu schlucken.

Warum: Feldnamen mit einem Ampersand wurden falsch angezeigt (das Zeichen verschwand und der nächste Buchstabe wurde zu einem Beschleuniger), was den Menüeintrag schwer erkennbar machte.


N29 – Stabiler, unübersetzter Einstellungsfenstertitel

Was: Der Titel des Einstellungsfensters ist auf den Markenstring „MusicBee UPnP Plugin“ festgelegt und variiert nicht mehr mit der Oberflächensprache; der sprachspezifische DialogTitle-String wurde aus jedem Locale-Bundle entfernt.

Warum: Ein Fenstertitel, dessen Wortlaut sich je nach Sprache änderte, war eine übersetzbare Oberfläche ohne Nutzen – der Titel ist ein Markenname. Das Festlegen hält ihn überall stabil und konsistent.

Lokalisierung

Das ursprüngliche Plugin ist nur auf Englisch verfügbar. Dieser Fork ist vollständig lokalisierbar – jeder benutzerspezifische String durchläuft ein Ressourcen-Bundle, und das Plugin erkennt automatisch die UI-Sprache von MusicBee.

N30 – Mehrsprachige Benutzeroberfläche (Übersetzungen ausstehend)

Was: Die Lokalisierungsmaschinerie ist vollständig und wird ausgeliefert. Localisation.vb liest die ausgewählte Sprache von MusicBee aus MusicBee3Settings.ini (<SystemLanguage> Endonym) und wendet die passende .NET-Kultur auf den Thread an, sodass My.Resources.Resources.* den lokalisierten String zurückgibt. Jedes benutzerspezifische Label/Button/Nachricht ist mit einem Ressourcenschlüssel verbunden (Designer-Steuerelemente über ApplyDesignerExtras + sync-en-locale.js; Laufzeit-Strings wie WarnPortInUse manuell hinzugefügt). Was noch nicht erledigt ist, ist die eigentliche Übersetzung: Es existiert nur das englische Quell-Bundle (Resources.resx) – die Satelliten-Bundles für die anderen Sprachen werden in einem Batch-Durchlauf erstellt, wenn das Plugin feature-complete ist (stückweises Übersetzen, während Strings noch in Bewegung sind, verschwendet Aufwand).

Zielsprachen (die von MusicBee selbst angebotenen, 1:1 von endonymToCulture abgeglichen, sodass das Plugin MusicBees Sprache automatisch folgt):

Arabisch (ar) Tschechisch (cs) Deutsch (de) Griechisch (el)
Spanisch (es) Französisch (fr) Ungarisch (hu) Italienisch (it)
Koreanisch (ko) Niederländisch (nl) Norwegisch (nb) Polnisch (pl)
Portugiesisch BR (pt-BR) Portugiesisch PT (pt-PT) Schwedisch (sv) Türkisch (tr)
Ukrainisch (uk) Russisch (ru) Japanisch (ja) Vereinfachtes Chinesisch (zh-CN)
Traditionelles Chinesisch (zh-TW) Englisch (en, Quelle)

Variantenrichtlinie (gemäß der Workspace-Locale-Regel): PT und ZH sind in separate Bundles aufgeteilt, da Vokabular/Schrift tatsächlich divergieren (pt-BR/pt-PT, zh-CN/zh-TW). EN ist ein einzelnes Bundle – MusicBees „English(US)“ (en-US) fällt über die .NET-Kulturkette auf en zurück, sodass kein separates US-Bundle erstellt wird. ES und FR sind ebenfalls Single-Locale.

Warum: Die Einstellungen eines UPnP-Plugins („kein rohes PCM verwenden“, „Little-Endian PCM erzwingen“, Port-Fallback-Warnungen) sind in der Muttersprache kryptisch genug. Der eigenen UI-Sprache von MusicBee zu folgen – anstatt Englisch zu erzwingen – ist der Unterschied zwischen einem Tool, das ein nicht-englischsprachiger Benutzer konfigurieren kann, und einem, das er nicht kann. Keiner der Upstreams hat dies versucht.


N31 – Hilfelink öffnet sich in der vollständigen Oberflächensprache

Was: Das Öffnen des Hilfelinks vom Plugin aus berücksichtigt die vollständige Oberflächensprache des Benutzers (z. B. pt-BR, zh-CN), anstatt auf die Basissprache zurückzufallen, und sendet einen klareren Update-Check-Identifikator.

Warum: Ein Benutzer, der MusicBee in einer regionalen Variante (Brasilianisches Portugiesisch, Vereinfachtes Chinesisch) ausführte, wurde auf die Hilfeseite in der Basissprache weitergeleitet. Das Übertragen der vollständigen Kultur führt sie auf die Hilfeseite in genau der Sprache, die sie verwenden.

Korrekturen und Verbesserungen am ursprünglichen Plugin

Kernprotokoll & Wiedergabe

F01 – Aktualisierte Standard-DLNA-Geräteprofile

Was: Liefert neue Standardprofile für PlayStation 4, Xbox 360/One und modernes BubbleUPnP, mit Fähigkeitsflags (Sampleraten, Bittiefen, Codecs), die widerspiegeln, was diese Geräte heute tatsächlich unterstützen.

Warum: Die Standardeinstellungen des ursprünglichen Plugins waren um 2014 eingefroren. PS4/Xbox/BubbleUPnP haben seitdem Hi-Res-Audio-Unterstützung erhalten. Eine Neuinstallation spielt auf diesen Geräten sofort die beste Qualität ab, ohne dass der Benutzer die Geräteprofileinstellungen ändern muss.


F02 – Steuerung von Geräten, die MediaRenderer:3 bewerben

Was: Das Plugin prüft die UPnP-Dienstbeschreibung eines Renderers, um zu entscheiden, ob MusicBee ihn steuern kann. Das Original passte nur auf urn:schemas-upnp-org:device:MediaRenderer:1. Moderne Geräte bewerben :2 oder :3. F02 erweitert die Übereinstimmung.

Warum: Ohne dies erscheinen neuere Sonos-/WiiM-/Eversolo-Geräte einfach nicht als Ziele in MusicBees „Wiedergabe an“-Geräteliste – obwohl sie dasselbe Protokoll sprechen. Eine einzelne String-Präfix-Übereinstimmungskorrektur schaltet die gesamte moderne Gerätegeneration frei.


F03 – Option „Natives Streaming erzwingen“ pro Profil (Standard: AN)

Was: Wenn aktiviert, sendet das Plugin die ursprünglichen Dateibytes an das Gerät, ohne dass Transkodierung, DSP oder ReplayGain-Verarbeitung angewendet wird. Nur die vom Benutzer ausgewählte Rohdatei, Byte für Byte (abzüglich HTTP-Framing).

Warum: Laut Forenberichten ist dies der größte Gewinn bei der Wiedergabequalität. Hi-Fi-Benutzer, die teure Renderer kaufen, wünschen explizit eine bitgenaue Ausgabe; jede DSP-Berührung macht den Sinn zunichte. Standardmäßig AN, da die meisten modernen Geräte jeden Codec verarbeiten, den der Benutzer ihnen vorsetzt, und ReplayGain/EQ optional sein sollte. Es ist pro Profil, sodass Sie die Transkodierung für eine alte Xbox beibehalten können, während Sie nativ an einen Hi-Fi-DAC senden.


F04 – „Transkodierung erzwingen“ pro Profil

Was: Eine pro-Profil-Überschreibung, die jeden Stream an dieses Gerät durch den Transcoder zwingt, unabhängig von der nativen Codec-Unterstützung. Das Gegenteil von F03 (ForceNativeStream). Gegenseitig ausschließend mit F03 – die Benutzeroberfläche deaktiviert das andere automatisch, wenn eines aktiviert wird.

Warum: Ein einziger globaler Schalter wäre widersprüchlich mit dem pro-profil „Natives Streaming erzwingen“ (F03). Realer Fall: Gerät A ist ein Hi-Fi-DAC, der bitgenaue native Streams wünscht; Gerät B ist ein alter AV-Receiver, der bei FLAC Schwierigkeiten hat. Mit einem globalen Schalter müsste der Benutzer wählen – auf Kosten des anderen Geräts. Mit pro-Profil erhält jedes Gerät die richtige Antwort.

Implementierung:

  • StreamingProfile.ForceTranscoding As Boolean = False.
  • Persistenzschema auf v9 erhöht. Dateien vor v9 laden den alten globalen Wert einmal und kopieren ihn in alle Profile, wodurch das alte Verhalten während des Upgrades erhalten bleibt.
  • UI: Aus dem Diagnose-Panel entfernt, zum Abschnitt „Geräteprofile“ neben „Natives Streaming erzwingen“ hinzugefügt. Zwei-Wege-Ausschluss-Handler (CheckedChanged bei jedem deaktiviert den anderen vor dem Umschalten, um eine Endlosschleife zu vermeiden).
  • Entscheidungsstelle: Settings.ForceTranscodingstreamingProfile.ForceTranscoding in WriteAudioFileDIDL.

F05 – „Little-Endian PCM erzwingen“ pro Profil

Was: PCM-Streams (L16/L24 Mime-Typen) sind laut Spezifikation Big-Endian. Einige Geräte erwarten fälschlicherweise Little-Endian und spielen weißes Rauschen ab, wenn sie korrekte Big-Endian-Daten erhalten. F05 schaltet die Byte-Reihenfolge pro Profil um.

Warum: Ohne dies geben bestimmte Geräte eine Wand aus Rauschen aus. Das Symptom ist dramatisch und die Ursache ohne Kenntnis der PCM-Kodierung unsichtbar – der Schalter bietet Benutzern eine Rate-und-Prüfe-Lösung.


F06 – „Kein rohes PCM verwenden“ pro Profil

Was: Wenn das Gerät angibt, rohes PCM zu unterstützen, verwendet das Plugin dies. Einige Geräte lügen – sie akzeptieren den SOAP-Handshake, verfälschen aber tatsächliche rohe PCM-Daten, während sie PCM, das in einem WAVE-Container verpackt ist, korrekt verarbeiten. F06 erzwingt PCM-über-Wave, unabhängig davon, was das Gerät bewirbt.

Warum: Speziell bestimmte Marantz-Modelle – sie bewerben rohes PCM, aber nur WAVE funktioniert. Ohne dies werden rohe PCM-Streams verzerrt ausgegeben, ohne Fehlermeldung, die darauf hinweist.


F07 – „Content-Length“ pro Profil

Was: Welchen Wert im HTTP Content-Length-Header senden. Vier Optionen:

  • Standard – tatsächliche Byte-Anzahl, wenn bekannt, weglassen, wenn unbekannt.
  • Keine – den Header niemals senden (nur Chunked Encoding).
  • Nur PCM – nur für rohes PCM senden; für alles andere weglassen.
  • FestUInt32.MaxValue - 8192 senden (ein Sentinel für „riesige unbekannte Länge“).

Warum: UPnP/DLNA-Geräte reagieren sehr unterschiedlich auf Content-Length. Einige benötigen eine genaue Zahl, einige hassen es bei Streams, einige benötigen einen Sentinel-Wert „wirklich groß“, um das Buffering aufrechtzuerhalten. Dies wurde später von nur PCM auf alle Ausgabeformate erweitert, da die gleichen Probleme bei transkodierten MP3-/AAC-Streams auftraten.


F08 – „NextURI nicht löschen“ pro Profil

Was: Normalerweise löscht das Plugin die in der Warteschlange befindliche NextURI des Geräts, wenn die Warteschlange leer ist (sendet SetNextAVTransportURI mit einer leeren URL). Einige Geräte (insbesondere Denon) interpretieren eine leere NextURI als „alles stoppen“ und beenden die Wiedergabe sofort. F08 verhindert, dass das Plugin sie jemals löscht.

Warum: Ohne dies erleben Denon-Besitzer, dass das Gerät mitten im Titel abbricht, wenn die Warteschlange leer ist. Wenn F08 aktiviert ist, behält das Gerät die veraltete NextURI im Speicher (harmlos – sie wird einfach überschrieben, wenn das nächste Mal etwas in die Warteschlange gestellt wird).


F09 – FLAC als Transkodierungs-Ausgabeformat

Was: Das Dropdown-Menü für das Transkodierungsformat in den Geräteprofilen bietet nun FLAC neben PCM 16/24, MP3, AAC, Ogg an. Die Auswahl leitet den BASS-Encoder über MusicBees Standard-FLAC-Konvertierungs-Kommandozeile (derselbe Mechanismus, den MP3/AAC/Ogg bereits verwenden).

Warum: Für Geräte, die FLAC gut verarbeiten, aber den Quell-Codec nicht dekodieren können (z. B. ein Eversolo, der MusicBees WMA-Bibliothek in FLAC konvertiert empfängt), bewahrt dies die verlustfreie Qualität, wo MP3/AAC Audiodaten verwerfen würden. Entsperrt N02 (5.1 Downmix-Steuerung), das ohne eine verlustfreie Transkodierungsoption nicht angegangen werden konnte.

Implementierung: Einzeilige Ergänzung zum Select Case Codec-Block von Encoder.StartEncode – FLAC reiht sich bei MP3/AAC/Ogg im Kommandozeilen-gesteuerten Zweig ein. Das UI-Dropdown erhält „FLAC“ als 6. Option. Die Lade-/Speicherzuordnung in SettingsDialog wird erweitert, um FileCodec.FlacSelectedIndex = 5 zu erkennen. Mime, DLNA-Typ und Encode-Funktion waren bereits in ItemManager.GetMimes / GetDlnaType / GetEncodeFeature aus früheren Arbeiten (F21, F26) verdrahtet.


Lückenlos (SetNextAVTransportURI)

F10 – SetNextAVTransportURI / NextURI Kern

Was: Echte lückenlose Wiedergabe. Wenn das Gerät die Unterstützung für SetNextAVTransportURI in seiner UPnP-Dienstbeschreibung bewirbt, reiht das Plugin den nächsten Titel auf dem Gerät vor dem Ende des aktuellen Titels vorab ein. Das Gerät wechselt intern ohne hörbare Lücke zwischen den Titeln – was Sie auf einem CD-Player hören. Dies ist nicht der „kontinuierliche Stream“-Hack (der alles zu einem langen Stream verkettet und Metadaten pro Titel verliert).

Warum: Das Flaggschiff-Tier-2-Feature. Alben, die als kontinuierliche Live-Performance aufgenommen wurden (Live-Aufnahmen, klassische Sätze, DJ-Sets), klingen falsch, wenn eine halbe Sekunde Stille zwischen den Titeln liegt. Dies richtig zu lösen, ist ein Flaggschiff-Feature, das nun in diesem Fork enthalten ist.

Hinweise: Das in der Warteschlange befindliche Audio wird über den HTTP-Server des Plugins mit streamHandle=0 (Bibliotheksabrufmodus) bereitgestellt, was bedeutet, dass MusicBees Audio-Engine für den in der Warteschlange befindlichen Titel nicht im Loop ist. Kompromiss: ReplayGain/DSP/EQ-Effekte werden nicht auf den nächsten Titel angewendet. Akzeptabel, wenn „Natives Streaming erzwingen“ aktiviert ist (Standard).


F11 – „NextURI-Unterstützung deaktivieren“ pro Profil

Was: Selbst wenn ein Gerät SetNextAVTransportURI bewirbt, zwingt dieses Kontrollkästchen das Plugin, diese Werbung zu ignorieren und auf die Wiedergabe eines Titels nach dem anderen zurückzugreifen.

Warum: Einige Geräte bewerben NextURI, haben aber eine fehlerhafte Implementierung (Abstürze, halbe Übergänge, Hänger). Anstatt jedes defekte Gerät zu reverse-engineeren, erhält der Benutzer einen Schalter zum „einfachen Ausschalten hier“.


F12 – NextURI-Lebenszyklus in der Wiedergabeliste

Was: Wenn MusicBee NowPlayingListChanged auslöst, bewertet das Plugin neu, was für den lückenlosen Übergang in die Warteschlange gestellt werden soll. Es fragt MusicBee nach dem neuen „nächsten“ Titel über NowPlayingList_GetNextIndex(1) + NowPlayingList_GetListFileUrl, vergleicht dies mit dem, was derzeit auf dem Gerät in der Warteschlange ist (verfolgt über das neue Feld nextPlaySourceUrl), und reiht es erneut ein, wenn es sich geändert hat (oder löscht die Warteschlange, wenn MusicBee sagt, dass kein nächster Titel vorhanden ist).

Warum: Ohne F12 spielte das Gerät eine veraltete NextURI weiter, wenn der Benutzer den in der Warteschlange befindlichen Titel entfernte/neu anordnete. Dies erforderte historisch mehrere Iterationen, da jede Listenmutation eine andere Behandlung benötigt – wir haben es vereinfacht, indem wir NowPlayingList_GetNextIndex vertrauten (das bereits Shuffle und Repeat-All-Wrap-Around berücksichtigt), sodass alle Varianten durch denselben Vergleich geleitet werden.

Implementierung:

  • Neues Feld nextPlaySourceUrl speichert die MusicBee-Bibliotheks-URL des in der Warteschlange befindlichen Titels (die Streaming-URL mit Handle-Suffix ist nicht mit einem Bibliothekspfad vergleichbar).
  • Neue Public Sub RefreshQueuedNextUri() auf MediaRendererDevice. Drei Ergebnisse: keine NextURI in der Warteschlange → No-Op; Warteschlange stimmt mit neuem „nächsten“ überein → No-Op; Warteschlange weicht ab → QueueNext mit der neuen URL aufrufen (oder QueueNext("") zum Löschen – was F08 DoNotClearNextUri berücksichtigt).
  • Verdrahtet in Plugin.ReceiveNotification unter NotificationType.NowPlayingListChanged.

F13 – NextURI-Fehler-Backoff

Was: Nach 4 aufeinanderfolgenden SetNextAVTransportURI-Fehlern auf demselben Gerät deaktiviert das Plugin die lückenlose Wiedergabe für dieses Gerät, bis MusicBee neu gestartet wird.

Warum: Wenn ein Gerät für NextURI tatsächlich defekt ist (sporadische SOAP-Fehler, Netzwerkstörungen), würde das Plugin sonst bei jedem Titel erneut versuchen. F13 stoppt das Rauschen und fällt stillschweigend auf die Wiedergabe eines Titels nach dem anderen zurück.


F14 – Wiederholungsmodus + NextURI-Integration

Was: F14 teilt sich in zwei Fälle auf, die am F15-Übergangsdetektor in OnAvTransportStatusCheck behandelt werden:

  • Alle wiederholen: MusicBee übergibt die korrekte „Wrap“-URL (Titel 1 am Ende der Liste) an Plugin.QueueNext selbst. Es ist keine spezielle Plugin-Logik erforderlich – das Gerät wechselt dazu, und der F15-Detektor ruft wie gewohnt Player_PlayNextTrack auf, wodurch MusicBees NPL-Index auf 0 zurückgesetzt wird.
  • Einen wiederholen: MusicBee übergibt dieselbe Titel-URL an Plugin.QueueNext. Das Gerät wechselt dazu (neues Stream-Handle, gleiche Quelle). Der F15-Detektor fragt nun Player_GetRepeat() ab – wenn es RepeatMode.One ist, überspringt er den Aufruf von Player_PlayNextTrack, damit MusicBee den NPL-Index nicht vom sich wiederholenden Titel wegbewegt.

Warum: Ohne das Überspringen bei „Einen wiederholen“ würde der Aufruf von Player_PlayNextTrack beim lückenlosen Übergang MusicBee zum nächsten Titel in der Liste vorrücken („Einen wiederholen“ beeinflusst nur das automatische Vorrücken am Ende des Titels in der Player-Benutzeroberfläche – Nächster Titel bewegt sich immer vorwärts), was dem widersprechen würde, was „Einen wiederholen“ bedeutet.

Hinweis zur Wiedergabezählung: Im Modus „Einen wiederholen“ hängt die Erhöhung der Wiedergabezählung davon ab, dass MusicBee 3.7.9563+ die Schleifenwiedergabe bemerkt. Ältere MusicBee-Versionen spielen die lückenlose Wiederholung korrekt ab, verpassen aber die Erhöhung der Wiedergabezählung. Dokumentiert; nicht blockierend.


F15 – Zustandsmaschine zur Erkennung von Titelübergängen

Was: Wenn das Gerät intern vom aktuellen Titel zu NextURI wechselt, muss das Plugin dies bemerken und MusicBee anweisen, seinen aktuellen Wiedergabeindex vorzurücken. Andernfalls denkt MusicBee, es sei immer noch auf dem vorherigen Titel, und Wiedergabezählungen / UI / Scrobbling geraten aus dem Takt.

Implementierung: Fragt GetPositionInfo.TrackURI bei jedem Status-Timer-Tick ab. Wenn die gemeldete URI mit der von uns über NextURI in die Warteschlange gestellten übereinstimmt, rufen wir Player_PlayNextTrack auf MusicBee auf und setzen suppressNextSoapCall, damit das resultierende PlayToDevice SetAVTransportURI nicht erneut sendet (was die lückenlose Wiedergabe unterbrechen würde).

Warum: Ohne F15 spielt das Gerät den nächsten Titel ab, aber MusicBees Benutzeroberfläche zeigt an, dass es sich noch auf dem vorherigen befindet. Verwirrend, unterbricht Scrobbling, unterbricht die Wiedergabezählung. Die Erkennung von Titelübergängen erfordert eine lange, pro-Renderer-Iteration, da jede Renderer-Marke ihre eigenen Eigenheiten hat, wann sie die URI-Änderung meldet (einige melden zuerst TRANSITIONING, einige springen direkt zu PLAYING mit neuer URI, einige haben eine kurze STOPPED-Phase dazwischen).

Hinweise: Unser erster Entwurf funktioniert auf dem BubbleUPnP-Renderer. Geräte-spezifische Randfälle bleiben in B6.


F16 – Knacksen bei lückenlosem Übergang behoben

Was: Das Knacksen tritt auf, wenn das Quellformat (Samplerate / Kanäle / Codec) des in der Warteschlange befindlichen Titels vom aktuell wiedergegebenen Titel abweicht, wodurch der DAC des Geräts gezwungen wird, sich beim Übergang neu zu synchronisieren. F16 fügt eine NextUri:FormatChange-Diagnose hinzu, die zur Warteschlangenzeit ausgelöst wird, wenn die Formate abweichen, und benennt beide Seiten – sodass Benutzer, die Knacksen hören, dies korrelieren können.

Die Diagnose weist auch auf die Abhilfe hin: Aktivieren Sie Transkodierung erzwingen im Geräteprofil. Dies homogenisiert jeden Titel zu einem einzigen Transkodierungs-Codec/Samplerate/Bittiefe, wodurch der Quellformatunterschied vollständig eliminiert wird.

Warum für die eigentliche Transkodierung-zur-Anpassung-Korrektur aufgeschoben: Die strukturelle Korrektur (Transkodierung des in der Warteschlange befindlichen Titels, um dem Format des abgespielten Titels zu entsprechen) erfordert Änderungen am URL-Schema des HTTP-Servers des Plugins – derzeit dient /encode/{id}0.{ext} die in der Warteschlange befindliche Datei nativ. Eine zukünftige v2 von F16 würde pro-Format /encode/{id}0_{rate}_{depth}.{ext}-Routen hinzufügen und diese über den Encoder verdrahten. Das ist eine größere architektonische Änderung, die sich lohnt, wenn ein echtes Gerät das Knacksen zeigt, nachdem „Transkodierung erzwingen“ nicht ausreicht.

Implementierung heute:

  • Das Feld lastSourceUrl verfolgt die aktuell wiedergegebene Quell-URL.
  • QueueNext liest FilePropertyType.SampleRate/Channels/Kind für den aktuellen und den in der Warteschlange befindlichen Titel und protokolliert NextUri:FormatChange bei Nichtübereinstimmung.

F17 – Fortschrittsbalken-Resynchronisation nach Sprung

Was: Die Funktion Seek() rief bereits GetPlayPositionInformation() nach einem erfolgreichen Seek-SOAP auf, was den Fall „überhaupt keine Resynchronisation“ behebt. F17 schließt die verbleibende Drift von bis zu 1 Sekunde, die durch die 1-Sekunden-RelTime-Quantisierung von UPnP verursacht wird: Wenn die gemeldete Position des Geräts auf innerhalb von 1 Sekunde des vom Benutzer angeforderten Ziels gerundet wird, vertraut das Plugin nun dem subsekundengenauen Wert des Benutzers anstelle der Geräte-Trunkierung. Nur wenn das Gerät etwas dramatisch anderes meldet (>1s Abweichung), verwenden wir seinen Wert (der Sprung landete woanders als angefordert, z. B. Snap-to-Keyframe bei einigen Codecs).

Warum: Ohne dies würde das Springen zu 2:30.500, verankert an der „2:30“-Meldung des Geräts, dazu führen, dass der Fortschrittsbalken etwa 500 ms hinter der Realität liegt. Nach F17 stimmt der Balken mit der Absicht des Benutzers für den gängigen In-Track-Scrub-Fall überein und respektiert weiterhin die Meldung des Geräts für den Snap-to-Keyframe-Ausreißer.


F18 – Kontinuierlicher Stream / NextURI-Verriegelung

Was: Zwei Verriegelungen sind nun vorhanden:

  1. Laufzeit: QueueNext gibt False frühzeitig zurück, wenn Settings.ContinuousOutput aktiviert ist. Der kontinuierliche Stream ist ein eigener lückenloser Mechanismus (ein langer, verketteter Stream); das Senden von SetNextAVTransportURI zusätzlich dazu verwirrt das Gerät darüber, ob jeder Titel eine diskrete URI oder Teil des kontinuierlichen Flusses ist.
  2. UI: Wenn der Benutzer das globale Kontrollkästchen für den kontinuierlichen Stream aktiviert, wird forceNativeStream des aktuell angezeigten Profils automatisch deaktiviert. Der kontinuierliche Stream transkodiert immer, daher ist „Natives Streaming erzwingen“ in Kombination bedeutungslos.

Warum: Verhindert, dass der Benutzer zwei widersprüchliche lückenlose Mechanismen gleichzeitig aktiviert. Ohne F18 würde das Gerät sowohl eine kontinuierliche Stream-URI als auch eine NextURI für jeden nachfolgenden Titel erhalten, mit undefiniertem Verhalten je nach Renderer.


F19 – Leere NextURI-Fehler ignoriert

Was: Wenn SetNextAVTransportURI mit einer leeren URL aufgerufen wird (z. B. letzter Titel in der Liste), geben einige Geräte einen SOAP-Fehler zurück. F19 schluckt diese stillschweigend – protokolliert, aber nicht als Fehler weitergegeben.

Warum: Die Bedingung „kein nächster Titel“ ist normal, kein Fehler. Sie als fatal zu behandeln, verschmutzt das Protokoll und löst (in einigen Abläufen) Wiederholungsstürme aus.


Mime-Typen & DLNA-Metadaten

F20 – MP3 Mime → audio/mpeg

Was: Der standardkonforme MP3-Mime-Typ ist audio/mpeg, nicht audio/mp3. Letzterer ist eine häufige Fehlbezeichnung, die die meisten Geräte tolerieren, aber strengere Renderer lehnen ihn ab.

Warum: Behebt stillschweigend die Wiedergabe auf strengeren Geräten, die dem Standard folgen. Die yaiol-Codebasis hatte dies bereits korrekt; keine Änderung erforderlich.


F21 – Mime-Typ-Reihenfolge: Nicht-x--Variante zuerst

Was: Wenn ein Gerät sowohl audio/flac als auch audio/x-flac bewirbt, gibt das Plugin zuerst die Nicht-x--Variante zurück. Dasselbe gilt für jeden Codec mit sowohl Standard- als auch experimentellen Mime-Typen.

Warum: Das x--Präfix kennzeichnet experimentelle/inoffizielle Mime-Typen. Einige Renderer verhalten sich mit der Standardform besser. Winzige Neuordnung, reale Auswirkungen.


F22 – Opus-Mime-Typ-Unterstützung

Was: Erkennt Opus als streamfähigen Audio-Codec; sendet audio/opus Mime beim Bereitstellen von Opus-Titeln.

Warum: Opus ist heute weit verbreitet (moderner Kompromiss-Codec für Sprache/Musik). Ohne F22 würde das Plugin sich weigern, Opus-Dateien zu streamen, selbst an Geräte, die sie verarbeiten können.


F23 – Monkey Audio (APE) Quelldatei-Unterstützung

Was: Erkennt .ape-Dateien als gültigen Quell-Codec für Streaming/Transkodierung.

Warum: APE ist ein verlustfreies Format mit einer Nische, aber einer loyalen Benutzerbasis. Das Hinzufügen kostet wenig und schaltet die Bibliothek für diese Benutzer frei.


F24 – AAC / ALAC Mime-Fallback

Was: Wenn ein Gerät AAC oder ALAC unterstützt, diese aber nicht explizit in seiner UPnP-Dienstbeschreibung bewirbt, bietet das Plugin sie trotzdem als Fallback an.

Warum: Mehrere Geräte, die AAC gut verarbeiten, vergaßen, es in ihrem Fähigkeiten-XML aufzulisten. Ohne F24 würde das Plugin es nicht einmal versuchen und Transkodierung erzwingen. Mit F24 versucht das Plugin es und lässt das Gerät es nativ verarbeiten, wenn es kann.


F25 – DLNA-Typ-Flag für native + kodierte WAV-Streams

Was: Das DLNA-Typ-Flag (ein Profil-Identifikator wie LPCM, WAVE, MP3) muss mit dem übereinstimmen, was das Gerät empfängt. F25 stellt sicher, dass native Streams und kodierte WAV-Streams korrekt gekennzeichnet werden.

Warum: Ein nicht übereinstimmender DLNA-Typ führt dazu, dass einige Geräte die Wiedergabe vollständig verweigern oder den falschen Decoder anwenden.


F26 – DLNA-Header für FLAC-Dateien

Was: FLAC-Streams erhalten den korrekten DLNA-Profil-Identifikator in ihren Headern.

Warum: Ohne ihn erkennen einige Geräte, die FLAC unterstützen, den Stream nicht als solchen.


F27 – Korrektur der Bitratenberechnung in Metadaten

Was: Die res@bitrate des kontinuierlichen Streams wurde als (sampleRate * channels * bitsPerSample) / 1000 – kbps – berechnet, was um einen Faktor von ~125 von der UPnP DIDL-Spezifikation abwich, die das Attribut als Bytes pro Sekunde definiert. Nun wird durch 8 anstatt durch 1000 geteilt.

Warum: Falsche Bitratenanzeige auf dem Gerät – kosmetisch bei den meisten Renderern, aber einige weisen Stream-Puffer aus dem Wert zu und stottern bei Streams, die ~125-mal kleiner aussehen, als sie sind. Der nicht-kontinuierliche Quelldateipfad hatte dies bereits richtig ((bitrate_kbps * 1000) \ 8 = Bytes/Sek.); nur der kontinuierliche Stream-Pfad war falsch.


F28 – Korrektur des Metadaten-Zeitformats (Marantz)

Was: res@duration in DIDL war als H:MM:SS formatiert (z. B. 0:03:42). Die UPnP DIDL-Spezifikation definiert das Format als H+:MM:SS[.F+] – streng genommen sind Bruchteile von Sekunden optional, aber empfohlen; einige Marantz-Geräte behandeln die reine Form als ungültig und lassen ihre Daueranzeige leer. Nun als H:MM:SS.fff formatiert (z. B. 0:03:42.000).

Warum: Anzeigeproblem, das spezifisch für eine Marke ist; konformes ISO-Format mit Bruchteilen von Sekunden behebt es, ohne andere Geräte zu beeinträchtigen. Angewendet an beiden DIDL-Emissionsstellen (Quelldateipfad + kodierter Stream-Pfad in WriteAudioFileDIDL).

Bonus-Korrektur im selben Durchlauf: pv:addedTime und pv:lastPlayedTime verwendeten hh (12-Stunden-Uhr) in ihren DateTime-Formatstrings anstelle von HH (24-Stunden-Uhr). Jeder Titel, der zwischen 13:00 und 23:59 Uhr hinzugefügt oder abgespielt wurde, würde mit einer falschen Stunde (z. B. 17:42 → „05:42“) auf Geräten gerendert, die das Feld anzeigen. Verwendet nun HH.


F29 – Unterstützung für Sprung in kodierten MP3-Streams (CBR)

Was: Transkodierte MP3-Streams bewerben nun DLNA.ORG_OP=11 (sowohl Byte- als auch Zeitsprung) anstelle von DLNA.ORG_OP=10 (nur Byte). Geräte, die zuvor Zeitsprünge bei transkodierten MP3s verweigerten, können nun ihren Fortschrittsbalken/Sprung-UI normal steuern.

Warum: MusicBees Transcoder erzeugt MP3 mit konstanter Bitrate im HighQuality-Preset, sodass die Byte-↔-Zeit-Zuordnung linear ist – das Gerät kann eine Zeitsprung-Anfrage selbst in einen HTTP-Range-Byte-Sprung umwandeln, ohne Encoder-seitige Unterstützung. Das Bewerben von OP=11 schaltet diese Benutzeroberfläche auf dem Gerät frei. Ohne F29 wurde der Sprung in einer transkodierten MP3 entweder stillschweigend ignoriert oder der Benutzer wurde zum Titelanfang zurückgesetzt.

Implementierung: GetEncodeFeature in ItemManager.vb wurde umstrukturiert, um das Inline-If in eine lesbare If/ElseIf/Else-Kette aufzubrechen. MP3 erhält explizit OP=11; andere Nicht-PCM-Codecs behalten OP=10. Keine Änderung für AAC/FLAC/etc. – diese würden eine Codec-spezifische Überprüfung der CBR-Eigenschaft erfordern, die MusicBee nicht garantiert.


F30 – .mpeg-Dateierweiterung wird verarbeitet

Was: Dateien mit der Erweiterung .mpeg (und der noch selteneren .mpe) werden nun in GetCodec als FileCodec.Mp3 erkannt. Vor F30 gaben sie FileCodec.Unknown zurück und wurden stillschweigend aus der Bibliothek abgelehnt / konnten nicht als Transkodierungsquellen dienen.

Warum: Alte MPEG-1 Layer 3-Archive verwendeten manchmal .mpeg anstelle von .mp3 (die Spezifikation erlaubt beides). Eine Handvoll Dateien in einer 300.000er Bibliothek reicht aus, um das Gefühl zu erzeugen: „MusicBee zeigt sie an, aber das Plugin nicht“ – verwirrend für den Benutzer.


Wiedergabeverhalten

F31 – Radiostreams verwenden automatisch den kontinuierlichen Modus

Was: WriteAudioFileDIDL prüft nun die Kind-Eigenschaft der Quell-URL über Library_GetFileProperty und behandelt jede Datei, deren Kind auf „Stream“ endet (MusicBee meldet „MP3 Stream“, „Internet Stream“ usw. für Radio), als kontinuierlich, unabhängig vom globalen Settings.ContinuousOutput-Schalter. Der kontinuierliche DIDL-Stream-Zweig (Titel: „Continuous Stream“, id="continuousstream", feste PCM/Wave-Ausgabe) wird verwendet; das Gerät sieht einen einzigen Stream im Unendlich-Stil.

Warum: Radiostreams haben keine Titelgrenzen, keine feste Länge, keine Suchfunktion. Die Behandlung als diskrete Dateien im DIDL führte dazu, dass das Plugin Byte-Bereiche und Dauern bewarb, die nicht existieren. Das automatische Umschalten, wenn MusicBee uns bereits mitgeteilt hat „dies ist ein Stream“, entfernt eine Falle, über die der Benutzer nicht nachdenken sollte.

Umfang: Gilt nur, wenn MusicBee die Wiedergabe steuert (musicBeePlayToMode). Der Bibliotheksabrufpfad (UPnP-Client-Browsing) bleibt unverändert – Radio-URLs sind dort selten, und das benutzerseitige Verhalten sollte sich ohne explizite Tests nicht ändern.


F32 – Codec-Werbungs-Fallback

Was: Wenn ein Gerät bestimmte Codecs nicht bewirbt (oder das Plugin das Fähigkeiten-XML des Geräts nicht parsen kann), lehnt das Plugin den Stream nicht sofort ab. Stattdessen versucht es, ihn bereitzustellen und lässt das Gerät entscheiden.

Warum: Viele Geräte haben unvollständige oder unlesbare Fähigkeiten-XML, verarbeiten den Codec aber tatsächlich einwandfrei. F32 tauscht ein kleines „Best-Guess und Versuch“ gegen eine kategorische Ablehnung ein.


F33 – Verbesserung der Fortschrittsbalken-Synchronisation

Was: Die Position zwischen den Abfragen wird bereits von einem einzelnen Anker (currentPlayStartTicks) per Wanduhr extrapoliert, sodass sich der Fortschrittsbalken reibungslos im Sub-Sekunden-Bereich aktualisiert. Die verbleibende Jitter-Quelle war der initiale Anker für einen frisch gestarteten Titel: Der vorherige Code nahm position=0 an, in dem Moment, als der Status-Timer zum ersten Mal bemerkte, dass der Zustand auf „Wiedergabe“ wechselte, aber zu diesem Zeitpunkt könnte das Gerät bereits 100-500 ms gespielt haben (ein Abfrageintervall). MusicBees Fortschrittsbalken würde bei 0 beginnen und dann vorwärts springen, wenn die Realität aufholte.

F33-Korrektur: Beim erstmaligen Übergang in den Zustand „Wiedergabe“ bei einem neuen Titel (currentPlayStartTimeEstimated=True) GetPlayPositionInformation() aufrufen, um die tatsächliche aktuelle Position des Geräts zu erhalten, und dann daran verankern. UPnP meldet nur eine 1-Sekunden-Auflösung, sodass der Anker immer noch quantisiert ist, aber er ist der Wahrheit viel näher als die Annahme von 0.

Warum: Sanftere + genauere Fortschrittsanzeige, besonders direkt nach einem Titelwechsel. An der 1-Sekunden-UPnP-Meldeauflösung selbst führt kein Weg vorbei – das ist Spezifikation.


F34 – Fortschrittsbalken-Jitter nach Titelwechsel

Was: Wenn PlayToDevice für einen neuen Titel aufgerufen wird, ließ das Plugin currentPlayPositionMs und currentPlayStartTicks für das ~100ms-Fenster zwischen SOAP-Play und der ersten Status-Timer-Abfrage, die den neuen Wiedergabezustand erkennt, auf ihren vorherigen Titelwerten. MusicBees Fortschrittsbalken zeigte kurz das Ende des vorherigen Titels an, sprang dann auf 0 zurück und stieg dann an. F34 setzt beide beim Eintritt in PlayToDevice auf Null – in dem Moment, in dem wir wissen, dass ein Titelwechsel stattfindet, bevor jegliche SOAP-Arbeit beginnt.

Warum: Visueller Fehler bei schnellen Überspringen-Anwendungsfällen (manuelles Weiter oder lückenloser Übergang). Nun gibt MusicBees erste PlayPositionMs-Abfrage nach dem Abspielen sauber 0 zurück, dann verfeinert F33s GetPlayPositionInformation diese auf die tatsächliche Position des Geräts beim ersten Statuswechsel-Tick.

Implementierung: Vier Zeilen am Anfang von PlayToDevice, gepaart mit F33s übergangszeitgenauer Verankerung.


F35 – Fehler bei „Transkodierung erzwingen“

Was: „Transkodierung erzwingen“ konnte in bestimmten Kombinationen immer noch die Transkodierung überspringen. Nach der F04-Überarbeitung pro Profil wurden zwei spezifische Lücken geschlossen:

  1. Priorität mit ForceNativeStream. Wenn beide True waren (was bei einer Schema-Migration oder einer partiellen Einstellungsdatei passieren kann), gewinnt ForceTranscoding nun eindeutig (If streamingProfile.ForceTranscoding Then forceEncode = True ElseIf streamingProfile.ForceNativeStream Then forceEncode = False). Die UI-Gegenseitigkeitsausschluss verhindert, dass der Benutzer beide aktiviert, aber die Laufzeitprüfung behandelt jeden Zustand, der inkonsistent von der Festplatte geladen wurde.
  2. bypassTranscodeDecision-Logik. Zuvor: streamingProfile.ForceNativeStream AndAlso Not Settings.ForceTranscoding. Jetzt: streamingProfile.ForceNativeStream AndAlso Not streamingProfile.ForceTranscoding – dieselbe Prioritätsregel, aber im selben pro-Profil-Bereich.

Warum: „Erzwingen“ sollte Erzwingen bedeuten. Wenn der Benutzer „Transkodierung erzwingen“ für ein Gerät explizit aktiviert hat, darf das Plugin niemals stillschweigend auf natives Streaming zurückgreifen, unabhängig davon, wie andere Flags kombiniert werden.


F36 – Renderer-geschlossene Ausnahme

Was: Plugin.ReceiveNotification wurde in einen Top-Level-Try/Catch gewickelt, der jede nicht abgefangene Ausnahme protokolliert, anstatt sie an MusicBees Benachrichtigungspumpe zurückzuleiten.

Warum: Benachrichtigungen von MusicBee (PlayStateChanged, VolumeMuteChanged usw.) werden an den ControlPointManager weitergeleitet, der über SOAP mit dem Renderer kommuniziert. Einzelne Aufrufstellen hatten bereits Try/Catch um ihre SOAP-Aufrufe, aber ein ausreichend seltsamer Timing-Fall (z. B. Renderer stirbt zwischen zwei SOAP-Aufrufen im selben Benachrichtigungs-Handler) könnte immer noch entweichen. Der Top-Level-Wrapper ist das letzte Sicherheitsnetz, damit der Benutzer niemals ein generisches „TargetInvocationException“-Popup von MusicBee sieht.

Implementierung: Der bestehende Body wurde in ReceiveNotificationInternal umbenannt und ein dünner Wrapper ReceiveNotification hinzugefügt, der Try { ReceiveNotificationInternal(...) } Catch { LogError(...) } ausführt. Die bereits vorhandene Pro-Methode-Try/Catch-Infrastruktur innerhalb des ControlPointManager (um jeden PostSoapRequest-Aufruf) bleibt bestehen – F36 ist Gürtel + Hosenträger.


F37 – Sprung in langen Titeln löst falschen Übergang aus

Was: Das Springen innerhalb eines langen Titels kann bei einigen Renderern einen kurzen Stopped→Playing-Zyklus erzeugen. Ohne Unterscheidung behandelt ProcessNewPlayState.Stopped dies als natürliches Titelende und ruft Player_PlayNextTrack auf, wodurch MusicBee vorrückt, obwohl der Benutzer nur scrubben wollte. F37 stempelt lastUserInitiatedSeek in Seek() und fügt einen 5-Sekunden-Schutz im Stopped-Handler hinzu (spiegelt das bestehende lastUserInitiatedStop-Fenster wider).

Warum: Das stille Überspringen zum nächsten Titel während eines Sprungs ist einer jener Fehler, dessen Ursache niemand erraten kann – der Benutzer denkt: „Seltsam, ich wollte vorspulen und jetzt spielt es den nächsten Song.“ Die Korrektur ist mechanisch: dasselbe Muster wie die bereits vorhandene Benutzer-Stopp-Diskriminierung.


F38 – Verbesserte Sprungbehandlung für absturzanfällige Codecs

Was: BubbleUPnP, das bei MP3-Suchen abstürzte, war das kanonische Symptom. Nach einer Überprüfung macht der aktuelle yaiol-Suchcode bereits das Richtige – der native Pfad behandelt HTTP Range korrekt (206, Content-Range, AcceptRanges), der kodierte Pfad bewirbt X-AvailableSeekRange und parst eingehende timeSeekRange.dlna.org / npt-Header, DLNA.ORG_OP-Flags spiegeln die tatsächlichen Stream-Fähigkeiten wider (mit DisablePcmTimeSeek-Opt-out für problematische Platinum-Geräte). Benutzertest auf aktuellem BubbleUPnP 4.6.4: keine Abstürze beobachtet.

Warum: Der BubbleUPnP MP3-Suchabsturz wurde um 2024 gemeldet, und die App hat seitdem etwa 16 Monate lang Korrekturen erhalten. F29 (kodiertes MP3 OP=11) war die neue Variable, die ihn hätte wieder aufdecken können; tut dies bei getesteten Versionen nicht.

Falls ein Absturz zurückkehrt: Die Korrekturform wäre ein pro-Profil „begrenzter Sprung“-Schalter, der DLNA.ORG_OP=10 (nur Byte) für markierte Codecs erzwingt – analog zur Funktionsweise von DisablePcmTimeSeek für PCM. Dann hinzufügen, nicht präventiv.


UI & Protokollierung

F39 – Schaltfläche „Hinzufügen“ wählt das neue Profil aus

Was: Das Klicken auf „Hinzufügen“ in der Liste der Geräteprofile erstellt ein neues Profil UND wählt es automatisch aus, sodass der Benutzer sofort Felder bearbeiten kann. Unser Refactoring des gegliederten Dialogs tut dies bereits – sowohl der direkte „Hinzufügen“-Pfad als auch der „Aus Vorlage“-Pfad enden mit Me.activeStreamingProfiles.SelectedIndex = Me.activeStreamingProfiles.Items.Count - 1. Eine Überprüfung bestätigte, dass unser Fork dies bereits handhabt – nichts zu ändern.

Warum: Kleiner UX-Schönheitsfehler, der sich hier bereits als nicht-existent herausstellte.


F40 – Größere maximale Verbindungen + Warnprotokoll

Was: Die Obergrenze für gleichzeitige Streams des Plugins (SemaphoreSlim um Sockets_Stream_File / Sockets_Encoder_Start) war fest auf 4 kodiert. F40 macht sie auf der Seite „Allgemeine Einstellungen“ benutzerkonfigurierbar (Standard 16, Bereich 1-256), fügt eine MaxConnections-Protokollzeile hinzu, wenn eine Anfrage auf einen Slot warten muss, UND zeigt ein rotes ⚠ Max. Verbindungen-Badge unten links im Einstellungsdialog an, wenn die Obergrenze mindestens einmal seit dem Start von MusicBee erreicht wurde.

Warum: Wenn ein Gerät parallele Anfragen sendet (einige Marantz/Linn während Artwork-Scans, BubbleUPnPs Metadaten-Probes neben der aktiven Wiedergabe), wurden zusätzliche Anfragen stillschweigend hinter dem Semaphor blockiert – der Benutzer sah „Gerät langsam“ ohne sichtbare Ursache. Die Protokollzeile ist gut für technisches Debugging, aber nicht-technische Benutzer lesen niemals Protokolle. Das sichtbare Badge im Einstellungsdialog macht die Bedingung des Erreichens der Obergrenze für jeden sichtbar, der die Plugin-Einstellungen öffnet.

Implementierung:

  • Die Wartezeit wurde in WaitOnSendBarrier(logTag) in MusicBeeUpnp.vb zentralisiert; beide Aufrufstellen (MediaServerDevice.GetFile, Encoder.StartEncode) verwenden sie.
  • Settings.MaxConnections in v8 des Einstellungsschemas persistent gespeichert.
  • Plugin.MaxConnectionsHit ist ein Sticky Session Flag, das in WaitOnSendBarrier gesetzt wird; wird nur bei einem MusicBee-Neustart zurückgesetzt.
  • SettingsDialog.maxConnectionsBadge ist ein rotes, fettes Label bei (16, 410), das nur angezeigt wird, wenn Plugin.MaxConnectionsHit True ist. Hat einen Tooltip, der Ursache und Abhilfe erklärt.
  • Das Semaphor wird einmal beim Typ-Laden initialisiert, daher erfordert eine Änderung der Einstellung einen MusicBee-Neustart (im Feldlabel vermerkt).

F41 – Protokoll „Kodierung aufgrund von ReplayGain/DSP“

Was: Anstelle separater Protokollzeilen „Kodierung für RG“ / „Kodierung für DSP“ enthält die einzelne StreamDecision-Zeile von F42 MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain als akkumulierte Gründe. Gleicher Diagnosewert, weniger Rauschen.

Warum: Benutzer sehen alle Gründe, warum die Transkodierung für einen bestimmten Titel stattfindet, in einer Protokollzeile, nicht verstreut. Siehe F42 für vollständige Details.


F42 – Protokoll „Renderer unterstützt Quell-Codec nicht“

Was: Eine StreamDecision-Protokollzeile pro Wiedergabe-an-Gerät-Titel wurde hinzugefügt, die entweder „nativer CODEC“ oder „Transkodierung CODEC→CODEC Grund=…“ angibt. Das Grundfeld sammelt jede Bedingung, die die Transkodierung ausgelöst hat: MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain, WebFile, VirtualFile, ForceTranscoding(global), SampleRate<min/SampleRate>max, DownmixToStereo, DeviceLacksCodec(X), BandwidthConstrained.

Warum: Benutzer waren verwirrt durch unerwartete CPU-Spitzen bei Dateien, die sie nativ streamen wollten. Eine Protokollzeile pro Titel sagt ihnen genau, welche Bedingung die Transkodierung verursacht hat – und wenn das Feld DeviceLacksCodec(Flac) anzeigt, wissen sie sofort, dass die Protokollinformationen des Geräts unvollständig waren und möchten möglicherweise, dass F32s Fallback greift.

Implementierung: Ein einzelner Akkumulator-String, der inkrementell durch die Entscheidungs-Kette aufgebaut wird; einmal am Ende protokolliert. Gesteuert durch Settings.LogDebugInfo, um Protokollrauschen in der Produktion zu vermeiden.


F43 – SetNextAVTransport-Protokoll zeigt Quell-URL an

Was: QueueNext-Protokolleinträge enthalten nun source=<MusicBee library path> neben stream=<HTTP streaming URL>. Dieselbe Änderung wurde auf den Erfolgs- und den Fehlerpfad (QueueNext:Failed) angewendet.

Warum: Beim Debuggen eines Problems mit einem in der Warteschlange befindlichen Titel ist die Streaming-URL (/encode/aabbccdd0.flac) für sich genommen undurchsichtig – für jeden Titel gleich. Die Quell-URL ist der von Menschen lesbare Bibliothekspfad, der Ihnen genau sagt, welche Datei MusicBee in die Warteschlange stellen wollte.


F44 – Bessere Mime-Typ-Fehlerprotokollierung

Was: Zwei neue Protokolleinträge während Activate:

  • Activate:MimeUnverified – wird pro fehlerhaftem Eintrag in der GetProtocolInfo-Antwort des Geräts ausgelöst und benennt, welcher Eintrag nicht geparst werden konnte (sodass der Benutzer z. B. sehen kann: „der Marantz gab http-get:*::* für einen Codec zurück – Fähigkeit ist unbestätigt, F32s Fallback wird raten“).
  • Activate:NoSinkInfo – wird einmal ausgelöst, wenn das Gerät überhaupt kein <Sink>-Element zurückgegeben hat. Bedeutet, dass SupportedMimeTypes Nothing bleibt und IsCodecSupported zu „nehme an, alles funktioniert“ degradiert – nützlicher Kontext, wenn später „Gerät hat Stream verweigert“-Fehler auftreten.

Warum: Vor F44 ließen diese stillschweigenden Fähigkeits-Fallbacks Benutzer raten, warum ihre Titel entweder entgegen den Erwartungen transkodiert oder vom Gerät abgelehnt wurden. Nun zeigt ein einzelnes Grep nach Activate: an, ob die Fähigkeitsinformationen des Geräts nutzbar waren.


F45 – Bessere Metadaten-Fehlerprotokollierung

Was: Das Browse-Ausnahmeprotokoll in ContentDirectoryService.vb wurde bereits in früheren yaiol-Arbeiten (der Alia Vox Bug-Session) mit ObjectID und Stack-Trace angereichert. F45 erweitert es weiter mit BrowseFlag (Metadaten vs. Kinder), Filter (welche Attribute der Client angefordert hat), sortCriteria und partialResultLength (wie viele Bytes DIDL vor dem Fehler produziert wurden – zeigt an, wie weit im Batch der fehlerhafte Titel sitzt).

Warum: Wenn mitten im DIDL etwas schiefgeht, sagt Ihnen der Teillängenwert, ob der Fehler beim ersten Titel des Batches (partial=0) oder mittendrin (partial=N) auftrat – kombiniert mit dem startingIndex des Batches können Sie den fehlerhaften Titelindex identifizieren. Filter und BrowseFlag erklären, welche Art von Browse der Client wollte; manchmal schlägt ein reiner Metadaten-Browse fehl, wo ein Kinder-Browse für dieselbe ID erfolgreich ist.


Netzwerk

F46 - Automatischer Modus kündigt sich nur auf echten Netzwerkadaptern an

Was: im Schnittstellenmodus Automatisch kündigte sich das Plugin bisher (SSDP) auf jedem betriebsbereiten IPv4-Adapter an. Auf einem Rechner, der zusätzlich einen VPN-Tunnel (NordLynx) oder einen virtuellen Switch (Hyper-V / WSL / Docker) betreibt, wurde dieselbe Bibliothek auch auf jedem dieser Adapter angekündigt, sodass der Kontrollpunkt, von dem aus Sie streamen, den Server zwei- oder dreimal entdeckte und die Bibliothek als doppelte Kopien auflistete. Der automatische Modus behält jetzt nur Adapter mit einem echten IPv4-Standardgateway (HasIPv4Gateway) - das die Tunnel- und virtuellen Switch-Adapter nicht haben - sodass diese aus der Ankündigungsliste entfernt werden. Eine vom Benutzer angeheftete Adresse gewinnt weiterhin uneingeschränkt (Ankündigung nur auf dieser Schnittstelle), und wenn kein Adapter ein Gateway meldet, fällt die Auswahl auf alle Adapter zurück, sodass die Liste der angekündigten Adressen nie leer ist und das Plugin nicht unsichtbar werden kann.

Warum: das Duplikat entsteht nicht dadurch, dass man „in einem VPN ist" - es entsteht durch das gleichzeitige Ankündigen auf dem LAN-Adapter und dem Tunnel-/virtuellen Adapter, sodass ein Kontrollpunkt denselben Server unter zwei Adressen sieht. Ein Verbraucher-VPN (NordVPN/NordLynx) tunnelt nur den ins Internet gerichteten Verkehr; der DLNA-Renderer lebt im LAN, und der Verkehr im lokalen Subnetz umgeht den Tunnel, sodass der Tunnel-Adapter ohnehin nie einen Renderer erreicht - ihn zu entfernen beseitigt eine Phantomkopie, nie einen funktionierenden Pfad. Der Gateway-Test ist das günstige, zuverlässige Signal, das einen echten LAN-/Wi-Fi-Adapter von einem Tunnel oder virtuellen Switch unterscheidet. Ergänzt N05 (das korrigierte, wie Ankündigungen auf solchen Verbindungen gesendet werden - Multicast statt Broadcast); F46 bestimmt, auf welchen Adaptern überhaupt angekündigt wird.

Bekannte Einschränkung: ein Mesh- / Fernzugriffs-VPN (Tailscale, ZeroTier, WireGuard nach Hause), dessen Renderer tatsächlich jenseits des Tunnels leben, weist meist einen Adapter ohne Standardgateway auf, sodass der automatische Modus auch ihn verwirft. Diese Benutzer heften stattdessen die VPN-Adresse an, die Vorrang vor dem Gateway-Filter hat.