NettyとMediaRecorderを利用したライブビデオストリーミングの実装

システムアーキテクチャの概要

本システムは以下の3つの主要コンポーネントで構成されます:

  1. Nettyベースのビデオフレーム転送システム
  2. ブラウザ上のMediaRecorderによる映像キャプチャ
  3. MediaSource APIを活用したリアルタイム再生機構

コア機能の実現方法

録画開始後、視聴者は1-3秒の遅延でライブストリームにアクセス可能です。新規視聴者も録画中のセッションに途中参加できます。

サーバーサイド実装(Netty)

必須依存関係

<dependencies>
  <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.86.Final</version>
  </dependency>
  <dependency>
    <groupId>io.protostuff</groupId>
    <artifactId>protostuff-runtime</artifactId>
    <version>1.8.0</version>
  </dependency>
</dependencies>

ビデオフレームハンドラ

public class VideoFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {
  private static final ChannelGroup activeChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
  
  @Override
  protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame frame) {
    activeChannels.writeAndFlush(frame.retain());
  }
  
  @Override
  public void channelActive(ChannelHandlerContext ctx) {
    activeChannels.add(ctx.channel());
  }
  
  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    ctx.close();
  }
}

クライアントサイド実装(録画端末)

ストリーミング制御ロジック

const mediaSource = new MediaSource();
const ws = new WebSocket('ws://localhost:9898/live');

navigator.mediaDevices.getUserMedia({ video: true, audio: true })
  .then(stream => {
    const recorder = new MediaRecorder(stream, { 
      mimeType: 'video/webm; codecs=vp9'
    });
    
    recorder.ondataavailable = e => {
      if (e.data.size > 0) ws.send(e.data);
    };
    
    document.getElementById('startBtn').onclick = () => {
      recorder.start(1000); // 1秒間隔でデータ送信
    };
  });

クライアントサイド実装(再生端末)

メディア再生処理

const mediaSource = new MediaSource();
const ws = new WebSocket('ws://localhost:9898/live');
const videoElement = document.getElementById('player');

videoElement.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', () => {
  const buffer = mediaSource.addSourceBuffer('video/webm; codecs=vp9');
  
  ws.onmessage = e => {
    e.data.arrayBuffer().then(data => {
      if (!buffer.updating) buffer.appendBuffer(data);
      if (videoElement.buffered.length > 0) {
        videoElement.currentTime = videoElement.buffered.end(0) - 0.5;
      }
    });
  };
});

ユーザーインターフェース

録画端末UI

<video id="localFeed" autoplay muted></video>
<button id="startBtn">配信開始</button>

再生端末UI

<video id="player" autoplay controls></video>

タグ: Netty MediaRecorder MediaSource websocket WebRTC

5月25日 15:47 投稿