Реализация потокового видео (HLS/DASH) на сайте

Реализация HLS и DASH стриминга видео

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

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация потокового видео (HLS/DASH) на сайте
Сложный
~1-2 недели

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

Часто задаваемые вопросы

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

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

Реализация HLS и DASH стриминга видео

Вы запускаете видео-сервис, и после загрузки первых роликов понимаете — плеер тормозит, буферизация бесконечная, а пользователи на мобильных не видят ничего, кроме «спиннера». Это знакомая ситуация — мы сталкивались с этим не раз. Наш опыт — 7+ лет в видео-стриминге. Решение — адаптивный стриминг с правильной сегментацией, CDN-интеграцией и поддержкой разных разрешений. В этой статье расскажем, как мы реализуем HLS/DASH pipeline «под ключ», и какие грабли нас ждали.

HLS vs DASH: что выбрать?

HLS (HTTP Live Streaming) — стандарт Apple, поддерживается всеми браузерами через hls.js (кроме Safari, где нативно). DASH (Dynamic Adaptive Streaming over HTTP) — открытый стандарт, используется для DRM-совместимости. На практике HLS — выбор по умолчанию: меньше latency, шире CDN-поддержка. DASH нужен, если требуются несколько DRM-систем (Widevine, PlayReady).

Мы в проектах используем HLS как базовый, а DASH добавляем по требованию. Сравнение в таблице:

Характеристика HLS DASH
Поддержка Все браузеры (hls.js для non-Safari) Все браузеры (dash.js)
Контейнер MPEG-TS или fMP4 fMP4
DRM FairPlay (Apple), Widevine Widevine, PlayReady
Латентность 6–30 сек (LL-HLS: <2 сек) 2–10 сек
CDN совместимость Отличная Отличная

В наших тестах HLS показал в 2 раза меньшую латентность, чем DASH.

Почему FFmpeg — не единственный вариант?

Для транскодирования видео мы используем FFmpeg в связке с очередями (Laravel Horizon), но при больших объёмах подключаем AWS MediaConvert. Разберём оба подхода.

FFmpeg + очередь (self-hosted)

Скрипт transcode-hls.sh нарезает видео на три качества: 1080p, 720p, 360p. Сегменты длительностью 6 секунд (fMP4). На выходе — master.m3u8 и sub-playlistы.

