最新消息
2.0.2 - 2026-07-20
- 當任何節點跟隨某個範本時,該範本將無法再被刪除 — 刪除按鈕會保持停用狀態,因此節點永遠不會變成孤立。每個範本列現在會顯示其追隨者的即時 (n) 計數,這能一目了然地解釋為何刪除按鈕被停用;刪除未使用的範本現在也會「生效」,而不是在下次載入時悄悄重新出現預設值。
- 檢視樹旁邊新增了一個 漏斗 按鈕,可以精確顯示哪些節點跟隨某個範本:它會將樹狀結構篩選為僅顯示所選範本的追隨者,當您選擇其他範本時會重新篩選,並在關閉時恢復完整的樹狀結構。
- 廣播 和 播客 節點現在永久與其類別的範本配對 — 在「路徑」分頁上重塑範本,節點會自行跟隨;無需應用任何內容,因此「應用」按鈕對它們是停用的。範本清單以兩個區塊反映了這一點:標準(所有新範本都在此建立)和 保留(廣播 + 播客)。
2.0.1 - 2026-07-20
- 路徑範本現在已即時連結到使用它們的節點。套用範本會使節點遵循它:稍後編輯範本,每個遵循它的節點都會立即重塑——無需逐個節點地尋找並重新套用。檢視樹會在每個節點名稱後顯示它遵循哪個範本,重新命名會立即顯示在那裡,刪除範本時會先告訴您有多少個節點遵循它(它們會保留當前佈局並停止遵循任何內容)。
- 將範本套用到隱藏節點也會使其再次可見——套用是「顯示此內容,形狀如彼」的手勢,而隱藏則保留在「可見」核取方塊中。此功能取代的保留「隱藏」範本已移除。
- 檢視樹不再會讓您迷失位置:勾選、展開的資料夾和捲動位置在套用範本和其他重新整理後都會保留。
2.0.0 - 2026-06-16
這是 MusicBee UPnP 外掛程式的開源 yaiol 分支的首次公開發行。它分為兩部分:此分支的新功能,以及對原始外掛程式的修復和改進。每個項目都保留了專案內部功能目錄的「內容 / 原因」形式,因此每個變更背後的原因都顯示在頁面上,而不僅僅是變更本身。
此分支中的新功能
MediaRenderer - 播放至 MusicBee
N01 - MusicBee 作為播放目標渲染器
內容: 通常,此外掛程式以單向方式運作:手機或其他裝置瀏覽 MusicBee 的媒體庫並在自身上播放音樂。此功能增加了相反的方向 - 它讓 MusicBee 成為 播放器。您可以從手機上的控制器應用程式(例如 BubbleUPnP)選擇您的桌面 MusicBee 作為播放器,然後從您的手中控制它:播放、暫停、停止、向前或向後跳過、跳到曲目中的某個點,以及更改音量或靜音。
原因: 它將您的手機變成您 PC 上已有音樂的遙控器。坐在沙發上,在手機上瀏覽您的媒體庫,點擊一首曲目,它就會從連接到您桌面的揚聲器中播放出來 - 並可從您所在的位置完全控制。原始外掛程式從未將此作為一個可運作的功能發布。
開啟它: 預設情況下它是關閉的,因為開啟它會讓您家庭網路上的任何東西開始在您的 PC 上播放。您可以在設定對話方塊的「一般」選項卡上勾選一個方塊來啟用它。此外掛程式的三個角色各自有自己的勾選方塊 - 分享我的媒體庫(伺服器)、讓其他人播放到我(渲染器)和 播放到其他裝置(控制點) - 並且對話方塊只顯示您開啟的角色實際需要的設定選項卡,因此您永遠不會遇到不適用於您的選項。
區分您的機器: 您可以為渲染器指定任何您喜歡的名稱(它最初是「MusicBee (yaiol)」)。該名稱會出現在您手機的播放目標清單中,因此當多台 PC 運行 MusicBee 時,您可以區分哪台是哪台。名稱更改會立即生效,無需重新啟動。
播放到自身時的最佳音質: 當您從手機瀏覽 MusicBee 自己的 媒體庫並將曲目傳回給 同一個 MusicBee 時,此外掛程式會識別出它被要求播放自己的檔案之一,並直接從您的磁碟播放。結果是精確且即時的 - 位元完美,並應用了 MusicBee 自己的等化器和音量平衡 - 而不是無意義地將音訊推送到網路並直接傳回自身。
私密運行: 這三個角色獨立運作,因此您可以在關閉媒體庫共享的情況下開啟渲染器。在這種「僅渲染器」設定中,您的媒體庫對網路保持完全隱藏 - 只會宣布播放目標 - 並且 MusicBee 永遠不會提供播放到自身。
播放行為
N02 - 5.1 FLAC 不會自動降混
內容: MediaServerDevice.GetEncodedFile 中的通道計數限制為 If StereoOnly OrElse Not isPcmData Then channelCount = 2。Not isPcmData 子句會靜默地將每個非 PCM 轉碼(FLAC、MP3、AAC、Ogg)降混為立體聲,無論來源通道計數如何,從而使來源為 5.1 FLAC 時,5.1 功能的渲染器失效。現在第二個子句排除了 FLAC:Not isPcmData AndAlso encoder.Codec <> FileCodec.Flac。FLAC 5.1 會通過;MP3/AAC/Ogg 仍然強制立體聲,因為 MusicBee 針對這些格式的命令列編碼器需要 2 通道輸入。
原因: 將 5.1 FLAC 來源轉碼為 FLAC 輸出的全部目的是保留多通道混音。靜默降混使 FLAC 轉碼選項對於環繞聲聆聽毫無用處。有了 N02,它就能做正確的事情。
架構
使分支在大型媒體庫上可行的結構性變更 - 原始外掛程式中沒有。
N03 - 惰性(按需)瀏覽樹
內容: 原始外掛程式在 MusicBee 啟動時建立整個瀏覽樹 - 列舉每個曲目,每個檔案完整的 Library_GetFileTags,組裝整個容器層次結構 - 在 開啟 HTTP 埠之前。在真實的媒體庫(5 萬多首曲目,5400 集播客,數百個電台)上,這需要數分鐘的冷啟動,並且樹會永遠保留在 RAM 中,包括客戶端從未開啟的分支。此分支不會預先建立任何內容:根目錄為每個端點公開一個 L: 前綴的佔位符(L:music、L:podcast、L:filter:…);每個層級僅在客戶端瀏覽到它時才計算(LazyBrowse → EnsureLazyEndpointInMemory → 每層快取),並且媒體庫變更通知會清除快取(SetLibraryDirty)。
原因: 冷啟動基本上是即時的 - 在 MusicBee 完成外掛程式初始化時 HTTP 埠已開啟 - 並且記憶體保持與已瀏覽的內容成比例,而不是與媒體庫大小成比例。權衡:首次瀏覽到端點會支付其載入成本;重新進入會被快取,直到下一次媒體庫變更。這是其他一切所依賴的基礎。完整說明:FIXES.md。
網路與穩健性
強化 HTTP 伺服器的綁定路徑。原始外掛程式在其埠不可用時會靜默死亡。
N04 - 自我修復 HTTP 埠綁定
內容: 外掛程式的 HTTP 伺服器在其配置的埠不可用時不再死亡。三個相關變更:
- 綁定失敗時自動回退。
HttpServer.Start嘗試配置的埠,並在SocketException時向上掃描最多 20 個埠以尋找第一個空閒埠。實際綁定的埠記錄在新的Plugin.boundServerPort中,並且所有宣傳伺服器的內容 - SSDPLOCATIONURL(NOTIFY + M-SEARCH 回應)、裝置 URL(PrimaryHostUrl)、路由器埠轉發以及 SSDP/控制點自我過濾器 - 現在都讀取boundServerPort而不是Settings.ServerPort。UPnP 客戶端透過 SSDP 發現真實埠,因此移動的埠對渲染器是透明的。 - 使用者通知。 當發生回退時(儲存的埠不是正在使用的埠),本地化的
MessageBox(WarnPortInUse)會告訴使用者哪個埠實際正在服務,以及裝置仍會找到它 - 因為此外掛程式以無頭模式運行,對話方塊中的訊息只有已經懷疑問題的人才會看到。 - 重新啟動恢復。
RestartServer(設定儲存重新啟動路徑)過去會盲目地解除引用Plugin.controller/Plugin.server。如果初始Initialise在建立它們之前拋出(正是綁定失敗導致的),下一次設定儲存會遇到NullReferenceException- 留下一個半死的外掛程式。它現在在Nothing時重新建立並啟動它們,因此儲存一個可運作的埠可以使外掛程式恢復,而無需完全重新啟動 MusicBee。
原因: 觸發因素是真實的使用者事件。舊的預設埠 49382 位於 Windows 動態 範圍(49152-65535)內,其中 Hyper-V/WSL2/Docker/WinNAT 保留了大量區塊,這些區塊在每次啟動時都會移動 - 因此在一個已經運作了數月的機器上,綁定失敗並出現 WSAEACCES(「存取被禁止」)。將預設值更改為空閒埠然後與 Serviio(一個單獨的 DLNA 伺服器已在新的埠上)衝突,失敗並出現 WSAEADDRINUSE。每次失敗都被 Initialise 吞噬,導致外掛程式靜默死亡,然後在下一次設定儲存時出現 NRE。在 N04 之後,埠衝突會自我修復 - 伺服器繼續在下一個空閒埠上運行,使用者會收到通知,客戶端會重新發現它 - 而不是使整個外掛程式崩潰。
實作:
- 預設埠在所有三個
ServerPort宣告 + 設定解析失敗回退中從49382移至9779(低於動態範圍,因此 Windows 永遠不會自動保留它;不是已知的媒體伺服器預設值)。 Plugin.boundServerPort(新的共享欄位)保存即時監聽埠;activeServerPort保持配置的 快照,因此「需要重新啟動」徽章邏輯不會在回退時錯誤觸發。HttpServer.PortScanRange = 20;掃描在第一次成功的TcpListener.Start()時停止,並且僅在所有嘗試都失敗時才拋出最後一個例外。- 新的 EN 資源鍵
WarnPortInUse(翻譯遵循發布時的地區設定)。
N05 - 透過多播群組進行 SSDP 公告 (VPN / 點對點)
內容: SSDP 公告會傳送到 UPnP 多播群組 (239.255.255.250),而不是 IP 廣播位址。當 SSDP 搜尋回應與伺服器重新啟動競爭時,記錄的無害「無法存取已處置的物件」錯誤也會被抑制。
原因: 在點對點 / VPN 網路介面卡上,IP 廣播不適用 - 舊的廣播傳送失敗並出現「無效引數」,並且公告被遺漏,因此此外掛程式對這些連結上的客戶端是不可見的。向正確的多播群組公告可以修復這些介面卡上的發現問題。
媒體庫導覽
這些功能已在此分支中發布,原始外掛程式中沒有。它們來自實際瀏覽此外掛程式在真實 UPnP 客戶端中的輸出。
N06 - 基於篩選器的媒體庫公開
內容: MusicBee 的篩選器選項卡(使用者 MusicBee 資料夾中的 .xautopf 檔案)成為此外掛程式媒體庫中的 UPnP 根容器。每個篩選器的曲目隨後可以在 AlbumArtistSort → Album → Tracks 的層次結構中瀏覽。
原因: 擁有策劃的 MusicBee 篩選器(例如「5 星級曲目」、「最近新增」、「古典 → 巴洛克」)的使用者希望在從 UPnP 客戶端瀏覽此外掛程式時找到它們。原始外掛程式只公開了原始媒體庫樹。
N07 - SortAlbumArtist 欄位連線
內容: 此外掛程式現在讀取 MusicBee 的 MetaDataType 165(排序專輯藝人)並使用它在瀏覽視圖中對藝人進行分組/排序。
原因: 高傳真瀏覽器和發燒友使用排序藝人名稱(「Beethoven, Ludwig van」而不是「Ludwig van Beethoven」)來組織媒體庫。這是嚴肅聽眾的標準期望。兩個上游都缺少此功能。
N08 - 多值專輯藝人處理
內容: 當專輯的 AlbumArtist 欄位包含多個以 "; " 分隔的藝人時(例如 "yaiol; Ars Ricercata"),曲目現在會出現在瀏覽視圖中 每個 藝人之下,而不是單一的組合名稱藝人之下。
原因: 合作專輯和合輯需要出現在每個合作者之下。沒有這個,尋找專輯的一半搜尋路徑都會中斷。
N09 - 專輯容器封面 (upnp:albumArtURI)
內容: DIDL 瀏覽回應中的專輯容器節點現在包含一個指向專輯封面的 upnp:albumArtURI 元素。
原因: 沒有這個,UPnP 客戶端瀏覽視圖中的每個專輯都會顯示一個通用圖示而不是專輯封面。這是導覽的視覺提示;每個現代高傳真瀏覽器都期望如此。
N10 - 篩選器專輯內的曲目排序
內容: 篩選器公開的專輯內的曲目現在按唱片編號,然後按曲目編號排序。
原因: 標準專輯順序。如果沒有明確排序,曲目會以篩選器碰巧返回的任何順序返回 - 通常看起來是隨機的。
N11 - 播放清單資料夾樹修復
內容: LoadLibraryPlaylists 函數(最初由 Steven Mayall 於約 2014 年編寫)未能進入新建立的播放清單資料夾。每個資料夾中的第一個播放清單,以及任何子資料夾,最終都孤立在根層級。
原因: 在原始外掛程式中存在了十一年。在開啟 BubbleUPnP 並點擊播放清單後 30 秒內即可看到。在 yaiol 中,透過在樹狀結構建構期間正確遞迴進入新建立的資料夾來修復。
N12 - XML 非法控制字元淨化
內容: 任何包含 C0 控制字元(例如來自錯誤編碼傳遞的 0x19 - UTF-8 → Latin-1 → 回溯截斷 0x99 為 0x19)的標籤的曲目,一旦錯誤曲目進入分頁批次,就會導致整個瀏覽回應失敗並出現 Action Failed。
原因: XML 1.0 禁止大多數 C0 控制字元,並且 XmlWriter 在要求寫入任何字元時會拋出錯誤。存在於原始外掛程式中。透過在每個 Library_GetFileTags 退出點透過 XmlConvert.IsXmlChar 剝離無效字元來修復。
N13 - 分頁瀏覽中電台列表的確定性
內容: 瀏覽電台容器會進入通用檔案列表分支,該分支在每次呼叫時都會呼叫 files.Sort(AlbumFileComparer)。電台條目具有空的專輯/光碟/曲目標籤,因此每次比較都返回 0 - List(Of T).Sort 不穩定,每次呼叫都會產生不同的順序。UPnP 控制點會分頁(BubbleUPnP 擷取 0..15,然後擷取 16..end);在兩次呼叫之間,列表會重新洗牌,因此有些電台會出現在兩個頁面中(重複),有些則沒有(遺失) - 每次重新整理都看起來是隨機的。
原因: 存在於原始外掛程式中(其作者從未透過 UPnP 瀏覽電台)。在此處透過 Browse 中專用的 ContainerCategory.Radio 分支修復,沒有每次呼叫排序;radioFiles 在載入時按標題排序一次(穩定)。分頁瀏覽現在看到確定性順序;頁面 1 和頁面 2 是不相交的。
N14 - UPnP 搜尋專輯類別返回專輯容器
內容: UPnP 搜尋專輯類別查詢(upnp:class = "object.container.album.musicAlbum",例如 BubbleUPnP 的「隨機專輯」)返回完整的曲目列表而不是專輯容器,因此客戶端顯示零專輯。原始處理程式只解析了括號內的條件,然後無論請求的類別如何都傾倒了所有曲目。
原因: 在此處修復 - 專輯類別查詢現在列舉不同的專輯(按專輯藝人+專輯分組)並將每個專輯作為一個適當的 musicAlbum 容器發出,帶有封面藝術,可透過 Salb<idx> 虛擬 ID 空間尋址,以便客戶端可以深入結果並播放它。
N15 - 可運作、範圍感知且可點擊的 UPnP 搜尋
內容: 原始版本沒有宣傳任何搜尋功能(GetSearchCapabilities 返回空),因此客戶端甚至拒絕發送搜尋;舊的後端從 musicFiles 讀取,在惰性樹時代永久為空。此分支宣傳真實可搜尋屬性,針對惰性媒體庫實作按標題搜尋曲目和按標題搜尋專輯(HandleLazySearch),當發送真實容器 ID 時將查詢範圍限定為客戶端的當前分支(否則替換為 L:music,以便頂部搜尋不會拖入播客/電台/有聲書雜訊),並透過合成的 Ssrch_alb_* ID 使專輯結果可點擊,Browse 的早期分支會將這些 ID 映射回專輯的曲目。(專輯類別結果作為容器的部分是 N14。)
原因: BubbleUPnP 中的搜尋從「媒體庫不支援搜尋」變為返回有用、範圍限定、可播放的結果。完整設計 + 拒絕的方法:SEARCH.md。
N16 - UPnP 快取失效 (SystemUpdateID)
內容: 原始版本返回一個常數 SystemUpdateID=0 - UPnP ContentDirectory 快取失效協定 - 因此符合規範的客戶端 (BubbleUPnP) 將媒體庫視為永不變更:過時的瀏覽結果、URL 方案變更後的 404 縮圖,以及「重新啟動 MusicBee 兩次才能看到變更」的麻煩。此分支在載入時從紀元秒數種子 SystemUpdateID (因此每次重新啟動都嚴格領先於上次),並在每次媒體庫變更和設定變更時遞增它 (SetLibraryDirty / ResetCache → BumpSystemUpdateId)。
原因: 客戶端在下次瀏覽時可靠地擷取編輯、新檔案和設定變更。已知限制:訂閱客戶端不會透過 GENA 主動重新推送新值 (作為未來工作擱置);他們仍然會在下次瀏覽時看到它。
N17 - 播客訂閱封面
內容: 播客磁磚沒有顯示圖片 - 每個 /PodcastThumbnail/ 請求都返回 404。兩個堆疊的錯誤:解析鏈從未檢查 MusicBee 的實際封面快取(%LocalAppData%\MusicBee\InternalCache\Subscriptions\<name>.jpg,桌面 UI 從中載入),並且 HTTP 層的 unescape+lowercase 將 feed-URL 路由鍵損壞到其最後一個路徑段。此分支從 MB 的 InternalCache 解析封面,並透過一個在 HTTP 層中保持完整的 URL 安全 slug 路由查找(PodcastSlug / podcastSubIdBySlug)。
原因: 訂閱封面現在在瀏覽視圖中呈現(所有 22 個以前返回 404 的請求都已解析)。
N18 - 階層式(分隔符號)標籤瀏覽
內容: 任何欄位都可以在「媒體庫選項」選項卡上標記為階層式,並給予一個單字元分隔符號(一個欄位選擇器 + 帶有新增/移除的分隔符號方塊,持久化在外掛程式設定中)。將分組設定為 /,值如 Jazz/Cool Jazz,然後瀏覽為 Jazz › Cool Jazz,而不是一個平面條目。精確標記在分支上的曲目(僅 Jazz)會獲得自己的 [Jazz] 節點,因此沒有任何內容被隱藏,帶有單個子項的分支會自行摺疊,並且 ; 被拒絕作為分隔符號,因為它是 MusicBee 自己的多值分隔符號。
原因: 使用者已經在單一欄位中編碼的深度標籤分類(流派樹、情緒層次結構、「古典/巴洛克/協奏曲」)最終會像標籤描述的樹一樣瀏覽,而不是使用者必須從頭到尾閱讀的斜線分隔字串的平面牆。
N19 - 以其分組欄位標記的單一根路徑
內容: 根目錄下的單一瀏覽路徑會以其分組欄位(例如「流派」)而不是其完整的短路徑來標記,這與合併的第一個欄位組的命名方式相符。
原因: 瀏覽樹的讀取方式一致 - 無論根條目是獨立存在還是與同級合併(N20),都採用相同的命名規則 - 而不是單獨的根條目顯示冗長的內部路徑,而其合併的鄰居則顯示清晰的欄位名稱。
N20 - 合併共享第一個欄位的瀏覽路徑
內容: 兩個共享相同第一個欄位的瀏覽路徑 - 「流派 / 排序專輯藝人」和「流派 / 播客人物」 - 會合併成一個單一的流派根資料夾,該資料夾首先列出流派值,然後分成兩個視圖,而不是在根目錄並排顯示兩個幾乎重複的「流派 / …」條目。
原因: 擁有幾個相關視圖嵌套在一個共同欄位下的使用者會看到根目錄被幾乎相同的頂層條目弄得雜亂無章。合併它們可以保持根目錄的可讀性,並將相關視圖分組到它們所屬的位置 - 在它們的共享欄位下。
N21 - 類別型瀏覽路徑(標準 / 電台 / 播客)
內容: 每個瀏覽路徑都按類別分類 - 標準、電台或播客。範本清單分為這三個部分,每個範本的欄位選擇器只提供該類別資料實際可以提供的欄位,並且範本只能應用於視圖樹中匹配的節點(不相容的節點會變灰且無法勾選)。保留的電台和播客範本無法刪除,因此其類別部分永遠不會消失。
原因: 如果沒有類型,使用者可能會建立一個靜默為空的佈局 - 電台沒有「專輯」,播客節目沒有「專輯藝人」 - 並且只有在從 UPnP 客戶端瀏覽到一個死資料夾時才會發現。將欄位選單和應用目標限制為類別的真實資料,可以使空的佈局無法建立。
N22 - 按發布年份分組播客
內容: 每個播客節目的發布日期都會被讀取,因此帶有年份層級的播客瀏覽路徑會按年份對節目進行分類,而不是將它們摺疊在單一的「未知」之下。
原因: 大型播客訂閱可以像媒體庫的其餘部分一樣按年份導覽,而不是每個節目都落在一個未註明日期的堆中,因為此外掛程式從未查看過每個節目的發布日期。
N23 - 摺疊單一結果分組層級
內容: 解析為單一值的分組層級 - 例如,對於只製作 LP 的藝人,唱片類型層級只顯示「LP」,或者帶有單一字母的字母層級 - 會自動跳過,直接將使用者帶入其內容。
原因: 瀏覽包含一個資料夾的資料夾純粹是摩擦。摺疊單一選擇層級可以消除不必要的點擊,而不會改變使用者可以到達的內容。
N24 - 針對 MusicBee 日期欄位的年份分組/搜尋
內容: 年份條件不再使用裸露的四位數值查詢 MusicBee 的完整日期「年份」欄位,並且硬編碼的年份欄位別名已消失,因此每個分組欄位現在都從路徑定義中通用解析。
原因: 對於其年份標籤包含完整日期的媒體庫,以前按年份分組或搜尋會返回空 - 四位數查詢從未匹配完整日期欄位。查詢正確的欄位會使年份分組和搜尋再次找到曲目。
N25 - 獨立的「年份」和「年份 (yyyy)」分組欄位
內容: 專輯分組和瀏覽路徑現在公開 MusicBee 自己的兩個年份欄位 - 年份(完整日期標籤)和年份 (yyyy)(僅四位數年份) - 因此使用者在定義專輯分組或瀏覽路徑時可以選擇其中一個。
原因: 這兩個欄位在 MusicBee 中有不同的含義,將它們摺疊會失去這種區別。同時顯示兩者讓使用者可以將某一年份的所有發行版聚集在一起 (yyyy) 或保持精確日期排序 (完整年份標籤),以符合他們的意圖。
N32 - 釘選的篩選器和播放清單在根目錄按類型分組
內容: 釘選的篩選器現在會直接顯示在瀏覽根目錄的「篩選器」資料夾下方,釘選的播放清單則直接顯示在「播放清單」資料夾下方,而不是所有釘選項目都集中在根目錄末端的一堆中。每個釘選的捷徑都與其同類放在一起。
原因: 隨著使用者釘選的捷徑越來越多,末端那一堆混雜的篩選器和播放清單會越來越難以瀏覽,並且將每個捷徑與其所屬的資料夾分隔開。將釘選項目歸入各自的類別之下,可以保持根目錄清晰易讀,並讓每個捷徑緊鄰它所屬的內容。
設定對話方塊與封裝
N26 - 分區設定對話方塊
內容: 「偏好設定」頁面採用了左側導覽佈局,分為多個區塊:一般 / 播放 / 媒體庫 / 裝置設定檔 / 診斷。
原因: 原始版本是一個單一的長扁平設定列表 - 對於開發人員來說沒問題,但對其他人來說卻令人困惑。分區將相關選項分組,使對話方塊感覺更像現代應用程式設定。
組件 + 外掛程式重新命名(無 F-id - 封裝說明)
內容: 編譯後的 DLL 命名為 mb_UPnP_yaiol.dll,此外掛程式將自身報告為「MusicBee UPnP (yaiol)」。與原始的 mb_Upnp.dll 不同。
原因: 使用者可以將 yaiol 與原始外掛程式並排安裝,並比較其行為。
徽章系統 - 顯示執行時狀態(F40 背後的機制)
內容: 一種通用的 UI 模式,用於將重要的執行時條件顯示為設定對話方塊中可見的彩色徽章。目前實例:
- ⚠ 最大連線數 (N04) - 當 MusicBee 啟動以來至少達到一次最大連線數上限時觸發。黏性會話標誌
Plugin.MaxConnectionsHit。在沒有空閒插槽時在WaitOnSendBarrier內部設定。 - ⚠ 需要重新啟動 - 當儲存的設定需要 MusicBee 重新啟動才能生效時觸發。黏性會話標誌
Plugin.RestartRequired。在對話方塊的儲存處理程式中設定,當新的持久值與執行時快照 (Plugin.activeMaxConnections、Plugin.activeServerPort、Plugin.activeIpAddress) 不同時。需要重新啟動的設定僅限於那些確實無法熱重載的設定 - HTTP 伺服器綁定參數和在 Initialise 時建立的 SemaphoreSlim。
原因: 外掛程式的日誌檔案對於技術使用者進行偵錯來說很好,但非技術使用者盯著「裝置聲音不對」或「播放緩慢」時永遠不會開啟 診斷 → 查看日誌。徽章捕捉了使用者需要知道發生了什麼事 的情況,並在他們下次開啟外掛程式時顯示出來 - 無需閱讀任何內容即可發現。
未來可重複使用:
- 偵測到設定檔不匹配(裝置使用者代理程式從未匹配任何設定檔,回退到通用)。
- 觸發 NextURI 退避(F13 - 對於不穩定的裝置,會話期間禁用無間隙播放)。
- 媒體庫掃描失敗 / 部分。
- 渲染器在會話中斷線。
- 任何其他「發生過一次,使用者應該知道」優於「在 1000 行其他日誌中靜默記錄」的情況。
實作慣例:
- 徽章標籤位於對話方塊層級(不在任何面板內),因此無論使用者在哪個區塊上都可見。
- 放置在底部行靠近儲存/取消按鈕(目前:y=410 水平堆疊)。
- 每個徽章在
Plugin中都有一個對應的黏性會話標誌,當條件發生時翻轉為 True,並且僅在 MusicBee 重新啟動時重置。 - 資源:
<Condition>Badge(標籤文字,前綴 ⚠)+<Condition>BadgeTip(解釋原因 + 補救措施的工具提示)。 - 對於「儲存的設定需要重新啟動」徽章,在
Plugin.Initialise()時取得執行時快照,並在對話方塊儲存處理程式中Settings.SaveSettings()之後與Settings.*進行比較。
N27 - 取消會捨棄路徑/範本編輯
內容: 在設定對話方塊中對路徑和範本所做的編輯,現在在使用者點擊「取消」時會被捨棄,而不是悄悄地保持應用,並且在會話期間移除的任何保留範本都會重新建立。(範本在編輯時會即時儲存 - 「路徑」選項卡上沒有單獨的「儲存」按鈕。)
原因: 「取消」應該意味著取消。以前,使用者嘗試路徑/範本變更並退出後,發現變更已經提交,除了手動重做每個變更之外,沒有辦法撤銷它們。
N28 - 欄位選擇器選單中的字面和號
內容: 名稱中包含「&」的欄位 - 例如「Mood & Context」 - 在欄位選擇器選單中會以字面形式呈現和號,而不是將其吞噬為 Alt 助記符前綴。
原因: 帶有和號的欄位名稱顯示錯誤(字元消失,下一個字母成為加速鍵),使得選單條目難以識別。
N29 - 穩定、未翻譯的設定視窗標題
內容: 設定視窗標題固定為品牌字串「MusicBee UPnP Plugin」,不再隨介面語言而變化;每個地區設定套件中都刪除了每個語言的 DialogTitle 字串。
原因: 視窗標題隨語言變更是一個可翻譯的表面,但沒有任何好處 - 標題是一個品牌標誌。固定它使其在任何地方都穩定且一致。
本地化
原始外掛程式僅支援英文。此分支完全可本地化 - 每個使用者介面字串都透過資源套件流動,此外掛程式會自動偵測 MusicBee 自己的 UI 語言。
N30 - 多語言 UI(翻譯待定)
內容: 本地化機制已完成並發布。Localisation.vb 從 MusicBee3Settings.ini 讀取 MusicBee 選定的語言(<SystemLanguage> 內名)並將匹配的 .NET 文化應用於執行緒,因此 My.Resources.Resources.* 返回本地化字串。每個使用者介面標籤/按鈕/訊息都連接到資源鍵(設計器控制項透過 ApplyDesignerExtras + sync-en-locale.js;WarnPortInUse 等執行時字串手動添加)。尚未完成的是實際翻譯:只有英文原始套件 (Resources.resx) 存在 - 其他語言的衛星套件將在此外掛程式功能完成時一次性生成(在字串仍在變動時零碎翻譯會浪費精力)。
目標語言(MusicBee 本身提供的集合,由 endonymToCulture 1:1 匹配,因此此外掛程式會自動遵循 MusicBee 的語言):
阿拉伯語 (ar) |
捷克語 (cs) |
德語 (de) |
希臘語 (el) |
西班牙語 (es) |
法語 (fr) |
匈牙利語 (hu) |
義大利語 (it) |
韓語 (ko) |
荷蘭語 (nl) |
挪威語 (nb) |
波蘭語 (pl) |
葡萄牙語 BR (pt-BR) |
葡萄牙語 PT (pt-PT) |
瑞典語 (sv) |
土耳其語 (tr) |
烏克蘭語 (uk) |
俄語 (ru) |
日語 (ja) |
簡體中文 (zh-CN) |
繁體中文 (zh-TW) |
英語 (en,來源) |
變體政策(根據工作區地區設定規則):PT 和 ZH 分為不同的套件,因為詞彙/腳本確實存在差異(pt-BR/pt-PT、zh-CN/zh-TW)。EN 是一個單一套件 - MusicBee 的「English(US)」(en-US) 透過 .NET 的文化鏈回退到 en,因此不會生成單獨的美國套件。ES 和 FR 同樣是單一地區設定。
原因: UPnP 外掛程式的設定(「不要使用原始 PCM」、「強制小端 PCM」、埠回退警告)在母語中已經夠神秘了。遵循 MusicBee 自己的 UI 語言 - 而不是強制使用英文 - 是非英文使用者能否配置工具的區別。兩個上游都沒有嘗試過這個。
N31 - 說明連結以完整的介面語言開啟
內容: 從外掛程式開啟「說明」連結會尊重使用者完整的介面語言(例如 pt-BR、zh-CN),而不是縮減為基本語言,並傳送更清晰的更新檢查識別碼。
原因: 運行 MusicBee 區域變體(巴西葡萄牙語、簡體中文)的使用者會被導向基本語言的說明頁面。攜帶完整的文化會將他們導向他們使用的確切語言的說明頁面。
對原始外掛程式的修復和改進
核心協定與播放
F01 - 更新預設 DLNA 裝置設定檔
內容: 隨附 PlayStation 4、Xbox 360/One 和現代 BubbleUPnP 的全新預設設定檔,其功能標誌(取樣率、位元深度、編解碼器)反映了這些裝置目前實際支援的功能。
原因: 原始外掛程式的預設值約在 2014 年凍結。PS4/Xbox/BubbleUPnP 此後已獲得高解析度音訊支援。開箱即用,新安裝可在這些裝置上提供最佳播放體驗,而無需使用者觸摸裝置設定檔設定。
F02 - 控制宣傳 MediaRenderer:3 的裝置
內容: 外掛程式會探測渲染器的 UPnP 服務描述,以決定 MusicBee 是否可以驅動它。原始版本只匹配 urn:schemas-upnp-org:device:MediaRenderer:1。現代裝置宣傳 :2 或 :3。F02 擴大了匹配範圍。
原因: 沒有這個,最近的 Sonos / WiiM / Eversolo 裝置根本不會出現在 MusicBee 的「播放至」裝置清單中 - 即使它們使用相同的協定。單一字串前綴匹配修復解鎖了整個現代裝置世代。
F03 - 「強制原生串流」每個設定檔選項(預設開啟)
內容: 勾選後,此外掛程式會將原始檔案位元組傳送到裝置,不應用轉碼、不應用 DSP、不應用 ReplayGain 處理。只是使用者選擇的原始檔案,逐位元組(除了 HTTP 框架)。
原因: 根據論壇證詞,這是最大的播放品質提升。高傳真使用者購買昂貴的渲染器明確需要位元完美輸出;任何 DSP 觸摸都會破壞其目的。預設開啟,因為大多數現代裝置都能處理使用者丟給它們的任何編解碼器,而 ReplayGain/EQ 應該是可選的。它是每個設定檔的,因此您可以為舊的 Xbox 保持轉碼,同時將原生串流傳送到高傳真 DAC。
F04 - 「強制轉碼」每個設定檔
內容: 每個設定檔覆寫,強制此裝置的每個串流都通過轉碼器,無論是否支援原生編解碼器。與 F03(強制原生串流)相反。與 F03 互斥 - 當其中一個開啟時,UI 會自動取消勾選另一個。
原因: 單一全域開關會與每個設定檔的強制原生串流 (F03) 相互矛盾。實際案例:裝置 A 是一個高傳真 DAC,需要位元完美的原生串流;裝置 B 是一個舊的 AV 接收器,無法處理 FLAC。使用全域開關,使用者必須選擇 - 以犧牲另一個裝置為代價。使用每個設定檔,每個裝置都能得到正確的答案。
實作:
StreamingProfile.ForceTranscoding As Boolean = False。- 持久化架構版本升級到 v9。v9 之前的文件會載入舊版全域值一次,並將其複製到所有設定檔中,在升級過程中保留舊版行為。
- UI:從診斷面板中移除,新增到裝置設定檔區塊中,與強制原生串流並列。雙向互斥處理程式(每個
CheckedChanged在翻轉之前取消訂閱另一個,以避免無限迴圈)。 - 決策點:
Settings.ForceTranscoding→streamingProfile.ForceTranscoding在WriteAudioFileDIDL中。
F05 - 「強制小端 PCM」每個設定檔
內容: PCM 串流(L16/L24 mime 類型)依規範為大端。有些裝置錯誤地預期小端,並在收到正確的大端資料時播放白噪音。F05 會切換每個設定檔的位元組順序。
原因: 沒有這個,某些裝置會輸出靜態牆。症狀很劇烈,如果沒有 PCM 編碼知識,原因就看不見 - 切換開關為使用者提供了一個猜測和檢查的修復方法。
F06 - 「不使用原始 PCM」每個設定檔
內容: 當裝置聲稱支援原始 PCM 時,此外掛程式會使用它。有些裝置會說謊 - 它們接受 SOAP 握手,但會破壞實際的原始 PCM 資料,同時正確處理包裝在 WAVE 容器中的 PCM。F06 會強制 PCM-over-Wave,無論裝置宣傳什麼。
原因: 某些 Marantz 型號特別如此 - 它們宣傳原始 PCM,但只有 WAVE 才能運作。沒有這個,原始 PCM 串流會失真,並且沒有錯誤訊息可指出。
F07 - 「內容長度」每個設定檔
內容: 在 HTTP Content-Length 標頭中傳送的值。四個選項:
- 預設 - 已知時為實際位元組數,未知時省略。
- 無 - 從不傳送標頭(僅限分塊編碼)。
- 僅限 PCM - 僅傳送原始 PCM;其他所有內容均省略。
- 固定 - 傳送
UInt32.MaxValue - 8192(「巨大未知長度」的哨兵)。
原因: UPnP/DLNA 裝置對 Content-Length 的反應差異很大。有些需要精確的數字,有些不喜歡串流上的它,有些需要一個哨兵「非常大」的值來保持緩衝。這後來從僅限 PCM 擴展到所有輸出格式,因為相同的問題出現在轉碼的 MP3/AAC 串流中。
F08 - 「不清除 NextURI」每個設定檔
內容: 通常,當佇列清空時,此外掛程式會清除裝置的佇列 NextURI(傳送帶有空白 URL 的 SetNextAVTransportURI)。有些裝置(尤其是 Denon)會將空白 NextURI 解釋為「停止所有內容」並立即停止播放。F08 會阻止此外掛程式清除它。
原因: 沒有這個,Denon 用戶會遇到當佇列清空時裝置在曲目中間切斷的情況。勾選 F08 後,裝置會將過時的 NextURI 保留在記憶體中(無害 - 它只會在下次有內容排隊時被覆寫)。
F09 - FLAC 作為轉碼輸出格式
內容: 裝置設定檔中的轉碼格式下拉選單現在除了 PCM 16/24、MP3、AAC、Ogg 之外還提供 FLAC。選擇它會將 BASS 編碼器透過 MusicBee 的標準 FLAC 轉換命令列路由(與 MP3/AAC/Ogg 已經使用的機制相同)。
原因: 對於能很好處理 FLAC 但無法解碼來源編解碼器的裝置(例如 Eversolo 接收 MusicBee 的 WMA 媒體庫轉換為 FLAC),這可以保留無損品質,而 MP3/AAC 會丟棄音訊資料。解鎖 N02(5.1 降混控制),如果沒有無損轉碼選項,就無法解決。
實作: Encoder.StartEncode 的 Select Case Codec 區塊中新增一行 - FLAC 加入 MP3/AAC/Ogg 的命令列驅動分支。UI 下拉選單獲得「FLAC」作為第 6 個選項。SettingsDialog 中的載入/儲存映射擴展以識別 FileCodec.Flac ↔ SelectedIndex = 5。Mime、DLNA 類型和編碼功能已在 ItemManager.GetMimes / GetDlnaType / GetEncodeFeature 中透過早期工作(F21、F26)連接。
無間隙播放 (SetNextAVTransportURI)
F10 - SetNextAVTransportURI / NextURI 核心
內容: 真正的無間隙播放。當裝置在其 UPnP 服務描述中宣傳支援 SetNextAVTransportURI 時,此外掛程式會在當前曲目結束前預先將下一曲目排入裝置佇列。裝置會在內部轉換,曲目之間沒有可聽見的間隙 - 就像您在 CD 播放器上聽到的那樣。這不是「連續串流」技巧(它將所有內容連接成一個長串流並丟失每個曲目的中繼資料)。
原因: 旗艦級 Tier-2 功能。以連續現場表演錄製的專輯(現場錄音、古典樂章、DJ 混音)在曲目之間有半秒的靜音時聽起來不對勁。正確解決這個問題是一個旗艦功能,現在已在此分支中。
注意: 佇列中的音訊透過此外掛程式的 HTTP 伺服器使用 streamHandle=0(媒體庫擷取模式)提供,這表示 MusicBee 的音訊引擎不會參與佇列中的曲目。權衡:ReplayGain/DSP/EQ 效果不適用於下一曲目。在「強制原生串流」開啟時(預設值)可接受。
F11 - 「停用 NextURI 支援」每個設定檔
內容: 即使裝置宣傳 SetNextAVTransportURI,此核取方塊也會強制此外掛程式忽略該廣告並回退到一次播放一首曲目。
原因: 有些裝置宣傳 NextURI 但實作有錯誤(崩潰、半轉換、掛起)。與其反向工程每個損壞的裝置,不如讓使用者獲得一個「在這裡關閉它」的開關。
F12 - 播放清單上的 NextURI 生命週期
內容: 當 MusicBee 觸發 NowPlayingListChanged 時,此外掛程式會重新評估應排入佇列以進行無間隙轉換的內容。它會透過 NowPlayingList_GetNextIndex(1) + NowPlayingList_GetListFileUrl 向 MusicBee 請求新的「下一首」曲目,與裝置上目前排入佇列的內容進行比較(透過新的 nextPlaySourceUrl 欄位追蹤),如果內容已更改則重新排入佇列(如果 MusicBee 表示沒有下一首曲目則清除佇列)。
原因: 沒有 F12,當使用者移除/重新排序佇列中的曲目時,裝置會繼續播放過時的 NextURI。這在歷史上經過了多次迭代,因為每次列表變動都需要不同的處理 - 我們 透過信任 NowPlayingList_GetNextIndex(它已經尊重隨機播放和重複播放所有環繞)來簡化,因此所有變體都透過相同的比較進行。
實作:
- 新的
nextPlaySourceUrl欄位儲存已排入佇列的 MusicBee 媒體庫 URL(帶有句柄後綴的串流 URL 無法與媒體庫路徑進行比較)。 MediaRendererDevice上新的Public Sub RefreshQueuedNextUri()。三個結果:沒有 NextURI 排入佇列 → 無操作;排入佇列的內容與新的「下一首」匹配 → 無操作;排入佇列的內容不同 → 使用新的 URL 呼叫QueueNext(或QueueNext("")以清除 - 這會尊重 F08 DoNotClearNextUri)。- 在
Plugin.ReceiveNotification下的NotificationType.NowPlayingListChanged中連接。
F13 - NextURI 失敗退避
內容: 在同一裝置上連續 4 次 SetNextAVTransportURI 失敗後,此外掛程式會停用該裝置的無間隙播放,直到 MusicBee 重新啟動。
原因: 如果裝置的 NextURI 確實損壞(間歇性 SOAP 錯誤、網路故障),此外掛程式否則會在每首曲目上不斷重試。F13 會停止噪音並靜默回退到一次播放一首曲目。
F14 - 重複模式 + NextURI 整合
內容: F14 分為兩種情況,由 OnAvTransportStatusCheck 中的 F15 轉換偵測器處理:
- 重複所有:MusicBee 將正確的「環繞」URL(列表末尾的曲目 1)傳遞給
Plugin.QueueNext本身。不需要特殊的外掛程式邏輯 - 裝置轉換到它,F15 偵測器像往常一樣呼叫Player_PlayNextTrack,這會將 MusicBee 的 NPL 索引環繞回 0。 - 重複一首:MusicBee 將相同的曲目 URL 傳遞給
Plugin.QueueNext。裝置轉換到它(新的串流句柄,相同的來源)。F15 偵測器現在查詢Player_GetRepeat()- 如果是RepeatMode.One,它會跳過Player_PlayNextTrack呼叫,這樣 MusicBee 就不會將 NPL 索引從循環曲目中推進。
原因: 如果沒有重複一首跳過,在無間隙轉換時呼叫 Player_PlayNextTrack 會將 MusicBee 推進到列表中的下一首曲目(重複一首只影響播放器 UI 中曲目結束自動推進行為 - 下一首曲目 總是向前移動),這與重複一首的含義相矛盾。
播放計數注意事項: 在重複一首中,播放計數增量取決於 MusicBee 3.7.9563+ 注意到循環播放。較舊的 MusicBee 版本會正確播放無間隙重複,但會錯過播放計數增加。已記錄;不阻礙。
F15 - 曲目轉換偵測狀態機
內容: 當裝置內部從當前曲目轉換到 NextURI 時,此外掛程式需要注意並告知 MusicBee 推進其正在播放的索引。否則 MusicBee 會認為它仍在上一曲目上,並且播放計數 / UI / scrobbling 會不同步。
實作: 在每個狀態計時器滴答時輪詢 GetPositionInfo.TrackURI。當報告的 URI 與我們透過 NextURI 排入佇列的 URI 匹配時,我們會在 MusicBee 上呼叫 Player_PlayNextTrack 並設定 suppressNextSoapCall,這樣產生的 PlayToDevice 就不會重新傳送 SetAVTransportURI(這會中斷無間隙播放)。
原因: 沒有 F15,裝置會播放下一首曲目,但 MusicBee 的 UI 會顯示它仍在上一首曲目上。令人困惑,破壞了 scrobbling,破壞了播放計數追蹤。曲目轉換偵測需要長時間、每個渲染器迭代,因為每個渲染器品牌在何時報告 URI 變更方面都有其自身的怪癖(有些先報告 TRANSITIONING,有些直接跳到 PLAYING 並帶有新 URI,有些在兩者之間有短暫的 STOPPED)。
注意: 我們的第一個版本適用於 BubbleUPnP 渲染器。每個裝置的邊緣情況保留在 B6 中。
F16 - 無間隙轉換時的爆音修復
內容: 當佇列中曲目的來源格式(取樣率 / 通道 / 編解碼器)與正在播放的曲目不同時,會發生爆音,這會迫使裝置的 DAC 在轉換時重新鎖定。F16 新增了一個 NextUri:FormatChange 診斷,只要格式不同,就會在佇列時觸發,並命名兩側 - 因此聽到爆音的使用者可以進行關聯。
診斷還指出了緩解措施:勾選裝置設定檔上的強制轉碼。這會將每個曲目同質化為單一轉碼編解碼器/取樣率/位元深度,完全消除來源格式差異。
為什麼延遲實際的轉碼匹配修復: 結構性修復(將佇列中的曲目轉碼以匹配正在播放曲目的格式)需要更改此外掛程式的 HTTP 伺服器 URL 方案 - 目前 /encode/{id}0.{ext} 以原生方式提供佇列中的檔案。F16 的未來 v2 將新增每個格式的 /encode/{id}0_{rate}_{depth}.{ext} 路由並透過編碼器連接它們。如果真實裝置在強制轉碼不足以解決問題後仍出現爆音,那麼這是一個值得進行的更大架構變更。
今天的實作:
lastSourceUrl欄位追蹤正在播放的來源 URL。QueueNext讀取當前和佇列中曲目的FilePropertyType.SampleRate/Channels/Kind,並在不匹配時記錄NextUri:FormatChange。
F17 - 搜尋後進度條重新同步
內容: Seek() 函數在成功的 Seek SOAP 之後已經呼叫了 GetPlayPositionInformation(),這修復了「完全沒有重新同步」的情況。F17 解決了 UPnP 1 秒 RelTime 量化導致的剩餘最多 1 秒的漂移:當裝置報告的位置四捨五入到使用者請求目標的 1 秒內時,此外掛程式現在信任使用者亞秒級精確的值,而不是裝置的截斷。只有當裝置報告的內容顯著不同(>1 秒偏差)時,我們才使用其值(搜尋落在與請求不同的位置,例如某些編解碼器上的關鍵影格對齊)。
原因: 沒有這個,以裝置的「2:30」報告為基準搜尋到 2:30.500 會使進度條顯示比實際慢約 500 毫秒。在 F17 之後,進度條在常見的曲目內拖曳情況下與使用者的意圖匹配,並且對於關鍵影格對齊的異常情況仍然尊重裝置的報告。
F18 - 連續串流 / NextURI 互鎖
內容: 現在有兩個互鎖:
- 執行時: 當
Settings.ContinuousOutput開啟時,QueueNext會提前返回False。連續串流有其自己的無間隙機制(一個長的串聯串流);在其之上傳送SetNextAVTransportURI會使裝置混淆每個曲目是離散 URI 還是連續流的一部分。 - UI: 當使用者勾選全域連續串流核取方塊時,目前顯示的設定檔的
forceNativeStream會自動取消勾選。連續串流總是轉碼,因此強制原生串流在組合中沒有意義。
原因: 防止使用者同時啟用兩個衝突的無間隙機制。沒有 F18,裝置將同時接收連續串流 URI 和每個後續曲目的 NextURI,其行為取決於渲染器而未定義。
F19 - 忽略空白 NextURI 錯誤
內容: 當使用空白 URL 呼叫 SetNextAVTransportURI 時(例如列表中的最後一首曲目),某些裝置會返回 SOAP 錯誤。F19 會靜默吞噬這些錯誤 - 記錄下來但不作為錯誤傳播。
原因: 「沒有下一首曲目」的條件是正常的,而不是錯誤。將其視為致命錯誤會污染日誌並(在某些流程中)觸發重試風暴。
Mime 類型與 DLNA 中繼資料
F20 - MP3 mime → audio/mpeg
內容: 符合標準的 MP3 mime 類型是 audio/mpeg,而不是 audio/mp3。後者是常見的錯誤名稱,大多數裝置都能容忍,但更嚴格的渲染器會拒絕它。
原因: 靜默修復了遵循標準的更嚴格裝置上的播放問題。yaiol 程式碼庫已經正確處理了這個問題;無需更改。
F21 - Mime 類型順序:非 x- 變體優先
內容: 當裝置同時宣傳 audio/flac 和 audio/x-flac 時,此外掛程式會優先返回非 x- 變體。對於任何同時具有標準和實驗性 mime 的編解碼器也是如此。
原因: x- 前綴標記實驗性/非官方 mime。有些渲染器在標準形式下表現更好。微小的重新排序,實際影響。
F22 - Opus mime 類型支援
內容: 識別 Opus 為可串流音訊編解碼器;在提供 Opus 曲目時傳送 audio/opus mime。
原因: Opus 現在很常見(現代語音/音樂折衷編解碼器)。沒有 F22,此外掛程式將拒絕串流 Opus 檔案,即使是處理它們的裝置。
F23 - Monkey Audio (APE) 來源檔案支援
內容: 識別 .ape 檔案為串流/轉碼的有效來源編解碼器。
原因: APE 是一種無損格式,擁有小眾但忠實的使用者群。新增它成本低廉,並為這些使用者解鎖了媒體庫。
F24 - AAC / ALAC mime 回退
內容: 如果裝置支援 AAC 或 ALAC 但未在其 UPnP 服務描述中明確宣傳它們,此外掛程式仍會將它們作為回退提供。
原因: 許多裝置確實能很好地處理 AAC,但忘記將其列在其功能 XML 中。沒有 F24,此外掛程式甚至不會嘗試,強制轉碼。有了 F24,此外掛程式會嘗試並讓裝置在可能的情況下原生處理它。
F25 - 原生 + 編碼 WAV 串流的 DLNA 類型標誌
內容: DLNA 類型標誌(例如 LPCM、WAVE、MP3 等設定檔識別碼)需要與裝置接收的內容匹配。F25 確保原生串流和編碼 WAV 串流被正確標記。
原因: DLNA 類型不匹配會導致某些裝置完全拒絕播放或應用錯誤的解碼器。
F26 - FLAC 檔案的 DLNA 標頭
內容: FLAC 串流在其標頭中獲得正確的 DLNA 設定檔識別碼。
原因: 沒有它,某些支援 FLAC 的裝置無法將串流識別為 FLAC。
F27 - 中繼資料中的位元率計算修復
內容: 連續串流 res@bitrate 以前計算為 (sampleRate * channels * bitsPerSample) / 1000 - kbps,與 UPnP DIDL 規範定義的屬性為每秒位元組數相差約 125 倍。現在除以 8 而不是 1000。
原因: 裝置上顯示的位元率錯誤 - 在大多數渲染器上是外觀問題,但有些會從該值分配串流緩衝區,並在看起來比實際小約 125 倍的串流上出現卡頓。非連續來源檔案路徑已經正確處理了這個問題((bitrate_kbps * 1000) \ 8 = 每秒位元組數);只有連續串流路徑是錯誤的。
F28 - 中繼資料時間格式修復 (Marantz)
內容: DIDL 中的 res@duration 格式為 H:MM:SS(例如 0:03:42)。UPnP DIDL 規範將格式定義為 H+:MM:SS[.F+] - 嚴格來說,小數秒是可選但建議的;某些 Marantz 裝置將裸露形式視為無效並將其持續時間顯示留空。現在格式化為 H:MM:SS.fff(例如 0:03:42.000)。
原因: 特定於品牌的顯示問題;符合 ISO 風格的帶有小數秒的格式可以解決此問題,而不會影響任何其他裝置。應用於兩個 DIDL 發射點(WriteAudioFileDIDL 中的來源檔案路徑 + 編碼串流路徑)。
同一批次的額外修復: pv:addedTime 和 pv:lastPlayedTime 在其 DateTime 格式字串中使用了 hh(12 小時制)而不是 HH(24 小時制)。任何在 13:00 到 23:59 之間新增或播放的曲目都會在顯示該欄位的裝置上以錯誤的小時呈現(例如 17:42 →「05:42」)。現在使用 HH。
F29 - 編碼 MP3 搜尋支援 (CBR)
內容: 轉碼的 MP3 串流現在宣傳 DLNA.ORG_OP=11(位元組和時間搜尋),而不是 DLNA.ORG_OP=10(僅位元組)。以前拒絕轉碼 MP3 時間搜尋的裝置現在可以正常驅動其進度條/搜尋 UI。
原因: MusicBee 的轉碼器以 HighQuality 預設值產生恆定位元率 MP3,因此位元組 ↔ 時間映射是線性的 - 裝置可以將時間搜尋請求轉換為 HTTP Range 位元組搜尋,而無需任何編碼器端支援。宣傳 OP=11 解鎖了裝置上的該 UI。沒有 F29,使用者在轉碼 MP3 內搜尋時,搜尋會被靜默忽略或跳到曲目開頭。
實作: 重構了 ItemManager.vb 中的 GetEncodeFeature,將內聯 If 分解為可讀的 If/ElseIf/Else 鏈。MP3 明確獲得 OP=11;其他非 PCM 編解碼器保留 OP=10。AAC/FLAC 等沒有變化 - 這些需要編解碼器特定的 CBR 驗證,而 MusicBee 不保證。
F30 - 處理 .mpeg 檔案副檔名
內容: 帶有 .mpeg(甚至更罕見的 .mpe)副檔名的檔案現在在 GetCodec 中被識別為 FileCodec.Mp3。在 F30 之前,它們返回 FileCodec.Unknown 並被媒體庫靜默拒絕 / 無法作為轉碼來源。
原因: 舊的 MPEG-1 Layer 3 檔案有時使用 .mpeg 而不是 .mp3(規範允許兩者)。在 30 萬個檔案的媒體庫中,少數幾個檔案足以讓人感覺「MusicBee 顯示它們,但外掛程式不顯示」 - 這讓使用者感到困惑。
播放行為
F31 - 電台串流自動使用連續模式
內容: WriteAudioFileDIDL 現在透過 Library_GetFileProperty 探測來源 URL 的 Kind 屬性,並將任何其 Kind 以「Stream」結尾的檔案(MusicBee 報告電台為「MP3 Stream」、「Internet Stream」等)視為連續串流,無論全域 Settings.ContinuousOutput 開關如何。使用連續串流 DIDL 分支(標題:「Continuous Stream」,id="continuousstream",固定 PCM/Wave 輸出);裝置會看到一個單一的無限風格串流。
原因: 電台串流沒有曲目邊界,沒有固定長度,無法搜尋。在 DIDL 中將它們視為離散檔案會導致此外掛程式宣傳不存在的位元組範圍和持續時間。當 MusicBee 已經告訴我們「這是一個串流」時自動切換,消除了使用者不應該考慮的腳槍。
範圍: 僅適用於 MusicBee 驅動播放時(musicBeePlayToMode)。媒體庫擷取路徑(UPnP 客戶端瀏覽)保持不變 - 電台 URL 在那裡很少見,並且使用者介面行為不應在沒有明確測試的情況下改變。
F32 - 編解碼器廣告回退
內容: 如果裝置沒有宣傳某些編解碼器(或者此外掛程式無法解析裝置的功能 XML),此外掛程式不會立即拒絕串流。相反,它會嘗試提供串流,並讓裝置決定。
原因: 許多裝置的功能 XML 不完整或無法讀取,但實際上可以很好地處理編解碼器。F32 以小小的「最佳猜測和嘗試」換取徹底拒絕。
F33 - 進度條同步改進
內容: 輪詢之間的進度位置已經從單一錨點 (currentPlayStartTicks) 進行時鐘外推,因此進度條以亞秒級速率平滑更新。剩餘的抖動來源是新開始曲目的初始錨點:以前的程式碼假設在狀態計時器首次注意到狀態變為播放時 position=0,但那時裝置可能已經播放了 100-500 毫秒(一個輪詢間隔)。MusicBee 的進度條會從 0 開始,然後在現實趕上時向前跳躍。
F33 修復:當在新曲目上首次轉換到播放狀態時 (currentPlayStartTimeEstimated=True),呼叫 GetPlayPositionInformation() 以獲取裝置的實際當前位置,然後以此為錨點。UPnP 只報告 1 秒解析度,因此錨點仍然是量化的,但它比假設 0 更接近真實。
原因: 更平滑、更準確的進度顯示,尤其是在曲目變更後。無法繞過 1 秒 UPnP 報告解析度本身 - 這是規範。
F34 - 曲目變更後的進度條抖動
內容: 當為新曲目呼叫 PlayToDevice 時,此外掛程式過去會將 currentPlayPositionMs 和 currentPlayStartTicks 保留在其上一曲目的值,在 SOAP-Play 和第一個狀態計時器輪詢偵測到新播放狀態之間的約 100 毫秒視窗內。MusicBee 的進度條會短暫顯示上一曲目的結尾,然後跳回 0,然後上升。F34 在 PlayToDevice 進入時將兩者歸零 - 在我們知道曲目變更正在發生之前,在任何 SOAP 工作之前。
原因: 快速跳過使用案例(手動下一首或無間隙轉換)上的視覺故障。現在 MusicBee 的第一個 PlayPositionMs 查詢在 Play 後乾淨地返回 0,然後 F33 的 GetPlayPositionInformation 在第一個狀態變更滴答時將其精確到裝置的實際位置。
實作: PlayToDevice 頂部的四行,與 F33 的轉換時間精確錨定配對。
F35 - 「強制轉碼」錯誤
內容: 強制轉碼在某些組合下仍然可以跳過轉碼。在 F04 每個設定檔重做之後,兩個特定的間隙被彌補:
- 與 ForceNativeStream 的優先順序。 當兩者都為 True 時(這可能發生在架構遷移或部分設定檔案中),ForceTranscoding 現在完全勝出(
If streamingProfile.ForceTranscoding Then forceEncode = True ElseIf streamingProfile.ForceNativeStream Then forceEncode = False)。UI 互斥防止使用者同時勾選兩者,但執行時防護處理從磁碟載入不一致的任何狀態。 - bypassTranscodeDecision 邏輯。 以前:
streamingProfile.ForceNativeStream AndAlso Not Settings.ForceTranscoding。現在:streamingProfile.ForceNativeStream AndAlso Not streamingProfile.ForceTranscoding- 相同的優先順序規則,但在相同的每個設定檔範圍內。
原因: 「強制」應該意味著強制。如果使用者明確為裝置啟用了強制轉碼,此外掛程式絕不能靜默回退到原生串流,無論其他標誌如何組合。
F36 - 渲染器關閉例外
內容: 將 Plugin.ReceiveNotification 包裝在頂層 Try/Catch 中,該 Try/Catch 會記錄任何未捕獲的例外,而不是讓它傳播回 MusicBee 的通知泵。
原因: 來自 MusicBee 的通知(PlayStateChanged、VolumeMuteChanged 等)會分派到 ControlPointManager,後者透過 SOAP 與渲染器通訊。個別呼叫站點已經在其 SOAP 呼叫周圍有 Try/Catch,但足夠奇怪的時序情況(例如渲染器在同一通知處理程式中的兩個 SOAP 呼叫之間死亡)仍然可能逃脫。頂層包裝器是最終的安全網,因此使用者永遠不會看到來自 MusicBee 的通用「TargetInvocationException」彈出視窗。
實作: 將現有主體重新命名為 ReceiveNotificationInternal,並新增一個薄包裝器 ReceiveNotification,它執行 Try { ReceiveNotificationInternal(...) } Catch { LogError(...) }。ControlPointManager 內部預先存在的每個方法 Try/Catch 基礎設施(圍繞每個 PostSoapRequest 呼叫)保持不變 - F36 是雙重保險。
F37 - 長曲目搜尋觸發錯誤轉換
內容: 在長曲目中搜尋可能會在某些渲染器上產生短暫的停止→播放循環。如果沒有區分,ProcessNewPlayState.Stopped 會將其視為自然曲目結束並呼叫 Player_PlayNextTrack,在使用者只想拖曳時推進 MusicBee。F37 在 Seek() 中標記 lastUserInitiatedSeek,並在停止處理程式中新增一個 5 秒的防護(反映現有的 lastUserInitiatedStop 視窗)。
原因: 搜尋期間靜默跳到下一曲目是那些沒人能猜到原因的錯誤之一 - 使用者會想「奇怪,我試圖向前拖曳,現在它正在播放下一首歌」。修復是機械的:與已經存在的用戶停止區分模式相同。
F38 - 針對易崩潰編解碼器的改進搜尋處理
內容: BubbleUPnP 在 MP3 搜尋時崩潰是典型的症狀。經過審核,目前的 yaiol 搜尋程式碼已經做了正確的事情 - 原生路徑正確處理 HTTP Range (206, Content-Range, AcceptRanges),編碼路徑宣傳 X-AvailableSeekRange 並解析傳入的 timeSeekRange.dlna.org / npt 標頭,DLNA.ORG_OP 標誌反映實際串流功能 (對於有問題的 Platinum 裝置,可選擇 DisablePcmTimeSeek)。在目前的 BubbleUPnP 4.6.4 上進行了使用者測試:未觀察到崩潰。
原因: BubbleUPnP MP3 搜尋崩潰報告約在 2024 年,此應用程式自那時起已進行了約 16 個月的修復。F29 (編碼 MP3 OP=11) 是可能重新暴露它的新變數;在測試版本上沒有。
如果崩潰再次發生: 修復形式將是每個設定檔的「有限搜尋」開關,強制對標記的編解碼器使用 DLNA.ORG_OP=10 (僅位元組) - 類似於 DisablePcmTimeSeek 對 PCM 的運作方式。屆時再新增,而不是預防性地新增。
UI 與日誌記錄
F39 - 「新增」按鈕選取新的設定檔
內容: 在裝置設定檔清單中點擊「新增」會建立一個新的設定檔並自動選取它,以便使用者可以立即編輯欄位。我們的分區對話方塊重構已經這樣做了 - 直接新增路徑和從範本新增路徑都以 Me.activeStreamingProfiles.SelectedIndex = Me.activeStreamingProfiles.Items.Count - 1 結束。檢查確認我們的分支已經處理了這個問題 - 無需更改。
原因: 一個小的 UX 痛點,結果在這裡已經不是痛點了。
F40 - 更大的最大連線數 + 警告日誌
內容: 外掛程式的並行串流上限(SemaphoreSlim 圍繞 Sockets_Stream_File / Sockets_Encoder_Start)硬編碼為 4。F40 使其在「一般」設定頁面中可由使用者配置(預設 16,範圍 1-256),在請求必須等待插槽時新增 MaxConnections 日誌行,並在 MusicBee 啟動以來至少達到一次上限時,在設定對話方塊的左下角顯示紅色 ⚠ 最大連線數徽章。
原因: 當裝置發出並行請求時(某些 Marantz/Linn 在封面掃描期間,BubbleUPnP 的中繼資料探測與活動播放同時進行),額外的請求會靜默地被信號量阻塞 - 使用者會看到「裝置緩慢」而沒有可見的原因。日誌行對於技術偵錯很有用,但非技術使用者從不閱讀日誌。設定對話方塊中可見的徽章使任何開啟此外掛程式偏好設定的人都能發現達到上限的條件。
實作:
- 將等待集中在
MusicBeeUpnp.vb中的WaitOnSendBarrier(logTag);兩個呼叫站點(MediaServerDevice.GetFile、Encoder.StartEncode)都使用它。 Settings.MaxConnections持久化在設定架構的 v8 中。Plugin.MaxConnectionsHit是一個黏性會話標誌,在WaitOnSendBarrier內部設定;僅在 MusicBee 重新啟動時重置。SettingsDialog.maxConnectionsBadge是一個位於(16, 410)的紅色粗體標籤,僅在Plugin.MaxConnectionsHit為 True 時顯示。帶有解釋原因和補救措施的工具提示。- 信號量在類型載入時初始化一次,因此更改設定需要重新啟動 MusicBee(在欄位標籤中註明)。
F41 - 日誌「因 ReplayGain/DSP 而編碼」
內容: 不再是單獨的「為 RG 編碼」/「為 DSP 編碼」日誌行,F42 中的單一 StreamDecision 行包含 MB-DSP/EQ、MB-ReplayGain、Profile-DSP/EQ、Profile-ReplayGain 作為累積原因。診斷價值相同,噪音更少。
原因: 使用者在一行日誌中看到給定曲目發生轉碼的所有原因,而不是分散的。有關完整詳細資訊,請參閱 F42。
F42 - 日誌「渲染器不支援來源編解碼器」
內容: 為每個播放到裝置的曲目新增了一個 StreamDecision 日誌行,說明是「原生 CODEC」還是「轉碼 CODEC→CODEC reason=…」。原因欄位累積了觸發轉碼的每個條件:MB-DSP/EQ、MB-ReplayGain、Profile-DSP/EQ、Profile-ReplayGain、WebFile、VirtualFile、ForceTranscoding(global)、SampleRate<min/SampleRate>max、DownmixToStereo、DeviceLacksCodec(X)、BandwidthConstrained。
原因: 使用者對他們預期原生串流的檔案上意外的 CPU 峰值感到困惑。每個曲目的一行日誌會告訴他們確切是哪個條件導致了轉碼 - 如果該欄位顯示 DeviceLacksCodec(Flac),他們會立即知道裝置的協定資訊不完整,並且可能希望 F32 的回退生效。
實作: 單一累加器字串在決策鏈中逐步建立;在結束時記錄一次。受 Settings.LogDebugInfo 控制,以避免在生產環境中產生日誌噪音。
F43 - SetNextAVTransport 日誌顯示來源 URL
內容: QueueNext 日誌條目現在除了 stream=<HTTP streaming URL> 之外,還包含 source=<MusicBee library path>。相同的變更應用於成功路徑和失敗路徑 (QueueNext:Failed)。
原因: 在偵錯佇列曲目問題時,串流 URL (/encode/aabbccdd0.flac) 本身是不透明的 - 對於每個曲目都相同。來源 URL 是人類可 grep 的媒體庫路徑,它會告訴您 MusicBee 嘗試佇列的確切檔案。
F44 - 更好的 mime 類型錯誤日誌記錄
內容: 在 Activate 期間新增了兩個日誌條目:
Activate:MimeUnverified- 針對裝置GetProtocolInfo回應中每個格式錯誤的條目觸發,命名無法解析的條目(因此使用者可以看到例如「Marantz 為某些編解碼器返回http-get:*::*- 功能未驗證,F32 的回退將猜測」)。Activate:NoSinkInfo- 如果裝置完全沒有返回<Sink>元素,則觸發一次。這表示SupportedMimeTypes保持 Nothing,並且IsCodecSupported降級為「假設一切正常」 - 當稍後出現「裝置拒絕串流」錯誤時,這是有用的上下文。
原因: 在 F44 之前,這些靜默的功能回退讓使用者猜測為什麼他們的曲目要麼被轉碼超出預期,要麼被裝置拒絕。現在,單一的 Activate: grep 會顯示裝置的功能資訊是否可用。
F45 - 更好的中繼資料錯誤日誌記錄
內容: ContentDirectoryService.vb 中的 Browse 例外日誌已在早期 yaiol 工作(Alia Vox 錯誤會話)中透過 ObjectID 和堆疊追蹤進行了豐富。F45 進一步擴展了它,包括 BrowseFlag(中繼資料與子項)、Filter(客戶端請求的屬性)、sortCriteria 和 partialResultLength(失敗前產生了多少位元組的 DIDL - 指示壞曲目在批次中的位置)。
原因: 當 DIDL 中途出現問題時,部分長度值會告訴您失敗是在批次的第一首曲目(partial=0)還是中途(partial=N) - 結合批次的 startingIndex,您可以識別出有問題的曲目索引。Filter 和 BrowseFlag 解釋了客戶端想要哪種瀏覽;有時僅中繼資料瀏覽失敗,而相同 ID 的子項瀏覽成功。
網路
F46 - 自動模式僅在真實網路介面上宣告
內容: 在自動介面模式下,外掛程式以前會在每個正常運作的 IPv4 介面上宣告自己(SSDP)。在同時執行 VPN 隧道(NordLynx)或虛擬交換器(Hyper-V / WSL / Docker)的機器上,同一個媒體庫也會在這些介面上被宣告,因此您投射所用的控制點會發現伺服器兩到三次,並將媒體庫列為重複副本。自動模式現在僅保留擁有真實 IPv4 預設閘道的介面(HasIPv4Gateway)——隧道和虛擬交換器介面沒有預設閘道——因此它們會從宣告清單中剔除。使用者釘選的位址仍然絕對優先(僅在該介面上宣告),如果沒有任何介面回報閘道,選擇器會退回到所有介面,因此宣告的位址清單永遠不會是空的,外掛程式也不會變得不可見。
原因: 重複並不是因為「使用了 VPN」——而是因為同時在 LAN 介面和隧道/虛擬介面上宣告,導致一個控制點在兩個位址上看到同一個伺服器。消費級 VPN(NordVPN/NordLynx)只透過隧道傳輸發往網際網路的流量;DLNA 渲染器位於區域網路中,本機子網路流量會繞過隧道,因此隧道介面無論如何都不會到達渲染器——剔除它只會移除一個幻影副本,絕不會移除可用路徑。閘道檢測是區分真實 LAN/Wi-Fi 介面與隧道或虛擬交換器的廉價而可靠的訊號。它與 N05 互補(N05 修復了此類連結上宣告如何傳送——用多播代替廣播);F46 則決定究竟在哪些介面上宣告。
已知限制: 網狀/遠端存取 VPN(Tailscale、ZeroTier、連回家中的 WireGuard)的渲染器確實位於隧道另一端,其介面通常沒有預設閘道,因此自動模式也會將其剔除。這類使用者應改為釘選 VPN 位址,釘選的位址優先於閘道過濾。