MusicBee UPnP Plugin 帮助

新功能

2.0.9 - 2026-08-23

更新通知以您的语言打开其页面

内容: 更新通知中的“更新内容”和“下载”链接现在以 MusicBee 自己的语言(而非英语)打开插件页面。

原因: 这两个链接在发送到网站之前将语言限制为四种之一——英语、法语、西班牙语或德语——因此即使存在翻译,其他所有人也会收到英语页面。它们现在将 MusicBee 的语言原样传递,并让网站决定提供什么,这正是它们旁边的“帮助”按钮一直以来的做法。

2.0.8 - 2026-08-22

该插件现在可以正确地向网络上发现它的应用程序和设备介绍自己,并且“设备配置文件”设置也已重新对齐。

您的设备显示正确的制造商、型号和版本

内容: 当控制应用程序、手机或电视在您的网络上找到 MusicBee 时,它现在会将此插件显示为由 yaiol 制作,指向插件自己的网站,将其描述为涵盖服务器、播放器和渲染器这三个角色,并报告您实际安装的版本。

原因: 每个 UPnP 设备都会宣布其制造商和用途,控制应用程序会将其显示为设备的身份。此插件仍然宣布其所派生原始插件的详细信息:另一个作者的姓名、MusicBee 的网站而不是其自己的网站,以及自首次发布以来一直冻结在“1.0”的型号。从您的手机上无法分辨您正在与哪个插件对话,更不用说它的哪个版本了。现在这些详细信息来自插件本身,因此设备旁边显示的版本在每次更新时都保持正确。

设备配置文件设置重新对齐

内容: 在“设备配置文件”选项卡上,标签及其框共享一个左边缘并以均匀的间距排列,并且采样率范围显示为一行。

原因: 随着时间的推移,选项被添加到选项卡中,字段已经漂移开来,采样率范围的“到”标签最终位于其旁边的框上方——一旦您知道它说什么就可以阅读,第一次看时会感到困惑。

2.0.7 - 2026-08-08

较大的音轨会保留其标题和位置滑块

内容: 从手机发送的较大音轨(例如长 FLAC、高分辨率或 DSD 文件)现在会显示其正确的标题,并且可以像小音轨一样进行移动。以前,其中一些音轨会通过网络播放,标题位置显示的是网址,滑块也无法操作。

原因: 插件会等待其本地副本就绪后才开始播放,但它以前会根据文件大小提前判断文件是否值得等待。这实际上是在猜测您的网络速度,而它无法得知:一个 65 MB 的音轨被判断为过大,但一秒钟后就下载完成了——轻松地在它已经放弃的等待时间内完成。它现在只是简单地监视下载。当文件仍在传输时,插件会一直等待,无论需要多长时间;它只会在传输真正停滞时才放弃,而且现在比旧的固定延迟更快地注意到这一点。

2.0.6 - 2026-08-03

从手机发送的专辑现在可以无缝播放,曲目之间没有间隙,并且网络电台被识别为电台,而不是被视为一首异常长的歌曲。

专辑无缝播放

内容: 当您从手机发送整张专辑时,MusicBee 现在可以从一首曲目无缝播放到下一首,中间没有暂停——因此现场录音、DJ 混音和连续的古典作品可以保持连贯。

原因: 标准允许控制应用程序说“接下来是什么”,这使得无缝连接成为可能。该指令之前根本不被接受,因此应用程序无法放置即将播放的曲目,并将其宣布为当前曲目——这是上一个版本中修复的专辑问题的原因。现在它被正确接受:下一首曲目在当前曲目仍在播放时被获取,MusicBee 自己的播放器会跨越边界。

网络电台被识别为电台

内容: 发送到 MusicBee 的直播电台将作为流播放,永不下载。

原因: 下载广播没有意义——它没有结尾,也没有什么可以跳转的——但插件之前无法将其与音乐文件区分开来,因此它开始获取并在下载量超过固定大小时停止。控制应用程序会说明它正在发送的是哪种类型,现在可以直接读取。无需设置,无需猜测。

长高分辨率曲目保留其副本

内容: DSD 文件和长 24 位录音现在可以像其他任何曲目一样进行移动。

原因: 它们受到上述大小限制的影响——20 分钟的高分辨率乐章或 10 分钟的 DSD 曲目都超过了它——因此它们的副本被放弃,并且位置滑块停止工作,而这正是最有可能值得仔细聆听的材料。正确识别电台后,不再需要大小限制。

