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 МБ вважався занадто великим, потім завантажувався за секунду — комфортно в межах очікування, від якого він вже відмовився. Тепер він просто спостерігає за завантаженням. Поки файл ще надходить, плагін продовжує чекати, незалежно від того, скільки часу це займе; він відмовляється лише тоді, коли передача дійсно зупиняється, що він тепер помічає швидше, ніж стара фіксована затримка.

2.0.6 - 2026-08-03

Альбоми, надіслані з телефону, тепер відтворюються без пауз між треками, а інтернет-радіо розпізнається як радіо, а не як надзвичайно довга пісня.

Альбоми відтворюються без пауз

Що: коли ви надсилаєте цілий альбом зі свого телефону, MusicBee тепер відтворює один трек за іншим без паузи — тож живі записи, діджейські сети та безперервні класичні твори звучать цілісно.

Чому: стандарт дозволяє керуючому додатку сказати "ось що буде далі", що робить можливим безшовне з'єднання. Ця інструкція взагалі не приймалася, тому додаток не мав куди помістити майбутній трек і оголошував його так, ніби це був поточний — причина проблеми з альбомами, виправленої в попередньому випуску. Тепер вона приймається належним чином: наступний трек завантажується, поки поточний ще грає, і власний плеєр MusicBee перетинає межу.

Інтернет-радіо розпізнається як радіо

Що: жива станція, надіслана до MusicBee, відтворюється як потік і ніколи не завантажується.

Чому: завантажувати трансляцію немає сенсу — вона не має кінця, і немає нічого, через що можна було б перестрибувати — але плагін раніше не мав способу відрізнити її від музичного файлу, тому він починав завантажувати і зупинявся, коли завантаження перевищувало фіксований розмір. Керуючий додаток вказує, що з двох він надсилає, і це тепер читається безпосередньо. Без налаштувань, без здогадок.

Довгі треки високої роздільної здатності зберігають свою копію

Що: файли DSD та довгі 24-бітові записи тепер можна переміщувати, як будь-який інший трек.

Чому: вони потрапляли під вищезгадане обмеження розміру — 20-хвилинний рух високої роздільної здатності або 10-хвилинний трек DSD перевищували його — тому їхня копія відкидалася, а повзунок позиції переставав працювати саме для того матеріалу, який найімовірніше варто було б перемотувати. З правильно ідентифікованим радіо обмеження розміру не потрібне.

2.0.5 - 2026-08-03

Два виправлення для керування MusicBee з телефону: гучність тепер означає те саме на обох кінцях, і трансляція цілого альбому продовжує працювати після першого треку.

Гучність на вашому телефоні відповідає гучності в MusicBee

Що: тепер, коли ви встановлюєте максимальну гучність на телефоні, вона досягає максимуму в MusicBee, а власне налаштування MusicBee правильно відображається на телефоні.

Чому: плагін ніколи не повідомляв керуючому додатку, яка його найвища гучність, тому кожен додаток мав вгадувати. Один з них зупинився на 69, що означало, що його 100% досягало лише 69% у MusicBee, тоді як 100% MusicBee поверталося як 144% на телефоні — і кнопки гучності телефону ніколи не могли досягти максимуму. Тепер рендерер чітко вказує діапазон, тому обидва кінці говорять про одну й ту саму шкалу.

Трансляція альбому зберігає його назви та повзунок позиції

Що: кожен трек альбому, надісланий з телефону, тепер показує свою правильну назву і може бути переміщений, а не лише перший.

Чому: керуючий додаток оголошує наступний трек через частку секунди після поточного, і це оголошення скасовувало копію, яка завантажувалася для треку, що мав відтворитися — тому більшість треків тихо поверталися до відтворення через мережу, що призводило до втрати як назви, так і можливості переміщатися. Копії для кількох треків тепер зберігаються поруч, тому оголошення більше не може скасувати ту, що використовується.

