itube-modern-player
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).
Содержание
- Возможности
- Установка
- Быстрый старт (vanilla JS / TS)
- Быстрый старт (Vue 3)
- Описание видео:
VideoSource - Все опции:
PlayerOptions - Реклама
- Конструктор
- Свойства
- Методы
- События:
PlayerEventMap - Vue 3:
<ITubePlayer> - Экспортируемые типы
- Экспортируемые утилиты
- Темизация
- Горячие клавиши
- Стримы
- Локализации и вес бандла
- Ленивая загрузка бандла
- SSR и LCP (рекомендованный паттерн)
- Сборка и публикация
- История изменений
Возможности
- Источники — MP4/WebM, HLS-стримы (
.m3u8, hls.js или нативно в Safari), live-потоки с бейджем LIVE, прогрессивные качества с переключением без потери позиции, HLS-уровни с пунктом Auto. - Плейлисты — массив видео без пересоздания плеера, prev/next, панель списка (сайдбар или горизонтальная лента снизу), имя плейлиста, перемешивание, повтор, автопереход.
- Тайм-коды (главы) — сегментированный таймлайн как у YouTube, название главы в контролах и тултипе, событие
chapterchange, источник: массив или VTT-файл. - Панель сцен — список глав с превью из VTT-спрайта, клик — переход к сцене.
- Типизированные сцены — несколько разбивок по типам (актёры/локации/действия…,
sceneGroups), контрол-дропдаун выбора типа перестраивает таймлайн и список сцен. - Спрайт-превью — кадр при наведении на таймлайн (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, // для плашки в плейлисте до загрузки метаданных
// чанки для превью следующего ролика (ховер + end-оверлей), разделяются «·».
// строка — обычный чанк; { text, icon } — чанк с инлайн-иконкой слева (сырой SVG).
previewMeta: ['2.3K просмотров', { text: 'User uploaded', icon: '<svg…></svg>' }],
// канал/автор — рендерится в экране паузы
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 заполняется автоматически
],
// типизированные сцены: несколько разбивок по типам (актёры/локации/действия…)
// переключаются контролом «тип сцен»; активная группа задаёт сегменты таймлайна
// и список сцен (имеет приоритет над chapters). Список типов открытый.
sceneGroups: [
{
id: 'actions', // стабильный id
title: 'Действия', // подпись на контроле и в дропдауне
icon: '<svg ...>...</svg>', // raw SVG или URL картинки (необязательно)
scenes: [ // те же {start, end?, title}, что у chapters
{ start: 0, title: 'Интро' },
{ start: 120, title: 'Погоня' },
],
},
{ id: 'locations', title: 'Локации', icon: '...', scenes: [/* … */] },
],
// субтитры: строка-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 не обязателен — берётся начало следующей главы или конец видео.
Типизированные сцены (sceneGroups)
Несколько разбивок видео по типам — актёры, локации, действия и т.п. (список открытый, задаётся по id). Каждая группа: { id, title, icon?, scenes: Chapter[] }. В баре появляется контрол выбора типа (иконка + тайтл текущего типа, дропдаун); переключение перестраивает сегменты прогресс-бара и панель сцен. Активная группа имеет приоритет над chapters.
- Управление в рантайме:
player.setSceneGroup(id), геттерыplayer.sceneTypes/player.activeSceneType, событиеscenetypechange. - Отключить контрол:
controls.sceneTypes: false. Заголовок дропдауна — лейблsceneTypes(локализован; переопределяется черезlabels).
Спрайт-превью (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: 15, // секунды для стрелок/кнопок перемотки (по умолчанию 15; показывается на кнопке)
keyboard: true, // горячие клавиши (на контейнере, не на document!)
className: 'my-player', // свой класс на контейнер — хук для темизации
crossOrigin: 'anonymous', // если VTT/постеры на другом домене
playsInline: true,
// ---- сохранение настроек в localStorage ----
// true — запоминать громкость/mute и автоплей и восстанавливать при следующей
// загрузке (восстановленные значения имеют приоритет над volume/muted/autoAdvance).
// Объект — точечно: { key?: 'itube-player', volume?: true, autoAdvance?: true }.
// По умолчанию выключено.
persist: 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,
// Настройки. Каждая: 'gear' (в едином дропдауне-шестерёнке, по умолчанию) | 'bar'
// (отдельной кнопкой в баре) | false (выкл). true = 'gear'. Шестерёнка скрыта,
// если внутри нет ни одной доступной опции.
speed: 'gear', // скорость воспроизведения (бывш. `settings`; алиас ещё работает)
quality: 'gear', // качество; пункт/кнопка появляется при source.qualities или HLS-уровнях
subtitles: 'gear', // субтитры; появляется только если у источника есть треки
scenes: true, // кнопка списка сцен (появляется при наличии chapters)
sceneTypes: true, // контрол выбора типа сцен (появляется при source.sceneGroups)
heatmap: true, // диаграмма популярности (нужны данные source.heatmap)
seekButtons: true, // или { back: 5, forward: 15, label: (sec, dir) => `${dir==='back'?'−':'+'}${sec}s` }
// порядок правых контролов слева направо; пусто/нет — встроенный порядок.
// перечисленные идут первыми, остальные сохраняют дефолтную позицию, ⋯ всегда последняя.
order: ['gear', 'pip', 'fullscreen'], // ControlBarItem[]: like|dislike|speed|quality|subtitles|gear|scenes|sceneTypes|playlist|pip|fullscreen|`custom:<id>`
seekPlacement: 'overlay', // 'overlay' (default) — ±N и play поверх видео на всех экранах
// (prev/next в оверлее только на мобиле; на десктопе play/prev/next
// в баре) | 'bar' — seek-кнопки в нижнем баре (поведение до 0.3)
playlist: true, // prev/next/список — рендерятся ТОЛЬКО в режиме плейлиста
hidePrev: true, // скрыть кнопку «prev» в баре (бар = только next); false — вернуть.
// На мобиле центр-кластер всё равно показывает prev
nextPreview: true, // ховер-превью следующего ролика над кнопкой next (десктоп).
// объект: { thumbnail?, title?, duration?, meta? } — какие поля показывать
// (иконки чанков задаются в самих source.previewMeta — см. { text, icon })
hideDelay: 2500, // мс до скрытия контролов при воспроизведении
},
// ---- экран паузы ----
// true (по умолчанию) — заголовок/описание/канал текущего видео (порядок: спонсор → тайтл → описание)
// false — выключить
// { title?, description?, sponsor? } — дефолтный экран, но скрыть отдельные части
// (напр. { title: false, description: 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>' }, // в дропдауне ⋯ (placement: 'menu' по умолчанию)
{ id: 'theater', title: 'Театр', icon: '<svg ...>', placement: 'bar' }, // отдельной кнопкой в баре (нужна icon, тултип = title)
],
},
// ---- кастомизация каждой кнопки ----
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 |
Показать/скрыть панель сцен |
setSceneGroup(id): void |
Переключить тип сцен; sceneTypes / activeSceneType — геттеры |
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 } |
Сменилась глава |
scenetypechange |
{ group | null } |
Сменился тип сцен (sceneGroups) |
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 |
стили (подключить один раз) |
История изменений
Версионирование по SemVer: major.minor.patch.
0.4.4
- Акцент на текущем времени в контрол-баре. Лейбл времени разбит на текущее (
00:31) и общее (/ 6:33); часть со слешем и длительностью приглушена (opacity: 0.6), чтобы взгляд цеплялся за текущую позицию.
0.4.3
- Иконки чанков
previewMeta(инлайн, per-chunk).previewMetaтеперь принимает не только строки, но и{ text, icon }— иконка привязана к конкретному чанку и рисуется слева именно от него (как «человечек» у «Загружено пользователем»), а не одним глифом слева от всего блока. Тип —PreviewMetaItem = string | { text, icon }. Мета-строка переносится пословно (инлайн-поток), а не целым чанком; разделитель «·» приклеен к концу чанка и не орфанится в начале строки. - Превью следующего ролика по ховеру получило ту же мета-строку, что и end-оверлей (единый рендер), вместе с иконками чанков. Добавлены дефолтные иконки
user/channelв набор (options.icons).
0.4.2
- Сохранение настроек в
localStorage. Новая опцияpersistзапоминает громкость/mute и автоплей-next и восстанавливает их при следующей загрузке.true— включить всё с дефолтами; объект{ key?, volume?, autoAdvance? }— точечно (свой ключ хранилища, отдельные тумблеры). По умолчанию выключено. Восстановленные значения имеют приоритет надvolume/muted/playlist.autoAdvance. Запись best-effort (приватный режим / SSR / квота не роняют плеер).
0.4.1
- Фикс: end-оверлей «Играть дальше» не кликался. Слой
.imp-upnextнаследовалpointer-events: noneот родителя — клик проходил сквозь оверлей на<video>и просто снимал текущий ролик с паузы (выглядело как «играет то же самое видео»). Теперь оверлей перехватывает клики, курсор над кнопкой —pointer. - Заголовок end-оверлея — «NEXT VIDEO». Вынесен в отдельный лейбл
nextVideo(раньше использовалсяnext), локализован во всех 11 языках; переопределяется черезoptions.labels.nextVideo. - Иконка аплоадера в end-оверлее «Следующее видео» (доработана в 0.4.3 до per-chunk и распространена на ховер-превью — см. выше).
0.4.0
- Типизированные сцены.
VideoSource.sceneGroups— массив групп{ id, title, icon?, scenes }(актёры / локации / действия и т.п., список открытый). Активная группа задаёт сегменты таймлайна и список сцен; имеет приоритет надchapters. - Контрол выбора типа сцен — дропдаун с иконкой и тайтлом текущего типа; показывается, когда у источника есть
sceneGroups. Переключение перестраивает прогресс-бар и панель сцен. Отключаетсяcontrols.sceneTypes: false. Новое событиеscenetypechange, методыplayer.setSceneGroup(id)/player.sceneTypes/player.activeSceneType. ЛейблsceneTypesлокализован во всех 11 языках. - Спонсор-ссылка на пауз-скрине — жёлтый бейдж + подчёркнутый текст (как в референс-промо).
- Автовоспроизведение (свитч в шестерёнке). Тоггл
Autoplayв дропдауне настроек (по умолчанию вкл, виден в режиме плейлиста). Вкл — следующий ролик стартует автоматически; выкл — по окончании показывается end-оверлей с превью следующего ролика и кнопкой «Играть дальше». API:player.autoAdvance/player.setAutoAdvance(bool). Лейблыautoplay/playNext— во всех 11 локалях. Меню настроек теперь поддерживает строки-переключатели. - Формат подписи перемотки —
controls.seekButtons.label: (seconds, direction) => string(по умолчанию−15s/+15s). - Порядок правых контролов —
controls.order: ControlBarItem[]задаёт порядок слева направо (gear / pip / playlist / like / …). Перечисленные идут первыми, остальные сохраняют дефолтную позицию, ⋯ всегда последняя.
0.3.0
Крупный апдейт UI и набора пропсов (есть смена дефолтов — см. «Миграция»).
- Единый дропдаун-шестерёнка для настроек. Скорость, качество и субтитры по умолчанию собраны в одну кнопку . Меню — drill-down (как у больших плееров): сначала список настроек строками с текущим значением (
Качество — Авто ›,Скорость — 1× ›,Субтитры — Русский ›), клик по строке открывает её опции, «‹» — назад. Каждый пункт можно вынести отдельной кнопкой в бар (controls.speed/quality/subtitles: 'bar') или убрать (false). Кнопка-шестерёнка не показывается, если внутри нет ни одной доступной опции. Сцены и плейлист остаются отдельными кнопками (это управление воспроизведением). - В контрол-баре по умолчанию только «next» (кнопка «prev» в баре скрыта —
controls.hidePrev: trueпо умолчанию;falseвозвращает её). На мобиле центр-кластер по-прежнему показывает и prev, и next. - Перемотка ±N и большая кнопка play — оверлеем поверх видео на всех экранах (раньше только на мобиле). prev/next в оверлее — только на мобиле; на десктопе play/prev/next остаются в контрол-баре (рядом друг с другом). Управляется
controls.seekPlacement('overlay'по умолчанию,'bar'— прежнее десктоп-поведение с seek в нижнем баре). Шаг перемотки виден на кнопке (число в стрелке). - Шаг перемотки по умолчанию 15 сек (был 10) и показывается на кнопке (число внутри стрелки). Меняется через
seekStepилиcontrols.seekButtons: { back, forward }. - Кастом-кнопки прямо в контрол-баре. У
actions.custom[]появилсяplacement: 'bar'(иконка + тултип изtitle);'menu'(дефолт) — как раньше, пункт в ⋯. - Превью следующего ролика по ховеру на кнопке next (десктоп): обложка, тайтл, длительность и доп. строки из
source.previewMeta. Конфиг полей —controls.nextPreview. - Пауз-скрин: порядок спонсор → тайтл → описание (рекламная ссылка теперь сверху). Новый объект-вариант
pauseScreen: { title?, description?, sponsor? }— скрыть отдельные части (когда сайт выводит их вне плеера).
Миграция:
controls.settings(булев, включал меню скорости) → переименован вcontrols.speedсо значениями'gear' | 'bar' | false. Старыйsettingsещё работает как алиас.controls.quality/controls.subtitlesтеперьboolean | 'gear' | 'bar'(булевы значения совместимы:true= в шестерёнке).seekStepпо умолчанию 15 вместо 10 — задайтеseekStep: 10, чтобы вернуть прежний шаг.- Seek-кнопки по умолчанию в оверлее, а не в нижнем баре — верните
controls: { seekPlacement: 'bar' }при необходимости.
0.2.2
Фиксы UX на реальных iOS-устройствах (в десктоп-девтулзах не воспроизводились):
- Реклама больше не перекрывает свои кнопки. На iOS
<video>композитится в отдельный GPU-слой и всплывал поверх HUD и кнопок «Skip»/«Visit» — тап по скипу проваливался в видео и открывал click-through, рекламу (в т.ч. бесконечные «стримы») нельзя было пропустить. HUD и блок кнопок подняты в собственный контекст наложения (z-index). - Кнопки рекламы реагируют на первый тап —
touch-action: manipulationубирает 300-мс задержку и дабл-тап-зум iOS. - Адекватная тач-модель контролов. На тач-устройствах тап по видео показывает/скрывает контролы (play/pause — центральной кнопкой), а не дёргает воспроизведение. Раньше одно касание порождало сразу несколько событий (показ контролов + переключение play). Поведение мыши на десктопе не изменилось.
- Скрытые контролы (авто-idle) перестали перехватывать тап «вслепую» — первое касание сначала показывает их.
- Скрытые контролы полностью неактивны. Кнопки ±10 сек и панель управления больше нельзя нажать в невидимом (idle) состоянии во время проигрывания:
pointer-events:noneна родителе не отключал потомков сpointer-events:auto, теперь инертен весь скрытый сабтри. - Панели сцен и плейлиста на мобиле закрываются при выборе. Раньше после выбора сцены/видео нужно было вручную закрывать full-screen лист крестиком, чтобы увидеть видео. Теперь на мобиле (≤767px) лист закрывается сразу по тапу; десктоп-сайдбар остаётся открытым (видео и так видно рядом).
0.2.1
- Мелкие багфиксы.
0.2.0
styling-проп: акцент темы, цвета лайка/дизлайка, радиус скругления, стиль кнопки play (solid/inverted).related.clickBehavior— поведение клика по похожему видео (player/newWindow/currentTab).- HLS-движок грузится через
hls.js/light(легче); полныйwindow.Hls, если он уже на странице, имеет приоритет. - LCP-постер реальным
<img fetchpriority="high">. - Восстановление после decode-ошибок воспроизведения.
0.1.0
- Первый публичный релиз. Ядро без фреймворк-зависимостей + Vue 3-обёртка, IIFE-бандл и lazy-вход. Плейлисты, главы/тайм-коды, панель сцен, спрайт-превью, VTT-субтитры, VAST 2–4 (pre/mid/post-roll), экран паузы, related-видео, кнопки действий, 11 локализаций, темизация CSS-переменными, полная типизация.