diff --git a/CHANGES.md b/CHANGES.md index fe2af3c4..eb229120 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,8 @@ ## develop +- [ADD] `Mp4MediaStream` の対応コーデックに VP8 を追加する + - @sile - [ADD] `Mp4MediaStream` の対応コーデックに AAC を追加する - @sile diff --git a/packages/mp4-media-stream/README.md b/packages/mp4-media-stream/README.md index 51cf85fe..66df35b0 100644 --- a/packages/mp4-media-stream/README.md +++ b/packages/mp4-media-stream/README.md @@ -28,10 +28,18 @@ video.srcObject = stream 本ライブラリは Chrome や Edge 等の Chromium ベースのブラウザで動作します。 +## 対応コーデック + +- 映像: + - H.264 + - VP8 +- 音声: + - AAC + - Opus + ## 未対応機能 以下の機能には現時点では対応していません: -- H.264 / Opus / AAC 以外のコーデックを含んだ MP4 の再生 - 再生開始位置の指定(シーク) - 再生の一時停止・再開 - 数 GB を超える MP4 ファイルの再生 diff --git a/packages/mp4-media-stream/src/mp4_media_stream.ts b/packages/mp4-media-stream/src/mp4_media_stream.ts index 130e05eb..d3ad83d0 100644 --- a/packages/mp4-media-stream/src/mp4_media_stream.ts +++ b/packages/mp4-media-stream/src/mp4_media_stream.ts @@ -201,6 +201,10 @@ class Mp4MediaStream { // JSON.parse() の結果では config.description の型は number[] となって期待とは異なるので // ここで適切な型に変換している config.description = new Uint8Array(config.description as object as number[]) + if (config.description.byteLength === 0) { + // コーデックによっては description が存在しないので空なら削除する + config.description = undefined + } if (!(await VideoDecoder.isConfigSupported(config)).supported) { throw new Error(`Unsupported video decoder configuration: ${JSON.stringify(config)}`) @@ -238,22 +242,29 @@ class Mp4MediaStream { // JSON.parse() の結果では config.description の型は number[] となって期待とは異なるので // ここで適切な型に変換している config.description = new Uint8Array(config.description as object as number[]) + if (config.description.byteLength === 0) { + // コーデックによっては description が存在しないので空なら削除する + config.description = undefined + } const init = { output: async (frame: VideoFrame) => { - if (player.canvas === undefined || player.canvasCtx === undefined) { - return - } - try { - player.canvas.width = frame.displayWidth - player.canvas.height = frame.displayHeight - player.canvasCtx.drawImage(frame, 0, 0) + if (player.canvas === undefined || player.canvasCtx === undefined) { + return + } + + try { + player.canvas.width = frame.displayWidth + player.canvas.height = frame.displayHeight + player.canvasCtx.drawImage(frame, 0, 0) + } catch (error) { + // エラーが発生した場合には再生を停止する + await this.stopPlayer(playerId) + throw error + } + } finally { frame.close() - } catch (error) { - // エラーが発生した場合には再生を停止する - await this.stopPlayer(playerId) - throw error } }, error: async (error: DOMException) => { @@ -286,21 +297,24 @@ class Mp4MediaStream { const config = this.wasmJsonToValue(configWasmJson) as AudioDecoderConfig const init = { output: async (data: AudioData) => { - if (player.audioInputNode === undefined) { - return - } - try { - const samples = new Float32Array(data.numberOfFrames * data.numberOfChannels) - data.copyTo(samples, { planeIndex: 0, format: 'f32' }) - data.close() + if (player.audioInputNode === undefined) { + return + } - const timestamp = data.timestamp - player.audioInputNode.port.postMessage({ timestamp, samples }, [samples.buffer]) - } catch (e) { - // エラーが発生した場合には再生を停止する - await this.stopPlayer(playerId) - throw e + try { + const samples = new Float32Array(data.numberOfFrames * data.numberOfChannels) + data.copyTo(samples, { planeIndex: 0, format: 'f32' }) + + const timestamp = data.timestamp + player.audioInputNode.port.postMessage({ timestamp, samples }, [samples.buffer]) + } catch (e) { + // エラーが発生した場合には再生を停止する + await this.stopPlayer(playerId) + throw e + } + } finally { + data.close() } }, error: async (error: DOMException) => { diff --git a/packages/mp4-media-stream/wasm/src/mp4.rs b/packages/mp4-media-stream/wasm/src/mp4.rs index 1312247e..a6c25f5a 100644 --- a/packages/mp4-media-stream/wasm/src/mp4.rs +++ b/packages/mp4-media-stream/wasm/src/mp4.rs @@ -6,7 +6,7 @@ use shiguredo_mp4::{ aux::SampleTableAccessor, boxes::{ Avc1Box, FtypBox, HdlrBox, IgnoredBox, MoovBox, Mp4aBox, OpusBox, SampleEntry, StblBox, - TrakBox, + TrakBox, Vp08Box, }, BaseBox, Decode, Either, Encode, }; @@ -38,6 +38,15 @@ impl VideoDecoderConfig { coded_height: b.visual.height, } } + + pub fn from_vp08_box(b: &Vp08Box) -> Self { + Self { + codec: "vp8".to_owned(), + description: Vec::new(), + coded_width: b.visual.width, + coded_height: b.visual.height, + } + } } #[derive(Debug, Serialize)] @@ -111,6 +120,7 @@ impl Track { match sample_table.stbl_box().stsd_box.entries.first() { Some(SampleEntry::Avc1(_)) => (), + Some(SampleEntry::Vp08(_)) => (), Some(SampleEntry::Opus(_)) => (), Some(SampleEntry::Mp4a(_)) => (), Some(b) => { @@ -201,6 +211,9 @@ impl Mp4 { SampleEntry::Avc1(b) => { video_configs.push(VideoDecoderConfig::from_avc1_box(b)); } + SampleEntry::Vp08(b) => { + video_configs.push(VideoDecoderConfig::from_vp08_box(b)); + } SampleEntry::Opus(b) => { audio_configs.push(AudioDecoderConfig::from_opus_box(b)); } diff --git a/packages/mp4-media-stream/wasm/src/player.rs b/packages/mp4-media-stream/wasm/src/player.rs index 7569a6c1..af09f4f4 100644 --- a/packages/mp4-media-stream/wasm/src/player.rs +++ b/packages/mp4-media-stream/wasm/src/player.rs @@ -180,6 +180,10 @@ impl TrackPlayer { let config = VideoDecoderConfig::from_avc1_box(b); WasmApi::create_video_decoder(self.player_id, config).await } + SampleEntry::Vp08(b) => { + let config = VideoDecoderConfig::from_vp08_box(b); + WasmApi::create_video_decoder(self.player_id, config).await + } SampleEntry::Opus(b) => { let config = AudioDecoderConfig::from_opus_box(b); WasmApi::create_audio_decoder(self.player_id, config).await