@yaneart/yaneplayer 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/GUIDE.md +716 -0
  2. package/LICENSE +21 -0
  3. package/README.md +62 -2
  4. package/THIRD_PARTY_NOTICES.md +46 -0
  5. package/assets/player-preview.jpg +0 -0
  6. package/dist/ass-renderer-DmsQqR82.js +84 -0
  7. package/dist/auto-captions/assets.js +9 -0
  8. package/dist/auto-captions/audio.js +42 -0
  9. package/dist/auto-captions/ort-wasm-simd-threaded.asyncify.mjs +124 -0
  10. package/dist/auto-captions/ort-wasm-simd-threaded.asyncify.wasm +0 -0
  11. package/dist/auto-captions/translation-worker.js +49 -0
  12. package/dist/auto-captions/worker.js +51 -0
  13. package/dist/core/ass-renderer.d.ts +5 -0
  14. package/dist/core/async.d.ts +5 -0
  15. package/dist/core/auto-caption-assets.d.ts +7 -0
  16. package/dist/core/auto-captions.d.ts +14 -0
  17. package/dist/core/caption-publication.d.ts +22 -0
  18. package/dist/core/commands.d.ts +10 -0
  19. package/dist/core/errors.d.ts +7 -0
  20. package/dist/core/hls.d.ts +4 -0
  21. package/dist/core/index.d.ts +6 -0
  22. package/dist/core/platform.d.ts +22 -0
  23. package/dist/core/preferences.d.ts +10 -0
  24. package/dist/core/runtime.d.ts +3 -0
  25. package/dist/core/source.d.ts +6 -0
  26. package/dist/core/subtitle-assets.d.ts +6 -0
  27. package/dist/core/subtitle-data.d.ts +11 -0
  28. package/dist/core/subtitles.d.ts +22 -0
  29. package/dist/core/tracks.d.ts +7 -0
  30. package/dist/core/translation-model.d.ts +6 -0
  31. package/dist/core/transport.d.ts +15 -0
  32. package/dist/core/types.d.ts +333 -0
  33. package/dist/core-Bl9Ox_wM.js +3352 -0
  34. package/dist/core.js +1 -0
  35. package/dist/element.d.ts +89 -0
  36. package/dist/hls-CrUfIOS7.js +20700 -0
  37. package/dist/index.d.ts +4 -0
  38. package/dist/index.js +4771 -0
  39. package/dist/licenses/@fontsource-variable-manrope.txt +93 -0
  40. package/dist/licenses/@huggingface-jinja.txt +21 -0
  41. package/dist/licenses/@huggingface-tokenizers.txt +201 -0
  42. package/dist/licenses/@huggingface-transformers.txt +202 -0
  43. package/dist/licenses/@mingcute-icons.txt +202 -0
  44. package/dist/licenses/@videojs-core.txt +20 -0
  45. package/dist/licenses/@videojs-element.txt +20 -0
  46. package/dist/licenses/@videojs-hlsjs-video.txt +20 -0
  47. package/dist/licenses/@videojs-html.txt +20 -0
  48. package/dist/licenses/@videojs-media.txt +20 -0
  49. package/dist/licenses/@videojs-native-hls-video.txt +20 -0
  50. package/dist/licenses/@videojs-spf.txt +20 -0
  51. package/dist/licenses/@videojs-store.txt +20 -0
  52. package/dist/licenses/@videojs-utils.txt +20 -0
  53. package/dist/licenses/flatbuffers.txt +202 -0
  54. package/dist/licenses/hls.js.txt +28 -0
  55. package/dist/licenses/jassub.txt +22 -0
  56. package/dist/licenses/long.txt +202 -0
  57. package/dist/licenses/platform.txt +21 -0
  58. package/dist/licenses/protobufjs.txt +39 -0
  59. package/dist/licenses/third-party.txt +7941 -0
  60. package/dist/react.d.ts +56 -0
  61. package/dist/subtitles/assets.js +7 -0
  62. package/dist/subtitles/default.woff2 +0 -0
  63. package/dist/subtitles/jassub.js +1 -0
  64. package/dist/subtitles/wasm/jassub-worker-modern.wasm +0 -0
  65. package/dist/subtitles/wasm/jassub-worker.wasm +0 -0
  66. package/dist/subtitles/worker/worker.js +144 -0
  67. package/dist/ui/auto-captions.d.ts +6 -0
  68. package/dist/ui/captions.d.ts +6 -0
  69. package/dist/ui/controls.d.ts +28 -0
  70. package/dist/ui/fonts.d.ts +2 -0
  71. package/dist/ui/generated-captions.d.ts +7 -0
  72. package/dist/ui/icons.d.ts +24 -0
  73. package/dist/ui/labels.d.ts +74 -0
  74. package/dist/ui/rolling-captions.d.ts +10 -0
  75. package/dist/ui/settings.d.ts +17 -0
  76. package/dist/ui/speed.d.ts +9 -0
  77. package/dist/ui/subtitle-settings.d.ts +5 -0
  78. package/dist/ui/tooltips.d.ts +5 -0
  79. package/dist/ui/touch.d.ts +5 -0
  80. package/dist/ui/types.d.ts +1 -0
  81. package/package.json +77 -4
