Что нового
2.0.9 - 2026-08-23
Уведомление об обновлении открывает свои страницы на вашем языке
Что: ссылки Что нового и Скачать в уведомлении об обновлении теперь открывают страницы плагина на языке MusicBee, а не на английском.
Почему: эти две ссылки сужали выбор языка до одного из четырех — английского, французского, испанского или немецкого — перед отправкой на веб-сайт, поэтому всем остальным предлагалась английская страница, даже если существовал ее перевод. Теперь они передают язык MusicBee без изменений и позволяют веб-сайту решать, что отображать, что всегда делала кнопка «Справка» рядом с ними.
2.0.8 - 2026-08-22
Плагин теперь корректно представляется приложениям и устройствам, которые обнаруживают его в вашей сети, а настройки профилей устройств снова выровнены.
Ваши устройства показывают правильного производителя, модель и версию
Что: когда управляющее приложение, телефон или телевизор находит MusicBee в вашей сети, оно теперь представляет этот плагин как созданный yaiol, указывает на собственный сайт плагина, описывает его как охватывающий все три роли — сервер, проигрыватель и рендерер — и сообщает версию, которая у вас фактически установлена.
Почему: каждое UPnP-устройство объявляет, кто его сделал и что оно собой представляет, и управляющие приложения показывают это как идентификатор устройства. Этот плагин все еще объявлял данные оригинального плагина, от которого он был ответвлен: имя другого автора, веб-сайт MusicBee вместо собственного, и номер модели, застывший на "1.0" с самого первого выпуска. С вашего телефона не было возможности определить, с каким плагином вы общаетесь, не говоря уже о его версии. Теперь эти данные поступают от самого плагина, поэтому версия, отображаемая рядом с устройством, остается правильной при каждом обновлении.
Настройки профилей устройств снова выровнены
Что: на вкладке Профили устройств метки и их поля имеют один левый край и расположены с равномерным интервалом, а диапазон частоты дискретизации отображается в одной строке.
Почему: поля разошлись по мере добавления опций на вкладку со временем, а метка "to" диапазона частоты дискретизации оказалась расположенной поверх поля рядом с ней — читаемой, когда вы знали, что она означает, но сбивающей с толку при первом взгляде.
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 и цели трансляции — больше не приводит к сбою. Плагин теперь загружает трек по запросу, когда он запрашивается по ID.
Почему: эти списки запрашивают трек сразу после перезапуска MusicBee, прежде чем что-либо было просмотрено, поэтому плагин никогда не видел ID и отвечал «Bad id». Теперь он принудительно загружает соответствующие источники по этому первому прямому запросу и находит трек.
«Случайные треки» и «Случайные альбомы» возвращают результаты
Что: папки перемешивания BubbleUPnP «Random Tracks» и «Random Albums», которые запрашивают случайный срез всей библиотеки, теперь возвращаются заполненными.
Почему: это поиски без названия для сопоставления, и плагин ранее отвечал на них из пустого внутреннего списка, поэтому они всегда ничего не показывали. Теперь они обслуживаются — правильно разбитые на страницы — из того же пути запроса по требованию, который использует остальная часть просмотра.
Альбомы с пустым тегом перечисляют свои треки
Что: открытие альбома, который был сгруппирован по пустому значению — например, треки входящих, которые не имеют года — теперь показывает его треки.
Почему: совпадение, которое собирает треки альбома, трактовало «этот тег пуст» как «нет совпадения», поэтому любой альбом, сформированный из пустого поля, просматривался как ничто. Пустое значение группы теперь правильно соответствует трекам, которые его разделяют.
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-порта. Для реальной библиотеки (50k+ треков, 5400 эпизодов подкастов, сотни станций) это минуты холодного старта, и дерево навсегда оставалось в ОЗУ, включая ветви, которые ни один клиент никогда не открывал. Этот форк ничего не строит заранее: корень предоставляет один заполнитель с префиксом 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(NOTIFY + M-SEARCH response), URL-адрес устройства (PrimaryHostUrl), перенаправление портов маршрутизатора и самофильтры SSDP/точки управления - теперь считываетboundServerPortвместоSettings.ServerPort. Клиенты UPnP обнаруживают реальный порт через SSDP, поэтому перемещенный порт прозрачен для рендереров. - Уведомление пользователя. Когда происходит откат (сохраненный порт не используется), локализованное
MessageBox(WarnPortInUse) сообщает пользователю, какой порт фактически обслуживает, и что устройства все равно его найдут - потому что плагин работает без графического интерфейса, и сообщение в диалоговом окне будет видно только тому, кто уже подозревал проблему. - Восстановление после перезапуска.
RestartServer(путь перезапуска при сохранении настроек) раньше слепо разыменовывалPlugin.controller/Plugin.server. Если начальныйInitialiseвыдавал ошибку до их создания (что именно и вызывал сбой привязки), следующее сохранение настроек приводило кNullReferenceException- оставляя полумертвый плагин. Теперь он воссоздает и запускает их, когдаNothing, поэтому сохранение рабочего порта оживляет плагин без полного перезапуска MusicBee.
Почему: причиной стал реальный инцидент с пользователем. Старый порт по умолчанию 49382 находится в динамическом диапазоне Windows (49152-65535), где Hyper-V/WSL2/Docker/WinNAT резервируют большие блоки, которые смещаются при каждой загрузке - поэтому привязка завершалась с ошибкой WSAEACCES ("доступ запрещен") на машине, где она работала месяцами. Изменение порта по умолчанию на свободный затем столкнулось с Serviio (отдельным DLNA-сервером, уже работающим на новом порту), завершившись с ошибкой WSAEADDRINUSE. Каждая ошибка поглощалась в Initialise, оставляя плагин молча мертвым, а затем NRE при следующем сохранении настроек. После N04 коллизия портов самовосстанавливается - сервер продолжает работать на следующем свободном порту, пользователь уведомляется, и клиенты снова его обнаруживают - вместо того, чтобы полностью вывести плагин из строя.
Реализация:
- Порт по умолчанию перемещен
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 и использует его для группировки/сортировки исполнителей в представлениях просмотра.
Почему: высококачественные браузеры и аудиофилы используют имена исполнителей для сортировки ("Beethoven, Ludwig van" вместо "Ludwig van Beethoven") для организации библиотек. Стандартное ожидание для серьезных слушателей. Отсутствует в обоих исходных кодах.
N08 - Обработка многозначного поля AlbumArtist
Что: когда поле AlbumArtist альбома содержит несколько исполнителей, разделенных "; " (например, "yaiol; Ars Ricercata"), трек теперь появляется под каждым исполнителем в представлениях просмотра, а не под одним "Франкенштейном", объединяющим имена.
Почему: совместные альбомы и сборники должны отображаться под каждым соавтором. Без этого половина путей поиска альбома не работает.
N09 - Обложка контейнера альбома (upnp:albumArtURI)
Что: узлы контейнеров альбомов в ответах DIDL Browse теперь включают элемент upnp:albumArtURI, указывающий на обложку альбома.
Почему: без этого каждый альбом в представлении просмотра клиента UPnP отображает общий значок вместо обложки альбома. Визуальный ориентир для навигации; ожидается каждым современным высококачественным браузером.
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 Search для класса альбомов возвращает контейнеры альбомов
Что: UPnP Search для запросов класса альбомов (upnp:class = "object.container.album.musicAlbum", например, "Случайные альбомы" BubbleUPnP) возвращал полный список треков вместо контейнеров альбомов, поэтому клиент показывал ноль альбомов. Оригинальный обработчик парсил только критерии в скобках, затем выводил все треки независимо от запрошенного класса.
Почему: исправлено здесь - запросы класса альбомов теперь перечисляют отдельные альбомы (сгруппированные по AlbumArtist+Album) и выдают каждый как правильный контейнер musicAlbum с обложкой, доступный через виртуальное ID-пространство Salb<idx>, чтобы клиент мог углубиться в результат и воспроизвести его.
N15 - Рабочий, учитывающий область действия поиск UPnP с возможностью перехода по ссылке
Что: оригинал не рекламировал никаких возможностей поиска (GetSearchCapabilities возвращал пустое значение), поэтому клиенты отказывались даже отправлять поиск; а старый бэкэнд считывал из musicFiles, постоянно пустого в эпоху ленивого дерева. Этот форк рекламирует реальные доступные для поиска свойства, реализует поиск треков по названию и альбомов по названию в ленивой библиотеке (HandleLazySearch), ограничивает запрос текущей ветвью клиента, когда отправляется реальный ID контейнера (в противном случае подставляет L:music, чтобы поиск в верхней строке не приводил к шуму подкастов/радио/аудиокниг), и делает результаты альбомов кликабельными через синтетические ID 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\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)" (en-US) MusicBee возвращается к 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 загружают устаревшее глобальное значение один раз и копируют его во все профили, сохраняя старое поведение при обновлении.
- UI: удалено из панели диагностики, добавлено в раздел "Профили устройств" рядом с 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 предотвращает очистку плагином.
Почему: без этого владельцы Denon сталкиваются с тем, что устройство обрывает воспроизведение в середине трека, когда очередь опустошается. При установленном F08 устройство сохраняет устаревший NextURI в памяти (безвредно - он просто перезаписывается при следующем добавлении в очередь).
F09 - FLAC как формат вывода транскодирования
Что: выпадающий список форматов транскодирования в профилях устройств теперь предлагает FLAC наряду с PCM 16/24, MP3, AAC, Ogg. Выбор его направляет кодировщик BASS через стандартную командную строку MusicBee для преобразования FLAC (тот же механизм, который уже используют MP3/AAC/Ogg).
Почему: для устройств, которые хорошо обрабатывают FLAC, но не могут декодировать исходный кодек (например, Eversolo, получающий библиотеку WMA MusicBee, преобразованную в FLAC), это сохраняет качество без потерь, где MP3/AAC отбросили бы аудиоданные. Разблокирует N02 (управление понижением 5.1), которое не могло быть решено без опции транскодирования без потерь.
Реализация: однострочное добавление в блок Select Case Codec Encoder.StartEncode - FLAC присоединяется к MP3/AAC/Ogg в ветви, управляемой командной строкой. Выпадающий список UI получает "FLAC" в качестве 6-й опции. Сопоставление загрузки/сохранения в SettingsDialog расширяется для распознавания FileCodec.Flac ↔ SelectedIndex = 5. Mime, тип DLNA и функция кодирования уже были подключены в ItemManager.GetMimes / GetDlnaType / GetEncodeFeature из более ранней работы (F21, F26).
Бесшовное воспроизведение (SetNextAVTransportURI)
F10 - SetNextAVTransportURI / NextURI core
Что: истинное бесшовное воспроизведение. Когда устройство рекламирует поддержку SetNextAVTransportURI в своем описании службы UPnP, плагин предварительно ставит в очередь следующий трек на устройстве до того, как закончится текущий. Устройство переключается внутренне без слышимого разрыва между треками - то, что вы слышите на CD-плеере. Это не хак "непрерывного потока" (который объединяет все в один длинный поток и теряет метаданные для каждого трека).
Почему: флагманская функция второго уровня. Альбомы, записанные как непрерывное живое выступление (концертные записи, классические произведения, диджейские сеты), звучат неправильно, когда между треками есть полусекундная тишина. Правильное решение этой проблемы - флагманская функция, теперь в этом форке.
Примечания: аудио, поставленное в очередь, обслуживается 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 от зацикленного трека.
Почему: без пропуска Repeat-One вызов Player_PlayNextTrack при бесшовном переходе продвинул бы MusicBee к следующему треку в списке (Repeat-One влияет только на поведение автоматического перехода в конце трека в пользовательском интерфейсе проигрывателя - Next Track всегда движется вперед), что противоречит смыслу Repeat-One.
Предостережение по количеству воспроизведений: в режиме Repeat-One увеличение количества воспроизведений зависит от того, заметит ли MusicBee 3.7.9563+ зацикленное воспроизведение. Более старые версии MusicBee правильно воспроизводят бесшовный повтор, но пропускают увеличение количества воспроизведений. Документировано; не блокирует.
F15 - Конечный автомат обнаружения перехода трека
Что: когда устройство внутренне переходит от текущего трека к NextURI, плагин должен это заметить и сообщить MusicBee, чтобы тот продвинул свой индекс воспроизведения. В противном случае MusicBee считает, что он все еще находится на предыдущем треке, и счетчики воспроизведения / UI / скробблинг расходятся.
Реализация: опрашивает 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 в момент, когда таймер состояния впервые заметил, что состояние стало Playing, но к тому времени устройство могло воспроизводить 100-500 мс (один интервал опроса). Индикатор выполнения MusicBee начинался с 0, затем прыгал вперед, когда реальность догоняла.
Исправление F33: при первом переходе в состояние Playing для нового трека (currentPlayStartTimeEstimated=True) вызовите GetPlayPositionInformation(), чтобы получить фактическую текущую позицию устройства, затем привяжитесь к ней. UPnP сообщает только разрешение в 1 секунду, поэтому точка привязки все еще квантована, но она намного ближе к истине, чем предположение 0.
Почему: более плавное + более точное отображение прогресса, особенно сразу после смены трека. Нет способа обойти само разрешение отчета UPnP в 1 секунду - это спецификация.
F34 - Дрожание индикатора выполнения после смены трека
Что: когда PlayToDevice вызывается для нового трека, плагин раньше оставлял currentPlayPositionMs и currentPlayStartTicks на их значениях предыдущего трека в течение ~100 мс между SOAP-Play и первым опросом таймера состояния, обнаруживающим новое состояние Playing. Индикатор выполнения 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 library path> наряду с stream=<HTTP streaming URL>. То же изменение применено к пути успеха и пути сбоя (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 пакета вы можете определить индекс offending track. Filter и BrowseFlag объясняют, какой вид просмотра хотел клиент; иногда просмотр только метаданных завершается неудачей, тогда как просмотр дочерних элементов для того же ID успешен.
Сеть
F46 - Автоматический режим рекламирует только на реальных сетевых адаптерах
Что: в Автоматическом режиме интерфейса плагин раньше объявлял себя (SSDP) на каждом работающем адаптере IPv4. На машине, которая также использует VPN-туннель (NordLynx) или виртуальный коммутатор (Hyper-V / WSL / Docker), та же библиотека объявлялась и на каждом из этих адаптеров, поэтому точка управления, с которой вы транслировали, обнаруживала сервер два или три раза и перечисляла библиотеку как дублирующие копии. Автоматический режим теперь сохраняет только адаптеры, которые имеют реальный шлюз IPv4 по умолчанию (HasIPv4Gateway) - чего нет у адаптеров туннеля и виртуального коммутатора - поэтому они исключаются из списка объявлений. Адрес, закрепленный пользователем, по-прежнему имеет приоритет (объявляется только на этом интерфейсе), и если ни один адаптер не сообщает о шлюзе, селектор возвращается ко всем адаптерам, поэтому список объявленных адресов никогда не пуст, и плагин не может стать невидимым.
Почему: дублирование вызвано не "нахождением в VPN" - оно вызвано объявлением на адаптере LAN и адаптере туннеля/виртуального адаптера одновременно, поэтому одна точка управления видит один и тот же сервер по двум адресам. Потребительский VPN (NordVPN/NordLynx) туннелирует только интернет-трафик; рендерер DLNA находится в LAN, и трафик локальной подсети обходит туннель, поэтому адаптер туннеля все равно никогда не достигает рендерера - его удаление устраняет фантомную копию, а не рабочий путь. Проверка шлюза - это дешевый, надежный сигнал, который отделяет реальный адаптер LAN/Wi-Fi от туннеля или виртуального коммутатора. Дополняет N05 (который исправлял как отправляются объявления по таким ссылкам - многоадресная рассылка вместо широковещательной); F46 регулирует, на каких адаптерах вообще объявляются.
Известное ограничение: VPN с сетчатой/удаленной доступом (Tailscale, ZeroTier, WireGuard-to-home), чьи рендереры действительно находятся через туннель, обычно представляет адаптер без шлюза по умолчанию, поэтому автоматический режим также отключает его. Эти пользователи вместо этого закрепляют адрес VPN, который имеет приоритет над фильтром шлюза.