Сетевой медиа-плеер из двух частей:
- Prism.Host — сервер (ASP.NET Core). Сканирует одну или несколько папок с видеофайлами (например,
двухчасовой
.mkv) и отдаёт только HTTP API: данные библиотеки и потоковое видео. Если файл уже в браузерном формате — стримит напрямую; иначе, при наличии нужного кодека в системе, обрабатывает через ffmpeg и выдаёт H264 или H265 по HLS. СамPrism.Host— библиотека; запускаемые обёртки над ней: Prism.Host.Console (обычный запуск, разработка) и Prism.Host.Service (служба Windows, ставится инсталляторомPrism.Host.Setup). - Prism.Client — клиент (Node.js + TypeScript + React + Vite). Минималистичный
веб-интерфейс: библиотека и плеер. У клиента одна-единственная настройка — URL
сервера; всё остальное он получает от сервера запросом (
/api/info). - Плагины (
Prism.Abstractions+Prism.Plugins.*) — опциональные модули, подключаемые списком вappsettings. Ядро корректно работает и без них. Метаданные библиотеки (названия фильмов, привязка эпизодов к сериалу) реализованы плагиномPrism.Plugins.Library— см. раздел про плагины.
Prism.Client (Vite/React) ──HTTP/CORS──> Prism.Host (ASP.NET Core / Kestrel)
├── GET /api/info данные о сервере (кодек, ffmpeg, …)
├── GET /api/media список библиотеки
├── GET /api/media/{id} одна запись (+ дорожки аудио/субтитров)
├── PUT/DELETE /api/media/{id}/meta мета файла (плагин Prism.Plugins.Library)
├── GET /api/library/tree + CRUD групп дерево библиотеки (плагин)
├── GET /api/media/{id}/subtitle/N.vtt субтитры в WebVTT (текстовые дорожки)
├── GET /raw/{id} прямой стрим с Range (браузерные файлы)
├── GET /hls/{id}/playlist.m3u8 HLS master-плейлист (рендиции дорожек)
├── GET /hls/{id}/video.m3u8 медиа-плейлист видео (VOD)
├── GET /hls/{id}/segment/N.ts видеосегмент H264/H265 на лету
├── GET /hls/{id}/audio/K.m3u8 медиа-плейлист аудиодорожки K
├── GET /hls/{id}/audio/K/N.ts аудиосегмент AAC на лету
├── GET /hls/{id}/subs/K.m3u8 плейлист субтитров (VOD)
└── GET /hls/{id}/subs/K/N.vtt WebVTT-сегмент субтитров
Сервер не содержит UI — весь интерфейс в клиенте.
Для каждого файла сервер выбирает режим воспроизведения (Media/MediaLibrary.cs),
а клиент получает его в поле streamType:
Режим (streamType) |
Когда | Доставка |
|---|---|---|
direct |
mp4/webm с браузерным кодеком (h264/vp9/av1 + aac/opus…) | /raw с HTTP Range/перемоткой |
hls |
любой другой контейнер/кодек и у ffmpeg есть декодер | HLS, перекодирование в H264/H265 + AAC |
unsupported |
нужна перекодировка, но ffmpeg или исходный кодек недоступны | клиент показывает пояснение |
playlist.m3u8 — это master-плейлист: в нём объявлены вариант видео и рендиции
дорожек (#EXT-X-MEDIA:TYPE=AUDIO на каждую аудиодорожку, TYPE=SUBTITLES на
каждую текстовую дорожку субтитров). Видео и каждая аудиодорожка — отдельные
сегментные потоки, поэтому плеер (hls.js, ExoPlayer, Safari) переключает дорожки
сам, локально и без перезагрузки, а смена аудио не запускает перекодирование видео.
Медиа-плейлисты — VOD, рассчитанные заранее по длительности файла (ffprobe),
поэтому браузер знает всю шкалу времени и может перематывать в любую точку.
Сегменты производят сессии (Media/HlsTranscoder.cs + Media/TranscodeSession.cs).
Сессия — это один процесс ffmpeg, который своим HLS-муксером (-f hls) нарезает
один поток (видео или выбранную аудиодорожку) на сегменты и пишет их во временную
папку, ограничиваясь по длительности (-t, по умолчанию 15 минут). Поток внутри
сессии непрерывный, поэтому на стыках сегментов нет priming-зазоров и перекрытий
(которые при схеме «отдельный ffmpeg на каждый сегмент» дают регулярные щелчки).
Таймстемпы смещаются на глобальную позицию (-output_ts_offset), так что сегменты
всех сессий ложатся на единую шкалу времени. Аудиосессии (без видео) почти бесплатны
по CPU; вытесняются видео- и аудиосессии раздельно.
Управление сессиями простое и без платформенных трюков:
- Границы. Внутри сессии звук бесшовный. Возможный микро-разрыв звука бывает
только на границе между сессиями — то есть примерно раз в
SessionMinutesминут при непрерывном просмотре. Чем длиннее сессия, тем реже разрывы (но больше расход CPU/диска на сессию). - Перемотка. Если для запрошенного сегмента нет подходящей сессии, запускается новая прямо с этой позиции (ключевые кадры форсируются ровно на сетке сегментов, поэтому новая сессия выравнивается по глобальной шкале). Разрыв возникает только там, куда пользователь и так перемотал.
- Без пауз и сигналов. Сессия ограничена по времени через
-t, поэтому ffmpeg не транскодирует весь фильм вперёд — он выдаёт свой диапазон и завершается. Старые сессии вытесняются по LRU (держим до 3 на файл). Это работает одинаково на macOS/Linux/Windows. - Временные сегменты лежат в
‹temp›/prism-hls/и удаляются при вытеснении сессии и при завершении приложения.
По умолчанию каждый процесс ffmpeg — один экземпляр на сессию. Чтобы он не
транскодировал весь 15-минутный диапазон на полной скорости (это грузит CPU и греет
ноутбук), включён пейсинг чтения: ffmpeg быстро выдаёт первые BufferBurstSeconds
секунд (быстрый старт/перемотка), а дальше читает вход со скоростью
MaxPlaybackRate×реального времени (-readrate + -readrate_initial_burst). Множитель
берётся с запасом (по умолчанию 2.0), чтобы воспроизведение на 2x не буксовало; общий
объём работы кодирования от этого не растёт. Плюс фоновый уборщик убивает сессии,
к которым SessionIdleSeconds секунд никто не обращался. На практике это снижает пик
CPU при перемотках в разы и опускает нагрузку до нуля, когда никто не смотрит.
Живая дебаг-панель — на клиенте по ссылке debug в шапке (или #/debug): число
сессий, суммарный %CPU и память ffmpeg, прогресс и метрики каждой сессии. Данные —
из GET /api/debug/sessions.
/api/media/{id} отдаёт списки audioTracks и subtitleTracks (язык, название,
кодек, каналы), клиент показывает селекторы в плеере.
Дорожки из отдельных файлов. Кроме вшитых в контейнер, подхватываются файлы
субтитров и аудио, лежащие рядом с видео. Где именно их искать — задаётся
шаблонами в настройках (Player:SubtitleFiles, Player:AudioFiles), по умолчанию
["{name}"]:
"Player": {
"SubtitleFiles": [ "{name}", "subs/{name}" ],
"AudioFiles": [ "{name}" ]
}Шаблон — путь относительно папки видеофайла, где {name} — имя видео без
расширения (слеши прямые на всех ОС). Он должен совпасть с началом пути файла, а
остаток имени становится подписью дорожки: при шаблоне {name} файл
Фильм.Дубляж Мосфильм.ac3 даст дорожку с подписью «Дубляж Мосфильм», а Фильм.srt
— дорожку без подписи. Шаблоны применяются по порядку, один файл берётся один раз.
Вид дорожки определяет формат: субтитры — .srt/.ass/.ssa/.vtt, аудио —
.mka/.m4a/.aac/.ac3/.eac3/.dts/.flac/.mp3/.opus/.ogg/.wav. Такие дорожки
дописываются в конец списка (номера вшитых не меняются) и помечены external: true.
Язык у них не выставляется: плееры (ExoPlayer) показывают в меню название языка
вместо подписи, и версия озвучки была бы не видна; у вшитых дорожек язык
по-прежнему берётся из метаданных контейнера. Список обновляется при каждом скане,
поэтому подложенный файл виден без перезапуска сервера. Считается, что дорожка из
файла синхронна с видео (сдвиги не поддерживаются). Отдельное аудио возможно только
в HLS-режиме, поэтому браузерный файл с такой дорожкой раздаётся через HLS.
Кодировка субтитров: файл не в UTF-8 читается как CP1251 (частый случай русских
.srt). Графические субтитры отдельными файлами (.sup, .idx/.sub) не
поддерживаются.
- Аудио. Каждая дорожка — отдельная рендиция в master-плейлисте со своим
сегментным потоком (лёгкая сессия ffmpeg только-аудио); для дорожки из соседнего
файла входом сессии служит этот файл. Плеер переключает дорожки локально, из
одного URL, без перезагрузки потока и без повторного перекодирования видео
(веб-клиент — через
hls.audioTrack). - Субтитры. Текстовые дорожки извлекаются в WebVTT
(
/api/media/{id}/subtitle/N.vtt) и доступны двумя путями: рендицииTYPE=SUBTITLESв master-плейлисте — WebVTT-сегменты той же сетки, что видео, каждый с заголовкомX-TIMESTAMP-MAP(без него ExoPlayer не привязывает реплики к шкале времени) — и<track>в веб-клиенте. Включаются/выключаются мгновенно, независимо от аудио. Поддерживаются текстовые субтитры (SRT/ASS/…); графические (PGS/VOBSUB/DVB) помеченыtextBased:falseи в списке недоступны.
Сервер расширяется плагинами — опциональными модулями. Ядро (стриминг, скан библиотеки) работает и без единого плагина.
- Контракт —
Prism.Abstractions:IPrismModule(регистрирует сервисы и эндпоинты) иIMediaMetaSource(подмешивает произвольные пары ключ-значение к записям/api/media). - Список активных плагинов — в
appsettings("Plugins": ["Prism.Plugins.Library"]). Каждый грузится изplugins/<имя>/<имя>.dll(папка рядом с приложением) в собственныйAssemblyLoadContext(зависимости плагина резолвятся из его папки; контракт — из хоста). Build плагина копирует его вPrism.Host.Console/plugins/автоматически; в установку службы плагины кладёт инсталлятор.
Хранит виртуальное дерево библиотеки и мета-информацию — источник истины на сервере:
-
Группы — виртуальные папки с вложенностью (
/api/library/nodes), не привязанные к расположению файлов на диске; файл может входить в несколько групп. Всё дерево с членством отдаётся одним запросомGET /api/library/tree— библиотека домашняя, дерево строит клиент. -
Мета — свободные пары ключ-значение у файлов и групп. Сервер семантику не знает: в БД может храниться что угодно, потребитель работает с ключами, о которых знает сам (например, клиент показывает
titleвместо имени файла). -
Id файла = fingerprint содержимого («размер-хеш краёв», считается ядром при скане с персистентным кэшем в
data/fingerprints.json): мета, членство и сами URL переживают переименование и перенос файла; копии одного файла в разных папках — одна запись библиотеки. Записи файлов, которых нет на диске, не удаляются автоматически (диск может быть отключён): в дереве они помеченыpresent:false, чистка — явной командойPOST /api/library/gc. Если файл по прежнему пути сменил содержимое (докачался/перезаписан), фоновый ремап (при старте и раз в 5 минут) переносит записи на новый id. -
СУБД задаётся конфигом (секция
Database), код про конкретную СУБД не знает:"Database": { "Provider": "sqlite", // "sqlite" | "postgres" "ConnectionString": "Data Source=data/prism.db" }
Для Postgres — своя строка подключения (можно ту же БД, что и у умного дома).
-
Схему ведёт мигратор ThinkingHome.Migrator (ключ версионирования
prism.library— своя история миграций в общей БД). Таблицы плоскиеPrism_Node/Prism_NodeItem/Prism_Meta(префиксPrism_, без схемы) — одинаково на SQLite и Postgres. -
Заполнение — вне ответственности сервера: он лишь отдаёт/принимает данные через API. Примеры:
# мета файла: слияние ключей; значение null удаляет ключ curl -X PUT http://localhost:8080/api/media/<id>/meta \ -H 'Content-Type: application/json' \ -d '{"title":"Патриот · S01E02","genre":"драма"}' # группы: вложенная структура + положить файл (id группы — GUID, id файла — из /api/media) curl -X POST http://localhost:8080/api/library/nodes \ -H 'Content-Type: application/json' -d '{"name":"Сериалы"}' # → {"id":"9131…bd"} curl -X POST http://localhost:8080/api/library/nodes \ -H 'Content-Type: application/json' -d '{"parentId":"<id Сериалов>","name":"Патриот"}' curl -X PUT http://localhost:8080/api/library/nodes/<nodeId>/items/<mediaId> # всё дерево (группы + мета групп + членство) одним запросом curl http://localhost:8080/api/library/tree # прогнать обслуживание немедленно (ремап + правила), не дожидаясь таймера; # ответ 202, завершения не ждёт — итог каждого прохода пишется в лог хоста curl -X POST http://localhost:8080/api/library/scan # отладка правил: очистить библиотеку целиком (группы, членство, мета) и # разложить её заново — удалятся и записи файлов, которых нет на диске curl -X POST "http://localhost:8080/api/library/scan?replace=true" # удалить записи файлов, которых больше нет на диске curl -X POST http://localhost:8080/api/library/gc
Правила автозаполнения (опционально, секция Library:Rules в appsettings.json)
раскладывают файлы по группам и заполняют мету по шаблону пути:
"Library": {
"Rules": [
{
"Path": "*/{series}.s{season:#}e{episode:#}*",
"Node": "Сериалы/{series}/Сезон {season}",
"Meta": { "title": "{series} · S{season}E{episode}", "season": "{season}", "episode": "{episode}" }
}
]
}Path— шаблон относительного пути файла от корня медиапапки (прямые слеши, расширение отбрасывается, регистр не учитывается), совпадать должен весь путь:{имя}— непустой кусок в пределах одного сегмента пути; захваченное значение обрезается от краевых пробелов;{имя:#}— то же, но только цифры. Для номеров сезона и серии берите именно его:s{season}e{episode}цепляется за буквы «s» и «e» в обычных словах и записывает в сериалы, например,Vechnoe.Siyanie.Chistogo…;*— любой кусок сегмента, в том числе пустой, никуда не подставляется: ею «съедают» произвольный мусор реальных имён (…1080p.WEB-DL.RGzs), которого у части файлов может и не быть.
Node— путь к группе (недостающие группы создаются),Meta— ключи меты; в оба подставляются значения плейсхолдеров. Хотя бы одно из действий обязательно.- Правила применяются фоново (при старте, раз в 5 минут и по требованию —
POST /api/library/scan) и только к файлам без единой записи в библиотеке — ни меты, ни членства. При отладке правил помогаетPOST /api/library/scan?replace=true: он очищает библиотеку целиком (группы, членство и мету — и файлов, и групп) и раскладывает её заново. Замена доступна только с этой ручки — проходы по таймеру заменой не бывают. Удаляются и записи файлов, которых сейчас нет на диске, поэтому мета файла с отключённого диска не вернётся, пока диск не подключат обратно (а ручные правки не вернутся вовсе — их правила не знают). Любая запись у файла — признак, что его уже трогали (правило или руки), и правила его больше не касаются: ручные правки, включая удаление отдельных ключей, не «возвращаются». Удаление всех записей файла = «разложить заново по правилам». Применяются все подходящие правила по порядку; при совпадении мета-ключа выигрывает первое.
Собирается всё решением:
dotnet build Prism.sln(build плагина сам разворачивает его вplugins/). Данные и развёрнутые плагины (data/,plugins/) — в.gitignore.
- .NET 10 SDK (сервер)
- Node.js 18+ и npm (клиент)
- ffmpeg + ffprobe в
PATH(или укажитеPlayer:FfmpegPath/Player:FfprobePath, или положите в подпапкуffmpeg/рядом с приложением). Без них напрямую отдаются только уже браузерные файлы. Нужно для запуска из исходников — служба Windows получает ffmpeg в комплекте инсталлятора.
Вариант через пакетный менеджер (рекомендуется):
# winget (Windows 10/11)
winget install --id Gyan.FFmpeg -e
# либо Chocolatey
choco install ffmpeg
# либо Scoop
scoop install ffmpegВручную: скачайте сборку с https://www.gyan.dev/ffmpeg/builds/ (архив
ffmpeg-release-full), распакуйте, например, в C:\ffmpeg, и добавьте
C:\ffmpeg\bin в переменную среды PATH (Параметры → Система → О системе →
Дополнительные параметры системы → Переменные среды). Проверка: ffmpeg -version.
# Homebrew
brew install ffmpeg
# либо MacPorts
sudo port install ffmpeg# Debian / Ubuntu
sudo apt update && sudo apt install -y ffmpeg
# Fedora
sudo dnf install -y ffmpeg # потребуется репозиторий RPM Fusion
# Arch / Manjaro
sudo pacman -S ffmpeg
# openSUSE
sudo zypper install ffmpeg
# универсально (Snap)
sudo snap install ffmpegПроверить установку на любой ОС: ffmpeg -version и ffprobe -version.
Нужно запустить оба приложения.
# один раз собрать решение — заодно развернутся включённые плагины в plugins/
dotnet build Prism.sln
# положите видео в Prism.Host.Console/videos/ (или укажите папку через --media)
dotnet run --project Prism.Host.Console
# API слушает http://localhost:8080Параметры командной строки:
dotnet run --project Prism.Host.Console -- --file "/путь/к/movie.mkv" # отдать папку с этим файлом
dotnet run --project Prism.Host.Console -- --media "/путь/к/библиотеке" # отдать папку
dotnet run --project Prism.Host.Console -- --media "/кино" --media "/сериалы" # несколько папок
dotnet run --project Prism.Host.Console -- --codec h265 # выдавать HEVC вместо H264
dotnet run --project Prism.Host.Console -- --ffmpeg /opt/homebrew/bin/ffmpeg--media (и --file) можно указывать несколько раз; каждая папка добавляется
к списку MediaDirectories из appsettings.json. Дубли и вложенные папки
схлопываются автоматически.
cd Prism.Client
npm install # один раз
npm run dev # дев-сервер на http://localhost:5173
# для продакшена: npm run build → статика в Prism.Client/distОткройте http://localhost:5173. Единственная настройка клиента — URL сервера
(шестерёнка в шапке, по умолчанию http://<хост>:8080, сохраняется в localStorage).
Остальные параметры клиент берёт с сервера через /api/info.
На Windows хост ставится как служба (Prism.Host.Service) — MSI собирает
проект Prism.Host.Setup (WiX 6 приезжает из NuGet при сборке, отдельно
ставить ничего не нужно). Проект входит в решение, но собирается только в
конфигурации Release:
dotnet build Prism.sln -c Release
# → Prism.Host.Setup/bin/Release/en-US/PrismHostSetup.msiСборка MSI сама публикует службу одним exe, плагин и лаунчер
(ProjectReference Publish="true" в .wixproj — MSI пакует publish-выход,
поэтому публикация и есть часть его сборки). Программы
framework-dependent: на целевой машине нужны .NET 10 ASP.NET Core Runtime
(служба) и Desktop Runtime (лаунчер) — проверки в инсталляторе пока нет.
ffmpeg едет в комплекте: при сборке MSI скачивается пинованная
essentials-сборка с gyan.dev (кэшируется в obj/, версия задаётся в
.wixproj) и ставится в подпапку ffmpeg/ рядом со службой — хост ищет её
там в первую очередь, на целевой машине ffmpeg отдельно не нужен. Из-за этого
MSI весит ~115 МБ. Сборка ffmpeg — GPL; проект — MIT: текст и ссылки на
FFmpeg — в LICENSE и на шаге лицензии в установщике.
Инсталлятор ставит всё в
C:\Program Files\Prism Host, регистрирует службу Prism Host (автозапуск,
LocalSystem, стартует сразу после установки) и открывает входящие подключения
в брандмауэре для локальной сети. Деинсталляция останавливает и удаляет службу.
Лаунчер (значок в трее + пункт «Отправить» проводника) ставится в подпапку
launcher/ — у него свой appsettings.json (адрес хоста и брокера MQTT), имя
файла совпадает с конфигом хоста, поэтому папка отдельная. Инсталлятор прописывает
его автозапуск при входе в систему (HKLM\…\CurrentVersion\Run); пункт «Отправить»
лаунчер создаёт себе сам при первом запуске — папка SendTo лежит в профиле
пользователя, а установка машинная.
Папки с видео в инсталляторе не спрашиваются — вся настройка живёт в
C:\Program Files\Prism Host\appsettings.json: там и список папок
(Player:MediaDirectories, их может быть несколько), и правила автозаполнения
библиотеки. Диалог выбора одной папки только создавал иллюзию, что конфиг править
не придётся. После правки — перезапуск службы:
sc stop PrismHost && sc start PrismHostБесшумная установка (без диалогов; из консоли администратора):
msiexec /i PrismHostSetup.msi /qn
:: с журналом установки, если что-то пошло не так
msiexec /i PrismHostSetup.msi /qn /l*v install.logОбновление поверх установленной версии сначала удаляет старую, поэтому
appsettings.jsonзаменяется дефолтным — сохраните свой перед обновлением.
Для сборки MSI ничего ставить не нужно (
dotnet buildсам тянет WiX из NuGet), но чтобы.wixprojоткрывался в Visual Studio, нужно бесплатное расширение HeatWave for VS от FireGiant. Без него VS просто покажет проект как невыгруженный — сборке из CLI это не мешает. JetBrains Rider открывает.wixprojбез расширений.
Android-плеер (Prism.Player.Android) управляется и публикует свой статус по MQTT — нужен любой брокер в локальной сети, например Eclipse Mosquitto.
-
Установите mosquitto (инсталлятор с сайта регистрирует его службой Windows).
-
По умолчанию mosquitto ≥ 2.0 слушает только localhost — внешние клиенты (плеер) подключиться не смогут. Добавьте в конец
C:\Program Files\Mosquitto\mosquitto.conf:listener 1883 0.0.0.0 allow_anonymous trueallow_anonymous trueобязателен: как только объявленlistener, анонимный доступ автоматически запрещается. Для домашней сети за роутером этого достаточно; если брокер доступен извне — вместо анонимного доступа заведите пользователя (mosquitto_passwd+password_fileв конфиге). -
Перезапустите службу и откройте порт в брандмауэре (PowerShell от администратора):
Restart-Service mosquitto New-NetFirewallRule -DisplayName "Mosquitto MQTT" -Direction Inbound -Protocol TCP -LocalPort 1883 -Action Allow
Проверка: Get-NetTCPConnection -State Listen -LocalPort 1883 должен показать
адрес 0.0.0.0, а не 127.0.0.1.
У каждого запускаемого проекта свой appsettings.json: у Prism.Host.Console —
конфигурация разработки, у Prism.Host.Service — то, что уезжает в установку.
| Ключ | По умолчанию | Назначение |
|---|---|---|
MediaDirectories |
["videos"] |
список папок, сканируемых на медиа (относительные — от папки приложения) |
SubtitleFiles |
["{name}"] |
шаблоны путей к файлам субтитров рядом с видео (см. выше) |
AudioFiles |
["{name}"] |
шаблоны путей к файлам отдельных аудиодорожек |
FfmpegPath |
(авто) | явный путь к бинарю ffmpeg |
FfprobePath |
(авто) | явный путь к бинарю ffprobe |
OutputCodec |
h264 |
h264 (libx264) или h265 (libx265/HEVC) |
SegmentSeconds |
6 |
длина HLS-сегмента |
SessionMinutes |
15 |
длина одной сессии транскодирования (реже разрывы ↔ больше диска) |
BufferBurstSeconds |
30 |
начальный «бёрст» перед пейсингом (быстрый старт ↔ нагрузка CPU); 0 — без пейсинга |
MaxPlaybackRate |
2.0 |
до какой скорости воспроизведения транскод «догоняет» (пейсинг = ×реального времени) |
SessionIdleSeconds |
25 |
через сколько секунд простоя убить сессию (освобождает CPU) |
EncoderPreset |
veryfast |
пресет скорости x264/x265 |
Crf |
23 |
качество видео (меньше = лучше/больше) |
AudioBitrateKbps |
256 |
битрейт аудио AAC, кбит/с |
AudioSampleRate |
48000 |
частота дискретизации аудио, Гц |
Аудио всегда перекодируется в AAC (libfdk_aac, если он есть в сборке ffmpeg,
иначе встроенный aac), 48 кГц, по умолчанию 256 кбит/с. Многоканальный звук
(5.1/7.1) сводится в стерео качественным даунмиксом (pan-фильтр), где центр/диалоги
идут на полном уровне, — это устраняет «тихий и глухой» звук обычного -ac 2.
HTTP-эндпоинт задаётся в секции Kestrel (по умолчанию http://0.0.0.0:8080).
Хост пишет логи в консоль и в файлы с ротацией: logs/prism-<дата>.log рядом с
приложением (у службы Windows — в папке установки, C:\Program Files\Prism Host\logs).
Новый файл начинается каждый день или по достижении 32 МБ; хранятся последние
14 файлов, старые удаляются автоматически. Уровни логирования настраиваются
секцией Serilog в appsettings.json (MinimumLevel:Default и
MinimumLevel:Override:<префикс категории>).
Про H265: HEVC по HLS воспроизводится нативно в Safari, но не поддерживается hls.js в Chrome/Firefox, поэтому по умолчанию выбран
h264для максимальной совместимости с браузерами.