Реалізація продажу аудіо/відеоконтенту на сайті
Продаж медіаконтенту (музичні треки, подкасти, навчальні відео, фільми, документалки) технічно складніше, ніж продаж PDF. Основні проблеми — захист стрімінгу від несанкціонованого доступу, управління пропускною здатністю та робота з двома бізнес-моделями одночасно: розова покупка (own) vs підписка (stream).
Два режими доставки
Download-to-own: користувач платить один раз, завантажує файл. Застосовується для музичних треків, аудіокниг, окремих відеолекцій. Механізм ідентичний продажу електронних книг — S3 + тимчасові підписані URL.
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 підписаних 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 підписані 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}:* перевищує ліміт — нова сесія блокується.







