システムアーキテクチャの概要
本システムは以下の3つの主要コンポーネントで構成されます:
- Nettyベースのビデオフレーム転送システム
- ブラウザ上のMediaRecorderによる映像キャプチャ
- 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>