2.0.4 - 2026-08-02

Музика, надіслана до MusicBee з телефону або іншого сервера, тепер поводиться як справжній трек: ви можете переміщатися по ньому, і він одразу показує свою правильну назву. Плюс виправлення для hi-fi стримерів, які представляють себе як один комбінований пристрій.

Переміщення по треку, надісланому з іншого місця

Що: перетягування повзунка позиції тепер працює для треку, надісланого з вашого телефону, NAS або іншого медіасервера. Щоб це стало можливим, MusicBee завантажує копію треку до тимчасової папки під час його відтворення та відтворює цю копію. Це займає близько секунди в домашній мережі, копія видаляється, як тільки ви надсилаєте інший трек, а будь-які залишки очищаються при наступному запуску MusicBee.

Чому: MusicBee може запускати та зупиняти те, що він слухає через мережу, але не може переміщатися по ньому — тому повзунок, здавалося, стрибав, а потім одразу повертався туди, де був, без пояснень. Відтворення звичайного файлу на власному диску повністю усуває це обмеження, а не обходить його.

Правильна назва та довжина з першої ноти

Що: трек, надісланий програмою, яка не надає своїм файлам звичайного розширення, тепер показує свою справжню назву та довжину, як тільки починається, замість того, щоб відображатися як довга веб-адреса.

Чому: MusicBee ідентифікує трек — і знаходить його теги — за розширенням файлу, а деякі плеєри видають адреси без розширення взагалі. Локальна копія завжди має правильне розширення, тому трек розпізнається незалежно від того, як його називає програма-відправник.

Стрибок, який неможливо зробити, тепер повідомляє про це

Що: якщо рендерер дійсно не може перейти до запитуваної точки, керуюча програма отримує повідомлення про це та звітує.

Чому: раніше він відповідав "виконано" незалежно від цього, тому повзунок повертався через секунду без пояснень. Чесна відмова легше для дії, ніж мовчазна.

Комбіновані hi-fi стримери читаються правильно

Що: коли MusicBee відтворює на пристрій, який представляє себе як єдиний комбінований блок — стример Marantz або Denon, де плеєр знаходиться всередині оболонки виробника разом з медіасервером — плагін тепер читає власні деталі плеєра замість медіасервера.

Чому: раніше він запитував у неправильної половини пристрою, які аудіоформати він може обробляти, не отримуючи жодної корисної відповіді, і продовжував роботу, ніколи не перевіряючи — саме те обладнання, де обробка форматів найбільше потребує правильності. Опис моделі пристрою також тепер враховується при зіставленні його з профілем пристрою; раніше він читався з неправильного місця та відкидався.

2.0.3 - 2026-08-02

Роль "відтворення на" дорослішає: MusicBee тепер може отримувати музику, якої ще немає в його бібліотеці — файл на вашому телефоні, на NAS, на іншому сервері — замість лише тих треків, які він вже має.

Відтворюйте музику, надіслану з телефону, а не лише з власної бібліотеки

Що: коли ви використовуєте програму керування, таку як Symfonium або BubbleUPnP, щоб надіслати музику до MusicBee, трек більше не повинен походити з власної бібліотеки MusicBee. Тепер відтворюється файл, що зберігається на самому телефоні, на NAS або на іншому медіасервері. Назва та тривалість надходять від програми, яка його надіслала, тому трек відображається належним чином, навіть якщо MusicBee ніколи не бачив цей файл.

Чому: роль "відтворення на" була створена для випадку, коли ви переглядаєте бібліотеку цього ПК зі свого телефону та натискаєте пісню — трек вже був на ПК, тому MusicBee просто відтворював свій власний файл. Все, що надходило з іншого місця, тихо відкидалося, що робило цю функцію марною для такого ж природного випадку передачі музики з телефону на хороші колонки.

Трек, який не може бути відтворений, повідомляє про це