2.0.5 - 2026-08-03

从手机控制 MusicBee 的两项修复:音量现在在两端表示相同,并且投射整个专辑在第一首曲目之后仍能继续播放。

手机上的音量与 MusicBee 中的音量匹配

内容: 现在,将手机音量调到最大,MusicBee 中的音量也会达到最大,并且 MusicBee 自身的设置会在手机上正确显示。

原因: 插件从未告诉控制应用程序其最高音量是多少,因此每个应用程序都必须猜测。其中一个应用程序将最高音量设为 69,这意味着其 100% 音量在 MusicBee 中只能达到 69%,而 MusicBee 的 100% 音量在手机上显示为 144%——手机的音量按钮也无法完全达到最大。现在,渲染器明确说明了音量范围,因此两端都在使用相同的刻度。

投射专辑时保留其标题和位置滑块

内容: 现在,从手机发送的专辑中的每首曲目都会显示其正确的标题,并且可以进行移动,而不仅仅是第一首。

原因: 控制应用程序在当前曲目播放后几分之一秒宣布下一首曲目,而该宣布会取消正在为即将播放的曲目获取的副本——因此大多数曲目悄悄地回退到通过网络播放,这会丢失标题和跳转功能。现在,多首曲目的副本会并排保存,因此宣布不再能取消正在使用的副本。

2.0.4 - 2026-08-02

从手机或其他服务器发送到 MusicBee 的音乐现在表现得像一个真正的音轨:您可以随意拖动进度,并且它会立即显示正确的标题。此外,还修复了将自身显示为组合设备的高保真流媒体播放器的问题。

随意拖动从其他地方发送的音轨

内容: 拖动进度滑块现在适用于从手机、NAS 或其他媒体服务器发送的音轨。为了实现这一点,MusicBee 会在开始播放时将音轨的副本下载到临时文件夹中,并播放该副本。这在家庭网络上大约需要一秒钟,一旦您发送另一个音轨,该副本就会被删除,并且任何残留物都会在 MusicBee 下次启动时清除。

原因: MusicBee 可以启动和停止它在网络上监听的内容,但它无法随意拖动——因此滑块似乎跳动了一下,然后立即滑回原位,没有任何解释。播放自己磁盘上的普通文件完全消除了这种限制,而不是绕过它。

从第一个音符开始显示正确的标题和长度

内容: 由不给文件提供普通文件扩展名的应用程序发送的音轨现在会在开始时立即显示其真实标题和长度,而不是显示为长长的网址。

原因: MusicBee 根据文件扩展名识别音轨并查找其标签,而某些播放器会发出根本没有扩展名的地址。本地副本始终带有正确的扩展名,因此无论发送应用程序如何称呼它,音轨都会被识别。

无法进行的跳转现在会告知

内容: 如果渲染器确实无法移动到请求的点,则会告知控制应用程序,并报告该情况。

原因: 以前它无论如何都会回答“完成”,因此滑块在一秒钟后滑回,没有任何解释。诚实的拒绝比无声的拒绝更容易处理。

正确读取组合式高保真流媒体播放器

内容: 当 MusicBee 播放到将自身显示为组合单元的设备时——例如 Marantz 或 Denon 流媒体播放器,其中播放器与媒体服务器一起位于制造商包装中——插件现在会读取播放器自己的详细信息,而不是媒体服务器的详细信息。

原因: 以前它一直在询问设备的错误部分可以处理哪些音频格式,没有得到可用的答案,并且在没有检查的情况下继续进行——这正是最需要正确处理格式的硬件。设备的型号描述现在也已纳入考虑范围,以便将其与设备配置文件匹配;以前它从错误的位置读取并被丢弃。

2.0.3 - 2026-08-02

播放目标角色成长:MusicBee现在可以播放其库中没有的音乐——手机上、NAS上或另一个服务器上的文件——而不仅仅是它已有的曲目。

播放从手机发送的音乐,而不仅仅是您自己的库

内容: 当您使用Symfonium或BubbleUPnP等控制应用程序向MusicBee发送音乐时,曲目不再必须来自MusicBee自己的库。存储在手机本身、NAS或另一个媒体服务器上的文件现在可以播放。标题和长度来自发送它的应用程序,因此即使MusicBee从未见过该文件,曲目也会正确显示。

