Що нового
2.0.2 - 2026-07-20
- Шаблон більше не можна видалити, якщо за ним слідує будь-який вузол — кнопка видалення просто залишається вимкненою, тому вузол ніколи не може залишитися без батьківського елемента. Кожен рядок шаблону тепер показує поточну кількість (n) його послідовників, що одразу пояснює вимкнене видалення; видалення невикористаного шаблону тепер також зберігається, замість того, щоб стандартні налаштування тихо з'являлися при наступному завантаженні.
- Нова кнопка воронки поруч із деревом перегляду показує, які саме вузли слідують за шаблоном: вона фільтрує дерево, щоб показати лише послідовників вибраного шаблону, повторно фільтрує, коли ви вибираєте інші шаблони, і відновлює повне дерево, коли її вимкнено.
- Вузли Радіо та Подкасти тепер постійно пов'язані з шаблоном своєї категорії — змініть шаблон на вкладці «Шляхи», і вузол сам буде слідувати за ним; застосовувати нічого не потрібно, тому кнопка «Застосувати» для них вимкнена. Список шаблонів відображає це двома смугами: Стандартні (де створюються всі нові шаблони) та Зарезервовані (Радіо + Подкасти).
2.0.1 - 2026-07-20
- Тепер шаблони шляхів динамічно пов'язані з вузлами, які їх використовують. Застосування шаблону змушує вузол слідувати йому: відредагуйте шаблон пізніше, і кожен вузол, що слідує за ним, негайно змінить форму — не потрібно шукати його та повторно застосовувати до кожного вузла. Дерево перегляду показує, якому шаблону слідує кожен вузол одразу після його назви, перейменування відображається там миттєво, а видалення шаблону спочатку повідомляє вам, скільки вузлів слідує за ним (вони зберігають поточний макет і просто припиняють слідувати будь-чому).
- Застосування шаблону до прихованого вузла також робить його знову видимим — застосування є жестом "покажи мені це, сформоване так", тоді як приховування залишається з прапорцем "Видимий". Зарезервований шаблон "Прихований", який це замінює, зник.
- Дерево перегляду більше не втрачає ваше місце: позначки, розгорнуті папки та позиція прокрутки зберігаються після застосування шаблонів та інших оновлень.
2.0.0 - 2026-06-16
Це перший публічний випуск відкритого форку yaiol плагіна MusicBee UPnP. Він представлений у двох частинах: все, що є новим у цьому форку, а потім виправлення та покращення, внесені до оригінального плагіна. Кожен пункт зберігає форму Що / Чому внутрішнього каталогу функцій проекту, тому обґрунтування кожної зміни знаходиться на сторінці, а не лише сама зміна.
Нове в цьому форку
MediaRenderer - відтворення на MusicBee
N01 - MusicBee як рендерер для відтворення
Що: зазвичай цей плагін працює в одному напрямку: телефон або інший пристрій переглядає бібліотеку MusicBee та відтворює музику на собі. Ця функція додає протилежний напрямок - вона дозволяє MusicBee бути програвачем. З програми-контролера на вашому телефоні (наприклад, BubbleUPnP) ви можете вибрати свій настільний MusicBee як пристрій для відтворення, а потім керувати ним з руки: відтворювати, призупиняти, зупиняти, пропускати вперед або назад, переходити до певної точки в треку та змінювати гучність або вимикати звук.
Чому: це перетворює ваш телефон на пульт дистанційного керування для музики, яка вже є на вашому ПК. Сядьте на диван, переглядайте свою бібліотеку на телефоні, торкніться треку, і він відтворюватиметься з динаміків, підключених до вашого робочого столу - з повним контролем з того місця, де ви сидите. Оригінальний плагін ніколи не постачав це як робочу функцію.
Увімкнення: за замовчуванням він вимкнений, оскільки його увімкнення дозволяє будь-якому пристрою у вашій домашній мережі розпочати відтворення на вашому ПК. Ви вмикаєте його за допомогою прапорця на вкладці "Загальні" діалогового вікна налаштувань. Кожна з трьох ролей плагіна має свій власний прапорець там - ділитися моєю бібліотекою (Сервер), дозволяти іншим відтворювати на мені (Рендерер) та відтворювати на інших пристроях (Точка керування) - і діалогове вікно показує лише ті вкладки налаштувань, які потрібні увімкненим ролям, тому ви ніколи не стикаєтеся з опціями, які до вас не застосовуються.
Розрізнення ваших машин: ви можете дати рендереру будь-яке ім'я, яке вам подобається (воно починається як "MusicBee (yaiol)"). Це ім'я з'являється у списку цілей відтворення на вашому телефоні, тому, коли на кількох ПК працює 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-сумісні рендерери неефективними, коли джерелом був 5.1 FLAC. Тепер другий пункт виключає 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-порту. На реальній бібліотеці (50 тис.+ треків, 5400 епізодів подкастів, сотні станцій) це хвилини холодного старту, і дерево залишається в RAM назавжди, включаючи гілки, які жоден клієнт ніколи не відкриває. Цей форк нічого не будує заздалегідь: корінь виставляє один заповнювач з префіксом L: для кожної кінцевої точки (L:music, L:podcast, L:filter:…); кожен рівень обчислюється лише тоді, коли клієнт переходить до нього (LazyBrowse → EnsureLazyEndpointInMemory → кеші для кожного рівня), а сповіщення про зміну бібліотеки очищають кеші (SetLibraryDirty).
Чому: холодний старт по суті миттєвий - HTTP-порт відкритий до того моменту, як MusicBee завершить ініціалізацію плагіна - і пам'ять залишається пропорційною тому, що було переглянуто, а не розміру бібліотеки. Компроміс: перший перегляд кінцевої точки оплачує її вартість завантаження; повторний вхід кешується до наступної зміни бібліотеки. Це основа, від якої залежить все інше. Повні примітки: FIXES.md.
Мережа та надійність
Посилення шляху прив'язки HTTP-сервера. Оригінальний плагін тихо вмирає, коли його порт недоступний.
N04 - Самостійне відновлення прив'язки HTTP-порту
Що: HTTP-сервер плагіна більше не виходить з ладу, коли його налаштований порт недоступний. Три пов'язані зміни:
- Автоматичний відкат при збої прив'язки.
HttpServer.Startнамагається використовувати налаштований порт, а приSocketExceptionсканує до 20 портів вгору для пошуку першого вільного. Фактичний прив'язаний порт записується в новеPlugin.boundServerPort, і все, що рекламує сервер - URL-адреси SSDPLOCATION(сповіщення + відповідь 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-ing при наступному збереженні налаштувань. Після N04 зіткнення портів самостійно відновлюється - сервер продовжує працювати на наступному вільному порту, користувач повідомляється, а клієнти знову його виявляють - замість того, щоб виводити з ладу весь плагін.
Реалізація:
- Порт за замовчуванням переміщено
49382→9779(нижче динамічного діапазону, тому Windows ніколи не резервує його автоматично; не є відомим портом за замовчуванням для медіа-серверів) у всіх трьох оголошенняхServerPort+ відкат при збої аналізу налаштувань. Plugin.boundServerPort(нове спільне поле) містить активний порт прослуховування;activeServerPortзалишається налаштованим знімком, щоб логіка значка "Потрібен перезапуск" не спрацьовувала помилково при відкаті.HttpServer.PortScanRange = 20; сканування зупиняється при першому успішномуTcpListener.Start()і видає останнє виключення лише у випадку, якщо всі спроби не вдалися.- Новий ресурсний ключ EN
WarnPortInUse(переклади слідують за локалізацією під час публікації).
N05 - Оголошення SSDP через групу багатоадресної розсилки (VPN / точка-точка)
Що: оголошення SSDP надсилаються до групи багатоадресної розсилки UPnP (239.255.255.250) замість IP-адреси широкомовної розсилки. Також пригнічується нешкідлива помилка "cannot access a disposed object", яка реєструється, коли відповідь на пошук SSDP випереджає перезапуск сервера.
Чому: на мережевих адаптерах точка-точка / VPN IP-широкомовна розсилка не застосовується - стара широкомовна розсилка не вдавалася з "invalid argument", і оголошення пропускалися, тому плагін був невидимим для клієнтів на цих посиланнях. Оголошення до належної групи багатоадресної розсилки виправляє виявлення саме на цих адаптерах.
Навігація по бібліотеці
Ці функції були включені в цей форк і відсутні в оригінальному плагіні. Вони виникли в результаті фактичного перегляду власного виводу плагіна з реальних клієнтів UPnP.
N06 - Виявлення бібліотеки на основі фільтрів
Що: вкладки фільтрів MusicBee (файли .xautopf у папці MusicBee користувача) стають кореневими контейнерами UPnP у бібліотеці плагіна. Треки кожного фільтра потім можна переглядати в ієрархії AlbumArtistSort → Album → Tracks.
Чому: користувачі з кураторськими фільтрами MusicBee (наприклад, "5-зіркові треки", "Нещодавно додані", "Класика → Бароко") очікують знайти їх під час перегляду плагіна з клієнта UPnP. Оригінальний плагін виставляв лише сире дерево бібліотеки.
N07 - Підключення поля SortAlbumArtist
Що: плагін тепер читає MetaDataType 165 (Sort Album Artist) MusicBee і використовує його для групування/сортування виконавців у вікнах перегляду.
Чому: hi-fi браузери та аудіофіли використовують імена виконавців для сортування ("Beethoven, Ludwig van" замість "Ludwig van Beethoven") для організації бібліотек. Стандартне очікування для серйозних слухачів. Відсутнє в обох вихідних версіях.
N08 - Обробка багатозначного поля AlbumArtist
Що: коли поле AlbumArtist альбому містить кількох виконавців, розділених "; " (наприклад, "yaiol; Ars Ricercata"), трек тепер з'являється під кожним виконавцем у вікнах перегляду, а не під одним виконавцем-Франкенштейном, що поєднує імена.
Чому: спільні альбоми та збірки повинні відображатися під кожним співробітником. Без цього половина шляхів пошуку альбому не працює.
N09 - Обкладинка контейнера альбому (upnp:albumArtURI)
Що: вузли контейнерів альбомів у відповідях DIDL Browse тепер містять елемент upnp:albumArtURI, що вказує на обкладинку альбому.
Чому: без цього кожен альбом у вікні перегляду клієнта UPnP показує загальну іконку замість обкладинки альбому. Візуальна підказка для навігації; очікується кожним сучасним hi-fi браузером.
N10 - Порядок треків у альбомах фільтрів
Що: треки всередині альбому, виставленого фільтром, тепер сортуються за номером диска, потім за номером треку.
Чому: стандартний порядок альбому. Без явного сортування треки поверталися в будь-якому порядку, в якому їх повертав фільтр - зазвичай випадковому.
N11 - Виправлення дерева папок списків відтворення
Що: функція LoadLibraryPlaylists (спочатку Стівена Мейалла, ~2014) не змогла спуститися в новостворені папки списків відтворення. Перший список відтворення в кожній папці, а також будь-які підпапки, виявилися осиротілими на кореневому рівні.
Чому: присутній в оригінальному плагіні протягом одинадцяти років. Видно протягом 30 секунд після відкриття BubbleUPnP та натискання "Списки відтворення". Виправлено в yaiol шляхом коректного рекурсивного переходу в новостворені папки під час побудови дерева.
N12 - Санітарна обробка XML-недопустимих керуючих символів
Що: будь-який трек з тегом, що містить керуючий символ C0 (наприклад, 0x19 від невдалого кодування - UTF-8 → Latin-1 → зворотне обрізання 0x99 до 0x19), призводив до збою всієї відповіді Browse з 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..кінець); між двома викликами список перетасовувався, тому деякі станції з'являлися на обох сторінках (дублікати), а деякі на жодній (відсутні) - виглядаючи випадковими при кожному оновленні.
Чому: присутній в оригінальному плагіні (його автор ніколи не переглядає радіо через UPnP). Виправлено тут за допомогою спеціальної гілки ContainerCategory.Radio у Browse, без сортування за викликом; radioFiles сортується один раз під час завантаження за назвою (стабільно). Перегляд з розбивкою на сторінки тепер бачить детермінований порядок; сторінка 1 і сторінка 2 є роз'єднаними.
N14 - Пошук UPnP за класом альбому повертає контейнери альбомів
Що: пошук UPnP за запитами класу альбому (upnp:class = "object.container.album.musicAlbum", наприклад, "Випадкові альбоми" BubbleUPnP) повертав повний список треків замість контейнерів альбомів, тому клієнт показував нуль альбомів. Оригінальний обробник аналізував лише критерії в дужках, а потім виводив усі треки незалежно від запитуваного класу.
Чому: виправлено тут - запити класу альбому тепер перераховують окремі альбоми (згруповані за AlbumArtist+Album) і видають кожен як належний контейнер musicAlbum з обкладинкою, доступний через віртуальний простір ідентифікаторів Salb<idx>, щоб клієнт міг деталізувати результат і відтворити його.
N15 - Працюючий, чутливий до області пошук UPnP з можливістю переходу
Що: оригінал не рекламував жодних можливостей пошуку (GetSearchCapabilities повертав порожній результат), тому клієнти відмовлялися навіть надсилати запит на пошук; а старий бекенд читав з musicFiles, який був постійно порожнім в епоху лінивого дерева. Цей форк рекламує реальні властивості, що підлягають пошуку, реалізує пошук треків за назвою та альбомів за назвою в лінивій бібліотеці (HandleLazySearch), обмежує запит поточною гілкою клієнта, коли надсилається реальний ідентифікатор контейнера (інакше замінює L:music, щоб пошук у верхній панелі не затягував шум подкастів/радіо/аудіокниг), і робить результати альбомів клікабельними за допомогою синтетичних ідентифікаторів Ssrch_alb_*, які рання гілка Browse відображає назад до треків альбому. (Частина результатів класу альбому як контейнерів - це N14.)
Чому: пошук у BubbleUPnP перетворився з "Бібліотека не підтримує пошук" на повернення корисних, обмежених, відтворюваних результатів. Повний дизайн + відхилені підходи: SEARCH.md.
N16 - Анулювання кешу UPnP (SystemUpdateID)
Що: оригінал повертав постійний SystemUpdateID=0 - контракт UPnP ContentDirectory на анулювання кешу - тому клієнти, що відповідають специфікації (BubbleUPnP), розглядали бібліотеку як таку, що ніколи не змінюється: застарілі результати перегляду, мініатюри 404 після зміни схеми URL-адрес та танець "перезапустіть MusicBee двічі, щоб побачити зміни". Цей форк ініціалізує SystemUpdateID з секунд епохи при завантаженні (тому кожен перезапуск строго випереджає попередній) і збільшує його при кожній зміні бібліотеки та зміні налаштувань (SetLibraryDirty / ResetCache → BumpSystemUpdateId).
Чому: клієнти надійно отримують зміни, нові файли та зміни налаштувань під час наступного перегляду. Відоме обмеження: підписані клієнти не отримують активно нове значення через GENA (відкладено як майбутня робота); вони все ще бачать його під час наступного перегляду.
N17 - Обкладинки підписок на подкасти
Що: плитки подкастів не показували зображень - кожен запит /PodcastThumbnail/ повертав 404. Дві послідовні помилки: ланцюжок вирішення ніколи не перевіряв фактичний кеш обкладинок MusicBee (%LocalAppData%\MusicBee\InternalCache\Subscriptions\<name>.jpg, звідки завантажується інтерфейс робочого столу), а шар HTTP, що розкодовував + перетворював на нижній регістр, спотворював ключ маршруту URL-адреси стрічки до останнього сегмента шляху. Цей форк вирішує проблему обкладинок з InternalCache MB і маршрутизує пошук через URL-безпечний 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 - Окремі поля групування "Рік" та "Рік (рррр)"
Що: групування альбомів та шляхи перегляду тепер виставляють обидва власні поля року MusicBee - Рік (повний тег дати) та Рік (рррр) (лише чотиризначний рік) - щоб користувач міг вибрати будь-яке з них при визначенні групування альбому або шляху перегляду.
Чому: два поля означають різні речі в MusicBee, і їхнє згортання втрачало цю відмінність. Виведення обох дозволяє користувачеві зібрати всі релізи року разом (рррр) або зберегти точний порядок за датою (повний тег року), як вони задумали.
N32 - Закріплені фільтри та списки відтворення згруповані за типом у корені
Що: закріплений фільтр тепер з'являється безпосередньо під папкою Фільтри в корені перегляду, а закріплений список відтворення — безпосередньо під папкою Списки відтворення, замість того, щоб усі закріплення збиралися в одну купу в кінці кореня. Кожен закріплений ярлик розташований поруч зі своїм типом.
Чому: що більше ярликів закріплює користувач, то важче переглядати єдину прикінцеву купу з перемішаних фільтрів і списків відтворення, і вона відділяє кожен ярлик від папки, до якої він належить. Групування закріплених елементів під власною категорією зберігає корінь читабельним, а кожен ярлик — поруч із тим, до чого він належить.
Діалогове вікно налаштувань та пакування
N26 - Розділене діалогове вікно налаштувань
Що: сторінка "Налаштування" отримала макет з лівою навігацією та розділами: Загальні / Відтворення / Бібліотека / Профілі пристроїв / Діагностика.
Чому: оригінал був єдиним довгим плоским списком усіх налаштувань - добре для розробника, який його створив, але заплутано для всіх інших. Розділення групує пов'язані опції та робить діалогове вікно більш схожим на сучасні налаштування додатків.
Перейменування збірки + плагіна (без F-id - примітка щодо пакування)
Що: скомпільована DLL називається mb_UPnP_yaiol.dll, а плагін повідомляє себе як "MusicBee UPnP (yaiol)". Відрізняється від оригінального mb_Upnp.dll.
Чому: користувачі можуть встановити yaiol поруч з оригінальним плагіном і порівняти поведінку пліч-о-пліч.
Система значків - відображення стану під час виконання (механізм за F40)
Що: загальний шаблон інтерфейсу користувача для відображення важливих умов під час виконання у вигляді видимих кольорових значків у діалоговому вікні налаштувань. Поточні випадки:
- ⚠ Макс. з'єднань (N04) - спрацьовує, коли ліміт максимальних з'єднань був досягнутий принаймні один раз з моменту запуску MusicBee. Липкий прапорець сесії
Plugin.MaxConnectionsHit. Встановлюється всерединіWaitOnSendBarrier, коли немає вільного слота. - ⚠ Потрібен перезапуск - спрацьовує, коли збережене налаштування потребує перезапуску MusicBee, щоб набути чинності. Липкий прапорець сесії
Plugin.RestartRequired. Встановлюється в обробнику збереження діалогового вікна, коли нове збережене значення відрізняється від знімка під час виконання (Plugin.activeMaxConnections,Plugin.activeServerPort,Plugin.activeIpAddress). Налаштування, що потребують перезапуску, обмежені тими, які дійсно не можуть гаряче перезавантажуватися - параметри прив'язки HTTP-сервера та SemaphoreSlim, створений один раз під час ініціалізації.
Чому: файл журналу плагіна підходить для технічних користувачів, які налагоджують, але нетехнічний користувач, який дивиться на "пристрій звучить неправильно" або "відтворення повільне", ніколи не відкриє Діагностика → Переглянути журнал. Значки фіксують випадки, коли користувачеві потрібно знати, що щось сталося, і відображають це наступного разу, коли він відкриває плагін - це можна виявити, нічого не читаючи.
Можна використовувати для майбутнього:
- Виявлено невідповідність профілю (агент користувача пристрою ніколи не відповідав жодному профілю, повернувся до Generic).
- Спрацював відкат NextURI (F13 - безперервне відтворення вимкнено для сесії на нестабільному пристрої).
- Сканування бібліотеки не вдалося / частково.
- З'єднання рендерера втрачено під час сесії.
- Будь-яка інша умова, коли "сталося один раз, користувач повинен знати" краще, ніж "зареєстровано тихо серед 1000 інших рядків".
Конвенції реалізації:
- Мітки значків знаходяться на рівні діалогового вікна (не всередині будь-якої панелі), тому вони видимі незалежно від того, в якому розділі знаходиться користувач.
- Розташовані вздовж нижнього рядка біля кнопок "Зберегти/Скасувати" (поточне: y=410, розташовані горизонтально).
- Кожен значок має відповідний липкий прапорець сесії в
Plugin, який перемикається на True, коли виникає умова, і скидається лише при перезапуску MusicBee. - Ресурси:
<Condition>Badge(текст мітки, префікс з ⚠) +<Condition>BadgeTip(підказка, що пояснює причину + засіб). - Для значків "збережене налаштування потребує перезапуску" зробіть знімок під час виконання в
Plugin.Initialise()та порівняйте зSettings.*післяSettings.SaveSettings()в обробнику збереження діалогового вікна.
N27 - Скасування відкидає зміни шляху/шаблону
Що: зміни, внесені до шляхів та шаблонів у діалоговому вікні налаштувань, тепер відкидаються, коли користувач натискає "Скасувати", замість того, щоб тихо залишатися застосованими, а будь-який зарезервований шаблон, видалений під час сесії, створюється заново. (Шаблони інакше зберігаються в реальному часі під час редагування - на вкладці "Шляхи" немає окремої кнопки "Зберегти".)
Чому: "Скасувати" має означати скасувати. Раніше користувач, який експериментував зі змінами шляхів/шаблонів і відмовився, виявляв, що зміни вже були зафіксовані, без можливості їх скасувати, окрім як переробити кожну вручну.
N28 - Буквальні амперсанди в меню вибору поля
Що: поле, назва якого містить "&" - наприклад, "Настрій & Контекст" - відображає амперсанд буквально в меню вибору поля замість того, щоб поглинати його як префікс Alt-мнемоніки.
Чому: назви полів з амперсандом відображалися неправильно (символ зникав, а наступна літера ставала прискорювачем), що ускладнювало розпізнавання пункту меню.
N29 - Стабільний, неперекладений заголовок вікна налаштувань
Що: заголовок вікна налаштувань фіксується на рядку бренду "MusicBee UPnP Plugin" і більше не змінюється залежно від мови інтерфейсу; рядок DialogTitle для кожної мови було вилучено з кожного пакету локалізації.
Чому: заголовок вікна, який змінював формулювання залежно від мови, був перекладаною поверхнею без переваг - заголовок є маркою бренду. Закріплення його робить його стабільним та послідовним скрізь.
Локалізація
Оригінальний плагін доступний лише англійською мовою. Цей форк повністю локалізований - кожен рядок, що відображається користувачеві, проходить через пакет ресурсів, і плагін автоматично визначає мову інтерфейсу MusicBee.
N30 - Багатомовний інтерфейс користувача (переклади очікуються)
Що: механізм локалізації завершено та випущено. Localisation.vb читає вибрану мову MusicBee з MusicBee3Settings.ini (ендонім <SystemLanguage>) і застосовує відповідну культуру .NET до потоку, тому My.Resources.Resources.* повертає локалізований рядок. Кожна мітка/кнопка/повідомлення, що відображається користувачеві, підключена до ключа ресурсу (елементи керування дизайнера через ApplyDesignerExtras + sync-en-locale.js; рядки під час виконання, такі як WarnPortInUse, додані вручну). Що не зроблено ще, так це фактичний переклад: існує лише англійський вихідний пакет (Resources.resx) - супутникові пакети для інших мов створюються одним пакетним проходом, коли плагін буде повністю функціональним (переклад частинами, поки рядки все ще змінюються, витрачає зусилля).
Цільові мови (набір, який пропонує сам MusicBee, відповідність 1:1 за endonymToCulture, тому плагін автоматично слідує мові 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 є єдиним пакетом - "English(US)" MusicBee (en-US) повертається до en через ланцюжок культур .NET, тому окремий пакет для США не створюється. ES та FR також є одномовними.
Чому: налаштування плагіна UPnP ("не використовувати сирий PCM", "примусово використовувати PCM з прямим порядком байтів", попередження про відкат порту) досить загадкові навіть рідною мовою. Дотримання власної мови інтерфейсу MusicBee - замість примусового використання англійської - це різниця між інструментом, який неангломовний користувач може налаштувати, і тим, який він не може. Жоден з попередніх розробників не намагався цього.
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-кадрування).
Чому: за свідченнями на форумах, це єдиний найбільший виграш у якості відтворення. Hi-fi користувачі, які купують дорогі рендерери, явно хочуть біт-перфектний вихід; будь-який дотик DSP зводить нанівець сенс. За замовчуванням УВІМКНЕНО, оскільки більшість сучасних пристроїв обробляють будь-який кодек, який їм надав користувач, а ReplayGain/EQ повинні бути опціональними. Це для кожного профілю, тому ви можете продовжувати перекодування для старого Xbox, надсилаючи нативний потік на hi-fi ЦАП.
F04 - "Примусове перекодування" для кожного профілю
Що: перевизначення для кожного профілю, яке примусово пропускає кожен потік до цього пристрою через транскодер, незалежно від підтримки нативного кодека. Зворотне до F03 (ForceNativeStream). Взаємовиключне з F03 - інтерфейс автоматично знімає позначку з іншого, коли один з них увімкнено.
Чому: єдиний глобальний перемикач суперечив би опції ForceNativeStream (F03) для кожного профілю. Реальний випадок: пристрій A - це hi-fi ЦАП, який потребує біт-перфектних нативних потоків; пристрій B - старий AV-ресивер, який задихається від FLAC. За допомогою глобального перемикача користувач повинен вибирати - за рахунок іншого пристрою. З опцією для кожного профілю кожен пристрій отримує правильну відповідь.
Реалізація:
StreamingProfile.ForceTranscoding As Boolean = False.- Схема збереження оновлена до v9. Файли до v9 завантажують застаріле глобальне значення один раз і копіюють його в усі профілі, зберігаючи стару поведінку після оновлення.
- Інтерфейс: видалено з панелі діагностики, додано до розділу "Профілі пристроїв" поруч з "ForceNativeStream". Двосторонні обробники взаємовиключення (
CheckedChangedна кожному відписує інший перед перемиканням, щоб уникнути нескінченного циклу). - Місце прийняття рішення:
Settings.ForceTranscoding→streamingProfile.ForceTranscodingуWriteAudioFileDIDL.
F05 - "Примусовий PCM з прямим порядком байтів" для кожного профілю
Що: потоки PCM (типи mime L16/L24) за специфікацією є big-endian. Деякі пристрої помилково очікують little-endian і відтворюють білий шум, коли отримують належні big-endian дані. F05 перемикає порядок байтів для кожного профілю.
Чому: без цього деякі пристрої видають стіну статичного шуму. Симптом драматичний, а причина невидима без знання кодування PCM - перемикач дає користувачам можливість спробувати та виправити.
F06 - "Не використовувати сирий PCM" для кожного профілю
Що: коли пристрій заявляє, що підтримує сирий PCM, плагін використовує його. Деякі пристрої брешуть - вони приймають рукостискання SOAP, але спотворюють фактичні сирі дані PCM, при цьому правильно обробляючи PCM, загорнутий у контейнер WAVE. 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, що стоїть у черзі пристрою, коли черга порожня (надсилаючи SetNextAVTransportURI з порожнім URL). Деякі пристрої (зокрема Denon) інтерпретують порожній NextURI як "зупинити все" і негайно припиняють відтворення. F08 запобігає очищенню плагіном NextURI.
Чому: без цього власники Denon стикаються з тим, що пристрій обриває відтворення посередині треку, коли черга порожня. Якщо F08 встановлено, пристрій зберігає застарілий NextURI в пам'яті (нешкідливо - він просто перезаписується наступного разу, коли щось ставиться в чергу).
F09 - FLAC як вихідний формат перекодування
Що: спадне меню форматів перекодування в профілях пристроїв тепер пропонує FLAC поряд з PCM 16/24, MP3, AAC, Ogg. Вибір його направляє кодер BASS через стандартний командний рядок конвертації FLAC MusicBee (той самий механізм, який вже використовують MP3/AAC/Ogg).
Чому: для пристроїв, які добре обробляють FLAC, але не можуть декодувати вихідний кодек (наприклад, Eversolo, що отримує бібліотеку WMA MusicBee, перетворену на FLAC), це зберігає якість без втрат, тоді як MP3/AAC відкидали б аудіодані. Розблоковує N02 (керування змішуванням 5.1), яке не могло бути вирішене без опції перекодування без втрат.
Реалізація: додавання одного рядка до блоку Select Case Codec у Encoder.StartEncode - FLAC приєднується до MP3/AAC/Ogg у гілці, керованій командним рядком. Спадне меню інтерфейсу отримує "FLAC" як 6-й варіант. Відображення завантаження/збереження в SettingsDialog розширюється, щоб розпізнавати FileCodec.Flac ↔ SelectedIndex = 5. Тип Mime, тип DLNA та функція кодування вже були підключені в ItemManager.GetMimes / GetDlnaType / GetEncodeFeature з попередньої роботи (F21, F26).
Безперервне відтворення (SetNextAVTransportURI)
F10 - SetNextAVTransportURI / NextURI ядро
Що: справжнє безперервне відтворення. Коли пристрій рекламує підтримку SetNextAVTransportURI у своєму описі служби UPnP, плагін попередньо ставить у чергу наступний трек на пристрої до того, як закінчиться поточний. Пристрій переходить внутрішньо без чутного розриву між треками - те, що ви чуєте на CD-плеєрі. Це не хак "безперервного потоку" (який об'єднує все в один довгий потік і втрачає метадані для кожного треку).
Чому: флагманська функція рівня 2. Альбоми, записані як безперервне живе виконання (живі записи, класичні рухи, DJ-сети), звучать неправильно, коли між треками є півсекундна тиша. Правильне вирішення цієї проблеми є флагманською функцією, тепер у цьому форку.
Примітки: аудіо, що стоїть у черзі, подається через HTTP-сервер плагіна за допомогою streamHandle=0 (режим отримання бібліотеки), що означає, що аудіодвигун MusicBee не задіяний для треку, що стоїть у черзі. Компроміс: ефекти ReplayGain/DSP/EQ не застосовуються до наступного треку. Прийнятно, коли увімкнено "примусовий нативний потік" (за замовчуванням).
F11 - "Вимкнути підтримку NextURI" для кожного профілю
Що: навіть якщо пристрій рекламує SetNextAVTransportURI, цей прапорець змушує плагін ігнорувати цю рекламу та повертатися до відтворення по одному треку.
Чому: деякі пристрої рекламують NextURI, але мають помилкову реалізацію (збої, неповні переходи, зависання). Замість того, щоб реверс-інжинірингувати кожен зламаний пристрій, користувач отримує перемикач "просто вимкніть його тут".
F12 - Життєвий цикл NextURI у списку відтворення
Що: коли MusicBee запускає NowPlayingListChanged, плагін переоцінює, що має бути поставлено в чергу для безперервного переходу. Він запитує у MusicBee новий "наступний" трек за допомогою NowPlayingList_GetNextIndex(1) + NowPlayingList_GetListFileUrl, порівнює з тим, що зараз стоїть у черзі на пристрої (відстежується за допомогою нового поля nextPlaySourceUrl), і знову ставить у чергу, якщо він змінився (або очищає чергу, якщо MusicBee каже, що наступного треку немає).
Чому: без F12 пристрій продовжував відтворювати застарілий NextURI, коли користувач видаляв/перевпорядковував трек у черзі. Історично це зайняло кілька ітерацій, оскільки кожна мутація списку потребує різної обробки - ми спростили, довіряючи NowPlayingList_GetNextIndex (який вже враховує перемішування та повторне відтворення всіх треків), тому всі варіанти проходять через одне й те саме порівняння.
Реалізація:
- Нове поле
nextPlaySourceUrlзберігає URL-адресу бібліотеки MusicBee того, що стоїть у черзі (URL-адреса потокового передавання з суфіксом дескриптора не порівнянна з шляхом бібліотеки). - Нова
Public Sub RefreshQueuedNextUri()наMediaRendererDevice. Три результати: NextURI не стоїть у черзі → бездіяльність; черга відповідає новому "наступному" → бездіяльність; черга відрізняється → викликатиQueueNextз новою URL-адресою (абоQueueNext("")для очищення - що враховує F08 DoNotClearNextUri). - Підключено в
Plugin.ReceiveNotificationпідNotificationType.NowPlayingListChanged.
F13 - Відкат при збої NextURI
Що: після 4 послідовних збоїв SetNextAVTransportURI на одному пристрої плагін вимикає безперервне відтворення для цього пристрою до перезапуску MusicBee.
Чому: якщо пристрій дійсно несправний для NextURI (періодичні помилки SOAP, мережеві збої), плагін інакше продовжував би повторювати спроби на кожному треку. F13 зупиняє шум і тихо повертається до відтворення по одному треку.
F14 - Режим повтору + інтеграція NextURI
Що: F14 розділяється на два випадки, які обробляються детектором переходу F15 у OnAvTransportStatusCheck:
- Повторити все: MusicBee передає правильний URL "обгортки" (трек 1 в кінці списку) до
Plugin.QueueNextсамостійно. Спеціальна логіка плагіна не потрібна - пристрій переходить до нього, і детектор F15 викликаєPlayer_PlayNextTrack, який обгортає індекс NPL MusicBee назад до 0. - Повторити один: MusicBee передає ОДИНАКОВИЙ URL треку до
Plugin.QueueNext. Пристрій переходить до нього (новий дескриптор потоку, те саме джерело). Детектор F15 тепер запитуєPlayer_GetRepeat()- якщо цеRepeatMode.One, він пропускає викликPlayer_PlayNextTrack, щоб MusicBee не просував індекс NPL від циклічного треку.
Чому: без пропуску "Повторити один" виклик Player_PlayNextTrack при безперервному переході пересунув би MusicBee до наступного треку в списку (Повторити один впливає лише на автоматичне просування в кінці треку в інтерфейсі програвача - Наступний трек завжди рухається вперед), що суперечить тому, що означає "Повторити один".
Застереження щодо кількості відтворень: у режимі "Повторити один" збільшення кількості відтворень залежить від того, чи MusicBee 3.7.9563+ помітить циклічне відтворення. Старіші версії MusicBee правильно відтворюють безперервний повтор, але пропускають збільшення кількості відтворень. Документовано; не блокує.
F15 - Скінченний автомат виявлення переходу треку
Що: коли пристрій внутрішньо переходить від поточного треку до NextURI, плагін повинен це помітити та повідомити MusicBee про необхідність просунути його індекс відтворення. Інакше MusicBee вважає, що він все ще на попередньому треку, і кількість відтворень / інтерфейс користувача / скробблінг розсинхронізуються.
Реалізація: опитує GetPositionInfo.TrackURI при кожному тику таймера стану. Коли повідомлений URI відповідає тому, який ми поставили в чергу через NextURI, ми викликаємо Player_PlayNextTrack на MusicBee та встановлюємо suppressNextSoapCall, щоб отриманий PlayToDevice не надсилав повторно SetAVTransportURI (що перервало б безперервне відтворення).
Чому: без F15 пристрій відтворює наступний трек, але інтерфейс MusicBee показує, що він все ще на попередньому. Заплутано, порушує скробблінг, порушує відстеження кількості відтворень. Виявлення переходу треку потребує тривалої ітерації для кожного рендерера, оскільки кожна марка рендерера має свої особливості в коли вона повідомляє про зміну URI (деякі спочатку повідомляють TRANSITIONING, деякі переходять безпосередньо до PLAYING з новим URI, деякі мають короткий STOPPED між ними).
Примітки: наш перший варіант працює на рендерері BubbleUPnP. Крайові випадки для кожного пристрою залишаються в B6.
F16 - Виправлення "поп" при безперервному переході
Що: "поп" виникає, коли формат джерела (частота дискретизації / канали / кодек) треку, що стоїть у черзі, відрізняється від треку, що відтворюється, змушуючи ЦАП пристрою повторно блокуватися під час переходу. F16 додає діагностику NextUri:FormatChange, яка спрацьовує під час постановки в чергу, коли формати відрізняються, називаючи обидві сторони - щоб користувачі, які чують "поп", могли співвіднести.
Діагностика також вказує на пом'якшення: встановіть прапорець ForceTranscoding у профілі пристрою. Це гомогенізує кожен трек до єдиного кодека/частоти дискретизації/бітової глибини перекодування, повністю усуваючи різницю у форматі джерела.
Чому відкладено для фактичного виправлення перекодування для відповідності: структурне виправлення (перекодування треку, що стоїть у черзі, для відповідності формату треку, що відтворюється) вимагає змін у схемі URL-адрес HTTP-сервера плагіна - наразі /encode/{id}0.{ext} обслуговує файл, що стоїть у черзі, нативно. Майбутня версія F16 додасть маршрути /encode/{id}0_{rate}_{depth}.{ext} для кожного формату та підключить їх через кодер. Це більша архітектурна зміна, яку варто зробити, якщо реальний пристрій демонструє "поп" після того, як ForceTranscoding не допомагає.
Реалізація сьогодні:
- Поле
lastSourceUrlвідстежує URL-адресу джерела, що відтворюється. QueueNextчитаєFilePropertyType.SampleRate/Channels/Kindдля поточного та чергового треків та реєструєNextUri:FormatChangeпри невідповідності.
F17 - Пересинхронізація індикатора прогресу після перемотування
Що: функція Seek() вже викликала GetPlayPositionInformation() після успішного Seek SOAP, що виправляє випадок "повна відсутність ресинхронізації". F17 усуває залишковий дрейф до 1 секунди, спричинений квантуванням RelTime UPnP з роздільною здатністю 1 секунда: коли повідомлена позиція пристрою округляється до 1 секунди від запитуваної користувачем цілі, плагін тепер довіряє значенню користувача з точністю до мілісекунд замість обрізання пристрою. Лише коли пристрій повідомляє щось кардинально інше (>1 с відхилення), ми використовуємо його значення (пошук приземлився десь в іншому місці, ніж було запитано, наприклад, прив'язка до ключового кадру на деяких кодеках).
Чому: без цього перемотування до 2:30.500, прив'язане до повідомлення пристрою "2:30", призвело б до того, що індикатор прогресу показував би відставання приблизно на 500 мс від реальності. Після F17 індикатор відповідає намірам користувача для звичайного випадку перемотування всередині треку і все ще враховує повідомлення пристрою для винятків прив'язки до ключового кадру.
F18 - Блокування безперервного потоку / NextURI
Що: тепер діють два блокування:
- Під час виконання:
QueueNextповертаєFalseна ранній стадії, колиSettings.ContinuousOutputувімкнено. Безперервний потік є власним механізмом безперервного відтворення (один довгий об'єднаний потік); надсиланняSetNextAVTransportURIповерх нього збиває пристрій з пантелику щодо того, чи є кожен трек окремим URI, чи частиною безперервного потоку. - Інтерфейс користувача: коли користувач встановлює глобальний прапорець безперервного потоку,
forceNativeStreamпоточного профілю автоматично знімається. Безперервний потік завжди перекодує, тому "примусовий нативний" не має сенсу в поєднанні.
Чому: запобігає одночасному ввімкненню користувачем двох конфліктуючих механізмів безперервного відтворення. Без F18 пристрій отримував би як URI безперервного потоку, так і NextURI для кожного наступного треку, з невизначеною поведінкою залежно від рендерера.
F19 - Ігнорування помилок порожнього NextURI
Що: коли SetNextAVTransportURI викликається з порожнім URL (наприклад, останній трек у списку), деякі пристрої повертають помилку SOAP. F19 тихо пригнічує їх - реєструються, але не поширюються як помилки.
Чому: умова "немає наступного треку" є нормальною, а не помилкою. Розгляд її як фатальної забруднює журнал і (в деяких потоках) викликає шторми повторних спроб.
Типи MIME та метадані DLNA
F20 - MP3 mime → audio/mpeg
Що: стандартний тип mime для MP3 - audio/mpeg, а не audio/mp3. Останній є поширеною помилковою назвою, яку більшість пристроїв терплять, але більш суворі рендерери відхиляють її.
Чому: тихо виправляє відтворення на більш суворих пристроях, які дотримуються стандарту. Кодова база yaiol вже мала це правильно; змін не потрібно.
F21 - Порядок типів MIME: спочатку не-x- варіант
Що: коли пристрій рекламує як audio/flac, так і audio/x-flac, плагін спочатку повертає не-x- варіант. Те саме стосується будь-якого кодека зі стандартними та експериментальними типами MIME.
Чому: префікс x- позначає експериментальні/неофіційні типи MIME. Деякі рендерери краще працюють зі стандартною формою. Невелике перевпорядкування, реальний вплив.
F22 - Підтримка типу MIME Opus
Що: розпізнає Opus як потоковий аудіокодек; надсилає тип MIME audio/opus при обслуговуванні треків Opus.
Чому: Opus зараз поширений (сучасний компромісний кодек для мовлення/музики). Без F22 плагін відмовлявся б передавати файли Opus навіть на пристрої, які їх обробляють.
F23 - Підтримка вихідних файлів Monkey Audio (APE)
Що: розпізнає файли .ape як дійсний вихідний кодек для потокової передачі/перекодування.
Чому: APE - це формат без втрат з нішевою, але відданою базою користувачів. Додавання його коштує небагато і розблоковує бібліотеку для цих користувачів.
F24 - Відкат MIME для AAC / ALAC
Що: якщо пристрій підтримує AAC або ALAC, але явно не рекламує їх у своєму описі служби UPnP, плагін все одно пропонує їх як резервний варіант.
Чому: кілька пристроїв, які добре обробляють AAC, забули вказати його у своєму XML-файлі можливостей. Без F24 плагін навіть не спробує, примусово перекодуючи. З F24 плагін спробує і дозволить пристрою обробити його нативно, якщо він зможе.
F25 - Прапорець типу DLNA для нативних та закодованих потоків WAV
Що: прапорець типу DLNA (ідентифікатор профілю, такий як LPCM, WAVE, MP3) повинен відповідати тому, що отримує пристрій. F25 гарантує, що нативні потоки та закодовані потоки WAV позначаються правильно.
Чому: невідповідний тип DLNA призводить до того, що деякі пристрої повністю відмовляються від відтворення або застосовують неправильний декодер.
F26 - Заголовок DLNA для файлів FLAC
Що: потоки FLAC отримують належний ідентифікатор профілю DLNA у своїх заголовках.
Чому: без нього деякі пристрої, що підтримують FLAC, не розпізнають потік як такий.
F27 - Виправлення розрахунку бітрейту в метаданих
Що: res@bitrate безперервного потоку обчислювався як (sampleRate * channels * bitsPerSample) / 1000 - кбіт/с, що відрізнялося приблизно в 125 разів від специфікації UPnP DIDL, яка визначає атрибут як байти на секунду. Тепер ділиться на 8 замість 1000.
Чому: неправильне відображення бітрейту на пристрої - косметичне на більшості рендерерів, але деякі виділяють буфери потоку з цього значення і заїкаються на потоках, які виглядають приблизно в 125 разів меншими, ніж є. Шлях до вихідного файлу безперервного потоку вже мав це правильно ((bitrate_kbps * 1000) \ 8 = байти/сек); неправильним був лише шлях безперервного потоку.
F28 - Виправлення формату часу метаданих (Marantz)
Що: res@duration у DIDL було відформатовано як 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 використовували hh (12-годинний формат) у своїх рядках формату DateTime замість HH (24-годинний). Будь-який трек, доданий або відтворений між 13:00 та 23:59, відображався б з неправильною годиною (наприклад, 17:42 → "05:42") на пристроях, які відображають це поле. Тепер використовується HH.
F29 - Підтримка перемотування закодованого MP3 (CBR)
Що: перекодовані потоки MP3 тепер рекламують DLNA.ORG_OP=11 (перемотування як за байтами, так і за часом) замість DLNA.ORG_OP=10 (лише за байтами). Пристрої, які раніше відмовлялися від перемотування за часом на перекодованих MP3, тепер можуть нормально керувати своїм індикатором прогресу/інтерфейсом перемотування.
Чому: транскодер MusicBee виробляє MP3 з постійним бітрейтом за пресетом HighQuality, тому відображення байт ↔ час є лінійним - пристрій може сам перетворити запит на перемотування за часом на запит на перемотування за байтами HTTP Range без будь-якої підтримки з боку кодера. Реклама OP=11 розблоковує цей інтерфейс на пристрої. Без F29 користувачі, які перемотували всередині перекодованого MP3, або мали перемотування тихо ігнорованим, або їх скидало на початок треку.
Реалізація: реструктуровано GetEncodeFeature у ItemManager.vb, щоб розбити вбудований If на читабельний ланцюжок If/ElseIf/Else. MP3 отримує OP=11 явно; інші не-PCM кодеки зберігають OP=10. Без змін для AAC/FLAC/тощо - для них потрібна була б перевірка CBR-ності, специфічна для кодека, яку MusicBee не гарантує.
F30 - Обробка розширення файлу .mpeg
Що: файли з розширенням .mpeg (і ще рідкіснішим .mpe) тепер розпізнаються як FileCodec.Mp3 у GetCodec. До F30 вони повертали FileCodec.Unknown і тихо відхилялися з бібліотеки / не могли бути джерелами для перекодування.
Чому: старі архіви MPEG-1 Layer 3 іноді використовували .mpeg замість .mp3 (специфікація дозволяє обидва). Кілька файлів у бібліотеці на 300 тис. достатньо, щоб відчути "MusicBee їх показує, але плагін ні" - заплутано для користувача.
Поведінка відтворення
F31 - Радіопотоки автоматично використовують безперервний режим
Що: WriteAudioFileDIDL тепер перевіряє властивість Kind URL-адреси джерела за допомогою Library_GetFileProperty і розглядає будь-який файл, чий Kind закінчується на "Stream" (MusicBee повідомляє "MP3 Stream", "Internet Stream" тощо для радіо) як безперервний, незалежно від глобального перемикача Settings.ContinuousOutput. Використовується гілка DIDL безперервного потоку (Title: "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.
Чому: більш плавне та точне відображення прогресу, особливо відразу після зміни треку. Немає способу обійти саму роздільну здатність звітування UPnP в 1 секунду - це специфікація.
F34 - Тремтіння індикатора прогресу після зміни треку
Що: коли PlayToDevice викликається для нового треку, плагін раніше залишав currentPlayPositionMs та currentPlayStartTicks на їхніх значеннях попереднього треку протягом ~100 мс між SOAP-Play та першим опитуванням таймера стану, що виявляє новий стан "Відтворення". Індикатор прогресу MusicBee коротко показував би кінець попереднього треку, потім повертався б до 0, потім піднімався. F34 обнуляє обидва при вході в PlayToDevice - у момент, коли ми знаємо, що відбувається зміна треку, до будь-якої роботи SOAP.
Чому: візуальний збій у випадках швидкого пропуску (ручний наступний або безперервний перехід). Тепер перший запит PlayPositionMs MusicBee після Play чисто повертає 0, потім GetPlayPositionInformation F33 уточнює його до фактичної позиції пристрою при першому тику зміни стану.
Реалізація: чотири рядки на початку PlayToDevice, у поєднанні з точним прив'язуванням F33 до часу переходу.
F35 - Помилка "Примусове перекодування"
Що: примусове перекодування все ще могло пропускати перекодування в певних комбінаціях. Після переробки F04 для кожного профілю було закрито дві конкретні прогалини:
- Пріоритет з ForceNativeStream. Коли обидва були True (що може статися під час міграції схеми або часткового файлу налаштувань), ForceTranscoding тепер однозначно виграє (
If streamingProfile.ForceTranscoding Then forceEncode = True ElseIf streamingProfile.ForceNativeStream Then forceEncode = False). Взаємовиключення інтерфейсу користувача запобігає встановленню обох користувачем, але захист під час виконання обробляє будь-який стан, який завантажився непослідовно з диска. - Логіка bypassTranscodeDecision. Раніше:
streamingProfile.ForceNativeStream AndAlso Not Settings.ForceTranscoding. Тепер:streamingProfile.ForceNativeStream AndAlso Not streamingProfile.ForceTranscoding- те саме правило пріоритету, але в тому ж обсязі для кожного профілю.
Чому: "примусово" має означати примусово. Якщо користувач явно ввімкнув ForceTranscoding для пристрою, плагін ніколи не повинен тихо переходити до нативного потокового передавання, незалежно від того, як поєднуються інші прапорці.
F36 - Виняток "Рендерер закрито"
Що: обгорнув Plugin.ReceiveNotification у верхній рівень Try/Catch, який реєструє будь-яке неперехоплене виключення замість того, щоб дозволяти йому поширюватися назад до механізму сповіщень MusicBee.
Чому: сповіщення від MusicBee (PlayStateChanged, VolumeMuteChanged тощо) надсилаються до ControlPointManager, який спілкується з рендерером через SOAP. Окремі місця виклику вже мали Try/Catch навколо своїх викликів SOAP, але досить дивний випадок синхронізації (наприклад, рендерер, що виходить з ладу між двома викликами SOAP в одному обробнику сповіщень) все ще міг вислизнути. Обгортка верхнього рівня є остаточною мережею безпеки, щоб користувач ніколи не бачив загального спливаючого вікна "TargetInvocationException" від MusicBee.
Реалізація: перейменовано існуюче тіло на ReceiveNotificationInternal та додано тонку обгортку ReceiveNotification, яка виконує Try { ReceiveNotificationInternal(...) } Catch { LogError(...) }. Існуюча інфраструктура Try/Catch для кожного методу всередині ControlPointManager (навколо кожного виклику PostSoapRequest) залишається - F36 є ременем + підтяжками.
F37 - Перемотування довгого треку викликає помилковий перехід
Що: перемотування всередині довгого треку може викликати короткий цикл Stopped→Playing на деяких рендерерах. Без розрізнення ProcessNewPlayState.Stopped розглядає це як природне закінчення треку та викликає Player_PlayNextTrack, просуваючи MusicBee, коли користувач просто хотів перемотати. F37 встановлює lastUserInitiatedSeek у Seek() та додає 5-секундний захист в обробнику Stopped (віддзеркалюючи існуюче вікно lastUserInitiatedStop).
Чому: тихий пропуск до наступного треку під час перемотування - це одна з тих помилок, причину якої ніхто не може вгадати - користувач думає "дивно, я спробував перемотати вперед, а тепер відтворюється наступна пісня". Виправлення механічне: той самий шаблон, що й дискримінація зупинки користувача, яка вже існує.
F38 - Покращена обробка перемотування для кодеків, схильних до збоїв
Що: збій BubbleUPnP при перемотуванні MP3 був канонічним симптомом. Після аудиту поточний код перемотування yaiol вже робить правильні речі - нативний шлях правильно обробляє HTTP Range (206, Content-Range, AcceptRanges), закодований шлях рекламує X-AvailableSeekRange та аналізує вхідні заголовки timeSeekRange.dlna.org / npt, прапорці DLNA.ORG_OP відображають фактичні можливості потоку (з опцією DisablePcmTimeSeek для проблемних пристроїв Platinum). Перевірено користувачами на поточній BubbleUPnP 4.6.4: збоїв не спостерігалося.
Чому: збій BubbleUPnP при перемотуванні MP3 був повідомлений приблизно в 2024 році, і додаток отримав близько 16 місяців виправлень з тих пір. F29 (закодований MP3 OP=11) був новою змінною, яка могла б знову його виявити; не виявляє, на перевірених версіях.
Якщо збій повернеться: форма виправлення буде перемикачем "обмежене перемотування" для кожного профілю, який примусово встановлює DLNA.ORG_OP=10 (лише байти) для позначених кодеків - віддзеркалюючи, як DisablePcmTimeSeek вже працює для PCM. Додати тоді, а не завчасно.
Інтерфейс користувача та ведення журналу
F39 - Кнопка "Додати" вибирає новий профіль
Що: натискання "Додати" у списку профілів пристроїв створює новий профіль І автоматично вибирає його, щоб користувач міг негайно редагувати поля. Наша рефакторинг розділеного діалогового вікна вже робить це - як прямий шлях "Додати", так і шлях "з шаблону" закінчуються Me.activeStreamingProfiles.SelectedIndex = Me.activeStreamingProfiles.Items.Count - 1. Перевірка підтвердила, що наш форк вже обробляє це - нічого змінювати не потрібно.
Чому: невелика проблема UX, яка виявилася вже не проблемою тут.
F40 - Більше максимальних з'єднань + попередження в журналі
Що: обмеження одночасних потоків плагіна (SemaphoreSlim навколо Sockets_Stream_File / Sockets_Encoder_Start) було жорстко закодовано на 4. F40 робить його настроюваним користувачем на сторінці загальних налаштувань (за замовчуванням 16, діапазон 1-256), додає рядок журналу MaxConnections, коли запит має чекати на слот, І відображає червоний значок ⚠ Max Conn у нижньому лівому куті діалогового вікна налаштувань, якщо обмеження було досягнуто принаймні один раз з моменту запуску MusicBee.
Чому: коли пристрій надсилає паралельні запити (деякі Marantz/Linn під час сканування обкладинок, метадані BubbleUPnP разом з активним відтворенням), додаткові запити блокувалися тихо за семафором - користувач бачив "пристрій повільний" без видимої причини. Рядок журналу добре підходить для технічного налагодження, але нетехнічні користувачі ніколи не читають журнали. Видимий значок у діалоговому вікні налаштувань робить умову досягнення обмеження доступною для будь-кого, хто відкриває налаштування плагіна.
Реалізація:
- Централізовано очікування в
WaitOnSendBarrier(logTag)уMusicBeeUpnp.vb; обидва місця виклику (MediaServerDevice.GetFile,Encoder.StartEncode) використовують його. Settings.MaxConnectionsзберігається у v8 схеми налаштувань.Plugin.MaxConnectionsHit- це липкий прапорець сесії, встановлений всерединіWaitOnSendBarrier; скидається лише при перезапуску MusicBee.SettingsDialog.maxConnectionsBadge- це червона жирна мітка на(16, 410), яка відображається лише тоді, колиPlugin.MaxConnectionsHitє True. Має підказку, що пояснює причину та засіб.- Семафор ініціалізується один раз при завантаженні типу, тому зміна налаштування вимагає перезапуску MusicBee (зазначено в мітці поля).
F41 - Журнал "кодування через ReplayGain/DSP"
Що: замість окремих рядків журналу "кодування для RG" / "кодування для DSP", єдиний рядок StreamDecision з F42 включає MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain як накопичені причини. Таке ж діагностичне значення, менше шуму.
Чому: користувачі бачать всі причини, чому відбувається перекодування для даного треку, в одному рядку журналу, а не розкидані. Дивіться F42 для повних деталей.
F42 - Журнал "рендерер не підтримує вихідний кодек"
Що: додано рядок журналу StreamDecision для кожного треку, що відтворюється на пристрої, який повідомляє "нативний CODEC" або "перекодування CODEC→CODEC причина=…". Поле причини накопичує кожну умову, яка викликала перекодування: MB-DSP/EQ, MB-ReplayGain, Profile-DSP/EQ, Profile-ReplayGain, WebFile, VirtualFile, ForceTranscoding(global), SampleRate<min/SampleRate>max, DownmixToStereo, DeviceLacksCodec(X), BandwidthConstrained.
Чому: користувачі були збентежені несподіваними стрибками ЦП на файлах, які вони очікували передавати нативно. Один рядок журналу для кожного треку точно повідомляє їм, яка умова викликала перекодування - і якщо поле показує DeviceLacksCodec(Flac), вони одразу знають, що інформація про протокол пристрою була неповною, і можуть захотіти, щоб спрацював відкат F32.
Реалізація: єдиний рядок-акумулятор, що поступово будується через ланцюжок рішень; реєструється один раз в кінці. Захищено Settings.LogDebugInfo, щоб уникнути шуму в журналі у виробництві.
F43 - Журнал SetNextAVTransport показує URL джерела
Що: записи журналу QueueNext тепер включають source=<шлях до бібліотеки MusicBee> поряд з stream=<URL потокового передавання HTTP>. Та сама зміна застосована до шляху успіху та шляху збою (QueueNext:Failed).
Чому: при налагодженні проблеми з треком у черзі URL потокового передавання (/encode/aabbccdd0.flac) сам по собі непрозорий - той самий для кожного треку. URL джерела - це зручний для пошуку шлях до бібліотеки, який точно повідомляє, який файл MusicBee намагався поставити в чергу.
F44 - Краще ведення журналу помилок типу MIME
Що: два нові записи в журналі під час Activate:
Activate:MimeUnverified- спрацьовує для кожного неправильно сформованого запису у відповідіGetProtocolInfoпристрою, вказуючи, який запис не вдалося розібрати (щоб користувач міг бачити, наприклад, "Marantz повернувhttp-get:*::*для деякого кодека - можливість не перевірена, відкат F32 буде вгадувати").Activate:NoSinkInfo- спрацьовує один раз, якщо пристрій взагалі не повернув елемент<Sink>. Це означає, щоSupportedMimeTypesзалишається Nothing, аIsCodecSupportedдеградує до "припускати, що все працює" - корисний контекст, коли пізніше з'являються помилки "пристрій відмовився від потоку".
Чому: до F44 ці тихі провали можливостей залишали користувачів гадати, чому їхні треки або перекодовувалися всупереч очікуванням, або відхилялися пристроєм. Тепер один пошук за Activate: показує, чи була інформація про можливості пристрою придатною для використання.
F45 - Краще ведення журналу помилок метаданих
Що: журнал винятків Browse у ContentDirectoryService.vb вже був збагачений у попередній роботі yaiol (сесія з помилкою Alia Vox) за допомогою ObjectID та трасування стека. F45 розширює його далі за допомогою BrowseFlag (метадані проти дочірніх елементів), Filter (які атрибути запитував клієнт), sortCriteria та partialResultLength (скільки байтів DIDL було створено до збою - вказує, наскільки далеко в пакеті знаходиться поганий трек).
Чому: коли щось йде не так під час DIDL, значення partial-length повідомляє вам, чи стався збій на першому треку пакета (partial=0) або десь посередині (partial=N) - у поєднанні з startingIndex пакета ви можете ідентифікувати індекс проблемного треку. Filter та BrowseFlag пояснюють, який тип перегляду хотів клієнт; іноді перегляд лише метаданих не вдається, тоді як перегляд дочірніх елементів для того ж ID вдається.
Мережа
F46 - Автоматичний режим оголошує лише на адаптерах реальної мережі
Що: у режимі інтерфейсу Автоматичний плагін раніше оголошував себе (SSDP) на кожному робочому адаптері IPv4. На машині, де також працює VPN-тунель (NordLynx) або віртуальний комутатор (Hyper-V / WSL / Docker), та сама бібліотека оголошувалася й на кожному з цих адаптерів, тому точка керування, з якої ви транслюєте, виявляла server двічі або тричі та показувала бібліотеку як дубльовані копії. Автоматичний режим тепер залишає лише адаптери зі справжнім шлюзом IPv4 за замовчуванням (HasIPv4Gateway) — якого адаптери тунелів і віртуальних комутаторів не мають — тому вони виключаються зі списку оголошень. Закріплена користувачем адреса, як і раніше, має безумовний пріоритет (оголошення лише на цьому інтерфейсі), а якщо жоден адаптер не повідомляє про шлюз, вибір повертається до всіх адаптерів, тому список оголошуваних адрес ніколи не буває порожнім і плагін не може стати невидимим.
Чому: дублікат виникає не через те, що «ви у VPN» — він виникає через одночасне оголошення на адаптері LAN і на тунельному/віртуальному адаптері, тому одна точка керування бачить той самий server за двома адресами. Споживчий VPN (NordVPN/NordLynx) тунелює лише трафік, спрямований в інтернет; рендерер DLNA живе в LAN, а трафік локальної підмережі оминає тунель, тому тунельний адаптер усе одно ніколи не досягає рендерера — його виключення прибирає фантомну копію, але ніколи — робочий шлях. Перевірка шлюзу — це дешевий, надійний сигнал, що відрізняє справжній адаптер LAN/Wi-Fi від тунелю чи віртуального комутатора. Доповнює N05 (який виправив, як оголошення надсилаються такими каналами — multicast замість broadcast); F46 визначає, на яких адаптерах узагалі оголошувати.
Відоме обмеження: mesh-VPN / VPN віддаленого доступу (Tailscale, ZeroTier, WireGuard додому), рендерери якого справді живуть по той бік тунелю, зазвичай показує адаптер без шлюзу за замовчуванням, тому автоматичний режим відкидає і його. Такі користувачі натомість закріплюють адресу VPN, яка має пріоритет над фільтром шлюзу.