From dbfd351ee4903566fdeded72134417212a52387d Mon Sep 17 00:00:00 2001 From: foxyz Date: Fri, 9 Oct 2026 20:44:14 +0300 Subject: [PATCH] Relay picks the most compact video codec (HEVC, AV1, VP9, then h264) Co-Authored-By: Claude Opus 5.5 --- docs/ARCHITECTURE.md | 5 +++-- packages/core/lib/src/yt_dlp.dart | 17 +++++++++++------ packages/core/test/yt_dlp_test.dart | 17 ++++++++++++++--- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 690d2ac..622b545 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -512,8 +512,9 @@ all three link modes. Nothing is bundled or installed by hand. - the best single file (`pickSingleFile`: video and audio not `none`, a video container, tallest up to 1080p, else the shortest above), unless - a video-only + audio-only pair is taller (`pickSplit`): video tallest - up to 1080p (else the shortest above), avc1 before vp9 before av01 - (hardware decoding), then bitrate; audio the original language, not a + up to 1080p (else the shortest above), HEVC before AV1 before VP9 + before h264 (the most compact first: relayed bytes cost every viewer + and the server buffer, and mpv decodes them all), then bitrate; audio the original language, not a `-drc` variant unless alone, m4a/aac before opus, then bitrate. YouTube has only these (299 + 140 for a 1080p60 video). - Neither (only HLS/DASH playlists) or a live stream: nothing to relay. diff --git a/packages/core/lib/src/yt_dlp.dart b/packages/core/lib/src/yt_dlp.dart index 009f312..631d70f 100644 --- a/packages/core/lib/src/yt_dlp.dart +++ b/packages/core/lib/src/yt_dlp.dart @@ -161,18 +161,23 @@ YtDlpFormat? pickSingleFile(List formats, {int maxHeight = 1080}) { return YtDlpFormat._of(ok.first); } +// Relayed bytes cost every viewer bandwidth and the server buffer, and mpv +// decodes all of these: the most compact codec first. HEVC (hardware +// decoding nearly everywhere), then AV1 (smallest; software is fine to +// 1080p), then VP9, then h264. int _videoCodecRank(Map f) { final c = '${f['vcodec']}'.toLowerCase(); - if (c.startsWith('avc') || c.startsWith('h264')) return 0; - if (c.startsWith('vp9') || c.startsWith('vp09')) return 1; - if (c.startsWith('av01')) return 2; - return 3; + if (c.startsWith('hvc') || c.startsWith('hev') || c.startsWith('h265')) return 0; + if (c.startsWith('av01')) return 1; + if (c.startsWith('vp9') || c.startsWith('vp09')) return 2; + if (c.startsWith('avc') || c.startsWith('h264')) return 3; + return 4; } /// The best video-only + audio-only pair of plain http(s) files (YouTube's /// DASH formats), or null. Video: the tallest up to [maxHeight] (else the -/// shortest above), avc1 before vp9 before av01 (hardware decoding), then -/// bitrate. Audio: the original language, not a `-drc` variant, m4a/aac +/// shortest above), HEVC before AV1 before VP9 before h264 (smallest +/// first, see [_videoCodecRank]), then bitrate. Audio: the original language, not a `-drc` variant, m4a/aac /// before others (opus), then bitrate. (YtDlpFormat, YtDlpFormat)? pickSplit(List formats, {int maxHeight = 1080}) { final video = [ diff --git a/packages/core/test/yt_dlp_test.dart b/packages/core/test/yt_dlp_test.dart index 72fbea4..2a88d31 100644 --- a/packages/core/test/yt_dlp_test.dart +++ b/packages/core/test/yt_dlp_test.dart @@ -90,10 +90,10 @@ void main() { _f('96', 'm3u8_native', 'avc1', 'mp4a', 1080), ]; - test('YouTube: avc1 video up to 1080p (highest bitrate) + m4a audio, with the chunk size', () { + test('YouTube: the most compact codec up to 1080p (VP9 over h264 here) + m4a audio, with the chunk size', () { final t = pickTracks(youtube); - expect([for (final x in t) (x.kind, x.format.id)], [('video', '299'), ('audio', '140')]); - expect(t.first.format.chunkSize, 10485760); + expect([for (final x in t) (x.kind, x.format.id)], [('video', '248'), ('audio', '140')]); + expect(YtDlpInfo.parse({'title': 'x', 'formats': youtube}).formats['299']?.chunkSize, 10485760); expect(YtDlpInfo.parse({'title': 'x', 'formats': youtube}).formats['137']?.height, 1080); }); @@ -200,4 +200,15 @@ void main() { await media.close(); expect(n, size); }, timeout: const Timeout(Duration(minutes: 3)), skip: Platform.environment['YTDLP_LIVE'] == '1' ? false : 'YTDLP_LIVE not set'); + + test('codec order: HEVC, then AV1, then VP9, then h264 at the same height', () { + final audio = _f('140', 'https', 'none', 'mp4a.40.2', null); + String pick(List codecs) => pickSplit([ + for (final (i, c) in codecs.indexed) _f('v$i', 'https', c, 'none', 1080), + audio, + ])!.$1.id; + expect(pick(['avc1.640033', 'vp09.00.40.08', 'av01.0.08M.08', 'hvc1.1.6.L12']), 'v3'); + expect(pick(['avc1.640033', 'vp09.00.40.08', 'av01.0.08M.08']), 'v2'); + expect(pick(['avc1.640033', 'vp09.00.40.08']), 'v1'); + }); }