Що: якщо рендерер дійсно не може відтворити те, що йому було надіслано, він тепер повідомляє про це програмі, яка його надіслала.

Чому: раніше він відповідав "отримано" на все, тому програма керування продовжувала і натискала відтворення. Оскільки нічого не було завантажено, MusicBee перезапускав будь-який трек, що залишився з попереднього разу — і якщо цей файл зник, скаржився, що його джерело не знайдено. Помилка називала непов'язаний трек і вказувала зовсім не на справжню проблему.

Надсилання нового треку під час паузи тепер відтворює цей трек

Що: якщо MusicBee призупинено, і ваша програма керування надсилає йому щось нове, новий трек починає відтворюватися.

Чому: відновлення мало пріоритет над завантаженням, тому призупинений трек продовжував відтворення з того місця, де він зупинився, а трек, який ви щойно вибрали, був відкинутий без жодного слова.

2.0.2 - 2026-08-01

Пошук — це головна тема: тепер він працює за виконавцем, повертає правильні результати та швидко працює з великою бібліотекою. Крім того, з'явився новий спосіб налаштувати перемішування в програмі керування та виправлення для трьох кнопок, які нікуди не вели.

Пошук за виконавцем тепер дійсно працює

Що: пошук виконавця з вашої програми керування тепер повертає музику цього виконавця. Пошук відповідає як полю Artist треку, так і полю Album Artist альбому, тому збірка буде знайдена незалежно від того, чи введете ви ім'я виконавця, чи назву, під якою альбом зберігається.

Чому: раніше пошук виконавця помилково сприймався як "дай мені все цього типу" — введений вами виконавець ігнорувався, і поверталася вся бібліотека, тому пошук, який мав би відповідати кільком сотням треків, повертав десятки тисяч. Відповідність лише одному з двох полів виконавця тихо втратила б половину результатів, тому перевіряються обидва.

Сторінка результатів пошуку правильно прокручується

Що: прокручування довгого списку результатів пошуку тепер переміщується по ньому. Кожна сторінка, до якої ви прокручуєте, є тією сторінкою, яку ви отримуєте.

Чому: раніше сервер відповідав на кожен запит першою жменькою результатів, повідомляючи при цьому повну кількість збігів, тому програма, яка прокручувала далі, продовжувала отримувати ті самі елементи і ніколи не досягала кінця.

Пошук значно швидший у великій бібліотеці

Що: тепер пошук виконує один запит для всього набору результатів і читає лише теги сторінки, яку ви переглядаєте.

Чому: раніше кожна сторінка повторно виконувала запит до бібліотеки, а потім завантажувала теги кожного збігу — тисячі з них — щоб показати дюжину. У великій колекції це призводило до паузи при кожному прокручуванні. Результати також відкидаються щоразу, коли бібліотека оновлюється, тому змінені дані ніколи не подаються застарілими.

Націльте перемішування на фільтр

Що: нова опція Random plays from на вкладці Library Options. Залиште її на All Music, і папки Random Tracks / Random Albums вашої програми керування будуть поводитися як раніше; виберіть один із ваших фільтрів MusicBee, і кожен випадковий запит буде брати дані з цього фільтра. Приховані фільтри також пропонуються.

Чому: папка перемішування запитує фрагмент "всього", і це єдиний запит, який не містить жодного натяку на те, що ви мали на увазі — тому він завжди брав дані з усієї бібліотеки, включаючи розмовні записи. Тут ви вказуєте, що означає "все". Відкриття папки перемішування всередині фільтра на пристрої все одно перемішує цей фільтр: вибір, який ви робите під час перегляду, має пріоритет над налаштуванням.

Довідка, GitHub та перевірка оновлень ведуть на реальні сторінки

Що: кнопки Help та GitHub у діалоговому вікні налаштувань, а також автоматична перевірка наявності нової версії, тепер відкривають сторінки, які вони називають.

