From eba3df3d2e5e6da7e9346f5b8bf7c898f30b5ceb Mon Sep 17 00:00:00 2001 From: mister-ben <1676039+mister-ben@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:57:55 +0100 Subject: [PATCH 1/3] fix: extract full HEVC codec string --- lib/mp4/probe.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/mp4/probe.js b/lib/mp4/probe.js index a7045a38..28f0cd49 100644 --- a/lib/mp4/probe.js +++ b/lib/mp4/probe.js @@ -353,6 +353,48 @@ getTracks = function(init) { // and are using the default track.codec = 'mp4a.40.2'; } + } else if(/^h(vc|ev)1$/i.test(track.codec)) { + // Just hvc1 or hev1 as codec string is not sufficient, canPlayType won't accept it + // It must be CODEC.PROFILE.COMPATIBILTY.LEVEL.CONSTRAINTS + // https://www.etsi.org/deliver/etsi_ts/103200_103299/103285/01.01.01_60/ts_103285v010101p.pdf + // https://github.com/wader/fq/blob/ad0c6ecd1b79cea97bbb3048106ce309e0ec7ce0/format/mpeg/hevc_dcr.go#L24 + codecConfig = codecBox.subarray(78); + codecConfigType = parseType$1(codecConfig.subarray(4, 8)); + + if (codecConfigType === 'hvcC' && codecConfig.length > 21) { + // These stored in one byte as int(2), int(1), int(5) + const profileSpaceTierIDC = codecConfig[9]; + const generalProfileSpace = ['', 'A', 'B', 'C'][profileSpaceTierIDC >> 6]; + const generalTierFlag = (profileSpaceTierIDC & 0x20) ? 'H' : 'L'; + const generalProfileIDC = profileSpaceTierIDC & 0x1f; + + const generalLevelIDC = codecConfig[20]; + + // general_profile_compatibility_flags, but in reverse bit order, in a hexadecimal representation + const profileCompatibility = parseInt(Array.from(codecConfig.subarray(10, 14)).map( + byte => byte.toString(2).padStart(8,'0') + ).join('').split('').reverse().join(''), 2).toString(16); + + // hexadecimal representation of the general_constraint_indicator_flags. + // Each byte isseparated by a '.', and trailing zero bytes may be omitted. + const contraintsBytes = Array.from(codecConfig.subarray(14, 20)).map( + byte => byte.toString(16).toUpperCase().padStart(2, '0') + ); + + // Trailing zeros removed from joined string. Although the spec says trailing zeros + // may be omitted, every example has one byte included even if zero. + track.codec = [ + track.codec, + generalProfileSpace + parseInt(generalProfileIDC, 2).toString(10), + profileCompatibility, + generalTierFlag + generalLevelIDC, + ...contraintsBytes + ].join('.').replace(/(\.00)+\.00$/, ''); + } else { + // TODO: show a warning that we couldn't parse the codec + // and are using the default + track.codec = 'hvc1.1.6.L93.B0'; + } } else { // flac, opus, etc track.codec = track.codec.toLowerCase(); From e566b6ac297d10db34c9a7826d02aba1fecd54f8 Mon Sep 17 00:00:00 2001 From: mister-ben <1676039+mister-ben@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:18:56 +0200 Subject: [PATCH 2/3] tidy comments --- lib/mp4/probe.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/mp4/probe.js b/lib/mp4/probe.js index 28f0cd49..a65d7d11 100644 --- a/lib/mp4/probe.js +++ b/lib/mp4/probe.js @@ -354,10 +354,13 @@ getTracks = function(init) { track.codec = 'mp4a.40.2'; } } else if(/^h(vc|ev)1$/i.test(track.codec)) { - // Just hvc1 or hev1 as codec string is not sufficient, canPlayType won't accept it - // It must be CODEC.PROFILE.COMPATIBILTY.LEVEL.CONSTRAINTS - // https://www.etsi.org/deliver/etsi_ts/103200_103299/103285/01.01.01_60/ts_103285v010101p.pdf - // https://github.com/wader/fq/blob/ad0c6ecd1b79cea97bbb3048106ce309e0ec7ce0/format/mpeg/hevc_dcr.go#L24 + /* + Using just hvc1 or hev1 as codec string is not sufficient as canPlayType won't accept it. + It must be CODEC.PROFILE.COMPATIBILTY.LEVEL.CONSTRAINTS + See these for approach: + https://www.etsi.org/deliver/etsi_ts/103200_103299/103285/01.01.01_60/ts_103285v010101p.pdf + https://github.com/wader/fq/blob/ad0c6ecd1b79cea97bbb3048106ce309e0ec7ce0/format/mpeg/hevc_dcr.go#L24 + */ codecConfig = codecBox.subarray(78); codecConfigType = parseType$1(codecConfig.subarray(4, 8)); @@ -381,8 +384,8 @@ getTracks = function(init) { byte => byte.toString(16).toUpperCase().padStart(2, '0') ); - // Trailing zeros removed from joined string. Although the spec says trailing zeros - // may be omitted, every example has one byte included even if zero. + // Trailing zeros removed from joined string. Leave one zero byte if there + // are multiple, as all examples are like this track.codec = [ track.codec, generalProfileSpace + parseInt(generalProfileIDC, 2).toString(10), @@ -391,8 +394,7 @@ getTracks = function(init) { ...contraintsBytes ].join('.').replace(/(\.00)+\.00$/, ''); } else { - // TODO: show a warning that we couldn't parse the codec - // and are using the default + // This is a fallback. Show a warning here? track.codec = 'hvc1.1.6.L93.B0'; } } else { From 25d3db15cdef825db4754ab22ae30de87a2ceffe Mon Sep 17 00:00:00 2001 From: mister-ben <1676039+mister-ben@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:46:54 +0200 Subject: [PATCH 3/3] update deprecated workflow actions and disable apparmor --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 769de977..49945521 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,11 @@ jobs: CI_TEST_TYPE: ${{matrix.test-type}} runs-on: ${{matrix.os}} steps: + - name: Disable apparmor because it breaks Chromium headless + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + - name: checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: read node version from .nvmrc run: echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_OUTPUT @@ -47,7 +50,7 @@ jobs: run: pulseaudio -D - name: setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v5 with: node-version: '${{steps.nvm.outputs.NVMRC}}' cache: npm