package/GUIDE.md ADDED
@@ -0,0 +1,716 @@
1
+ # Руководство yanePlayer
2
+
3
+ Подключение, API и настройка плеера. Короткий пример — в [README](./README.md).
4
+
5
+ - [Подключение](#подключение)
6
+ - [Элемент и lifecycle](#элемент-и-lifecycle)
7
+ - [Управление](#управление)
8
+ - [Headless API](#headless-api)
9
+ - [Host и timeline](#host-и-timeline)
10
+ - [Transport и requests](#transport-и-requests)
11
+ - [Субтитры и локальные файлы](#субтитры-и-локальные-файлы)
12
+ - [Тема](#тема)
13
+ - [Разработка](#разработка)
14
+
15
+ ## Подключение
16
+
17
+ В приложении с ESM bundler:
18
+
19
+ ```sh
20
+ npm install /path/to/yaneart-yaneplayer-0.1.0.tgz
21
+ ```
22
+
23
+ Обычный HTML/JavaScript:
24
+
25
+ ```js
26
+ import "@yaneart/yaneplayer";
27
+ ```
28
+
29
+ ```html
30
+ <yane-player
31
+ src="/video.mp4"
32
+ poster="/poster.jpg"
33
+ label="Видео"
34
+ lang="ru"
35
+ ></yane-player>
36
+ ```
37
+
38
+ Для браузера без bundler скопируйте содержимое `dist/` вместе с chunks на свой сервер
39
+ и подключите `<script type="module" src="/vendor/yaneplayer/index.js"></script>`.
40
+ Bare npm specifier в браузере требует bundler либо import map.
41
+
42
+ React (JSX):
43
+
44
+ ```jsx
45
+ import "@yaneart/yaneplayer";
46
+
47
+ export default function App() {
48
+ return <yane-player src="/video.mp4" />;
49
+ }
50
+ ```
51
+
52
+ В React + TypeScript добавьте один type-only import — это JSX declarations, без wrapper/JS:
53
+
54
+ ```tsx
55
+ import "@yaneart/yaneplayer";
56
+ import type {} from "@yaneart/yaneplayer/react";
57
+
58
+ export default function App() {
59
+ return (
60
+ <yane-player
61
+ src="/video.mp4"
62
+ onyp-statechange={(event) => console.log(event.detail.snapshot.playback)}
63
+ />
64
+ );
65
+ }
66
+ ```
67
+
68
+ Vue:
69
+
70
+ ```vue
71
+ <script setup lang="ts">
72
+ import "@yaneart/yaneplayer";
73
+ </script>
74
+
75
+ <template>
76
+ <yane-player src="/video.mp4"></yane-player>
77
+ </template>
78
+ ```
79
+
80
+ Однократно настройте Vue compiler в `vite.config.ts`:
81
+
82
+ ```ts
83
+ vue({
84
+ template: {
85
+ compilerOptions: {
86
+ isCustomElement: (tag) => tag === "yane-player",
87
+ },
88
+ },
89
+ });
90
+ ```
91
+
92
+ Angular:
93
+
94
+ ```ts
95
+ import "@yaneart/yaneplayer";
96
+ import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
97
+
98
+ @Component({
99
+ selector: "app-player",
100
+ standalone: true,
101
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
102
+ template: '<yane-player src="/video.mp4"></yane-player>',
103
+ })
104
+ export class PlayerExample {}
105
+ ```
106
+
107
+ ## Элемент и lifecycle
108
+
109
+ | API | Назначение |
110
+ | ----------------------------- | --------------------------------------------------------------------------- |
111
+ | `src` | MP4 URL; pathname .m3u8 автоматически выбирает HLS |
112
+ | `type` | video/mp4, application/vnd.apple.mpegurl или application/x-mpegurl |
113
+ | `poster` | URL обложки; изменение не перезагружает source/runtime |
114
+ | `label` | Имя внутреннего region; default Video player |
115
+ | `lang` | Язык controls: ru или en, default en |
116
+ | `theater` | Boolean property/attribute; host применяет layout по yp-theaterchange |
117
+ | `navigation` property | PlayerNavigation: optional previous/next callbacks приложения |
118
+ | `preferences` property | PreferencesAdapter приложения; смена отменяет старые load/save |
119
+ | `platform` property | PlatformOptions: wakeLock/mediaSession и optional orientation |
120
+ | `thumbnail` property | Async callback приложения для миниатюры absolute time |
121
+ | `source` property | Полный PlayerSource, имеет приоритет над shorthand; null очищает source/src |
122
+ | `host` property | HostAdapter; функции/объекты передаются properties, без JSON attributes |
123
+ | `initial` property | SourceOptions: positionSeconds/intent/trackPreferences, default paused |
124
+ | `commandTimeoutMs` property | Положительный media timeout, default 15000 |
125
+ | `runtime` | Readonly PlayerRuntime или null вне document |
126
+ | `setSource(source, options?)` | Явная async загрузка/retry после подключения элемента |
127
+
128
+ Без источника — idle без media requests. Изменение src/type переключает на shorthand.
129
+ Равный descriptor при rerender не перезапускает поток. Source/revision/transport replacement
130
+ сохраняет один video. Смена host/timeout создаёт новый runtime с тем же video.
131
+ Disconnect немедленно dispose/abort; reconnect создаёт runtime и загружает source с initial,
132
+ без автоматического сохранения позиции. До подключения используйте properties, после — методы.
133
+
134
+ Элемент делегирует play/pause/seek/selectAudio/selectText/selectQuality/setVolume/setMuted/
135
+ setPlaybackRate/setPreservesPitch, enterFullscreen/exitFullscreen,
136
+ requestPictureInPicture/exitPictureInPicture и navigate тому же runtime. Команды возвращают Promise.
137
+ setPlatformOptions меняет настройки платформы синхронно; setPreferences(adapter?) позволяет
138
+ дождаться загрузки профиля и обработать отказ. Изменение properties не требует пересоздания runtime.
139
+ Для framework bindings: React `source={source}`, Vue `:source="source"`, Angular `[source]="source"`.
140
+
141
+ `yp-ready` сообщает готовность runtime, не media; detail — PlayerRuntime.
142
+ `yp-statechange`, `yp-positionchange`, `yp-trackchange`, `yp-qualitychange`, `yp-commandchange`,
143
+ `yp-capabilitieschange`, `yp-ended`, `yp-error` содержат PlayerEvent в detail. События bubbles/composed.
144
+ Подписывайтесь перед подключением, либо читайте runtime после mount. Progress — максимум раз
145
+ в 5 секунд и дополнительно на pause/seeked/ended/source leave; это подтверждённая позиция.
146
+ После disconnect событий нет. Ошибки source/media/host нормализованы, без provider URL/secrets.
147
+ Blocked autoplay оставляет explicit play; fatal source/media error требует setSource retry.
148
+
149
+ ## Управление
150
+
151
+ На компьютере плеер показывает нижнюю панель с воспроизведением, перемоткой, звуком,
152
+ временем, скоростью, качеством, дорожками и полноэкранным режимом. Качество и озвучки
153
+ берутся из источника; возможности зависят от формата и браузера.
154
+ Списки озвучек и субтитров прокручиваются независимо, настройки субтитров всегда видны.
155
+
156
+ На телефоне Play находится по центру, дополнительные параметры — в окне настроек.
157
+ Одиночное касание кадра переключает воспроизведение, двойное слева или справа перематывает
158
+ на 10 секунд. Блокировка в настройках отключает случайные касания; кнопка разблокировки
159
+ появляется при касании кадра. Escape также снимает блокировку.
160
+
161
+ Горячие клавиши работают, когда фокус находится внутри плеера:
162
+
163
+ | Клавиши | Действие |
164
+ | --------- | -------------------------------------- |
165
+ | Space / K | Воспроизведение и пауза |
166
+ | J / L | Назад / вперёд на 10 секунд |
167
+ | ← / → | Назад / вперёд на 5 секунд |
168
+ | M | Включить / выключить звук |
169
+ | C | Выключить / вернуть выбранные субтитры |
170
+ | F | Полный экран |
171
+ | T | Режим театра |
172
+
173
+ Буквенные сочетания работают и в русской раскладке. В меню используются стрелки,
174
+ Home/End, Enter и Escape; на ползунках стрелки меняют значение. В текстовых полях
175
+ горячие клавиши плеера не перехватывают ввод. Анимации учитывают reduced motion.
176
+
177
+ ### Режим театра
178
+
179
+ Плеер сообщает приложению о переключении режима; размер области видео задаёт приложение:
180
+
181
+ ```js
182
+ player.addEventListener("yp-theaterchange", ({ detail }) => {
183
+ document.querySelector("main").classList.toggle("theater", detail.theater);
184
+ });
185
+ ```
186
+
187
+ Свойство `player.theater` отражается в HTML-атрибуте. Для React доступен
188
+ `onyp-theaterchange`. При изменении layout учитывайте высоту окна и возврат scroll.
189
+
190
+ ### Переход между сериями
191
+
192
+ Приложение задаёт порядок серий и callbacks. Если направления нет, соответствующая
193
+ кнопка скрыта. Пока Promise выполняется, повторный переход заблокирован:
194
+
195
+ ```js
196
+ player.navigation = {
197
+ previous: () => openEpisode(currentEpisode - 1),
198
+ next: () => openEpisode(currentEpisode + 1),
199
+ };
200
+ ```
201
+
202
+ `openEpisode` загружает новый источник через `player.setSource(...)` и обновляет navigation.
203
+ Для перехода из ОС используется тот же callback. `player.navigate("next")` работает через runtime.
204
+ `snapshot.navigation` содержит доступные направления, pending и безопасную ошибку; после отказа
205
+ можно повторить переход. Автоматический переход выключен. Для явного разрешения задайте
206
+ `autoAdvance: true`; next вызывается только после настоящего ended текущего видео.
207
+ Приложение загружает новый source и само задаёт intent; плеер не предзагружает следующую серию.
208
+
209
+ ### Автосубтитры
210
+
211
+ ```js
212
+ await player.setAutoCaptionMode("ru"); // Web Component, из действия пользователя
213
+ const { mode, loading, progress, error, videoOnly } =
214
+ player.autoCaptionPresentation;
215
+ player.addEventListener("yp-captionpresentationchange", (event) => {
216
+ console.log(event.detail); // readonly AutoCaptionPresentation; progress 0…100/null
217
+ });
218
+ await player.setAutoCaptionMode("original"); // сохраняет работающий ASR и playback
219
+ await player.setAutoCaptionMode("off"); // отменяет загрузку, распознавание и перевод
220
+ ```
221
+
222
+ Переводчик OPUS en→ru WASM q8 загружается отдельно (~118 МБ моделей) только при
223
+ выборе русского режима. Это эксперимент: ошибки ASR переходят в перевод, смысл,
224
+ отрицания и имена могут теряться. Перед Play дождитесь подготовки моделей:
225
+ пропущенный во время загрузки звук не распознаётся задним числом.
226
+ При ошибке перевода доступен оригинал и «Повторить»; «Выкл» отменяет подготовку.
227
+ В PiP native video показывает оригинал, перевод остаётся в окне плеера.
228
+
229
+ Опубликованные слова не переписываются: новые дополняют строку справа, заполненная
230
+ строка поднимается вверх. Максимум две строки, подложка по словам строки или самой
231
+ длинной строке карточки. Все три оформления доступны оригиналу и русскому.
232
+ Стабилизация и подавление overlap — ограниченные текстовые эвристики, не гарантия
233
+ смысла или полноты. Явная смена размера/оформления может переразложить текущий текст.
234
+
235
+ Модель [Xenova/opus-mt-en-ru](https://huggingface.co/Xenova/opus-mt-en-ru) закреплена
236
+ на revision `e050376e960175e8ddc5cff85025dc8436cddd68`; исходная
237
+ [Helsinki-NLP модель](https://huggingface.co/Helsinki-NLP/opus-mt-en-ru) — Apache-2.0.
238
+ Worker и WASM поставляются в пакете, веса скачиваются с Hugging Face в браузер.
239
+ Для CSP нужны разрешения загрузки моделей с Hugging Face и его download/CDN origins,
240
+ Worker и WASM так же, как для ASR. Аудио и текст остаются на устройстве.
241
+ Использование translation pipeline и pinned revision соответствует
242
+ [официальному API Transformers.js](https://huggingface.co/docs/transformers.js/pipelines).
243
+
244
+ Headless `/core` сохраняет API распознавания ниже: режимы перевода и rolling DOM
245
+ предоставляет Web Component. Существующий `setAutoCaptions(boolean)` совместим:
246
+ включение из Off даёт оригинал; выключение останавливает также перевод.
247
+
248
+ Если доступной готовой дорожки нет, раздел «Субтитры» предлагает «Выкл / Оригинал / Русский».
249
+ Речь распознаётся на устройстве. Русский режим переводит только английскую речь;
250
+ автоматического определения языка для перевода нет. Видео продолжает играть;
251
+ текст появляется с задержкой и может содержать ошибки. Это экспериментальная функция:
252
+ точность маленькой модели уступает подготовленным субтитрам, реальные телефоны ещё не проверены.
253
+
254
+ ```js
255
+ await player.setAutoCaptions(true); // также runtime в /core; вызывать из действия пользователя
256
+ const { available, reason, status, progress, error, backend, latencySeconds } =
257
+ player.runtime.getSnapshot().autoCaptions;
258
+ await player.setAutoCaptions(false); // немедленная отмена загрузки/захвата/распознавания
259
+ ```
260
+
261
+ Статусы: `off`, `loading`, `listening`, `error`; progress — 0…1 или null.
262
+ Причины недоступности: `source` (нет готового source/anchor), `tracks` (есть готовые субтитры),
263
+ `browser` (нет secure context/captureStream/AudioWorklet/Worker/WASM). Клавиша C также
264
+ переключает распознавание, когда готовых субтитров нет. Ошибка допускает повтор и не останавливает видео.
265
+ Source change сбрасывает автосубтитры в Off; профиль не сохраняет включение или распознанный текст.
266
+ Seek, смена аудио/скорости очищают старый результат. Пауза сохраняет текущий текст по media clock.
267
+
268
+ Загрузка только после явного включения: около 75–100 МБ при первом использовании, включая движок,
269
+ модели Whisper tiny и Silero VAD; дальнейшие загрузки зависят от browser cache.
270
+ При доступном GPU используется WebGPU encoder fp32 + WASM decoder q8 (`backend: "gpu"`),
271
+ иначе — WASM q8 (`"cpu"`; до подготовки/после Off — null). Прогрев входит в подготовку.
272
+ После ошибки GPU CPU запускается в новом worker; облачного fallback нет.
273
+ Быстрый режим распознаёт растущий контекст до 6 с, обновляет предварительный текст и отправляет
274
+ остаток после короткой паузы в речи. Следующая гипотеза уточняет строку без повторного вывода
275
+ уже показанного префикса. CPU использует более редкие блоки, чтобы не накапливать работу.
276
+ Задержка зависит от устройства, языка и речи; секунда после конца фразы не гарантируется.
277
+ `latencySeconds` сохраняет возраст начала аудио в последней показанной гипотезе. Для растущего
278
+ контекста это не задержка новых слов или конца фразы; UI не показывает это число в быстром режиме.
279
+ Whisper и VAD закреплены на конкретных revisions. Одна операция распознавания и один ожидающий
280
+ фрагмент; при отставании старый звук отбрасывается, длительное отставание показывает ошибку.
281
+ Аудио/текст не отправляются с устройства; микрофон и захват экрана не используются.
282
+ Необходим доступ браузера к звуку media, включая CORS; Safari без captureStream недоступен.
283
+
284
+ Для прямого использования dist сохраните `auto-captions/` рядом с JS entries. Bundler находит
285
+ worker/worklet/WASM через `auto-captions/assets.js`. CSP: `worker-src 'self'`,
286
+ `script-src 'self' 'wasm-unsafe-eval'`; `connect-src` должен разрешать собственные assets,
287
+ `https://huggingface.co`, его домены загрузки весов (например `https://cas-bridge.xethub.hf.co`)
288
+ и `https://raw.githubusercontent.com` для VAD. Облачного fallback нет.
289
+ Сгенерированные native cues используют существующие presets/delay и предупреждение video-only PiP/fullscreen.
290
+
291
+ Focused lifecycle/UI проверка после сборки: `npm run check:auto-captions`.
292
+ Она использует настоящий capture/worklet и контролируемый worker; результаты настоящей модели,
293
+ качество и измерения на устройствах проверяются отдельно в локальном demo.
294
+
295
+ ### Платформа
296
+
297
+ PiP доступен на панели desktop и в дополнительных настройках compact, если его поддерживает
298
+ текущий media path. Fullscreen/PiP вызываются через публичные методы foundation Video.js;
299
+ snapshot читает их фактическое состояние. Меню fullscreen остаются внутри плеера.
300
+ Для headless задайте `container` в createPlayer, иначе используется родитель video.
301
+
302
+ ```js
303
+ player.platform = {
304
+ wakeLock: true,
305
+ mediaSession: true,
306
+ orientation: "landscape",
307
+ };
308
+ // Вызывать из пользовательского действия после проверки capabilities:
309
+ await player.enterFullscreen();
310
+ await player.exitFullscreen();
311
+ await player.requestPictureInPicture();
312
+ await player.exitPictureInPicture();
313
+ ```
314
+
315
+ Wake lock и Media Session включены по умолчанию. Landscape lock опционален и ограничен
316
+ fullscreen; отказ не прерывает видео. Wake lock удерживается при видимом playback и освобождается
317
+ при pause/ended/hidden/source replacement/dispose. Browser denial виден в `snapshot.platform.error`;
318
+ новое Play или возвращение видимости разрешает новую попытку wake lock, без постоянных retries.
319
+
320
+ Последний плеер, начавший playback, владеет Media Session. Title и `source.metadata`
321
+ (artist/album/artwork) попадают в системный интерфейс; play/pause/stop/seek/previous/next
322
+ используют runtime. Stop ставит на паузу. Seek и position state используют absolute timeline.
323
+ `mediaSession: false` отключает эту интеграцию. Dispose старого владельца не очищает нового.
324
+
325
+ `snapshot.platform` содержит fullscreen/pictureInPicture/wakeLock/orientationLocked/
326
+ mediaSessionActive/pending/error; capability flags и причины доступны в `snapshot.capabilities`.
327
+ Ошибки платформы не заменяют playback error. `snapshot.preservesPitch` отражает browser property;
328
+ `setPreservesPitch(boolean)` и checkbox в настройках скорости/compact управляют высотой голоса.
329
+ Rate/volume/mute обновляются по media events, включая изменения вне controls.
330
+
331
+ PiP и системный video fullscreen могут не показывать DOM/canvas captions. Для styled ASS
332
+ используйте inline или container fullscreen; отображается предупреждение. WebKit path использует
333
+ публичную поддержку foundation. Native Safari, системный PiP, orientation и реальные устройства
334
+ требуют отдельной проверки; Chromium doubles подтверждают lifecycle, а не работу системного UI.
335
+
336
+ ### Настройки приложения
337
+
338
+ Библиотека не использует localStorage и не знает об аккаунтах. Приложение передаёт адаптер:
339
+
340
+ ```js
341
+ const adapter = {
342
+ async load({ signal }) {
343
+ return profileStore.load({ signal }); // PlayerPreferences или null
344
+ },
345
+ async save(preferences, { signal }) {
346
+ await profileStore.save(preferences, { signal });
347
+ },
348
+ };
349
+ await player.setPreferences(adapter);
350
+ ```
351
+
352
+ В React передавайте стабильный объект через `preferences={adapter}`; в headless —
353
+ createPlayer({ media, preferences: adapter }). setSource ждёт начальную загрузку.
354
+ `SourceOptions.trackPreferences` перекрывает languages/forced/SDH профиля для этого source. Выбор дорожки
355
+ сохраняет language/forced/SDH или Off, без ID; local file и его содержимое не сохраняются.
356
+
357
+ PlayerPreferences: volume 0…1, muted, playbackRate 0.1…4 (применяется только поддерживаемый),
358
+ preservesPitch, tracks: TrackPreferences, captionStyle и captionDelaySeconds −60…60.
359
+ Defaults нового профиля: volume 1, unmuted, 1×, pitch on, captions Off, исходный стиль и delay 0.
360
+ Временное ограничение rates у source включает 1×; preferred rate возвращается на следующем
361
+ поддерживаемом source. Весь загруженный record проверяется до применения; unknown keys запрещены.
362
+
363
+ Записи объединяются и выполняются последовательно. `snapshot.preferences` сообщает
364
+ idle/loading/ready/saving/error и безопасное сообщение. После load error повторите
365
+ setPreferences(adapter); после save error новое изменение параметра повторяет запись.
366
+ Смена адаптера или setPreferences() отменяет прежние load/save и сбрасывает профиль.
367
+ Host обязан соблюдать AbortSignal и привязывать операции адаптера к конкретному аккаунту;
368
+ серверные изменения, уже принятые до abort, разрешает приложение. Source/history меняет приложение.
369
+ На dispose незавершённая запись отменяется; для гарантированного сохранения при закрытии host
370
+ может наблюдать snapshot и реализовать собственный lifecycle. Телеметрии внутри библиотеки нет.
371
+
372
+ ### Главы и миниатюры
373
+
374
+ `source.chapters` — до 500 глав с возрастающими absolute `startSeconds` и title (до 200 символов).
375
+ Пункт «Главы» в desktop настройках скорости и раздел compact появляются только при наличии
376
+ данных и позволяют переходить с keyboard/touch через обычный seek. Отдельной кнопки на панели нет.
377
+ Локально недоступные главы disabled; host seek может открыть главу вне текущего окна.
378
+ Главы передаёт приложение: библиотека не извлекает их из видео и не создаёт из torrent/magnet.
379
+ При наличии глав в контейнере backend может извлечь их и передать через этот API.
380
+
381
+ ```js
382
+ source.chapters = [
383
+ { startSeconds: 0, title: "Начало" },
384
+ { startSeconds: 120, title: "Глава 2" },
385
+ ];
386
+ player.thumbnail = async ({ source, seconds, signal }) => {
387
+ return thumbnails.resolve(source.id, seconds, { signal }); // { src } или null
388
+ };
389
+ ```
390
+
391
+ UI запрашивает descriptor сразу при наведении/перемотке, в том числе во время движения; старый запрос
392
+ отменяется при уходе указателя, source replacement, смене resolver или dispose. При отказе остаётся
393
+ временная метка. URL должен быть http/https/blob без credentials. Источники картинок выбирает host;
394
+ никакого автоматического сканирования видео или torrent нет. Headless использует
395
+ getThumbnail(seconds, signal), `snapshot.chapters` и seek. Для headless callback задаётся через
396
+ options.thumbnail или setThumbnailResolver; navigation — через options.navigation/setNavigation.
397
+ Предыдущая миниатюра остаётся до декодирования новой; отменённый ответ не меняет картинку.
398
+ Точность и скорость первого появления зависят от подготовленных host кадров и их доставки.
399
+
400
+ Распознавание речи и автоматический перевод библиотека не выполняет. Автоматический выбор языка
401
+ выбирает готовую дорожку. Chrome Live Caption/Live Translate — отдельные функции браузера с
402
+ собственной панелью; их текст и оформление не управляются API субтитров плеера.
403
+
404
+ ## Headless API
405
+
406
+ | Import | Содержимое |
407
+ | ------------------------- | --------------------------------------------------------------------- |
408
+ | @yaneart/yaneplayer | Авто-регистрация элемента, YanePlayerElement, version, публичные типы |
409
+ | @yaneart/yaneplayer/core | createPlayer, version и типы; без UI registration |
410
+ | @yaneart/yaneplayer/react | Только опциональные JSX declarations, type-only import |
411
+
412
+ Оба JS entry безопасно импортируются без DOM при SSR. Создание runtime/элемента — browser-only.
413
+ SSR выводит custom tag; клиентский import выполняет upgrade. Повторные imports/HMR не
414
+ регистрируют tag повторно; изменение самого класса при HMR требует reload страницы.
415
+
416
+ ```ts
417
+ import { createPlayer, type PlayerSource } from "@yaneart/yaneplayer/core";
418
+
419
+ const source: PlayerSource = {
420
+ id: "example",
421
+ revision: "1",
422
+ kind: "mp4",
423
+ src: "/video.mp4",
424
+ };
425
+ const player = createPlayer({ media: document.querySelector("video")! });
426
+ await player.setSource(source);
427
+ await player.play(); // из пользовательского действия
428
+ await player.pause();
429
+ player.dispose();
430
+ ```
431
+
432
+ getSnapshot() возвращает стабильный readonly snapshot; subscribe(listener) возвращает unsubscribe.
433
+ Независимы playback, pending с commandId и последний intent. positionSeconds/durationSeconds
434
+ могут быть null. Не записывайте pending target в историю просмотра. dispose идемпотентен;
435
+ отменённые/замещённые операции отклоняются с AbortError.
436
+
437
+ ## Host и timeline
438
+
439
+ Начальный выбор дорожек задаётся без ID, действующих только внутри source/revision:
440
+
441
+ ```js
442
+ await player.setSource(source, {
443
+ trackPreferences: {
444
+ audioLanguages: ["ru", "en"],
445
+ text: { languages: ["ru", "en"], forced: false, sdh: true },
446
+ },
447
+ });
448
+ ```
449
+
450
+ Для каждого языка по порядку выбирается точное совпадение без учёта регистра, затем
451
+ основной язык (`en-GB` → `en`); DEFAULT разрешает равные варианты. Forced/SDH — строгие
452
+ фильтры. Без совпадения остаются текущая озвучка и выключенные субтитры. Без `text` или
453
+ с `text: null` субтитры выключены даже для DEFAULT/FORCED. Без `audioLanguages` сохраняется
454
+ выбор транспорта. Передайте сохранённые языки при загрузке следующего источника;
455
+ библиотека сама не читает и не записывает localStorage, cookies или аккаунт.
456
+
457
+ Snapshot и оба вида меню показывают предоставленные channels/default/forced/SDH/description/
458
+ codec/format. HLS получает метаданные из manifest, native media — только доступные browser
459
+ данные. Host может объявить `available: false, unavailableReason: "…"`: пункт виден с причиной,
460
+ но недоступен для UI и API. При отсутствии controller показывается отсутствие выбора.
461
+ После сборки `npm run check:tracks` проверяет preferences, metadata, async host и recovery
462
+ изолированно, без demo. Реальные HLS fixtures проверяются отдельно в локальном demo.
463
+
464
+ `host: HostAdapter` реализует `execute(command, context)`. Source отдельно объявляет поддерживаемые
465
+ controllers в `capabilities` и доступные `audioTracks`, `textTracks`, `qualities`.
466
+ Сам callback не включает capability. Host не получает media engine, account DTO или route из библиотеки.
467
+
468
+ | Значение | Назначение |
469
+ | ---------------- | --------------------------------------------------------------------------------- |
470
+ | `command.type` | `seek`, `audio`, `text` или `quality`, с `seconds`/`id` |
471
+ | `command.target` | Последняя совокупная цель: position/audio/text/quality |
472
+ | `context` | sourceId/revision/commandId/signal, подтверждённая positionSeconds; getter intent |
473
+ | Результат | `{source?, selection?}` после готовности и подтверждения host |
474
+
475
+ Очередь сериализует mutations и объединяет быстрые изменения по полям `target`.
476
+ Host обязан применять всю цель, соблюдать version/idempotency и доводить admission до ready.
477
+ Abort отменяет ожидание клиента; host самостоятельно согласует принятые server mutations.
478
+ Следующий `execute` ждёт завершения предыдущего Promise, даже после abort. Ограничивайте ожидание
479
+ и выполняйте reconciliation внутри adapter. `commandTimeoutMs` (по умолчанию 15000) ограничивает
480
+ ожидание **media**, а не серверную транзакцию. Автоматического повтора host mutations нет.
481
+
482
+ Replacement сохраняет source id, меняет revision и содержит фактические timeline anchors:
483
+
484
+ ```ts
485
+ const timeline = {
486
+ mode: "windowed-vod" as const,
487
+ absoluteAnchorSeconds: 599,
488
+ mediaAnchorSeconds: 0,
489
+ durationSeconds: 7200,
490
+ };
491
+ ```
492
+
493
+ Абсолютная позиция = `absoluteAnchorSeconds + media.currentTime - mediaAnchorSeconds`.
494
+ После запроса 600 с anchor 599 означает фактический старт 599 с. Host возвращает actual anchors,
495
+ а не requested offset. Seek внутри реальных local ranges остаётся local, за пределами требует
496
+ объявленного `seek: "host"`. Неизвестные anchors (`null`) отключают точный absolute seek.
497
+ `timeline.seekableRanges` может ограничить реальные local ranges; duration `null` не угадывается.
498
+ Track ids принадлежат source revision; replacement selection должна ссылаться на новые metadata.
499
+ При replacement без server seek runtime восстанавливает прежнюю confirmed position, если
500
+ она входит в новый локальный seekable range; иначе фактический anchor остаётся авторитетным.
501
+ Host подтверждает host-managed selections в `result.selection`; транспортные audio/text
502
+ восстанавливаются по metadata. Последний play/pause intent, rate/volume/mute сохраняются.
503
+
504
+ ## Transport и requests
505
+
506
+ HLS `hlsPlayback: "auto"` (default) предпочитает MSE/hls.js и использует native fallback;
507
+ `"mse"`/`"native"` явно выбирают путь. Capabilities пересекают descriptor, transport и браузер.
508
+ Auto quality появляется только для настоящей ABR ladder. Скорости ограничиваются
509
+ `source.capabilities.playbackRates`; volume зависит от устройства.
510
+
511
+ Для MSE можно задать `request: {allowedOrigins, credentials?, headers?}`.
512
+ Проверяются manifests, segments, keys и subtitle requests. Redirects запрещены.
513
+ Headers и credentials отправляются только исходному origin; другие разрешённые origins получают
514
+ запросы без них. По умолчанию нет auth headers, credentials — same-origin, разрешены origin страницы
515
+ и исходного manifest. Buffers ограничены; dispose/source change уничтожают transport и запросы.
516
+
517
+ MP4/native HLS используют browser CORS/auth (`crossOrigin="anonymous"`) и не поддерживают
518
+ произвольные JS headers или строгий контроль redirects/nested origins. Descriptor с `request`
519
+ для native path отклоняется. Для такого playback host предоставляет browser-compatible URL/auth;
520
+ не используйте `request`, если строгая проверка origin/redirect не нужна.
521
+
522
+ С 0.1.1 `source.crossOrigin: "use-credentials"` включает browser cookies для native media
523
+ и credentials только исходного HLS origin в MSE. Cross-origin сервер должен разрешить
524
+ конкретный origin приложения и credentials; браузерная cookie policy продолжает действовать.
525
+ Это browser-compatible auth без строгой `request` policy: native transport не контролирует
526
+ redirect/nested origins. Для строгой политики используйте `request` и MSE.
527
+
528
+ Preferences adapter и platform API описаны в разделе «Платформа» выше.
529
+
530
+ ## Субтитры и локальные файлы
531
+
532
+ Меню дорожек → «Настройки субтитров» (на телефоне — раздел «Субтитры» общей панели) открывает
533
+ единый раздел выбора дорожки/Off. Для активного native text доступны три пресета: «Контур»
534
+ (белый текст с обводкой без фона, default), «Подложка» (белый текст на полупрозрачном тёмном фоне)
535
+ и «YaneMedia» (Manrope SemiBold, светлый текст, скруглённая тёмная
536
+ подложка с синими уголками). Цвет акцента и шрифт используют tokens темы плеера.
537
+ Это UI-карточка поверх активных browser cues; timing и выбор дорожки остаются native.
538
+ Авторские positioned/vertical/region cues, недостаток места и headless используют native fallback.
539
+ ASS/SSA сохраняет авторское оформление, presets для него скрыты. «Синхронизация» свёрнута
540
+ по умолчанию и позволяет менять задержку или сбросить её в 0; burn-in настраивает источник.
541
+
542
+ Встроенный UI не содержит загрузки файлов, редактора оформления и переключателя тональности
543
+ голоса. Тональность сохраняется по умолчанию; host может изменить это через публичный API.
544
+ Загрузка локальных файлов и полная настройка остаются API библиотеки для приложений.
545
+ Файлы остаются на устройстве: библиотека их не загружает на сервер.
546
+
547
+ ```ts
548
+ await player.loadSubtitles(file); // File: .vtt, .srt, .ass или .ssa, UTF-8
549
+ await player.setCaptionDelay(0.5); // положительное значение показывает позже
550
+ await player.setCaptionStyle({
551
+ fontScale: 1.25,
552
+ color: "#ffff00",
553
+ background: "transparent",
554
+ });
555
+ await player.selectText(null); // Off; останавливает загрузку/renderer
556
+ await player.removeLocalSubtitles();
557
+ ```
558
+
559
+ Те же методы есть в headless runtime. Локальный файл заменяет предыдущий локальный файл;
560
+ формат, размер и UTF-8 проверяются перед заменой. Новая `setSource` очищает файл, host replacement
561
+ того же source сохраняет его вместе с confirmed position/intent/rate и caption settings.
562
+ Дорожки источника описываются отдельно от host metadata:
563
+
564
+ ```ts
565
+ const source: PlayerSource = {
566
+ id: "movie",
567
+ revision: "1",
568
+ kind: "mp4",
569
+ src: "/movie.mp4",
570
+ subtitles: [
571
+ {
572
+ id: "ru",
573
+ label: "Русский",
574
+ language: "ru",
575
+ format: "srt",
576
+ src: "/ru.srt",
577
+ },
578
+ {
579
+ id: "styled",
580
+ label: "ASS",
581
+ format: "ass",
582
+ src: "/styled.ass",
583
+ fonts: [{ src: "/fonts/allowed.woff2" }],
584
+ },
585
+ ],
586
+ };
587
+ await player.setSource(source); // captions Off по умолчанию
588
+ await player.selectText("subtitle:ru");
589
+ ```
590
+
591
+ Snapshot IDs для sidecar имеют префикс `subtitle:`; `subtitle:local` зарезервирован для файла.
592
+ Transport/host IDs сохраняют прежний смысл. Если host управляет text selection, переход на
593
+ sidecar сначала подтверждает host Off. Host-rendered/burn-in captions нельзя оформить или
594
+ сдвинуть в браузере: интерфейс объясняет ограничение и предлагает текстовую дорожку/файл.
595
+ Языковые preferences применяются к sidecar тем же способом, что и к транспортным дорожкам.
596
+
597
+ Таймкоды sidecar/local абсолютные: `media cue = absolute cue − timeline offset + delay`.
598
+ Встроенные transport cues уже находятся в media time, к ним применяется только delay.
599
+ Неизвестный anchor делает sidecar/local недоступными с причиной. Delay — от −60 до +60 секунд;
600
+ style: `fontScale` .75..2, `color` #RRGGBB, `background` #RRGGBB/#RRGGBBAA/`transparent`,
601
+ `edge` `outline | shadow | none`. `snapshot.captions` содержит настройки, renderer, status и warning.
602
+ Optional `presentation: "native" | "card"` включает rounded card в готовом UI (default native).
603
+ В native video-only fullscreen/PiP остаётся стандартное ограничение DOM overlay.
604
+ Оформление применяется к native text. ASS/SSA сохраняет авторские стили; native style
605
+ предпочтение остаётся для следующей текстовой дорожки. Settings сохраняются в runtime, без storage.
606
+
607
+ Text использует браузерный WebVTT parser/renderer; SRT преобразуется в VTT. Cue payload
608
+ не вставляется в DOM как HTML. Сохраняются overlap и cue positioning. ASS/SSA лениво включает
609
+ JASSUB 2.5.23, worker/WASM и Liberation Sans; online/local font queries выключены.
610
+ Необходимы Workers, OffscreenCanvas, WASM и native video frame callbacks. Без них ASS помечается
611
+ недоступным. PGS/VobSub — видимые disabled options: нужен host burn-in или текстовая альтернатива.
612
+ Библиотека сама не выполняет OCR и не создаёт burn-in поток.
613
+
614
+ Ресурсы ограничены: VTT/SRT ≤5 MiB/100000 cues, ASS ≤10 MiB/100000 events;
615
+ до 8 явно заданных fonts, каждый ≤5 MiB (включая заявленный decoded WOFF размер), всего ≤20 MiB,
616
+ плюс фиксированный fallback font. Поддерживаются TTF/OTF/WOFF/WOFF2; embedded ASS Fonts/Graphics
617
+ отклоняются. Каждый subtitle/font resource может иметь собственный `request` с allowedOrigins,
618
+ credentials и headers. Cookies по умолчанию только same-origin; redirects запрещены,
619
+ загрузка ограничена streaming byte limit и `commandTimeoutMs`. HTTP(S) ресурсы требуют CORS.
620
+
621
+ При прямом использовании `dist` сохраните дерево `subtitles/` рядом с JS entries.
622
+ Для bundler статические URL в `subtitles/assets.js` позволяют перенести worker/WASM/font;
623
+ проверены Vite dev и production. CSP для ASS дополнительно требует `worker-src 'self'`,
624
+ `script-src 'self' 'wasm-unsafe-eval'` и разрешённый `connect-src`; для text — `media-src blob:`.
625
+ `style-src 'self'` работает без unsafe-inline. COOP/COEP не обязательны; при isolation JASSUB
626
+ использует ограниченный пул до 8 pthread workers, без isolation — один renderer worker.
627
+
628
+ Container fullscreen сохраняет ASS canvas. Системные video fullscreen/PiP могут не показать
629
+ DOM/canvas captions: интерфейс предупреждает об ограничении и предлагает inline/container
630
+ fullscreen. Native text выводит браузер; Safari/реальные устройства/AT и system captions требуют
631
+ ручной проверки. Незаметного ASS → plain text downgrade нет.
632
+
633
+ После сборки `npm run check:subtitles` проверяет parsing/resources, реальные text/ASS pixels,
634
+ absolute timing, host replacement, pending/error/retry и cleanup изолированно от demo.
635
+ `node tests/subtitles.mjs --isolated` проверяет тот же путь под COOP/COEP.
636
+ JASSUB/WASM и fallback font берутся из установленного npm-пакета `jassub`.
637
+ Архивы сторонних исходников в пакет не включаются. Файлы лицензий npm-зависимостей
638
+ сборка копирует в `dist/licenses/`.
639
+ Состав и изменения стороннего кода перечислены в [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
640
+
641
+ ## Тема
642
+
643
+ ```css
644
+ yane-player {
645
+ --yp-accent: #a6e3a1;
646
+ --yp-radius: 8px;
647
+ --yp-font-family: system-ui, sans-serif;
648
+ }
649
+ ```
650
+
651
+ Доступны также `--yp-text`, `--yp-surface`, `--yp-control-size` (default48px/minimum44px),
652
+ `--yp-accent-text` и `--yp-pattern`. По умолчанию Manrope Variable, radius24px,
653
+ accent#315fbd и accent-text#6f94df, графит#101112/#191a1b/#222325 и текст#f1f0ec.
654
+ Стили controls изолированы в Shadow DOM. Reduced motion отключает переходы.
655
+ Auto styles используют constructable stylesheets/adoptedStyleSheets в современных браузерах.
656
+ Проверена CSP `style-src 'self'` без unsafe-inline; host задаёт разрешённые media/connect origins.
657
+
658
+ `--yp-pattern: url('/brand-pattern.svg')` добавляет декоративную текстуру на подложки меню.
659
+ Изображение предоставляет приложение; по умолчанию текстуры нет. Для небольшого знака
660
+ поверх видео используйте слот `watermark`:
661
+
662
+ ```html
663
+ <yane-player src="/video.mp4">
664
+ <img slot="watermark" src="/brand-mark.svg" alt="" />
665
+ </yane-player>
666
+ ```
667
+
668
+ ## Разработка
669
+
670
+ Node 24, npm 11. В Windows PowerShell используйте `npm.cmd`, если запуск `npm.ps1` запрещён.
671
+
672
+ `npm run format` форматирует исходники, тесты, конфиги и README через Prettier 3.9.10;
673
+ `npm run format:check` проверяет стиль без изменений. Используются стандартные настройки
674
+ Prettier: 2 пробела, двойные кавычки, LF. Сборки, зависимости, lockfile и сторонние лицензии
675
+ исключены. Локальный стенд имеет свои аналогичные команды и установку Prettier.
676
+
677
+ ```sh
678
+ npm ci
679
+ npm run build
680
+ npm run check:runtime
681
+ npm run check:package
682
+ npm pack
683
+ ```
684
+
685
+ `build` собирает ESM, declarations, embedded CSS и лицензии в `dist/`.
686
+ `check:runtime` проверяет очередь mutations: coalescing, serialization, cancellation и late replies.
687
+ `check:package` устанавливает tarball в изолированные consumers и проверяет core без DOM/React,
688
+ SSR, публичные типы и production consumers HTML/React/Vue/Angular. Нужен Playwright Chromium;
689
+ framework consumers устанавливают exact dependencies через npm (первый запуск требует сеть).
690
+ Для focused проверки общего UI используйте `npm run check:package -- --frameworks=html`:
691
+ production HTML consumer проверяет desktop controls, host seek и lifecycle под CSP;
692
+ общие SSR/публичные типы/React JSX также проверяются, Vue/Angular не запускаются.
693
+ `npm pack` повторно собирает библиотеку и создаёт tarball; публикация не выполняется.
694
+
695
+ Для публикации версии `0.1.0` владелец пакета выполняет из корня проекта:
696
+
697
+ ```sh
698
+ npm publish
699
+ ```
700
+
701
+ В PowerShell — `npm.cmd publish`. Публичный доступ задан через `publishConfig.access`.
702
+ После публикации yaneMedia подключает эту версию командой
703
+ `npm install @yaneart/yaneplayer@0.1.0 --save-exact`.
704
+
705
+ ```text
706
+ src/core/ TypeScript core
707
+ src/element.ts общий Web Component
708
+ src/ui/ controls, локальные labels/icons, native VTT layout
709
+ src/react.d.ts опциональные JSX declarations
710
+ src/styles.css тема
711
+ scripts/ подготовка файлов пакета
712
+ tests/ проверки библиотеки и публичных exports
713
+ ```
714
+
715
+ В npm-пакет входят `dist`, README, GUIDE и [MIT LICENSE](./LICENSE).
716
+ Исходники, тесты и настройки сборки остаются в Git.