itube-modern-player 0.7.1 → 0.8.0

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.
package/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  ## Содержание
8
8
 
9
+ - [Подключить за 2 минуты (копипаст)](#подключить-за-2-минуты-копипаст)
9
10
  - [Возможности](#возможности)
10
11
  - [Установка](#установка)
11
12
  - [Быстрый старт (vanilla JS / TS)](#быстрый-старт-vanilla-js--ts)
@@ -40,6 +41,87 @@
40
41
  - [Сборка и публикация](#сборка-и-публикация)
41
42
  - [История изменений](#история-изменений)
42
43
 
44
+ ## Подключить за 2 минуты (копипаст)
45
+
46
+ Три готовых рецепта. Скопируй подходящий целиком, подставь свои URL — работает.
47
+
48
+ ### Рецепт 1: просто HTML-страница (без npm и сборщика)
49
+
50
+ Сохрани как `index.html`, открой в браузере. Всё.
51
+
52
+ ```html
53
+ <!doctype html>
54
+ <html>
55
+ <head>
56
+ <link rel="stylesheet" href="https://unpkg.com/itube-modern-player/dist/style.css">
57
+ </head>
58
+ <body>
59
+ <div id="player"></div>
60
+
61
+ <!-- hls.js нужен ТОЛЬКО если видео — .m3u8 (стрим). Для .mp4 эту строку можно удалить. -->
62
+ <script src="https://unpkg.com/hls.js"></script>
63
+ <script src="https://unpkg.com/itube-modern-player/dist/itube-modern-player.iife.js"></script>
64
+ <script>
65
+ new ITubePlayer('#player', {
66
+ source: {
67
+ src: 'https://example.com/video.m3u8', // ← твоё видео (.m3u8 или .mp4)
68
+ poster: 'https://example.com/poster.jpg', // ← твоя обложка
69
+ title: 'Название ролика',
70
+ },
71
+ })
72
+ </script>
73
+ </body>
74
+ </html>
75
+ ```
76
+
77
+ Что получится: обложка с кнопкой play; по клику — воспроизведение со всеми контролами.
78
+
79
+ ### Рецепт 2: проект со сборщиком (Vite / webpack / любой)
80
+
81
+ ```bash
82
+ npm i itube-modern-player
83
+ ```
84
+
85
+ ```ts
86
+ import { Player } from 'itube-modern-player'
87
+ import 'itube-modern-player/style.css'
88
+
89
+ new Player('#player', {
90
+ source: { src: '/video.m3u8', poster: '/poster.jpg', title: 'Название' },
91
+ })
92
+ ```
93
+
94
+ hls.js для `.m3u8` подтянется сам, отдельным чанком — ставить и импортировать его не нужно.
95
+
96
+ ### Рецепт 3: Nuxt 3 / Vue 3 (рекомендуемый — быстрый LCP из коробки)
97
+
98
+ ```vue
99
+ <script setup lang="ts">
100
+ import { ITubePlayer } from 'itube-modern-player/vue'
101
+ </script>
102
+
103
+ <template>
104
+ <!-- lazy = постер приходит в первом HTML (SSR), тяжёлый плеер качается
105
+ только когда пользователь потянулся к видео. Никаких ClientOnly. -->
106
+ <ITubePlayer
107
+ lazy
108
+ :source="{ src: '/video.m3u8', poster: '/poster.jpg', title: 'Название' }"
109
+ :options="{ muted: true }"
110
+ @ended="onEnded"
111
+ />
112
+ </template>
113
+ ```
114
+
115
+ ### Если что-то не так
116
+
117
+ | Симптом | Причина и лечение |
118
+ | --- | --- |
119
+ | Видео не стартует само | Браузеры блокируют автоплей со звуком. Нужен автостарт — `playOnInit: true` **плюс** `muted: true`. Иначе — старт только по клику, это норма |
120
+ | Чёрный прямоугольник вместо плеера | Не подключён `style.css` (рецепт 1 — `<link>`, рецепт 2 — `import`) |
121
+ | `.m3u8` не играет (в рецепте 1) | Удалил строку с hls.js — верни её |
122
+ | Контролы на английском | Добавь в опции `language: 'ru'` |
123
+ | Реклама не нужна | Просто не передавай `adConfig` — её и не будет |
124
+
43
125
  ## Возможности
44
126
 
45
127
  - **Источники** — MP4/WebM, HLS-стримы (`.m3u8`, hls.js или нативно в Safari), live-потоки с бейджем LIVE, прогрессивные качества с переключением без потери позиции, HLS-уровни с пунктом Auto.
@@ -267,7 +349,7 @@ const source: VideoSource = {
267
349
 
268
350
  Стандартный формат — WebVTT, каждый cue указывает картинку и регион спрайта:
269
351
 
270
- ```
352
+ ```text
271
353
  WEBVTT
272
354
 
273
355
  00:00:00.000 --> 00:00:05.000
@@ -620,13 +702,38 @@ player.off('timeupdate', fn)
620
702
 
621
703
  ## Vue 3: `<ITubePlayer>`
622
704
 
705
+ Ядро подтягивается **динамическим импортом** — сам компонент весит ~2 KB gzip и не тащит плеер в главный бандл приложения.
706
+
623
707
  | Что | API |
624
708
  | --- | --- |
625
709
  | Пропсы | `source: VideoSource \| VideoSource[]` (реактивный — смена вызывает `load()`), `options: Omit<PlayerOptions, 'source'>` |
710
+ | `lazy` | **poster-first, player-on-intent**: компонент рендерит декларативную SSR-заглушку (постер уходит в первый HTML и становится LCP-кандидатом — `<ClientOnly>` не нужен), по триггеру качает чанк ядра + CSS и свапает через реактивное состояние. Клик по заглушке = загрузить и играть; фоновые триггеры не автоплеят |
711
+ | `loadOn` | триггер при `lazy`: `'placeholder'` (по умолчанию — любой интерактив в зоне заглушки), `'interaction'`, `'visible'`, `'immediate'` |
712
+ | `adConfig` | `AdsOptions` или **async-фабрика** `() => Promise<AdsOptions \| undefined>` (например, запрос VAST-тега) — выполняется параллельно загрузке чанка, плеер конструируется с готовым конфигом. Приоритетнее `options.adConfig` |
626
713
  | События | Все события `PlayerEventMap` ретранслируются 1:1 (`@timeupdate`, `@aderror`, `@customaction`, …) |
627
714
  | Слоты | `#pauseScreen="{ player, close }"` — контент экрана паузы (scoped: доступ к плееру и `close()`) |
628
715
  | Expose | `player: ShallowRef<Player \| null>` — доступ к ядру: `playerRef.value.player.seek(0)` |
629
716
 
717
+ ```vue
718
+ <!-- Nuxt 3: без ClientOnly, без ручного постера, без createLazyPlayer -->
719
+ <ITubePlayer lazy :source="video" :options="{ muted: true }" :ad-config="fetchVast" @ended="onEnded" />
720
+ ```
721
+
722
+ CSS компонент подгружает сам (self-reference `itube-modern-player/style.css` через exports пакета); ручной импорт `style.css` в приложении продолжает работать и дедуплицируется бандлером — это фолбэк для экзотических сборок.
723
+
724
+ ## Изоморфный шаблон заглушки: `itube-modern-player/placeholder`
725
+
726
+ `renderPlaceholder(options, flags?) → string` — чистая строка без DOM-зависимостей, единый источник правды по разметке «постер + кнопка play». Используется внутри `createLazyPlayer` и Vue-`lazy`; экспортируется для SSR любого фреймворка:
727
+
728
+ ```ts
729
+ import { renderPlaceholder } from 'itube-modern-player/placeholder'
730
+ // options: { poster?, icon?, styling?, className?, playLabel? }
731
+ // flags: { inlineStyles?: true } — самодостаточные инлайн-стили (SSR без style.css)
732
+ const html = renderPlaceholder({ poster: { src: '/p.jpg', width: 1280, height: 720 } }, { inlineStyles: true })
733
+ ```
734
+
735
+ Там же `posterSrc(poster)` — нормализация `PosterSource` в URL.
736
+
630
737
  ## Экспортируемые типы
631
738
 
632
739
  `PlayerOptions, VideoSource, ChannelInfo, SubtitleTrack, Chapter, QualityLevel, PlaylistOptions, ControlsOptions, ActionsOptions, CustomAction, BuiltinActionId, RelatedOptions, RelatedItem, AdsOptions, AdRoll, ResolvedAd, ThumbnailCue, PlayerLabels, IconName, PlayerEvent, PlayerEventMap, Level, SourceController, NormalizedChapter`
@@ -739,13 +846,16 @@ lazy.destroy() // работает на любой стадии
739
846
  </div>
740
847
  ```
741
848
 
742
- 2. **Инициализируйте плеер лениво** — после первого интерактива/появления во вьюпорте, через `itube-modern-player/lazy` (или свой динамический `import`). До инициализации виден ваш серверный постер; JS-бандл и `hls.js` не блокируют первый экран.
849
+ 2. **Инициализируйте плеер лениво** — `createLazyPlayer` **подхватит серверную разметку** (adopt): если в mount-ноде уже есть содержимое, свой плейсхолдер не создаётся вешаются только триггеры, а при загрузке бандла содержимое заменяется плеером. CSS плеера для постера не нужен (разметка ваша).
743
850
 
744
851
  ```ts
745
852
  import { createLazyPlayer } from 'itube-modern-player/lazy'
746
- createLazyPlayer('#player', { source: { src: '/stream.m3u8', poster: '/poster-1280.jpg' }, loadOn: 'interaction' })
853
+ // #player уже содержит SSR-постер adopt, ничего не перерисовывается
854
+ createLazyPlayer('#player', { source: { src: '/stream.m3u8', poster: '/poster-1280.jpg' }, loadOn: 'placeholder' })
747
855
  ```
748
856
 
857
+ **Nuxt 3**: постер — обычная разметка компонента (SSR/SEO-friendly, никакого `<ClientOnly>` вокруг него), а `createLazyPlayer(mountRef.value, …)` — в `onMounted`. `import 'itube-modern-player/style.css'` можно грузить вместе с ленивым чанком, а не в главном бандле — постеру он не нужен.
858
+
749
859
  Итог: LCP — это ваш серверный `<img>`-постер (приходит в первом HTML-ответе, приоритизируется браузером), а вес плеера и стрима подключается только когда пользователь реально собрался смотреть. Так делает и тестовый стенд проекта (постер `<picture><img>` в шаблоне страницы, плеер поверх по интерактиву).
750
860
 
751
861
  > Если задать `source.poster`, плеер отрисует **свой** постер тоже (реальным `<img fetchpriority="high">`). Он сработает как LCP в чисто клиентских сценариях, но для лучшего LCP всё равно предпочтительнее серверный постер из пункта 1 — он есть в HTML сразу, без ожидания JS.
@@ -766,7 +876,8 @@ npm publish # prepublishOnly прогонит typecheck + build
766
876
  | Импорт | Что это |
767
877
  | --- | --- |
768
878
  | `itube-modern-player` | ядро (`Player`, все типы, утилиты) |
769
- | `itube-modern-player/vue` | Vue 3-компонент `ITubePlayer` |
879
+ | `itube-modern-player/vue` | Vue 3-компонент `ITubePlayer` (ядро — динамическим импортом; проп `lazy`) |
880
+ | `itube-modern-player/placeholder` | изоморфный `renderPlaceholder()` + `posterSrc()` (без DOM — можно на сервере) |
770
881
  | `itube-modern-player/style.css` | стили (подключить один раз) |
771
882
 
772
883
  ---
@@ -775,6 +886,19 @@ npm publish # prepublishOnly прогонит typecheck + build
775
886
 
776
887
  Версионирование по [SemVer](https://semver.org/lang/ru/): `major.minor.patch`.
777
888
 
889
+ ### 0.8.0
890
+
891
+ - **Vue-обёртка: ядро — динамическим импортом.** `itube-modern-player/vue` больше не тащит весь плеер в бандл приложения (~2 KB gzip вместо ~30): чанк ядра качается на mount, а с новым пропом **`lazy`** — по интенту. `lazy` = poster-first: декларативная SSR-заглушка (постер в первом HTML, LCP-кандидат, `<ClientOnly>` не нужен), триггер `loadOn` (`'placeholder'` по умолчанию / `'interaction'` / `'visible'` / `'immediate'`), своп через реактивное состояние — никакого чужого DOM. CSS подгружается самим компонентом (self-reference через exports; ручной импорт `style.css` остаётся рабочим фолбэком). NB: `import { Player } from '…/vue'` теперь только тип (рантайм-класс — из главного entry).
892
+ - **`adConfig` как async-фабрика** в lazy-слоях (`createLazyPlayer` и Vue-компонент): `adConfig: () => Promise<AdsOptions | undefined>` выполняется параллельно загрузке чанка — интегратору не нужно самому синхронизировать VAST-запрос с инициализацией.
893
+ - **Изоморфный шаблон заглушки** — новый entry `itube-modern-player/placeholder`: `renderPlaceholder(options, { inlineStyles? }) → string` без DOM-зависимостей (SSR любого фреймворка) + `posterSrc()`. Единый источник правды по разметке: `createLazyPlayer` и Vue-`lazy` рендерят её же — кастомная иконка `bigPlay`, `styling`, постер с полными `<img>`-атрибутами.
894
+ - **`poster` как объект.** `VideoSource.poster: string | { src, srcset?, sizes?, width?, height?, alt? }` — LCP-качество главного постера (responsive srcset, intrinsic-размеры против layout shift); тумбы используют `src`. Строка работает как раньше.
895
+
896
+ ### 0.7.2
897
+
898
+ - **Скип битых роликов в «следующем видео».** Если при подготовке превью следующего ролика (ховер-карточка / end-оверлей) его постер не загрузился (4xx/5xx/сеть) — видео тоже не загрузится: источник помечается мёртвым и все вычисления «следующего» (превью, `next()`, автопереход, `hasNext`) пропускают его, подставляя следующий живой из списка. Карточка перерисовывается на лету; если живых впереди нет — прячется, next-кнопка дизейблится. Публичный метод `player.markSourceDead(source)` — пометить источник вручную. Панель плейлиста по-прежнему показывает все элементы.
899
+ - **lazy: плейсхолдер теперь пиксель-в-пиксель с плеером.** Кнопка play на заглушке использует кастомную иконку `options.icons.bigPlay` (раньше — всегда дефолтный треугольник, «менялась» после загрузки), плюс к заглушке применяются `styling.themeColor` / `borderRadius` / `playButtonStyle: 'inverted'` и `className`.
900
+ - **lazy: SSR-adopt — плейсхолдер можно рендерить на сервере.** Если mount-нода уже содержит разметку (SSR-постер любого вида, свои классы), `createLazyPlayer` **не создаёт** свой плейсхолдер: подхватывает существующий, вешает триггеры и заменяет содержимое плеером при загрузке. SSR-`<img>` остаётся LCP-кандидатом страницы — JS для первой отрисовки не нужен вовсе (лечит просадку Lighthouse/LCP при `ClientOnly`-обёртках в Nuxt/Next).
901
+
778
902
  ### 0.7.1
779
903
 
780
904
  - **`options.autoplay` переименован в `playOnInit`** — «стартовать воспроизведение сразу после инициализации». Новое имя не путается с тоглером Autoplay в шестерёнке (тот — про автопереход, `playlist.autoAdvance`). Старый `autoplay` работает как deprecated-алиас (`playOnInit` приоритетнее); существующие интеграции не ломаются.