Разработка платформы стриминга музыки

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка платформы стриминга музыки
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Разработка платформы стриминга музыки

Стриминг музыки — это не просто «отдать mp3 через HTTP». Платформа, которая должна выдерживать нагрузку, обеспечивать low-latency воспроизведение, управлять правами и монетизацией, — инженерная задача с десятком нетривиальных узлов. Ниже — архитектура и реализация с реальными компромиссами.

Протоколы доставки аудио

Три варианта, каждый под свою задачу.

Progressive download (псевдостриминг) — самый простой. Файл отдаётся через обычный HTTP с поддержкой Range-запросов. Браузер буферизует и воспроизводит. Подходит для небольших библиотек без строгих ограничений на скачивание.

location /audio/ {
    root /var/media;
    add_header Accept-Ranges bytes;
    add_header Cache-Control "no-store"; # для DRM
    # X-Accel-Redirect если файлы за авторизацией
}

HLS (HTTP Live Streaming) — стандарт для продакшна. Файл нарезается на сегменты по 5–10 секунд, клиент подгружает по манифесту. Позволяет адаптивный битрейт (ABR): клиент переключается между 128/256/320 kbps в зависимости от канала.

# FFmpeg: нарезка в HLS с тремя качествами
ffmpeg -i input.flac \
  -filter_complex "[0:a]asplit=3[a1][a2][a3]" \
  -map "[a1]" -codec:a aac -b:a 128k -vn \
    -hls_time 6 -hls_list_size 0 \
    -hls_segment_filename "out/128k_%03d.aac" out/128k.m3u8 \
  -map "[a2]" -codec:a aac -b:a 256k -vn \
    -hls_time 6 -hls_list_size 0 \
    -hls_segment_filename "out/256k_%03d.aac" out/256k.m3u8 \
  -map "[a3]" -codec:a aac -b:a 320k -vn \
    -hls_time 6 -hls_list_size 0 \
    -hls_segment_filename "out/320k_%03d.aac" out/320k.m3u8

Манифест верхнего уровня (master.m3u8) перечисляет варианты, клиент выбирает сам.

MPEG-DASH — альтернатива HLS, лучше поддерживает DRM через EME (Encrypted Media Extensions). Если нужна защита контента уровня лейблов — DASH + Widevine/FairPlay.

Архитектура обработки контента

Загрузка трека — это пайплайн, а не просто сохранение файла.

Upload → Validation → Transcoding → Waveform → Fingerprint → CDN → DB
# Celery task: полный пайплайн обработки
from celery import chain

@app.task
def process_upload(track_id: int, raw_path: str):
    pipeline = chain(
        validate_audio.s(track_id, raw_path),
        transcode_variants.s(),       # 128/256/320 + HLS segments
        generate_waveform.s(),        # peaks.json для визуализации
        fingerprint_audio.s(),        # AcoustID / Chromaprint
        push_to_cdn.s(),
        update_track_status.s('ready')
    )
    pipeline.delay()

@app.task
def transcode_variants(track_id: int, validated_path: str):
    qualities = [
        ('128k', '128k', 'aac'),
        ('256k', '256k', 'aac'),
        ('320k', '320k', 'mp3'),   # для офлайн-скачивания
        ('lossless', None, 'flac'), # для hi-fi тира
    ]
    results = []
    for name, bitrate, codec in qualities:
        out = transcode(validated_path, bitrate, codec)
        segment_hls(out, name, track_id)
        results.append((name, out))
    return track_id, results

Генерация волновой формы

Waveform — стандартный UI элемент. Библиотека audiowaveform от BBC:

audiowaveform -i track.mp3 -o peaks.json \
  --pixels-per-second 10 \
  --bits 8
# peaks.json: { "bits": 8, "length": 1234, "data": [-12, 15, -8, 22, ...] }

На фронтенде — WaveSurfer.js или кастомный Canvas-рендер:

function drawWaveform(peaks: number[], canvas: HTMLCanvasElement, progress: number) {
  const ctx = canvas.getContext('2d')!;
  const { width, height } = canvas;
  const mid = height / 2;
  const barWidth = width / peaks.length;

  ctx.clearRect(0, 0, width, height);

  peaks.forEach((peak, i) => {
    const x = i * barWidth;
    const h = (Math.abs(peak) / 128) * mid;
    const played = x / width < progress;

    ctx.fillStyle = played ? '#6366f1' : '#94a3b8';
    ctx.fillRect(x, mid - h, barWidth - 1, h * 2);
  });
}

Система прав и лицензирование

Без управления правами платформу не запустить легально. Минимальная модель:

CREATE TABLE tracks (
  id           BIGSERIAL PRIMARY KEY,
  title        TEXT NOT NULL,
  duration_sec INT,
  isrc         CHAR(12),          -- International Standard Recording Code
  status       TEXT DEFAULT 'processing'
);

