WebCodecs и медиа компоненты

Lit — это легковесный фреймворк для создания веб-компонентов, основанных на стандартных технологиях Web Components. Он обеспечивает декларативное обновление DOM через шаблоны и реактивные свойства, что позволяет создавать сложные интерфейсы, включая медиа-компоненты, с минимальным количеством кода.

Создание компонента для воспроизведения видео

Для интеграции видео можно использовать стандартный HTML-элемент <video> внутри Lit-компонента, при этом свойства и события элемента можно связывать с реактивными свойствами класса.

import { LitElement, html, css } from 'lit';
import { property } from 'lit/decorators.js';

class VideoPlayer extends LitElement {
  static styles = css`
    video {
      width: 100%;
      border-radius: 8px;
      background: #000;
    }
  `;

  @property({ type: String }) src = '';
  @property({ type: Boolean }) autoplay = false;
  @property({ type: Boolean }) controls = true;

  render() {
    return html`
      <video
        src="${this.src}"
        ?autoplay="${this.autoplay}"
        ?controls="${this.controls}">
      </video>
    `;
  }
}

customElements.define('video-player', VideoPlayer);

Ключевые моменты:

  • Реактивные свойства (@property) автоматически обновляют DOM при изменении значений.
  • Шаблоны Lit (`html``) позволяют связывать свойства напрямую с атрибутами элементов.
  • CSS внутри компонента изолирован через Shadow DOM.

Работа с WebCodecs

WebCodecs предоставляет низкоуровневый API для работы с видео- и аудиопотоками, позволяя декодировать и кодировать медиа практически в реальном времени. В Lit-компонентах WebCodecs можно использовать для расширенного управления медиаконтентом.

class CodecVideoPlayer extends LitElement {
  @property({ type: String }) videoUrl = '';
  private videoFrameQueue = [];
  private decoder;

  connectedCallback() {
    super.connectedCallback();
    this.initDecoder();
  }

  async initDecoder() {
    const response = await fetch(this.videoUrl);
    const buffer = await response.arrayBuffer();

    this.decoder = new VideoDecoder({
      output: frame => this.queueFrame(frame),
      error: e => console.error('Decoder error:', e)
    });

    const config = {
      codec: 'vp8',
      codedWidth: 640,
      codedHeight: 480
    };

    this.decoder.configure(config);
    this.decoder.decode(new EncodedVideoChunk({ type: 'key', timestamp: 0, data: new Uint8Array(buffer) }));
  }

  queueFrame(frame) {
    this.videoFrameQueue.push(frame);
    this.requestUpdate();
  }

  render() {
    return html`
      <canvas id="videoCanvas" width="640" height="480"></canvas>
    `;
  }

  updated() {
    const canvas = this.renderRoot.getElementById('videoCanvas');
    const ctx = canvas.getContext('2d');

    while (this.videoFrameQueue.length > 0) {
      const frame = this.videoFrameQueue.shift();
      ctx.drawImage(frame, 0, 0);
      frame.close();
    }
  }
}

customElements.define('codec-video-player', CodecVideoPlayer);

Ключевые моменты:

  • VideoDecoder используется для декодирования видео в формате VP8/VP9/H.264.
  • Метод output получает VideoFrame, который можно отобразить на <canvas>.
  • Frames нужно освобождать методом close(), чтобы не создавать утечки памяти.

Создание кастомного аудиоплеера с WebCodecs

WebCodecs также поддерживает аудиодекодеры (AudioDecoder) и позволяет работать с аудиопотоками напрямую, что полезно для создания визуализаторов или обработчиков звука в реальном времени.

class AudioPlayer extends LitElement {
  @property({ type: String }) audioUrl = '';
  private audioDecoder;
  private audioContext = new AudioContext();

  connectedCallback() {
    super.connectedCallback();
    this.initDecoder();
  }

  async initDecoder() {
    const response = await fetch(this.audioUrl);
    const buffer = await response.arrayBuffer();

    this.audioDecoder = new AudioDecoder({
      output: audioData => this.playAudio(audioData),
      error: e => console.error('AudioDecoder error:', e)
    });

    const config = {
      codec: 'opus',
      sampleRate: 48000,
      numberOfChannels: 2
    };

    this.audioDecoder.configure(config);
    this.audioDecoder.decode(new EncodedAudioChunk({ type: 'key', timestamp: 0, data: new Uint8Array(buffer) }));
  }

  playAudio(audioData) {
    const buffer = this.audioContext.createBuffer(
      audioData.numberOfChannels,
      audioData.numberOfFrames,
      audioData.sampleRate
    );

    for (let i = 0; i < audioData.numberOfChannels; i++) {
      audioData.copyTo(buffer.getChannelData(i), { planeIndex: i });
    }

    const source = this.audioContext.createBufferSource();
    source.buffer = buffer;
    source.connect(this.audioContext.destination);
    source.start();
    audioData.close();
  }

  render() {
    return html`<div>Аудиоплеер готов к воспроизведению</div>`;
  }
}

customElements.define('audio-player', AudioPlayer);

Ключевые моменты:

  • AudioDecoder декодирует сжатый аудиопоток (Opus, AAC и др.) в PCM.
  • AudioContext используется для воспроизведения и обработки аудио.
  • Разделение на каналы позволяет реализовать стерео и многоканальный звук.

Реактивное управление состоянием медиа

Lit позволяет легко связывать состояния плеера с DOM:

  • Использование булевых свойств (playing, paused) для управления кнопками.
  • Отслеживание текущего времени через свойства и события.
  • Реактивное обновление элементов интерфейса без необходимости прямой манипуляции DOM.
@property({ type: Boolean }) playing = false;

togglePlay() {
  this.playing = !this.playing;
  const video = this.renderRoot.querySelector('video');
  this.playing ? video.play() : video.pause();
}

Стилизация и интеграция компонентов

Lit позволяет применять локальные стили через css и использовать Shadow DOM для изоляции. В медиа-компонентах это важно для:

  • Защиты стилей плеера от внешних CSS.
  • Настройки внешнего вида кнопок, прогресс-бара и оберток.
  • Создания адаптивного интерфейса с помощью медиазапросов внутри компонента.
static styles = css`
  :host {
    display: block;
    max-width: 640px;
    margin: auto;
  }
  button {
    background: #444;
    color: #fff;
    border: none;
    padding: 8px 16px;
    border-radius: 4px;
    cursor: pointer;
  }
`;

Эти подходы позволяют строить мощные медиа-компоненты, полностью управляемые через реактивные свойства и события Lit, с возможностью интеграции низкоуровневых WebCodecs для сложной обработки аудио и видео.