Чому: всі три були побудовані на основі скороченої форми назви плагіна, за якою ніколи не існувало жодної сторінки, тому кожна з них тихо завершувалася невдачею — кнопки, здавалося, нічого не робили, а перевірка оновлень ніколи нічого не повідомляла, незалежно від того, як довго була доступна нова версія.

2.0.1 - 2026-07-26

Раунд виправлень відтворення та перегляду, зосереджений на подкастах і програмах-контролерах (таких як BubbleUPnP), які керують відтворенням та перемішуванням.

Подкасти починають відтворюватися без паузи

Що: завантажений епізод подкасту тепер одразу вказує свою реальну довжину та розмір файлу, перенесені безпосередньо з файлу на диску в метадані медіа.

Чому: без зазначеної тривалості контролер, такий як BubbleUPnP, повторно сканує весь аудіопотік щоразу, коли ви натискаєте відтворення, просто щоб визначити, скільки триває епізод — тому відтворення починалося лише після помітної паузи. Тепер, коли тривалість оголошена, воно починається без затримок.

Подкасти з'являються під час перегляду за виконавцем

Що: назва шоу подкасту тепер зберігається як його виконавець — відображається в полях Artist та Album Artist (і їхніх варіантах сортування), так само, як вона вже заповнює поле Album.

Чому: шлях перегляду, який групує за полем виконавця, раніше знаходив виконавця кожного подкасту порожнім і заходив у глухий кут на порожньому рівні. Розгляд самого шоу як виконавця — відповідно до розгляду його як альбому — означає, що ці шляхи тепер ведуть до епізодів замість нічого.

"Нещодавно відтворені" та трансляція працюють одразу після перезапуску

Що: відтворення подкасту, аудіокниги, вхідної або радіотреку безпосередньо — без попереднього перегляду, як це роблять списки "Нещодавно відтворені" та цілі трансляції BubbleUPnP — більше не завершується помилкою. Плагін тепер завантажує трек за запитом, коли його запитують за ідентифікатором.

Чому: ці списки запитують трек одразу після перезапуску MusicBee, перш ніж щось було переглянуто, тому плагін ніколи не бачив ідентифікатор і відповідав "Невірний ідентифікатор". Тепер він примусово завантажує відповідні джерела за цим першим прямим запитом і знаходить трек.

"Випадкові треки" та "Випадкові альбоми" повертають результати

Що: папки перемішування BubbleUPnP "Випадкові треки" та "Випадкові альбоми", які запитують випадковий фрагмент усієї бібліотеки, тепер повертаються заповненими.

Чому: це пошуки без назви для відповідності, і плагін раніше відповідав на них з порожнього внутрішнього списку, тому вони завжди показували нічого. Тепер вони обслуговуються — правильно розбиті на сторінки — з того ж шляху запиту за запитом, який використовує решта перегляду.

Альбоми з порожнім тегом перераховують свої треки

Що: відкриття альбому, який був згрупований за порожнім значенням — наприклад, треки вхідних, які не мають року — тепер показує його треки.

Чому: відповідність, яка збирає треки альбому, розглядала "цей тег порожній" як "немає відповідності", тому будь-який альбом, сформований з порожнього поля, не показував нічого. Порожнє значення групи тепер правильно відповідає трекам, які його мають.


2.0.0 - 2026-07-22

Це перший публічний випуск відкритого форку 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:…); кожен рівень обчислюється лише тоді, коли клієнт переглядає його (LazyBrowseEnsureLazyEndpointInMemory → кеші для кожного рівня), а сповіщення про зміну бібліотеки очищають кеші (SetLibraryDirty).

Чому: холодний запуск по суті миттєвий - HTTP-порт відкритий до того моменту, як MusicBee завершить ініціалізацію плагіна - і пам'ять залишається пропорційною тому, що було переглянуто, а не розміру бібліотеки. Компроміс: перший перегляд кінцевої точки оплачує її вартість завантаження; повторний вхід кешується до наступної зміни бібліотеки. Це основа, від якої залежить все інше. Повні примітки: FIXES.md.


