Реализация видеоплеера (Video.js/Plyr) на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация видеоплеера (Video.js/Plyr) на сайте
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация видеоплеера (Video.js/Plyr) на сайте

Встроенный <video> тег — минимально рабочее решение, которое ломается на первом же нестандартном требовании: кастомный дизайн, субтитры, качество, аналитика просмотра, реклама, DRM. Для продакшена нужен плеер с экосистемой.

Video.js vs Plyr: когда что

Plyr — лёгкий (~28 KB gzip), красивый из коробки, поддерживает HTML5 video/audio, YouTube, Vimeo. Достаточен для 80% задач. API простой и понятный.

Video.js — тяжёлый (~150 KB core + плагины), промышленный стандарт. Поддерживает HLS, DASH, DRM (Widevine, PlayReady, FairPlay через плагины), рекламу (IMA SDK), аналитику. Используется на крупных медиаплатформах.

Выбор: если нужен просто красивый плеер для mp4/YouTube — Plyr. Если нужны адаптивные потоки (HLS/DASH), live-трансляции или монетизация — Video.js.

Plyr: базовая интеграция

<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css">

<video id="player" playsinline controls>
  <source src="/video/movie.mp4" type="video/mp4">
  <source src="/video/movie.webm" type="video/webm">
  <track kind="captions" label="Русский" srclang="ru" src="/captions/ru.vtt" default>
</video>

<script src="https://cdn.plyr.io/3.7.8/plyr.js"></script>
<script>
const player = new Plyr('#player', {
  controls: ['play-large', 'play', 'progress', 'current-time', 'mute',
             'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen'],
  settings: ['captions', 'quality', 'speed', 'loop'],
  speed: { selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 2] },
  youtube: { noCookie: true, rel: 0, showinfo: 0 },
  ratio: '16:9',
  keyboard: { focused: true, global: false },
  tooltips: { controls: true, seek: true },
  captions: { active: true, language: 'ru', update: true },
})

// Аналитика просмотра
player.on('timeupdate', () => {
  const pct = Math.floor((player.currentTime / player.duration) * 100)
  if ([25, 50, 75, 90].includes(pct)) {
    analytics.track('video_progress', { percent: pct, src: player.source })
  }
})
</script>

Plyr с React

import Plyr from 'plyr'
import 'plyr/dist/plyr.css'
import { useEffect, useRef } from 'react'

interface VideoPlayerProps {
  src: string
  poster?: string
  captions?: { src: string; label: string; language: string }[]
}

export function VideoPlayer({ src, poster, captions = [] }: VideoPlayerProps) {
  const videoRef = useRef<HTMLVideoElement>(null)
  const playerRef = useRef<Plyr>()

  useEffect(() => {
    if (!videoRef.current) return

    playerRef.current = new Plyr(videoRef.current, {
      ratio: '16:9',
      controls: ['play-large', 'play', 'progress', 'current-time',
                 'mute', 'volume', 'settings', 'fullscreen'],
    })

    return () => {
      playerRef.current?.destroy()
    }
  }, [])

  return (
    <video ref={videoRef} poster={poster} crossOrigin="anonymous">
      <source src={src} type="video/mp4" />
      {captions.map(cap => (
        <track key={cap.language} kind="captions"
          label={cap.label} srcLang={cap.language} src={cap.src} />
      ))}
    </video>
  )
}

Video.js: HLS-стриминг

<link href="https://vjs.zencdn.net/8.10.0/video-js.css" rel="stylesheet">
<video id="my-video" class="video-js vjs-big-play-centered" controls preload="auto"
  poster="/poster.jpg" data-setup='{}' style="width:100%;height:auto">
</video>

<script src="https://vjs.zencdn.net/8.10.0/video.min.js"></script>
<script>
const player = videojs('my-video', {
  fluid: true,
  responsive: true,
  sources: [{
    src: 'https://stream.example.com/hls/video.m3u8',
    type: 'application/x-mpegURL',
  }],
  html5: {
    vhs: {
      overrideNative: true,      // использовать VHS вместо нативного HLS
      enableLowInitialPlaylist: true,
      smoothQualityChange: true,
      limitRenditionByPlayerDimensions: true,
    },
  },
  controlBar: {
    children: [
      'playToggle',
      'volumePanel',
      'currentTimeDisplay',
      'timeDivider',
      'durationDisplay',
      'progressControl',
      'liveDisplay',
      'remainingTimeDisplay',
      'customControlSpacer',
      'playbackRateMenuButton',
      'chaptersButton',
      'descriptionsButton',
      'subsCapsButton',
      'qualitySelector',    // требует плагин videojs-contrib-quality-levels
      'fullscreenToggle',
    ],
  },
})