CREATE TABLE track_rights (
  track_id     BIGINT REFERENCES tracks(id),
  territory    CHAR(2),           -- ISO 3166-1 alpha-2, NULL = worldwide
  right_type   TEXT,              -- 'stream', 'download', 'sync'
  holder_id    BIGINT,
  expires_at   TIMESTAMPTZ,
  PRIMARY KEY (track_id, territory, right_type)
);

-- Проверка права перед выдачей URL
CREATE OR REPLACE FUNCTION can_stream(p_track_id BIGINT, p_territory CHAR(2))
RETURNS BOOLEAN AS $$
  SELECT EXISTS (
    SELECT 1 FROM track_rights
    WHERE track_id = p_track_id
      AND right_type = 'stream'
      AND (territory IS NULL OR territory = p_territory)
      AND (expires_at IS NULL OR expires_at > NOW())
  );
$$ LANGUAGE sql STABLE;

Signed URLs и защита стримов

Прямые ссылки на файлы отдавать нельзя — их сохранят и распространят. Подписанные URL с коротким TTL:

// Laravel: генерация подписанного URL через CDN (Cloudflare / AWS CloudFront)
class StreamController extends Controller
{
    public function stream(Request $request, int $trackId): JsonResponse
    {
        $track = Track::findOrFail($trackId);

        // Проверяем территорию по IP
        $territory = $this->geoService->getCountry($request->ip());
        if (!$track->canStream($territory)) {
            return response()->json(['error' => 'not_available'], 451);
        }

        // Signed URL на 60 секунд — достаточно для начала буферизации
        $url = $this->cdn->signedUrl(
            path: "hls/{$trackId}/master.m3u8",
            ttl: 60,
            ip: $request->ip() // привязка к IP
        );

        // Логируем прослушивание для роялти
        StreamEvent::dispatch($trackId, $request->user()->id, now());

        return response()->json(['url' => $url]);
    }
}

Офлайн и Progressive Web App

Для мобильного PWA — кеширование через Service Worker:

// sw.js: кешируем сегменты треков для офлайн
const AUDIO_CACHE = 'audio-v1';

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  if (url.pathname.includes('/hls/') && url.pathname.endsWith('.aac')) {
    event.respondWith(
      caches.open(AUDIO_CACHE).then(async cache => {
        const cached = await cache.match(event.request);
        if (cached) return cached;

        const response = await fetch(event.request);
        // Кешируем только треки из плейлиста пользователя
        if (isInUserLibrary(url)) {
          cache.put(event.request, response.clone());
        }
        return response;
      })
    );
  }
});

Масштабирование и CDN

HLS-сегменты — статические файлы, идеально для CDN. Но есть нюанс: при пиковой нагрузке (новый релиз популярного исполнителя) нужен origin shield — промежуточный кеш между CDN и origin, чтобы не положить S3/хранилище.

User → CDN Edge (Cloudflare/CloudFront) → Origin Shield → S3/MinIO

Манифесты .m3u8 кешируются с коротким TTL (5–30 сек) — они меняются при live-трансляциях. Сегменты .aac/.ts кешируются агрессивно (365 дней, immutable), потому что имена включают хеш.

Роялти и статистика

Для каждого прослушивания нужно считать секунды (не просто факт воспроизведения). Threshold обычно 30 секунд по стандартам IFPI.

# Агрегация стримов для роялти-отчётов
# Kafka consumer: каждые 30 секунд плеер шлёт heartbeat
@dataclass
class PlaybackEvent:
    track_id: int
    user_id: int
    seconds_played: int
    quality: str  # '128k', '256k', 'lossless'
    timestamp: datetime

def aggregate_streams(events: list[PlaybackEvent]) -> dict:
    """
    Агрегируем за месяц: только полные прослушивания (>= 30 сек)
    учитываются как monetizable stream
    """
    from collections import defaultdict
    counts = defaultdict(int)

    for e in events:
        if e.seconds_played >= 30:
            counts[e.track_id] += 1

    return dict(counts)

Поиск по каталогу

Elasticsearch для полнотекстового поиска с анализом транслитерации и фонетики:

PUT /tracks
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "standard",
        "fields": {
          "phonetic": { "type": "text", "analyzer": "phonetic_analyzer" },
          "autocomplete": { "type": "search_as_you_type" }
        }
      },
      "artist": { "type": "text", "fields": { "keyword": { "type": "keyword" } } },
      "genre": { "type": "keyword" },
      "release_date": { "type": "date" },
      "play_count": { "type": "long" }
    }
  }
}

Сроки

Базовый стриминг с каталогом, плеером (HLS, waveform), плейлистами и авторизацией — 10–14 недель. Добавление системы прав, роялти-учёта и DRM — ещё 6–8 недель. Рекомендательный движок, офлайн-режим PWA, мобильные приложения — отдельные треки, оцениваются независимо.