From c6e505a6fe7ae6ab4688956e6bf354c85dc5c638 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Mon, 13 Apr 2026 17:47:48 +0200 Subject: [PATCH] Make default keyFrameInterval 2 seconds to align with default HLS segment duration --- docs/guide/media-sources.md | 2 +- src/encode.ts | 2 +- src/media-source.ts | 2 +- src/output-format.ts | 3 +- test/browser/hls-output.test.ts | 60 +++++++++++++++++++++++++++++++++ todo.txt | 2 -- 6 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 test/browser/hls-output.test.ts delete mode 100644 todo.txt diff --git a/docs/guide/media-sources.md b/docs/guide/media-sources.md index 3c0596b..5e34203 100644 --- a/docs/guide/media-sources.md +++ b/docs/guide/media-sources.md @@ -73,7 +73,7 @@ type VideoEncodingConfig = { - `'keep'`: The samples' alpha data is also encoded as side data. Make sure to pair this mode with a container format that supports transparency (such as WebM or Matroska). - `bitrateMode`: Can be used to control constant vs. variable bitrate. - `latencyMode`: The latency mode as specified by the WebCodecs API. Browsers default to `quality`. Media stream-driven video sources will automatically use the `realtime` setting. -- `keyFrameInterval`: The maximum interval in seconds between two adjacent key frames. Defaults to 5 seconds. More frequent key frames improve seeking behavior but increase file size. When using multiple video tracks, this value should be set to the same value for all tracks. +- `keyFrameInterval`: The maximum interval in seconds between two adjacent key frames. Defaults to 2 seconds. More frequent key frames improve seeking behavior but increase file size. When using multiple video tracks, this value should be set to the same value for all tracks. - `fullCodecString`: Allows you to optionally specify the full codec string used by the video encoder, as specified in the [Mediabunny Codec Registry](/codec-registry/overview). For example, you may set it to `'avc1.42001f'` when using AVC. Keep in mind that the codec string must still match the codec specified in `codec`. If you don't set this field, a codec string will be generated automatically. - `hardwareAcceleration`: A hint that configures the hardware acceleration method of this codec. This is best left on `'no-preference'`. - `scalabilityMode`: An encoding scalability mode identifier as defined by [WebRTC-SVC](https://w3c.github.io/webrtc-svc/#scalabilitymodes*). diff --git a/src/encode.ts b/src/encode.ts index 5de3094..cd27631 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -43,7 +43,7 @@ export type VideoEncodingConfig = { */ bitrate: number | Quality; /** - * The interval, in seconds, of how often frames are encoded as a key frame. The default is 5 seconds. Frequent key + * The interval, in seconds, of how often frames are encoded as a key frame. The default is 2 seconds. Frequent key * frames improve seeking behavior but increase file size. When using multiple video tracks, you should give them * all the same key frame interval. */ diff --git a/src/media-source.ts b/src/media-source.ts index d7a0939..7806a8b 100644 --- a/src/media-source.ts +++ b/src/media-source.ts @@ -531,7 +531,7 @@ class VideoEncoderWrapper { } assert(this.encoderInitialized); - const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 5; + const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 2; const multipleOfKeyFrameInterval = Math.floor(sampleToEncode.timestamp / keyFrameInterval); // Ensure a key frame every keyFrameInterval seconds. It is important that all video tracks diff --git a/src/output-format.ts b/src/output-format.ts index 7622007..6252c62 100644 --- a/src/output-format.ts +++ b/src/output-format.ts @@ -1205,7 +1205,8 @@ export type HlsOutputFormatOptions = { * * Mediabunny will try not to emit media segments longer than the target duration, but it is forced to if key frames * are provided with a longer period than the target duration. Therefore, make sure to encode a key frame at least - * every `targetDuration` seconds to guarantee segment length. + * every `targetDuration` seconds to guarantee segment length, controllable via + * {@link VideoEncodingConfig.keyFrameInterval}. */ targetDuration?: number; /** diff --git a/test/browser/hls-output.test.ts b/test/browser/hls-output.test.ts new file mode 100644 index 0000000..f231276 --- /dev/null +++ b/test/browser/hls-output.test.ts @@ -0,0 +1,60 @@ +import { expect, test } from 'vitest'; +import { Output } from '../../src/output.js'; +import { HlsOutputFormat, MpegTsOutputFormat } from '../../src/output-format.js'; +import { BufferTarget, PathedTarget } from '../../src/target.js'; +import { CanvasSource } from '../../src/media-source.js'; +import { QUALITY_HIGH } from '../../src/encode.js'; + +test('HLS output, key frames aligning with segment boundaries by default', async () => { + let playlistText: string | null = null; + + const output = new Output({ + format: new HlsOutputFormat({ + segmentFormat: new MpegTsOutputFormat(), + onPlaylist: (text) => { playlistText = text; }, + }), + target: new PathedTarget('', () => new BufferTarget()), + }); + + const canvas = new OffscreenCanvas(640, 480); + const ctx = canvas.getContext('2d')!; + ctx.fillStyle = '#ff0000'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const videoSource = new CanvasSource(canvas, { + codec: 'avc', + bitrate: QUALITY_HIGH, + }); + output.addVideoTrack(videoSource); + + await output.start(); + + const fps = 2; + const frameDuration = 1 / fps; + const totalFrames = 10 * fps; + for (let i = 0; i < totalFrames; i++) { + await videoSource.add(i * frameDuration, frameDuration); + } + + await output.finalize(); + + expect(playlistText).toBe(`#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-TARGETDURATION:2 +#EXT-X-INDEPENDENT-SEGMENTS + +#EXTINF:2, +segment-1-1.ts +#EXTINF:2, +segment-1-2.ts +#EXTINF:2, +segment-1-3.ts +#EXTINF:2, +segment-1-4.ts +#EXTINF:2, +segment-1-5.ts + +#EXT-X-ENDLIST +`); +}); diff --git a/todo.txt b/todo.txt deleted file mode 100644 index 398bc83..0000000 --- a/todo.txt +++ /dev/null @@ -1,2 +0,0 @@ -clash between target duration and default keyframe interval which is 5. This is easy to miss. Also messes up in -conversion api. \ No newline at end of file