原因: 播放目标角色最初是为了您从手机浏览此PC的库并点击一首歌曲的情况而构建的——曲目已经在PC上,因此MusicBee只是播放它自己的文件。任何来自其他地方的内容都会被悄悄丢弃,这使得该功能对于同样自然的将音乐手机推送到好音箱的情况变得无用。

无法播放的曲目会显示提示

内容: 如果渲染器确实无法播放它收到的内容,它现在会将其报告回发送它的应用程序。

原因: 以前它对任何内容都回答“收到”,因此控制应用程序继续按下播放。由于实际上没有加载任何内容,MusicBee会重新播放之前留下的任何曲目——如果该文件已丢失,则会抱怨源无法找到。错误会命名一个不相关的曲目,并且与真正的问题相去甚远。

暂停时发送新曲目现在会播放该曲目

内容: 如果MusicBee暂停,并且您的控制应用程序向其发送新内容,则新曲目会开始播放。

原因: 恢复优先于加载,因此暂停的曲目会从上次中断的地方继续播放,而您刚刚选择的曲目则会悄无声息地被丢弃。

2.0.2 - 2026-08-01

搜索是本次更新的主题:它现在可以按艺术家搜索,返回正确的结果,并且在大曲库中速度很快。此外,还新增了一种控制应用随机播放目标的方式,并修复了三个无效按钮。

按艺术家搜索现在可以正常工作

内容: 从您的控制应用中搜索艺术家现在会返回该艺术家的音乐。搜索会同时匹配曲目的“艺术家”和专辑的“专辑艺术家”,因此无论您输入表演者的姓名还是专辑归档的名称,都可以找到合辑。

原因: 以前,艺术家搜索被误认为是“给我所有这类内容”——您输入的艺术家姓名被丢弃,整个曲库都被返回,因此本应匹配数百首曲目的搜索却返回了数万首。如果只匹配两个艺术家字段中的一个,会悄无声息地丢失一半结果,因此现在会检查两个字段。

搜索结果页面正常显示

内容: 滚动浏览长长的搜索结果列表现在可以正常移动。您滚动到的每一页都是您实际获得的页面。

原因: 服务器以前会用第一批结果来响应每个请求,同时报告完整的匹配计数,因此应用在滚动以获取更多内容时会不断收到相同的项目,从未到达末尾。

大曲库搜索速度大幅提升

内容: 搜索现在对整个结果集运行单个查询,并且只读取您正在查看的页面的标签。

原因: 以前,每个页面都会重新针对曲库运行查询,然后加载所有匹配项(数千个)的标签,只为了显示十几个。在大型收藏中,这会导致每次滚动都暂停。当曲库刷新时,结果也会被丢弃,因此编辑后的内容永远不会过时。

将随机播放目标设定为筛选器

内容: “曲库选项”选项卡上新增了“随机播放来源”设置。将其保留为“所有音乐”,您的控制应用的“随机曲目”/“随机专辑”文件夹将像以前一样运行;选择您的一个 MusicBee 筛选器,所有随机请求都将从该筛选器中提取。隐藏的筛选器也会提供。

原因: 随机播放文件夹请求的是“所有内容”的一部分,它是唯一一个没有提供任何线索表明您意图的请求——因此它总是从整个曲库中提取,包括有声读物。这是您定义“所有内容”含义的地方。从设备上筛选器内部打开随机播放文件夹仍然会随机播放该筛选器:您在浏览时做出的选择优先于设置。

帮助、GitHub 和更新检查现在指向实际页面

内容: 设置对话框中的“帮助”和“GitHub”按钮,以及新版本的自动检查,现在会打开它们所命名的页面。

原因: 这三个功能都是根据插件名称的缩写形式构建的,而这个缩写形式从未存在过任何页面,因此它们都悄无声息地失败了——按钮似乎什么也没做,更新检查也从未报告任何内容,无论新版本发布了多久。

2.0.1 - 2026-07-26

一轮播放和浏览修复,重点关注播客和驱动播放及随机播放的控制器应用(如 BubbleUPnP)。

播客无需暂停即可开始播放

内容: 下载的播客剧集现在会预先显示其真实长度和文件大小,直接从磁盘文件传输到媒体元数据中。

原因: 如果没有明确的持续时间,像 BubbleUPnP 这样的控制器每次按下播放时都会重新扫描整个音频流,只是为了计算剧集的长度——因此播放会在明显的暂停后才开始。现在有了明确的长度,播放会流畅地开始。

按艺术家浏览时会显示播客