// Логирование ошибок
player.on('error', () => {
  const error = player.error()
  console.error('Video error:', error.code, error.message)
  Sentry.captureException(new Error(`Video error ${error.code}: ${error.message}`))
})
</script>

Переключение качества в Video.js

npm install @videojs/http-streaming videojs-contrib-quality-levels videojs-hls-quality-selector
import videojs from 'video.js'
import 'videojs-contrib-quality-levels'
import 'videojs-hls-quality-selector'

const player = videojs('player')
player.hlsQualitySelector({ displayCurrentQuality: true })

// Программное переключение
const qualityLevels = player.qualityLevels()
qualityLevels.on('addqualitylevel', (event) => {
  const level = event.qualityLevel
  console.log(`Quality: ${level.height}p, bitrate: ${level.bitrate}`)
})

Lazy-загрузка плеера

Инициализировать Video.js на 10 плеерах одновременно — убийство производительности страницы. Используем IntersectionObserver:

const observers = new Map<Element, IntersectionObserver>()

function lazyInitPlayer(container: HTMLElement) {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const video = entry.target.querySelector('video')!
        videojs(video, { fluid: true })
        observer.disconnect()
        observers.delete(container)
      }
    })
  }, { threshold: 0.1, rootMargin: '200px' })

  observer.observe(container)
  observers.set(container, observer)
}

document.querySelectorAll('.video-lazy').forEach(el => lazyInitPlayer(el as HTMLElement))

Кастомный постер с превью при наведении

// Генерируем спрайт превью (storyboard)
// На сервере: ffmpeg -i input.mp4 -vf "fps=1/10,scale=160:-1,tile=10x10" sprites.jpg
// Клиентский код:
function setupThumbnailPreview(player: any) {
  const progressBar = player.controlBar.progressControl.seekBar.el()
  const thumb = document.createElement('div')
  thumb.className = 'vjs-thumbnail'
  progressBar.appendChild(thumb)

  progressBar.addEventListener('mousemove', (e: MouseEvent) => {
    const rect = progressBar.getBoundingClientRect()
    const pct = (e.clientX - rect.left) / rect.width
    const time = pct * player.duration()
    const frame = Math.floor(time / 10)  // 1 кадр каждые 10 сек

    const col = frame % 10
    const row = Math.floor(frame / 10)

    thumb.style.backgroundImage = 'url(/sprites.jpg)'
    thumb.style.backgroundPosition = `-${col * 160}px -${row * 90}px`
    thumb.style.left = `${e.clientX - rect.left - 80}px`
    thumb.style.display = 'block'
  })
}

Встраивание YouTube через Plyr

const player = new Plyr('#youtube-player', {
  debug: false,
})
// HTML: <div id="youtube-player" data-plyr-provider="youtube" data-plyr-embed-id="dQw4w9WgXcQ"></div>

// Получить текущее время из YouTube iframe
player.on('timeupdate', ({ detail: { plyr } }) => {
  console.log(plyr.currentTime)
})

Производительность и Core Web Vitals

Видеоплеер — один из главных виновников плохого LCP/CLS. Рецепты:

<!-- Резервируем место под плеер через aspect-ratio, предотвращаем CLS -->
<div style="aspect-ratio: 16/9; background: #000;">
  <video ...></video>
</div>

<!-- Poster изображение для быстрого LCP -->
<video poster="/poster-small.webp" preload="none">
// Не загружать JS плеера до взаимодействия
const loadPlayer = async () => {
  const { default: Plyr } = await import('plyr')
  await import('plyr/dist/plyr.css')
  return new Plyr('#player')
}

document.getElementById('play-btn')?.addEventListener('click', async () => {
  const player = await loadPlayer()
  player.play()
}, { once: true })

Сроки

Plyr с базовой конфигурацией и стилизацией под бренд — 1 день. Video.js с HLS, переключением качества, субтитрами и аналитикой — 3–4 дня. DRM-интеграция (Widevine + сервер лицензий) — отдельный проект на 1–2 недели.