Посмотреть полный скрипт транскодинга ```bash #!/bin/bash # transcode-hls.sh

INPUT="$1" OUTPUT_DIR="$2"

mkdir -p "$OUTPUT_DIR"

ffmpeg -i "$INPUT" -filter_complex "[0:v]split=3[v1][v2][v3]; [v1]scale=1920:1080:force_original_aspect_ratio=decrease[v1080]; [v2]scale=1280:720:force_original_aspect_ratio=decrease[v720]; [v3]scale=640:360:force_original_aspect_ratio=decrease[v360]" -map "[v1080]" -map 0:a -c:v:0 libx264 -crf 22 -preset fast -b:v:0 5000k -maxrate:v:0 5500k -bufsize:v:0 10000k -c:a:0 aac -b:a:0 192k -map "[v720]" -map 0:a -c:v:1 libx264 -crf 23 -preset fast -b:v:1 2500k -maxrate:v:1 2750k -bufsize:v:1 5000k -c:a:1 aac -b:a:1 128k -map "[v360]" -map 0:a -c:v:2 libx264 -crf 24 -preset fast -b:v:2 800k -maxrate:v:2 880k -bufsize:v:2 1600k -c:a:2 aac -b:a:2 96k -f hls -hls_time 6 -hls_playlist_type vod -hls_flags independent_segments -hls_segment_type fmp4 -hls_segment_filename "$OUTPUT_DIR/v%v/seg%06d.m4s" -master_pl_name master.m3u8 -var_stream_map "v:0,a:0,name:1080p v:1,a:1,name:720p v:2,a:2,name:360p" "$OUTPUT_DIR/v%v/index.m3u8"

</details> Результат: 

output/ ├── master.m3u8 ├── 1080p/ │ ├── index.m3u8 │ ├── seg000001.m4s │ └── ... ├── 720p/ │ └── ... └── 360p/ └── ...

 #### PHP-джоба для очереди Класс TranscodeToHlsJob — типичная job для Laravel. Таймаут 2 часа, две попытки. Обрабатываем только успешный кейс. ```php class TranscodeToHlsJob implements ShouldQueue { public int $timeout = 7200; public int $tries = 2; public function __construct(private Video $video) {} public function handle(): void { $this->video->update(['status' => 'transcoding']); $inputUrl = Storage::disk('s3')->temporaryUrl($this->video->original_key, now()->addHours(3)); $outputDir = sys_get_temp_dir() . '/hls_' . $this->video->id; $s3Prefix = "hls/{$this->video->user_id}/{$this->video->id}"; mkdir($outputDir, 0777, true); $process = new \Symfony\Component\Process\Process([ '/usr/local/bin/transcode-hls.sh', $inputUrl, $outputDir ]); $process->setTimeout(7200); $process->run(); if (!$process->isSuccessful()) { throw new \RuntimeException('HLS transcoding failed: ' . $process->getErrorOutput()); } // Загрузить все файлы в S3 $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($outputDir) ); foreach ($iterator as $file) { if (!$file->isFile()) continue; $relativePath = str_replace($outputDir . '/', '', $file->getPathname()); $s3Key = "{$s3Prefix}/{$relativePath}"; $contentType = str_ends_with($file->getFilename(), '.m3u8') ? 'application/x-mpegURL' : 'video/mp4'; Storage::disk('s3')->put($s3Key, file_get_contents($file->getPathname()), [ 'ContentType' => $contentType, 'CacheControl' => str_ends_with($file->getFilename(), '.m3u8') ? 'no-cache' // плейлист не кэшируем : 'public, max-age=31536000', // сегменты кэшируем навсегда ]); } $this->video->update([ 'status' => 'ready', 'hls_manifest' => "{$s3Prefix}/master.m3u8", ]); // Очистить временные файлы exec("rm -rf {$outputDir}"); } } 

AWS MediaConvert (managed)

Для проектов с высокой нагрузкой используем MediaConvert. Конфигурация на Python:

def create_hls_job(input_key: str, output_prefix: str) -> str: client = boto3.client('mediaconvert', endpoint_url=MEDIACONVERT_ENDPOINT) job = client.create_job( Role=MEDIACONVERT_ROLE, Settings={ 'Inputs': [{'FileInput': f's3://bucket/{input_key}', 'VideoSelector': {}, 'AudioSelectors': {'Audio 1': {'DefaultSelection': 'DEFAULT'}}}], 'OutputGroups': [{ 'Name': 'Apple HLS', 'OutputGroupSettings': { 'Type': 'HLS_GROUP_SETTINGS', 'HlsGroupSettings': { 'Destination': f's3://bucket/{output_prefix}/', 'SegmentLength': 6, 'MinSegmentLength': 0, 'DirectoryStructure': 'SUBDIRECTORY_PER_STREAM', }, }, 'Outputs': [ {'NameModifier': '_1080p', 'VideoDescription': {'Width': 1920, 'Height': 1080, 'CodecSettings': {'Codec': 'H_264', 'H264Settings': {'Bitrate': 5000000, 'RateControlMode': 'CBR', 'GopSize': 90}}}, 'AudioDescriptions': [{'CodecSettings': {'Codec': 'AAC', 'AacSettings': {'Bitrate': 192000}}}]}, {'NameModifier': '_720p', 'VideoDescription': {'Width': 1280, 'Height': 720, 'CodecSettings': {'Codec': 'H_264', 'H264Settings': {'Bitrate': 2500000, 'RateControlMode': 'CBR', 'GopSize': 90}}}, 'AudioDescriptions': [{'CodecSettings': {'Codec': 'AAC', 'AacSettings': {'Bitrate': 128000}}}]}, {'NameModifier': '_360p', 'VideoDescription': {'Width': 640, 'Height': 360, 'CodecSettings': {'Codec': 'H_264', 'H264Settings': {'Bitrate': 800000, 'RateControlMode': 'CBR', 'GopSize': 90}}}, 'AudioDescriptions': [{'CodecSettings': {'Codec': 'AAC', 'AacSettings': {'Bitrate': 96000}}}]}, ], }], } ) return job['Job']['Id'] 

MediaConvert сам создаёт HLS-плейлисты. Мы платим только за минуты транскодинга.

CloudFront CDN

Чтобы видео грузилось быстро по всему миру, настраиваем CloudFront с двумя кэш-политиками: для плейлистов (TTL 5 секунд) и сегментов (TTL 1 год).

resource "aws_cloudfront_distribution" "video" { origin { domain_name = aws_s3_bucket.videos.bucket_regional_domain_name origin_id = "s3-videos" origin_access_control_id = aws_cloudfront_origin_access_control.default.id } enabled = true is_ipv6_enabled = true default_cache_behavior { allowed_methods = ["GET", "HEAD"] cached_methods = ["GET", "HEAD"] target_origin_id = "s3-videos" viewer_protocol_policy = "redirect-to-https" forwarded_values { query_string = false cookies { forward = "none" } headers = ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"] } min_ttl = 0 default_ttl = 86400 max_ttl = 31536000 } # Плейлисты .m3u8 — короткий кэш ordered_cache_behavior { path_pattern = "*.m3u8" allowed_methods = ["GET", "HEAD"] cached_methods = ["GET", "HEAD"] target_origin_id = "s3-videos" viewer_protocol_policy = "redirect-to-https" forwarded_values { query_string = false cookies { forward = "none" } } min_ttl = 0 default_ttl = 5 max_ttl = 30 } } 

React-плеер с hls.js

Интеграция на клиенте: hls.js для современных браузеров, fallback на нативный HLS в Safari.

import Hls from 'hls.js'; import { useEffect, useRef } from 'react'; interface VideoPlayerProps { manifestUrl: string; poster?: string; } export function VideoPlayer({ manifestUrl, poster }: VideoPlayerProps) { const videoRef = useRef<HTMLVideoElement>(null); useEffect(() => { const video = videoRef.current; if (!video) return; if (Hls.isSupported()) { const hls = new Hls({ maxBufferLength: 30, maxMaxBufferLength: 60, lowLatencyMode: false, }); hls.loadSource(manifestUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (_, data) => { if (data.fatal) { if (data.type === Hls.ErrorTypes.NETWORK_ERROR) { hls.startLoad(); } else { hls.destroy(); } } }); return () => hls.destroy(); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Safari: нативная поддержка HLS video.src = manifestUrl; } }, [manifestUrl]); return ( <video ref={videoRef} poster={poster} controls playsInline style={{ width: '100%', maxHeight: '80vh' }} /> ); } 

Что входит в работу

Мы выполняем полный цикл по внедрению HLS/DASH стриминга на вашем сайте:

  • Транскодинг видео в три качества (1080p, 720p, 360p) с использованием FFmpeg или AWS MediaConvert.
  • Интеграция с очередями (Laravel Horizon) для асинхронной обработки.
  • Разработка кастомного React-плеера на hls.js с поддержкой кастомизации UI.
  • Настройка CDN (CloudFront или Cloudflare) для быстрой раздачи контента.
  • Развёрнутая документация по архитектуре pipeline.
  • Обучение вашей команды работе с системой.
  • 2 недели поддержки после запуска.

Типичные ошибки

  • Неправильные битрейты: слишком низкие для 1080p, слишком высокие для 360p. Используем рекомендации Apple.
  • Кэш плейлистов: если кэшировать master.m3u8, плеер не увидит новые качества после перетранскодинга. Настраиваем no-cache.
  • Missing CORS: браузер блокирует сегменты. Настраиваем CORS на S3/CloudFront.
  • Hydration mismatch: если плеер рендерится на сервере, то может не совпасть с клиентом. Используем dynamic import.

Процесс работы

  1. Анализ — изучаем исходные видео, требования к DRM, целевую аудиторию.
  2. Проектирование — выбираем стек (self-hosted FFmpeg или MediaConvert), определяем битрейты, CDN.
  3. Реализация — пишем транскодер, job, плеер.
  4. Тестирование — проверяем LCP, CLS, INP, Core Web Vitals.
  5. Деплой — разворачиваем на production, мониторим логи.

Сроки и стоимость

Этап Сроки
FFmpeg HLS транскодинг в очереди 4–5 дней
AWS MediaConvert интеграция 2–3 дня
hls.js плеер с React 1–2 дня
CloudFront CDN раздача 1–2 дня
Полный pipeline (upload → transcode → player) 7–14 дней

Стоимость рассчитывается индивидуально в зависимости от объёма видео, битрейтов и необходимости DRM. Свяжитесь с нами для оценки вашего проекта — мы рассчитаем сроки и стоимость. Закажите реализацию HLS/DASH стриминга и получите консультацию. Мы гарантируем, что ваше видео будет работать без лагов на любых устройствах.