内容: 播客的节目名称现在归档为艺术家——镜像到“艺术家”和“专辑艺术家”字段(及其排序变体)中,就像它已经填充“专辑”字段一样。

原因: 以前,按艺术家字段分组的浏览路径会发现每个播客的艺术家为空,并止步于空级别。现在将节目本身视为艺术家——与将其视为专辑保持一致——意味着这些路径现在可以到达剧集,而不是什么都没有。

重启后“最近播放”和投射功能正常

内容: 直接播放播客、有声读物、收件箱或广播曲目——无需先浏览到它,就像 BubbleUPnP 的“最近播放”列表和投射目标那样——不再失败。插件现在在按 ID 请求时按需加载曲目。

原因: 这些列表在 MusicBee 重启后立即请求曲目,在任何内容被浏览之前,因此插件从未见过 ID 并回复“Bad id”。它现在在第一次直接请求时强制加载相关源并找到曲目。

“随机曲目”和“随机专辑”返回结果

内容: BubbleUPnP 的“随机曲目”和“随机专辑”随机播放文件夹,它们请求整个库的随机片段,现在会返回填充结果。

原因: 这些是没有标题匹配的搜索,插件以前从空的内部列表回复它们,所以它们总是显示为空。它们现在从其余浏览使用的相同按需查询路径提供——正确分页。

标签为空的专辑会列出其曲目

内容: 打开一个按空值分组的专辑——例如没有年份的收件箱曲目——现在会显示其曲目。

原因: 收集专辑曲目的匹配将“此标签为空”视为“不匹配”,因此由空字段形成的任何专辑都无法浏览到任何内容。现在,空组值可以正确匹配共享它的曲目。


2.0.0 - 2026-07-22

这是 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 = 2Not 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:musicL:podcastL:filter:…);每个级别仅在客户端浏览到它时才计算(LazyBrowseEnsureLazyEndpointInMemory → 每级缓存),并且库更改通知会清除缓存(SetLibraryDirty)。

原因: 冷启动基本上是即时的 - MusicBee 完成插件初始化时 HTTP 端口已打开 - 并且内存与已浏览的内容成比例,而不是与库大小成比例。权衡:第一次浏览到端点会支付其加载成本;重新进入会缓存直到下一次库更改。这是其他一切所依赖的基础。完整说明:FIXES.md


网络和健壮性

强化 HTTP 服务器的绑定路径。原始插件在其端口不可用时会静默死亡。

N04 - 自愈 HTTP 端口绑定

内容: 插件的 HTTP 服务器在其配置的端口不可用时不再崩溃。三个相关更改:

  1. 绑定失败时自动回退。 HttpServer.Start 尝试配置的端口,并在 SocketException向上扫描多达 20 个端口以查找第一个空闲端口。实际绑定的端口记录在一个新的 Plugin.boundServerPort 中,并且 所有宣传服务器的内容 - SSDP LOCATION URL(NOTIFY + M-SEARCH 响应)、设备 URL(PrimaryHostUrl)、路由器端口转发以及 SSDP/控制点自过滤器 - 现在都读取 boundServerPort 而不是 Settings.ServerPort。UPnP 客户端通过 SSDP 发现真实端口,因此移动的端口对渲染器是透明的。
  2. 用户通知。 当发生回退时(保存的端口不是正在使用的端口),本地化的 MessageBoxWarnPortInUse)会告诉用户哪个端口实际在服务,以及设备仍然会找到它 - 因为插件是无头运行的,对话框中的消息只有已经怀疑有问题的人才能看到。
  3. 重启恢复。 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 之后,端口冲突会 自愈 - 服务器继续在下一个可用端口上运行,用户会收到通知,并且客户端会重新发现它 - 而不是使整个插件崩溃。

实现:

  • 默认端口从 49382 移至 9779(低于动态范围,因此 Windows 永远不会自动保留它;不是已知的媒体服务器默认值)在所有三个 ServerPort 声明 + 设置解析失败回退中。
  • Plugin.boundServerPort(新的共享字段)保存实时监听端口;activeServerPort 保持 配置的 快照,因此“需要重启”徽章逻辑不会在回退时错误触发。
  • HttpServer.PortScanRange = 20;扫描在第一次成功的 TcpListener.Start() 时停止,并且只有在所有尝试都失败时才抛出最后一个异常。
  • 新的 EN 资源键 WarnPortInUse(翻译遵循发布时区域设置传递)。

