Реализация продажи аудио/видеоконтента на сайте
Продажа медиаконтента (музыкальные треки, подкасты, обучающие видео, фильмы, документалки) технически сложнее, чем продажа PDF. Основные проблемы — защита стриминга от несанкционированного доступа, управление полосой пропускания и работа с двумя бизнес-моделями одновременно: разовая покупка (own) vs подписка (stream).
Два режима доставки
Download-to-own: пользователь платит один раз, скачивает файл. Применимо для музыкальных треков, аудиокниг, отдельных видеолекций. Механизм идентичен продаже электронных книг — S3 + временные signed URLs.
Stream-only: пользователь смотрит/слушает онлайн, скачать нельзя. Применимо для фильмов, сериалов, подписочных курсов. Требует специальной инфраструктуры.
Для стриминга прямая раздача через S3 — не лучшая идея по двум причинам: стоимость трафика и невозможность контролировать параллельные сессии. Правильный стек:
S3 (origin) → CloudFront CDN → Signed Cookies → Player (HLS.js / Shaka Player)
Видеостриминг через CloudFront + HLS
Видео хранится в S3 в исходном качестве (MOV/MP4 4K). AWS MediaConvert транскодирует его в несколько битрейтов и упаковывает в HLS (.m3u8 + сегменты .ts):
output/
movie-uuid/
index.m3u8 # master playlist
360p/stream.m3u8
720p/stream.m3u8
1080p/stream.m3u8
360p/seg-000.ts
360p/seg-001.ts
...
CloudFront раздаёт эти файлы с подписанными cookies (не URL — именно cookies, чтобы покрыть все сегменты одной подписью):
// Генерация CloudFront signed cookies
use Aws\CloudFront\CloudFrontClient;
$cf = new CloudFrontClient(['region' => 'us-east-1', 'version' => 'latest']);
$policy = json_encode([
'Statement' => [[
'Resource' => "https://cdn.example.com/output/{$movie->uuid}/*",
'Condition' => [
'DateLessThan' => ['AWS:EpochTime' => time() + 14400], // 4 часа
'IpAddress' => ['AWS:SourceIp' => $request->ip() . '/32'],
],
]],
]);
$cookies = $cf->getSignedCookie([
'policy' => $policy,
'private_key' => storage_path('app/cf-private-key.pem'),
'key_pair_id' => env('CLOUDFRONT_KEY_PAIR_ID'),
]);
// Вернуть cookies в Set-Cookie заголовках
foreach ($cookies as $name => $value) {
Cookie::queue($name, $value, 240, '/', '.example.com', true, true, false, 'None');
}
Плеер на фронтенде получает cookies через API-эндпоинт /api/media/{id}/access, затем воспроизводит HLS:
import Hls from 'hls.js';
const hls = new Hls({
xhrSetup: (xhr) => {
xhr.withCredentials = true; // передаём CloudFront cookies
},
});
hls.loadSource(`https://cdn.example.com/output/${movieUuid}/index.m3u8`);
hls.attachMedia(videoElement);
Аудиостриминг
Для аудио (музыка, подкасты) схема проще. Файлы MP3/AAC/FLAC хранятся в S3. Для стриминга без скачивания используем CloudFront signed URL с ограниченным сроком жизни и привязкой к IP:
$signedUrl = $cloudFront->getSignedUrl([
'url' => "https://cdn.example.com/audio/{$track->file_key}",
'expires' => time() + 7200,
'private_key' => storage_path('app/cf-private-key.pem'),
'key_pair_id' => env('CLOUDFRONT_KEY_PAIR_ID'),
]);
Для превью (30-секундный сэмпл) храним отдельный файл {uuid}-preview.mp3 — он лежит в публично доступном пути CDN.
Аналитика просмотров
Для видео критична аналитика: досматриваемость, точки выхода. Это влияет на рекомендательную систему и помогает определить, какой контент работает.
// Отправляем heartbeat каждые 10 секунд
let lastReported = 0;
videoElement.addEventListener('timeupdate', () => {
const current = Math.floor(videoElement.currentTime);
if (current - lastReported >= 10) {
navigator.sendBeacon('/api/analytics/heartbeat', JSON.stringify({
content_id: contentId,
position: current,
duration: Math.floor(videoElement.duration),
}));
lastReported = current;
}
});
navigator.sendBeacon не блокирует закрытие вкладки — данные доходят даже если пользователь резко закрыл браузер.
Подписочная модель
Для стримингового сервиса (Netflix-like) используем Stripe Subscriptions:
const subscription = await stripe.subscriptions.create({
customer: customer.stripe_id,
items: [{ price: 'price_monthly_plan' }],
payment_behavior: 'default_incomplete',
expand: ['latest_invoice.payment_intent'],
});
Таблица subscriptions(user_id, stripe_subscription_id, plan, status, current_period_end). Webhook customer.subscription.updated / customer.subscription.deleted синхронизирует статус. Middleware проверяет subscriptions.status = 'active' и current_period_end > now().
Управление качеством и адаптивный стриминг
ABR (Adaptive Bitrate) — HLS автоматически переключает качество под скорость соединения. Но важно задать правила транскодинга в MediaConvert:
| Профиль | Разрешение | Битрейт видео | Битрейт аудио |
|---|---|---|---|
| 360p | 640×360 | 800 kbps | 96 kbps |
| 720p | 1280×720 | 2500 kbps | 128 kbps |
| 1080p | 1920×1080 | 5000 kbps | 192 kbps |
| 1080p HDR | 1920×1080 | 8000 kbps | 192 kbps |
Сроки реализации
| Этап | Время |
|---|---|
| S3 + MediaConvert pipeline | 2–3 дня |
| CloudFront signed cookies + HLS плеер | 2 дня |
| Платёжная интеграция (разовая/подписка) | 2 дня |
| Аналитика просмотров | 1 день |
| Административная панель (загрузка контента) | 2–3 дня |
Итого: 9–11 рабочих дней для полноценной платформы с подпиской и стримингом.
Частые проблемы
Горячие ссылки (hotlinking). Если URL предсказуем или не защищён, боты и пользователи будут шарить прямые ссылки. CloudFront Signed Cookies + IP-привязка решают это.
Буферизация на мобильных. HLS сегменты по 6–10 секунд дают нормальный баланс. Слишком короткие сегменты (2 с) увеличивают количество HTTP-запросов, слишком длинные — начальную задержку.
Конкурентные сессии. Для подписочных платформ часто ограничивают количество одновременных просмотров. Реализуется через Redis: SET stream:{user_id}:{session_id} EX 30 с обновлением каждые 10 секунд. Если количество активных ключей по stream:{user_id}:* превышает лимит — новый сеанс блокируется.