Мережа та надійність

Посилення шляху прив'язки HTTP-сервера. Оригінальний плагін тихо помирає, коли його порт недоступний.

N04 - Самостійне відновлення прив'язки HTTP-порту

Що: HTTP-сервер плагіна більше не виходить з ладу, коли його налаштований порт недоступний. Три пов'язані зміни:

  1. Автоматичний відкат при збої прив'язки. HttpServer.Start намагається використовувати налаштований порт, і при SocketException сканує до 20 портів вгору для першого вільного. Фактичний прив'язаний порт записується в нове Plugin.boundServerPort, і все, що рекламує сервер - URL-адреси SSDP LOCATION (сповіщення + відповідь M-SEARCH), URL-адреса пристрою (PrimaryHostUrl), перенаправлення порту маршрутизатора та самофільтри SSDP/контрольної точки - тепер читає boundServerPort замість Settings.ServerPort. Клієнти UPnP виявляють реальний порт через SSDP, тому переміщений порт прозорий для рендерерів.
  2. Повідомлення користувача. Коли відбувається відкат (збережений порт не використовується), локалізоване MessageBox (WarnPortInUse) повідомляє користувачеві, який порт фактично обслуговує, і що пристрої все одно його знайдуть - оскільки плагін працює без інтерфейсу, і повідомлення в діалоговому вікні побачить лише той, хто вже підозрював проблему.
  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-ing при наступному збереженні налаштувань. Після N04 колізія порту самостійно відновлюється - сервер продовжує працювати на наступному вільному порту, користувачеві повідомляється, і клієнти знову його виявляють - замість того, щоб виводити з ладу весь плагін.

Реалізація:

  • Порт за замовчуванням переміщено 493829779 (нижче динамічного діапазону, тому 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-широкомовна розсилка не застосовується - стара широкомовна розсилка завершувалася з помилкою "недійсний аргумент", і оголошення пропускалися, тому плагін був невидимий для клієнтів на цих з'єднаннях. Оголошення до відповідної багатоадресної групи виправляє виявлення саме на цих адаптерах.

Навігація по бібліотеці

Ці функції були включені в цей форк і відсутні в оригінальному плагіні. Вони виникли в результаті фактичного перегляду вихідних даних плагіна з реальних 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 - Детермінований список радіостанцій при розбитому на сторінки перегляді UPnP

Що: перегляд контейнера радіо потрапляв у загальну гілку списку файлів, яка викликала files.Sort(AlbumFileComparer) при кожному виклику. Записи радіо мають порожні теги Album/Disc/Track, тому кожне порівняння повертало 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 / ResetCacheBumpSystemUpdateId).

Чому: клієнти надійно отримують зміни, нові файли та зміни налаштувань під час наступного перегляду. Відоме обмеження: підписані клієнти не отримують активно нове значення через 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 - Літеральні амперсанди в меню вибору поля

Що: поле, назва якого містить "&" - наприклад, "Mood & Context" - відображає амперсанд буквально в меню вибору поля замість того, щоб поглинати його як префікс 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)" (en-US) MusicBee повертається до en через ланцюжок культур .NET, тому окремий пакет US не створюється. 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.ForceTranscodingstreamingProfile.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.FlacSelectedIndex = 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