N05 - 通过多播组(VPN / 点对点)进行 SSDP 公告

内容: 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(排序专辑艺术家)并使用它在浏览视图中对艺术家进行分组/排序。

原因: 高保真浏览器和发烧友使用排序艺术家名称(“贝多芬,路德维希·范”而不是“路德维希·范·贝多芬”)来组织库。这是严肃听众的标准期望。两个上游都缺少。


N08 - 多值 AlbumArtist 处理

内容: 当专辑的 AlbumArtist 字段包含由 "; " 分隔的多个艺术家(例如 "yaiol; Ars Ricercata")时,曲目现在会出现在浏览视图中 每个 艺术家下,而不是出现在一个组合了所有名称的“弗兰肯斯坦”艺术家下。

原因: 合作专辑和合辑需要出现在每个合作者下。没有这个,查找专辑的一半搜索路径都会中断。


N09 - 专辑容器封面 (upnp:albumArtURI)

内容: DIDL 浏览响应中的专辑容器节点现在包含一个指向专辑封面的 upnp:albumArtURI 元素。

原因: 没有这个,UPnP 客户端浏览视图中的每个专辑都会显示一个通用图标而不是专辑封面。这是导航的视觉提示;每个现代高保真浏览器都期望如此。


N10 - 过滤专辑内的曲目排序

内容: 过滤专辑内的曲目现在按碟片编号排序,然后按曲目编号排序。

原因: 标准专辑顺序。如果没有明确排序,曲目会以过滤器返回的任何顺序返回 - 通常看起来是随机的。


N11 - 播放列表文件夹树修复

内容: LoadLibraryPlaylists 函数(最初由 Steven Mayall 编写,约 2014 年)未能深入到新创建的播放列表文件夹中。每个文件夹中的第一个播放列表以及任何子文件夹最终都会孤立在根级别。

原因: 原始插件中存在了十一年。在打开 BubbleUPnP 并点击“播放列表”后 30 秒内可见。通过在树构建期间正确递归到新创建的文件夹中,在 yaiol 中修复。


N12 - XML 非法控制字符净化

内容: 任何包含 C0 控制字符(例如,由于错误的编码传递 - UTF-8 → Latin-1 → 截断 0x990x19 而产生的 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 / ResetCacheBumpSystemUpdateId)。

原因: 客户端在下次浏览时可靠地获取编辑、新文件和设置更改。已知限制:订阅客户端不会通过 GENA 主动重新推送新值(作为未来工作搁置);它们仍然在下次浏览时看到它。


N17 - 播客订阅封面

