itube-modern-player
v0.2.1
Published
Lightweight, framework-agnostic HTML5 video player: HLS streams, playlists, chapters, VTT subtitles, sprite previews, VAST ads. Ships with a Vue 3 wrapper.
Maintainers
Readme
itube-modern-player
Лёгкий универсальный HTML5-видеоплеер на TypeScript для видео-сайтов и встраиваемых страниц. Ядро — чистый класс без фреймворк-зависимостей (new Player(...)), поверх него — Vue 3-компонент, IIFE-бандл для <script>-подключения и lazy-обёртка для отложенной загрузки. Сделан как замена fluid-player: та же область применения (контентные видео с рекламой, плейлистами и стримами), но без его архитектурных болячек — без глобального реестра инстансов, без россыпи absolute-элементов, с честным destroy() и полной типизацией.
Ядро ~19–23 КБ gzip (+ hls.js, который догружается динамически только для m3u8). Поддержка: все вечнозелёные браузеры, Safari/iOS (нативный HLS, playsinline, нативный fullscreen).
Возможности
- Источники — MP4/WebM, HLS-стримы (
.m3u8, hls.js или нативно в Safari), live-потоки с бейджем LIVE, прогрессивные качества с переключением без потери позиции, HLS-уровни с пунктом Auto. - Плейлисты — массив видео без пересоздания плеера, prev/next, панель списка (сайдбар или горизонтальная лента снизу), имя плейлиста, перемешивание, повтор, автопереход.
- Тайм-коды (главы) — сегментированный таймлайн как у YouTube, название главы в контролах и тултипе, событие
chapterchange, источник: массив или VTT-файл. - Панель сцен — список глав с превью из VTT-спрайта, клик — переход к сцене.
- Спрайт-превью — кадр при наведении на таймлайн (WebVTT +
#xywh). - Диаграмма популярности — «most replayed»-кривая над таймлайном из разреженных точек.
- Субтитры — VTT-треки (строка/объект/массив), выключены по умолчанию, меню выбора.
- Реклама — VAST 2–4 (InLine + Wrapper), preroll/midroll/postroll, прямые
src-роллы, skip-таймер, click-through, impression/quartile/pause/resume-пиксели, отдельный<video>(контент не трогается), префетч контента во время преролла, watchdog на зависший креатив, частота показа для плейлистов, полный набор событий. - Метаданные видео — тайтл, описание, постер, канал (аватар/заглушка, форма, ссылка) в экране паузы.
- Экран паузы — дефолтный или полностью свой («слот»: элемент/фабрика в ядре,
<template #pauseScreen>во Vue). - Related-видео — сетка похожих по паузе и/или окончанию.
- Кнопки действий — лайк/дизлайк (с управляемым состоянием), «добавить в», шеринг (нативный share sheet с фолбэком на событие), жалоба, свои кнопки в ⋯-дропдауне (
customaction). - Кастомизация — каждая фича отключаема, каждая иконка заменяема (SVG), все строки переводимы, темизация CSS-переменными +
styling-проп (акцент, цвета лайков). - Локализации — 11 встроенных языков по коду
language, свои словари черезregisterLocale, облегчённый вход без словарей. - UX-мелочи — превью-постер, горячие клавиши (на контейнере, не на document), PiP, полноэкранный режим, спиннер буферизации, оверлей ошибок, авто-скрытие контролов.
- Интеграции — vanilla / Vue 3 /
<script>-тег / ленивая загрузка бандла по интерактиву. - Типизация — полные типы всех опций, событий и методов из коробки.
Чем отличается от fluid-player и подобных:
- Никакой каши из absolute-элементов. Поверх видео лежит один overlay-слой на CSS grid, всё внутри — обычный flex-поток. Верстать поверх и кастомизировать — легко.
- Нет глобального состояния.
new Player()сколько угодно раз на странице, никаких реестров инстансов и привязок по id. - Честный
destroy(). Плеер полностью убирает за собой DOM и слушатели. - Всё типизировано. События, опции, методы — полные типы из коробки.
- Всё отключаемо и заменяемо. Каждая кнопка, каждая надпись, каждая иконка.
Установка
npm install itube-modern-playerhls.js подтягивается автоматически и грузится только когда плееру дают m3u8-источник (динамический импорт) — в бандл проектов без стримов он не попадает. Vue — опциональная peer-зависимость, нужна только если используете itube-modern-player/vue.
Быстрый старт (vanilla JS / TS)
Ядро — обычный класс без фреймворков, это основной способ использования.
import { Player } from 'itube-modern-player'
import 'itube-modern-player/style.css'
const player = new Player('#mount', {
source: {
src: 'https://cdn.example.com/stream.m3u8',
title: 'Название ролика',
poster: 'https://cdn.example.com/poster.jpg',
},
})
player.on('ended', () => console.log('done'))
// ...
player.destroy()Первый аргумент — элемент или селектор контейнера, в который плеер отрендерит себя. destroy() возвращает контейнер в исходное состояние.
Без сборщика (script-tag / CDN)
Отдельный IIFE-бандл, глобальная переменная ITubePlayer — это сам класс:
<link rel="stylesheet" href="https://unpkg.com/itube-modern-player/dist/style.css">
<!-- hls.js нужен только если будут m3u8-источники: -->
<script src="https://unpkg.com/hls.js"></script>
<script src="https://unpkg.com/itube-modern-player/dist/itube-modern-player.iife.js"></script>
<script>
const player = new ITubePlayer('#mount', {
source: { src: 'https://cdn.example.com/stream.m3u8', title: 'Demo' },
})
</script>Быстрый старт (Vue 3)
<script setup lang="ts">
import { ITubePlayer } from 'itube-modern-player/vue'
import 'itube-modern-player/style.css'
import type { VideoSource } from 'itube-modern-player'
const video: VideoSource = {
src: '/stream.m3u8',
title: 'Название',
}
</script>
<template>
<ITubePlayer
:source="video"
:options="{ seekStep: 5, controls: { pip: false } }"
@ended="onEnded"
@timeupdate="onTime"
>
<!-- слот экрана паузы: реклама, промо, что угодно.
Scoped-слот даёт доступ к плееру и к close() ("Закрыть и продолжить") -->
<template #pauseScreen="{ player, close }">
<MyAdBlock @close="close" />
</template>
</ITubePlayer>
</template>Кастомный экран паузы (рекламный блок)
Вместо дефолтного оверлея (тайтл/описание/канал) можно показать свой блок — например рекламу с кнопкой «Закрыть и продолжить». Контент рендерится full-cover по центру плеера поверх контролов; клик по фону не возобновляет (только ваша кнопка). Доступ к плееру есть во всех сборках:
- Vue 3 — scoped-слот:
#pauseScreen="{ player, close }".player— инстанс (реактивный,nullдо монтирования),close()— возобновить воспроизведение и закрыть блок. - vanilla / IIFE — фабрика, получающая плеер:
pauseScreen: (player) => HTMLElement. Кнопка вызываетplayer.play(). - В рантайме —
player.setPauseScreenContent(el | null).
// vanilla / Vue2 (внутри компонента)
new Player('#mount', {
pauseScreen: (player) => {
const box = document.createElement('div')
box.innerHTML = '<!-- ваш рекламный блок -->'
const btn = document.createElement('button')
btn.textContent = 'Закрыть и продолжить'
btn.onclick = () => player.play() // возобновляет и скрывает блок
box.append(btn)
return box
},
}):source— одно видео или массив (массив включает режим плейлиста). Реактивен: смена объекта вызываетplayer.load(), инстанс плеера не пересоздаётся.:options— все остальныеPlayerOptions(см. ниже).- Все события плеера ретранслируются как события компонента.
- Доступ к ядру:
refна компонент →componentRef.value.player(ShallowRef<Player>).
Описание видео: VideoSource
const source: VideoSource = {
src: 'https://cdn.example.com/video.m3u8', // m3u8 определяется автоматически
type: 'application/x-mpegurl', // необязательно, для нестандартных URL
title: 'Заголовок',
description: 'Описание — показывается в экране паузы',
poster: 'poster.jpg', // превью-постер до первого запуска
duration: 1284, // для плашки в плейлисте до загрузки метаданных
// канал/автор — рендерится в экране паузы
channel: {
name: 'Мой канал',
avatar: 'avatar.png', // без avatar — стилизованная заглушка из первой буквы
url: 'https://example.com/channel', // клик по каналу откроет URL
avatarShape: 'circle', // 'circle' (default) | 'rounded' | 'square'
},
// тайм-коды: массив или URL WebVTT-файла глав
chapters: [
{ start: 0, title: 'Интро' },
{ start: 120, title: 'Основная часть' }, // end заполняется автоматически
],
// субтитры: строка-URL, один трек или массив. По умолчанию ВЫКЛЮЧЕНЫ.
subtitles: [
{ src: 'subs-ru.vtt', label: 'Русский', srclang: 'ru' },
{ src: 'subs-en.vtt', label: 'English', srclang: 'en', default: true }, // default включает
],
// спрайт-превью при наведении на таймлайн (WebVTT с фрагментами #xywh=)
thumbnails: 'thumbs.vtt',
// диаграмма популярности над таймлайном (как "most replayed" у YouTube)
// разреженные точки {time: сек, value: сырое число кликов/досмотров};
// внутри: бакетирование → сглаживание → нормализация к максимуму с базовым полом,
// появляется при наведении на прогресс-бар; выключается controls.heatmap = false
heatmap: [
{ time: 95, value: 220 },
{ time: 290, value: 900 },
{ time: 430, value: 540 },
],
// альтернативные качества для прогрессивных файлов (для HLS уровни берутся из манифеста)
// переключение сохраняет позицию и состояние воспроизведения
qualities: [
{ quality: 1080, label: '1080p', src: 'video-1080.mp4' },
{ quality: 720, label: '720p', src: 'video-720.mp4' },
],
meta: { anyOwnData: true }, // ваши данные, плеер их не трогает
}Тайм-коды (главы)
Поддержаны нативно: сегментированный таймлайн (как у YouTube), название текущей главы в контрол-баре, название главы в ховер-тултипе, событие chapterchange. Источник — массив { start, end?, title } либо URL VTT-файла глав. end не обязателен — берётся начало следующей главы или конец видео.
Спрайт-превью (thumbnails)
Стандартный формат — WebVTT, каждый cue указывает картинку и регион спрайта:
WEBVTT
00:00:00.000 --> 00:00:05.000
sprite.jpg#xywh=0,0,160,90
00:00:05.000 --> 00:00:10.000
sprite.jpg#xywh=160,0,160,90Относительные пути резолвятся от URL VTT-файла. Можно и без #xywh — по картинке на cue.
Все опции: PlayerOptions
new Player('#mount', {
source, // VideoSource | VideoSource[]
autoplay: false,
muted: false,
loop: false,
volume: 1,
playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 2], // пункты меню скорости
seekStep: 10, // секунды для стрелок/кнопок перемотки
keyboard: true, // горячие клавиши (на контейнере, не на document!)
className: 'my-player', // свой класс на контейнер — хук для темизации
crossOrigin: 'anonymous', // если VTT/постеры на другом домене
playsInline: true,
// ---- стилизация ----
styling: {
themeColor: '#00b3a4', // акцентный цвет (вместо встроенного красного)
likeColor: 'forestgreen', // цвет активного лайка (default forestgreen)
dislikeColor: '#e53935', // цвет активного дизлайка (не зависит от themeColor)
borderRadius: 14, // скругление углов плеера (число → px), '0' — квадратные
playButtonStyle: 'solid', // 'solid' (акцентный круг, белая иконка) | 'inverted' (полупрозрачный белый круг, иконка цвета темы)
},
// ---- язык интерфейса ----
// встроенные статические локали: en, ru, de, es, it, ja, ko, zh, pt, ar, hi
// словарь терминов = тип PlayerLabels; defaultLabels (en) экспортируется как эталон
// точечные оверрайды — через labels (применяются поверх локали)
language: 'ru',
// ---- плейлист ----
playlist: {
title: 'Моя подборка', // имя плейлиста: маленький кикер "Плейлист" + имя приоритетным шрифтом
autoAdvance: true, // автопереход к следующему
loop: false, // репит списка (кнопка в панели плейлиста)
shuffle: false, // перемешивание (кнопка в панели плейлиста)
startIndex: 0,
layout: 'sidebar', // 'sidebar' — справа вертикально | 'bottom' — горизонтальная лента снизу
},
// ---- панель сцен (тайм-коды с превью из VTT-спрайта) ----
scenes: {
layout: 'bottom', // 'bottom' (default) | 'sidebar'
},
// ---- контролы: каждая фича отключаемая ----
controls: {
play: true,
progress: true,
time: true,
volume: true,
fullscreen: true,
pip: true,
settings: true, // меню скорости (иконка-спидометр)
quality: false, // меню качества (иконка-шестерёнка); ВЫКЛЮЧЕНО по умолчанию,
// кнопка появляется при наличии source.qualities или HLS-уровней
scenes: true, // кнопка списка сцен (появляется при наличии chapters)
heatmap: true, // диаграмма популярности (нужны данные source.heatmap)
subtitles: true, // кнопка появляется только если у источника есть треки
seekButtons: true, // или { back: 5, forward: 15 }
playlist: true, // prev/next/список — рендерятся ТОЛЬКО в режиме плейлиста
hideDelay: 2500, // мс до скрытия контролов при воспроизведении
},
// ---- экран паузы ----
// true (по умолчанию) — заголовок/описание/канал текущего видео
// false — выключить
// HTMLElement или (player) => HTMLElement — свой контент (full-cover оверлей,
// напр. реклама с «Закрыть и продолжить»; фабрика получает плеер).
// Во Vue — scoped-слот #pauseScreen="{ player, close }". См. раздел про кастомный экран паузы.
pauseScreen: true,
// ---- related-видео ----
related: {
title: 'Смотрите также',
showOn: ['ended', 'pause'], // когда показывать; по умолчанию ['ended']
// что делает клик (событие relatedclick эмитится в любом случае):
// 'player' (default) — загрузить source в плеер | 'newWindow' — открыть url в новой вкладке | 'currentTab' — перейти в текущей
clickBehavior: 'player',
items: [
{
title: 'Другой ролик',
poster: 'p.jpg',
duration: '12:34',
source: { src: 'other.mp4', title: 'Другой ролик' }, // клик загрузит в плеер
// или url: 'https://...' — клик откроет ссылку
},
],
},
// ---- реклама (см. раздел «Реклама») ----
adConfig: {
adList: [
{ roll: 'preRoll', vastTag: 'https://ads.example.com/vast.xml' },
{ roll: 'midRoll', vastTag: 'https://ads.example.com/vast2.xml', timer: 300 },
{ roll: 'postRoll', src: 'https://cdn.example.com/ad.mp4', clickUrl: 'https://sponsor.example.com' },
],
skipDelay: 5, // сек до кнопки «Пропустить»; -1 — без пропуска
playOn: 'every', // 'every' — реклама на каждом видео плейлиста, 'first' — только на первом
maxWrapperDepth: 3, // лимит VAST Wrapper-редиректов
requestTimeout: 8000, // таймаут запроса VAST-тега, мс
mediaTimeout: 10000, // если креатив не стартовал/завис на столько мс — aderror и сразу контент
},
// ---- кнопки действий (все по умолчанию ВЫКЛЮЧЕНЫ) ----
actions: {
like: true, // видимая кнопка 👍 → событие action {id:'like'}
dislike: true, // видимая кнопка 👎 → событие action {id:'dislike'}
likeState: 'like', // начальное состояние оценки ('like' | 'dislike' | null);
// менять в рантайме: player.setLikeState(...)
addTo: true, // в дропдауне ⋯ → action {id:'addTo'}
share: true, // в дропдауне ⋯: нативный share-диалог устройства,
// если его нет — событие action {id:'share'}
report: true, // в дропдауне ⋯ → action {id:'report'}
// свои пункты дропдауна: клик эмитит customaction {id}
custom: [
{ id: 'download', title: 'Скачать', icon: '<svg ...>...</svg>' }, // icon: URL картинки или raw SVG
],
},
// ---- кастомизация каждой кнопки ----
icons: {
play: '<svg viewBox="0 0 24 24">...</svg>', // любая иконка — ваш SVG
pause: '<svg ...>...</svg>',
// полный список имён: тип IconName
},
// ---- все надписи (i18n) ----
labels: {
play: 'Смотреть',
skipAd: 'Пропустить рекламу',
related: 'Похожие видео',
// полный список: тип PlayerLabels
},
})IconName: play, pause, replay, bigPlay, volumeHigh, volumeLow, volumeMute, fullscreen, fullscreenExit, pip, settings, subtitles, list, next, previous, seekForward, seekBack, close.
Реклама
Формат списка совместим по духу с fluid-player: массив роллов, каждый — preRoll, midRoll (с timer — секундой срабатывания) или postRoll. Источник ролла:
vastTag— URL VAST-тега. Поддержано: VAST 2/3/4, InLine + Wrapper (редиректы с лимитом глубины), выбор MediaFile (прогрессивный mp4/webm приоритетнее),ClickThrough,Impression, квартильныеTrackingEvents(start / firstQuartile / midpoint / thirdQuartile / complete / skip / click),skipoffset(время или процент). VPAID не поддержан намеренно — это исполнение стороннего JS в вашей странице.src— прямой URL медиафайла без VAST (плюс опциональныйclickUrl).
Ключевое отличие от fluid-player: реклама играет в отдельном <video>, наложенном поверх. Контентное видео не трогается — его позиция, HLS-сессия, выбранные субтитры и качество переживают любой рекламный брейк без «восстановления состояния».
Поведение:
preRoll— перед стартом видео,midRoll— на секундеtimer,postRoll— после окончания; роллов каждого типа может быть несколько.playOn: 'first'— рекламный список отрабатывает только на первом видео плейлиста;'every'(по умолчанию) — на каждом.- Любой фейл (недоступный тег, битый XML, неиграющий медиафайл, таймаут) → событие
aderrorи немедленный переход к контенту. Реклама никогда не блокирует ролик. - Кнопка пропуска появляется через
skipDelayсекунд (илиskipoffsetиз VAST, если он есть). - Префетч контента: пока играет преролл, основное видео буферизует первые фрагменты в фоне (
preload="auto"на время брейка; для HLS hls.js грузит сегменты сразу после attach) — после рекламы контент стартует мгновенно.
События: adstart, adend, adskip, adclick, aderror.
player.on('aderror', ({ roll, error }) => report(roll, error))Справочник API
Конструктор
new Player(target: string | HTMLElement, options?: PlayerOptions)target — элемент-контейнер или CSS-селектор. Плеер рендерит свой DOM внутрь и полностью убирает его в destroy(). Инстансов на странице — сколько угодно, глобального состояния нет.
Свойства
| Свойство | Тип | Описание |
| --- | --- | --- |
| container | HTMLElement | Корневой элемент плеера (.imp-player) |
| video | HTMLVideoElement | Контентный видео-элемент (прямой доступ для нестандартных нужд) |
| paused | boolean | На паузе |
| ended | boolean | Дошло до конца |
| currentTime | number | Текущая секунда |
| duration | number | Длительность (0, пока метаданные не загружены) |
| live | boolean | Live-поток (duration === Infinity) |
| bufferedEnd | number | Конец буферизованного диапазона, сек |
| volume | number | Громкость 0..1 |
| muted | boolean | Заглушен |
| playbackRate | number | Текущая скорость |
| playbackRates | number[] | Доступные скорости (из опций) |
| qualityLevels | Level[] | { index, label }[] — HLS-уровни или прогрессивные качества |
| currentQuality | number | Индекс выбранного качества, -1 = auto |
| qualityAutoAvailable | boolean | Есть ли пункт Auto (только HLS) |
| subtitleTracks | SubtitleTrack[] | Треки текущего источника |
| activeSubtitle | number | Индекс включённого трека, -1 = выкл |
| playlist | VideoSource[] | Текущий список |
| index | number | Индекс играющего элемента |
| source | VideoSource \| null | Текущий источник |
| hasPlaylist | boolean | Больше одного элемента |
| hasNext / hasPrevious | boolean | Есть куда листать (учитывает playlist.loop) |
| chapterList | NormalizedChapter[] | Главы текущего видео (после загрузки метаданных) |
| chapter | NormalizedChapter \| null | Текущая глава |
| isFullscreen | boolean | Полноэкранный режим |
| adPlaying | boolean | Идёт рекламный брейк |
Методы
Воспроизведение
| Метод | Описание |
| --- | --- |
| play(): Promise<void> | Запуск. Если есть несыгранный преролл — сначала отыграет его |
| pause(): void | Пауза (во время рекламы игнорируется) |
| togglePlay(): void | Переключить |
| seek(sec: number): void | Абсолютная перемотка (клампится в 0..duration) |
| skip(delta: number): void | Относительная перемотка, например skip(-10) |
Громкость и скорость
| Метод | Описание |
| --- | --- |
| setVolume(v: number): void | 0..1; значение > 0 снимает mute |
| setMuted(m: boolean): void | Установить mute |
| toggleMute(): void | Переключить mute |
| setPlaybackRate(r: number): void | Скорость воспроизведения |
Качество и субтитры
| Метод | Описание |
| --- | --- |
| setQuality(index: number): void | Для HLS -1 = auto; для прогрессивных источников переключает файл с сохранением позиции |
| setSubtitle(index: number): void | Включить трек по индексу, -1 — выключить |
Плейлист и источники
| Метод | Описание |
| --- | --- |
| load(source, startIndex = 0): void | Заменить источник(и) без пересоздания плеера — громкость, скорость, подписки и DOM сохраняются. Массив включает режим плейлиста |
| next(): void / previous(): void | Переход по плейлисту (учитывает loop и shuffle) |
| playItem(i: number): void | Запустить конкретный элемент |
| togglePlaylistPanel(): void | Показать/скрыть панель списка |
| toggleScenesPanel(): void | Показать/скрыть панель сцен |
| setShuffle(b) / shuffle | Режим перемешивания |
| setRepeat(b) / repeat | Режим повтора списка |
UI и прочее
| Метод | Описание |
| --- | --- |
| toggleFullscreen(): Promise<void> | Полный экран (на iOS — нативный fullscreen видео) |
| togglePip(): Promise<void> | Картинка-в-картинке |
| setPauseScreenContent(el: HTMLElement \| null): void | Подменить контент экрана паузы (то, что во Vue делает слот) |
| setLikeState(s: 'like' \| 'dislike' \| null): void | Подсветить лайк/дизлайк |
| share(): Promise<void> | Нативный share-диалог; без него — событие action {id:'share'} |
| destroy(): void | Полный демонтаж: DOM, слушатели, hls-сессия, реклама |
События
const off = player.on('timeupdate', fn) // вернёт функцию отписки
player.once('ready', fn)
player.off('timeupdate', fn)События: PlayerEventMap
| Событие | Payload | Когда |
| --- | --- | --- |
| ready | { player } | Плеер создан |
| play / pause / ended | — | Воспроизведение |
| timeupdate | { currentTime, duration } | Тик времени |
| progress | { buffered } | Буферизация |
| volumechange | { volume, muted } | Громкость |
| ratechange | { rate } | Скорость |
| seeking / seeked | { currentTime } | Перемотка |
| sourcechange | { source, index } | Источник заменён |
| playlistitemchange | { source, index } | Переход по плейлисту |
| chapterchange | { chapter \| null } | Сменилась глава |
| fullscreenchange | { active } | Полный экран |
| pipchange | { active } | PiP |
| qualitychange | { label } | Качество (в т.ч. авто-переключение HLS) |
| subtitlechange | { track \| null } | Субтитры |
| relatedshow | — | Показана сетка related |
| relatedclick | { item } | Клик по related-карточке |
| action | { id } | Кнопки: like, dislike, addTo, share (если нет нативного шеринга), report |
| customaction | { id } | Ваша кнопка из actions.custom |
| adstart / adend / adskip / adclick | { ad: ResolvedAd } | Жизненный цикл рекламы |
| adpause / adresume | { ad: ResolvedAd } | Креатив поставлен на паузу / возобновлён (+ VAST-пиксели pause/resume) |
| aderror | { roll, error } | Ролл не отыграл (контент продолжается автоматически) |
| error | { message, cause? } | Ошибка медиа/сети/треков |
| destroy | — | Плеер демонтирован |
Vue 3: <ITubePlayer>
| Что | API |
| --- | --- |
| Пропсы | source: VideoSource \| VideoSource[] (реактивный — смена вызывает load()), options: Omit<PlayerOptions, 'source'> |
| События | Все события PlayerEventMap ретранслируются 1:1 (@timeupdate, @aderror, @customaction, …) |
| Слоты | #pauseScreen="{ player, close }" — контент экрана паузы (scoped: доступ к плееру и close()) |
| Expose | player: ShallowRef<Player \| null> — доступ к ядру: playerRef.value.player.seek(0) |
Экспортируемые типы
PlayerOptions, VideoSource, ChannelInfo, SubtitleTrack, Chapter, QualityLevel, PlaylistOptions, ControlsOptions, ActionsOptions, CustomAction, BuiltinActionId, RelatedOptions, RelatedItem, AdsOptions, AdRoll, ResolvedAd, ThumbnailCue, PlayerLabels, IconName, PlayerEvent, PlayerEventMap, Level, SourceController, NormalizedChapter
Экспортируемые утилиты
| Экспорт | Что делает |
| --- | --- |
| formatTime(sec) | 3673 → "1:01:13" |
| locales, supportedLanguages, getLocale(code) | Встроенные локали (11 языков) и их коды |
| buildHeatmapValues(points, duration), heatmapPath(values) | Математика диаграммы популярности |
| isHlsSource(src, type?) | Определение m3u8 |
| normalizeChapters(chapters, duration) | Сортировка/заполнение end |
| loadChaptersVtt(url) | VTT-файл глав → Chapter[] |
| ThumbnailTrack.load(url) | Парсер спрайт-VTT (cueAt(time)) |
| resolveVast(roll, opts) | Самостоятельное использование VAST-резолвера |
| defaultIcons, defaultLabels | Дефолты для частичного переопределения |
Темизация
Внешний вид настраивается CSS-переменными — на контейнере плеера или любом родителе:
.my-player {
--imp-accent: #00bcd4; /* цвет прогресса, кнопки play, активных пунктов */
--imp-bg: #000;
--imp-text: #fff;
--imp-control-bg: rgba(20, 20, 20, 0.85); /* фон меню/панелей */
--imp-track: rgba(255, 255, 255, 0.25); /* фон таймлайна */
--imp-radius: 8px;
--imp-font: Inter, sans-serif;
--imp-transition: 180ms ease;
}Все классы стабильны и начинаются с imp- — можно дотюнить точечно обычным CSS.
Горячие клавиши
Слушаются на контейнере плеера (не на document — несколько плееров на странице не конфликтуют). Space/K — play/pause, ←/→ — перемотка на seekStep, ↑/↓ — громкость, M — mute, F — полный экран, 0–9 — переход на 0–90% длительности, Home/End.
Стримы
*.m3u8(илиtype: 'application/x-mpegurl') → hls.js, который загружается динамически при первом использовании; в Safari используется нативный HLS.- По умолчанию подтягивается light-сборка
hls.js/light(без alt-audio/субтитров/EME/LL — заметно легче полной) — достаточно для обычного VOD m3u8. Если нужна полная сборка, положите её вwindow.Hls(плеер подхватит готовый глобал и не будет грузить свой). - Live-потоки: автоматически определяются (
duration === Infinity), вместо таймкода показывается бейдж LIVE, таймлайн скрывается. - Качества HLS-уровней попадают в меню настроек (с пунктом Auto).
Локализации и вес бандла
Главный вход itube-modern-player включает все 11 локалей (+~4 КБ gzip) — language: 'ru' работает без настройки. Если важен каждый килобайт, есть облегчённый вход без словарей (в ядре остаётся только английский):
import { Player, registerLocale } from 'itube-modern-player/core' // −4 КБ gzip
import { locales } from 'itube-modern-player/locales' // словари отдельным чанком
registerLocale('ru', locales.ru) // регистрируем только нужное
new Player('#mount', { language: 'ru' })registerLocale принимает и собственные словари — любой объект формы PlayerLabels под любым кодом. Незарегистрированный код тихо откатывается к английскому.
| Вход | gzip | Локали |
| --- | --- | --- |
| itube-modern-player | ~23 КБ | все 11 встроены |
| itube-modern-player/core | ~19 КБ | только en, остальное вручную |
| itube-modern-player/locales | ~4 КБ | только словари (отдельный чанк) |
Ленивая загрузка бандла
Точка входа itube-modern-player/lazy (~1.5 КБ) для паттерна «бандл плеера грузим после первого интерактива». Мгновенно рисует постер-заглушку (использует те же CSS-классы — подмена незаметна), а полный чанк плеера подтягивает динамическим импортом:
import { createLazyPlayer } from 'itube-modern-player/lazy'
import 'itube-modern-player/style.css'
const lazy = createLazyPlayer('#mount', {
source: { src: 'stream.m3u8', poster: 'poster.jpg', title: '…' },
// когда качать бандл:
loadOn: 'interaction', // первый pointerdown/keydown/touch/wheel на странице (по умолчанию)
// loadOn: 'visible', // когда заглушка вошла во вьюпорт (IntersectionObserver)
// loadOn: 'immediate' // сразу (но отдельным чанком)
})
lazy.ready.then((player) => { /* полный Player готов */ })
lazy.destroy() // работает на любой стадииКлик по заглушке всегда грузит бандл немедленно и запускает воспроизведение.
Если нужен полный контроль снаружи — он никуда не делся: обычный import('itube-modern-player') по любому вашему триггеру, ядро не делает никаких предположений о моменте своей загрузки.
SSR и LCP (рекомендованный паттерн)
Сам плеер рендерится на клиенте — он целиком конструирует свой DOM через JS (document/HTMLVideoElement), на сервере его «отрисовать» нельзя, как и любой императивный JS-плеер. Это нормально: на сервере у вас и так нет видео, нужен лишь быстрый постер. Поэтому правильный паттерн — постер серверный, плеер ленивый:
Отрендерьте постер на сервере обычным
<img>(неbackground-image— фон не участвует в LCP и грузится позже). Сделайте его LCP-кандидатом:fetchpriority="high", корректныеwidth/height(чтобы не было layout shift), при необходимости<picture>сsrcsetпод мобайл/десктоп.<div id="player" class="my-player-box"> <img src="/poster-1280.jpg" srcset="/poster-480.jpg 480w, /poster-1280.jpg 1280w" sizes="100vw" width="1280" height="720" alt="" fetchpriority="high" decoding="async" class="my-player-poster"> <button class="my-player-play" aria-label="Play"></button> </div>Инициализируйте плеер лениво — после первого интерактива/появления во вьюпорте, через
itube-modern-player/lazy(или свой динамическийimport). До инициализации виден ваш серверный постер; JS-бандл иhls.jsне блокируют первый экран.import { createLazyPlayer } from 'itube-modern-player/lazy' createLazyPlayer('#player', { source: { src: '/stream.m3u8', poster: '/poster-1280.jpg' }, loadOn: 'interaction' })
Итог: LCP — это ваш серверный <img>-постер (приходит в первом HTML-ответе, приоритизируется браузером), а вес плеера и стрима подключается только когда пользователь реально собрался смотреть. Так делает и тестовый стенд проекта (постер <picture><img> в шаблоне страницы, плеер поверх по интерактиву).
Если задать
source.poster, плеер отрисует свой постер тоже (реальным<img fetchpriority="high">). Он сработает как LCP в чисто клиентских сценариях, но для лучшего LCP всё равно предпочтительнее серверный постер из пункта 1 — он есть в HTML сразу, без ожидания JS.
Сборка и публикация
npm run dev # демо-страница на vite
# /?proxyads — гонит VAST-теги через дев-прокси /__vast
# (для окружений, где рекламные домены режутся сетью/блокировщиком)
npm run typecheck # tsc --noEmit
npm run build # dist/: ESM + CJS + d.ts + style.css
npm publish # prepublishOnly прогонит typecheck + buildЭкспорты пакета:
| Импорт | Что это |
| --- | --- |
| itube-modern-player | ядро (Player, все типы, утилиты) |
| itube-modern-player/vue | Vue 3-компонент ITubePlayer |
| itube-modern-player/style.css | стили (подключить один раз) |