Що: тепер діють два блокування:

  1. Під час виконання: QueueNext повертає False на ранній стадії, коли увімкнено Settings.ContinuousOutput. Безперервний потік є власним механізмом безперервного відтворення (один довгий об'єднаний потік); надсилання SetNextAVTransportURI поверх нього заплутує пристрій щодо того, чи є кожен трек окремим URI, чи частиною безперервного потоку.
  2. Інтерфейс користувача: коли користувач встановлює прапорець глобального безперервного потоку, 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 - кбіт/с, що відрізнялося від специфікації UPnP DIDL приблизно в 125 разів, яка визначає атрибут як байти на секунду. Тепер ділиться на 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 безперервного потоку (Назва: "Безперервний потік", 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, потім точне прив'язування F33 до часу переходу уточнює його до фактичної позиції пристрою при першому такті зміни стану.

Реалізація: чотири рядки на початку PlayToDevice, у парі з точним прив'язуванням F33 до часу переходу.


F35 - Помилка "Примусове транскодування"

Що: примусове транскодування все ще могло пропускати транскодування в певних комбінаціях. Після переробки F04 для кожного профілю було закрито дві конкретні прогалини:

  1. Пріоритет з ForceNativeStream. Коли обидва були True (що може статися при міграції схеми або частковому файлі налаштувань), ForceTranscoding тепер виграє повністю (If streamingProfile.ForceTranscoding Then forceEncode = True ElseIf streamingProfile.ForceNativeStream Then forceEncode = False). Взаємовиключення інтерфейсу користувача запобігає встановленню обох прапорців користувачем, але захист під час виконання обробляє будь-який стан, який завантажився непослідовно з диска.
  2. Логіка 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-seek був повідомлений приблизно в 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=0) або десь посередині (partial=N) - у поєднанні з startingIndex пакета ви можете ідентифікувати індекс проблемного треку. Filter та BrowseFlag пояснюють, який саме тип перегляду хотів клієнт; іноді перегляд лише метаданих завершується невдачею там, де перегляд дочірніх елементів для того ж ID успішний.


Мережа

F46 - Автоматичний режим рекламує лише на реальних мережевих адаптерах

Що: в автоматичному режимі інтерфейсу плагін раніше оголошував себе (SSDP) на кожному робочому адаптері IPv4. На машині, яка також працює з VPN-тунелем (NordLynx) або віртуальним комутатором (Hyper-V / WSL / Docker), та сама бібліотека оголошувалася на кожному з цих адаптерів, тому точка керування, з якої ви транслювали, виявляла сервер два або три рази та перераховувала бібліотеку як дублікати. Автоматичний режим тепер зберігає лише адаптери, які мають реальний шлюз IPv4 за замовчуванням (HasIPv4Gateway) - чого не мають адаптери тунелю та віртуального комутатора - тому вони виключаються зі списку оголошень. Адреса, закріплена користувачем, все ще має пріоритет (оголошується лише на цьому інтерфейсі), і якщо жоден адаптер не повідомляє про шлюз, селектор повертається до кожного адаптера, тому список оголошених адрес ніколи не порожній, і плагін не може стати невидимим.

Чому: дублікат не викликаний "перебуванням у VPN" - він викликаний оголошенням на адаптері локальної мережі та адаптері тунелю/віртуальному адаптері одночасно, тому одна точка керування бачить той самий сервер за двома адресами. Споживчий VPN (NordVPN/NordLynx) тунелює лише трафік, що йде в Інтернет; рендерер DLNA знаходиться в локальній мережі, і трафік локальної підмережі обходить тунель, тому адаптер тунелю все одно ніколи не досягає рендерера - його видалення видаляє фантомну копію, а не робочий шлях. Перевірка шлюзу є дешевим, надійним сигналом, який відокремлює реальний адаптер локальної мережі/Wi-Fi від тунелю або віртуального комутатора. Доповнює N05 (який виправляв як надсилаються оголошення на таких з'єднаннях - багатоадресна розсилка замість широкомовної); F46 регулює які адаптери взагалі отримують оголошення.

Відоме обмеження: сітчастий / віддалений доступ VPN (Tailscale, ZeroTier, WireGuard-to-home), чиї рендерери дійсно знаходяться через тунель, зазвичай представляє адаптер без шлюзу за замовчуванням, тому автоматичний режим також його відкидає. Ці користувачі замість цього закріплюють адресу VPN, яка має пріоритет над фільтром шлюзу.

Зміст