内容: 播客磁贴不显示图像 - 每个 /PodcastThumbnail/ 请求都返回 404。两个堆叠的错误:解析链从未检查 MusicBee 的实际封面缓存(%LocalAppData%\MusicBee\InternalCache\Subscriptions\<name>.jpg,桌面 UI 从中加载),并且 HTTP 层的 unescape+lowercase 将 feed-URL 路由键损坏到其最后一个路径段。此分支从 MB 的 InternalCache 解析封面,并通过一个 URL 安全的 slug 路由查找,该 slug 在 HTTP 层中保持完整(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.activeMaxConnectionsPlugin.activeServerPortPlugin.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.vbMusicBee3Settings.ini 读取 MusicBee 选择的语言(<SystemLanguage> 内名)并将匹配的 .NET 文化应用于线程,因此 My.Resources.Resources.* 返回本地化字符串。每个面向用户的标签/按钮/消息都连接到资源键(设计器控件通过 ApplyDesignerExtras + sync-en-locale.jsWarnPortInUse 等运行时字符串手动添加)。尚未 完成的是实际翻译:只有英语源包(Resources.resx)存在 - 其他语言的卫星包将在插件功能完整时一次性生成(在字符串仍在变化时零碎翻译会浪费精力)。

目标语言(MusicBee 自身提供的集合,与 endonymToCulture 1:1 匹配,因此插件会自动遵循 MusicBee 的语言):

阿拉伯语 (ar) 捷克语 (cs) 德语 (de) 希腊语 (el)
西班牙语 (es) 法语 (fr) 匈牙利语 (hu) 意大利语 (it)
韩语 (ko) 荷兰语 (nl) 挪威语 (nb) 波兰语 (pl)
葡萄牙语 巴西 (pt-BR) 葡萄牙语 葡萄牙 (pt-PT) 瑞典语 (sv) 土耳其语 (tr)
乌克兰语 (uk) 俄语 (ru) 日语 (ja) 简体中文 (zh-CN)
繁体中文 (zh-TW) 英语 (en, 源)

变体策略(根据工作区区域设置规则):PTZH 分为不同的捆绑包,因为词汇/脚本确实存在差异(pt-BR/pt-PTzh-CN/zh-TW)。EN 是一个单一捆绑包 - MusicBee 的“English(US)”(en-US)通过 .NET 的文化链回退到 en,因此不会生成单独的美国捆绑包。ES 和 FR 同样是单一区域设置。

原因: UPnP 插件的设置(“不要使用原始 PCM”、“强制小端 PCM”、“端口回退警告”)在母语中已经足够神秘。遵循 MusicBee 自己的 UI 语言 - 而不是强制使用英语 - 是非英语用户可以配置工具和无法配置工具之间的区别。两个上游都没有尝试过这一点。


N31 - 帮助链接以完整的界面语言打开

内容: 从插件打开帮助链接时,会尊重用户的完整界面语言(例如 pt-BRzh-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.ForceTranscodingstreamingProfile.ForceTranscodingWriteAudioFileDIDL 中。

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 作为转码输出格式

内容: 设备配置文件中的转码格式下拉菜单现在提供 FLAC 以及 PCM 16/24、MP3、AAC、Ogg。选择它会将 BASS 编码器通过 MusicBee 的标准 FLAC 转换命令行路由(与 MP3/AAC/Ogg 已经使用的机制相同)。

原因: 对于能够很好处理 FLAC 但无法解码源编解码器(例如 Eversolo 接收 MusicBee 的 WMA 库转换为 FLAC)的设备,这可以在 MP3/AAC 会丢弃音频数据的情况下保留无损质量。解锁 N02(5.1 降混控制),如果没有无损转码选项,N02 无法解决。

实现:Encoder.StartEncodeSelect Case Codec 块中添加一行 - FLAC 加入 MP3/AAC/Ogg 的命令行驱动分支。UI 下拉菜单获得“FLAC”作为第 6 个选项。SettingsDialog 中的加载/保存映射扩展以识别 FileCodec.FlacSelectedIndex = 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 但实现有 bug(崩溃、半转换、挂起)。与其逆向工程每个损坏的设备,用户可以获得一个“在这里关闭它”的开关。


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.500 锚定在设备的“2:30”报告上会使进度条显示比实际慢约 500 毫秒。在 F17 之后,进度条与用户在常见曲目内擦洗情况下的意图匹配,并且仍然尊重设备对关键帧捕捉异常的报告。


F18 - 连续流 / NextURI 互锁

内容: 现在有两个互锁:

  1. 运行时:Settings.ContinuousOutput 开启时,QueueNext 会提前返回 False。连续流有其自己的无缝机制(一个长连接流);在其之上发送 SetNextAVTransportURI 会使设备混淆每个曲目是离散 URI 还是连续流的一部分。
  2. UI: 当用户勾选全局连续流复选框时,当前显示的配置文件的 forceNativeStream 会自动取消勾选。连续流总是转码,因此强制原生流组合起来毫无意义。

原因: 防止用户同时启用两个冲突的无缝机制。没有 F18,设备将同时接收连续流 URI 和每个后续曲目的 NextURI,行为取决于渲染器。


F19 - 忽略空白 NextURI 错误

内容:SetNextAVTransportURI 使用空白 URL 调用时(例如列表中的最后一曲),某些设备会返回 SOAP 故障。F19 会静默吞噬这些故障 - 记录但不作为错误传播。

原因: “没有下一曲目”是正常情况,而不是错误。将其视为致命错误会污染日志,并在某些流程中触发重试风暴。


Mime 类型和 DLNA 元数据

F20 - MP3 mime → audio/mpeg

内容: 符合标准的 MP3 mime 类型是 audio/mpeg,而不是 audio/mp3。后者是大多数设备都能容忍的常见错误名称,但更严格的渲染器会拒绝它。

原因: 静默修复了遵循标准的更严格设备上的播放问题。yaiol 代码库已经正确处理了这一点;无需更改。


F21 - Mime 类型顺序:非 x- 变体优先

内容: 当设备同时宣传 audio/flacaudio/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 类型标志(例如 LPCMWAVEMP3 等配置文件标识符)需要与设备接收到的内容匹配。F25 确保原生流和编码 WAV 流被正确标记。

原因: DLNA 类型不匹配会导致某些设备完全拒绝播放或应用错误的解码器。


F26 - FLAC 文件的 DLNA 头

内容: FLAC 流在其头中获得正确的 DLNA 配置文件标识符。

原因: 没有它,一些支持 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:addedTimepv: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 时,插件过去会在 SOAP-Play 和第一个状态计时器轮询检测到新播放状态之间的约 100 毫秒窗口内,将 currentPlayPositionMscurrentPlayStartTicks 保留在其上一曲目的值。MusicBee 的进度条会短暂显示上一曲目的 末尾,然后跳回 0,然后爬升。F34 在 PlayToDevice 入口处将两者归零 - 在我们知道曲目更改正在发生的那一刻,在任何 SOAP 工作之前。

原因: 快速跳过用例(手动下一曲或无缝过渡)中的视觉故障。现在 MusicBee 的第一个 PlayPositionMs 查询在 Play 之后干净地返回 0,然后 F33 的 GetPlayPositionInformation 在第一次状态更改滴答时将其细化为设备的实际位置。

实现: PlayToDevice 顶部的四行,与 F33 的过渡时间精确锚定配对。


F35 - “强制转码”错误

内容: 强制转码在某些组合中仍然可能跳过转码。在 F04 每配置文件重做之后,两个特定的漏洞被堵塞:

  1. 与强制原生流的优先级。 当两者都为 True 时(这可能发生在模式迁移或部分设置文件之间),强制转码现在完全胜出(If streamingProfile.ForceTranscoding Then forceEncode = True ElseIf streamingProfile.ForceNativeStream Then forceEncode = False)。UI 互斥阻止用户同时勾选两者,但运行时保护处理从磁盘加载不一致的任何状态。
  2. 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 内部(围绕每个 PostSoapRequest 调用)预先存在的每方法 Try/Catch 基础设施保持不变 - 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.GetFileEncoder.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/EQMB-ReplayGainProfile-DSP/EQProfile-ReplayGain 作为累积原因。诊断价值相同,噪音更少。

原因: 用户可以在一行日志中看到给定曲目发生转码的 所有 原因,而不是分散的。有关详细信息,请参阅 F42。


F42 - 记录“渲染器不支持源编解码器”

内容: 为每个播放到设备的曲目添加了一个 StreamDecision 日志行,说明是“原生 CODEC”还是“转码 CODEC→CODEC reason=…”。原因字段累积了触发转码的每个条件:MB-DSP/EQMB-ReplayGainProfile-DSP/EQProfile-ReplayGainWebFileVirtualFileForceTranscoding(global)SampleRate<min/SampleRate>maxDownmixToStereoDeviceLacksCodec(X)BandwidthConstrained

原因: 用户对他们期望原生流式传输的文件上意外的 CPU 峰值感到困惑。每曲目一行日志会告诉他们哪个条件导致了转码 - 如果字段显示 DeviceLacksCodec(Flac),他们会立即知道设备的协议信息不完整,并且可能希望 F32 的回退生效。

实现: 单个累加器字符串在决策链中逐步构建;在末尾记录一次。受 Settings.LogDebugInfo 限制,以避免生产中的日志噪音。


F43 - SetNextAVTransport 日志显示源 URL

内容: QueueNext 日志条目现在包含 source=<MusicBee library path> 以及 stream=<HTTP streaming URL>。相同的更改应用于成功路径和失败路径(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(客户端请求的属性)、sortCriteriapartialResultLength(在失败之前生成了多少字节的 DIDL - 指示不良曲目在批次中的位置)。

原因: 当 DIDL 中途出现问题时,partial-length 值会告诉您失败是在批次的第一个曲目(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 上,本地子网流量绕过隧道,因此隧道适配器无论如何都无法到达渲染器 - 删除它会删除一个幻影副本,而不是一个工作路径。网关测试是区分真实 LAN/Wi-Fi 适配器与隧道或虚拟交换机的廉价、可靠信号。补充 N05(它修复了在此类链路上发送公告的 方式 - 多播而不是广播);F46 管理 哪些 适配器根本不进行公告。

已知限制: 跨隧道真正存在渲染器的网状/远程访问 VPN(Tailscale、ZeroTier、WireGuard-to-home)通常会呈现一个没有默认网关的适配器,因此自动模式也会将其删除。这些用户会固定 VPN 地址,该地址优先于网关过滤器。

目录