diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts index 814dc8e..ddaaafa 100644 --- a/src/isobmff/isobmff-demuxer.ts +++ b/src/isobmff/isobmff-demuxer.ts @@ -3151,17 +3151,24 @@ abstract class IsobmffTrackBacking implements InputTrackBacking { data = readBytes(slice, sampleInfo.sampleSize); - if (this.internalTrack.encryptionAuxInfo) { - assert(this.internalTrack.encryptionInfo); + if (this.internalTrack.encryptionInfo) { + let sampleEncryption: SampleEncryptionInfo | null = null; - const entries = await resolveEncryptionAuxInfo( - this.internalTrack.demuxer.reader, - this.internalTrack.encryptionInfo, - this.internalTrack.encryptionAuxInfo, - ); + if (this.internalTrack.encryptionAuxInfo) { + const entries = await resolveEncryptionAuxInfo( + this.internalTrack.demuxer.reader, + this.internalTrack.encryptionInfo, + this.internalTrack.encryptionAuxInfo, + ); - if (sampleIndex < entries.length) { - data = await decryptSample(this.internalTrack, entries[sampleIndex]!, data, null); + if (sampleIndex < entries.length) { + sampleEncryption = entries[sampleIndex]!; + } + } + + sampleEncryption ??= getDefaultSampleEncryption(this.internalTrack.encryptionInfo); + if (sampleEncryption) { + data = await decryptSample(this.internalTrack, sampleEncryption, data, null); } } } @@ -3207,8 +3214,12 @@ abstract class IsobmffTrackBacking implements InputTrackBacking { data = readBytes(slice, fragmentSample.byteSize); - if (fragmentSample.encryption) { - data = await decryptSample(this.internalTrack, fragmentSample.encryption, data, fragment); + if (this.internalTrack.encryptionInfo) { + const sampleEncryption = fragmentSample.encryption + ?? getDefaultSampleEncryption(this.internalTrack.encryptionInfo); + if (sampleEncryption) { + data = await decryptSample(this.internalTrack, sampleEncryption, data, fragment); + } } } @@ -3779,6 +3790,17 @@ const resolveEncryptionAuxInfo = async ( return entries; }; +const getDefaultSampleEncryption = (encryptionInfo: TrackEncryptionInfo): SampleEncryptionInfo | null => { + if (!encryptionInfo.defaultConstantIv) { + return null; + } + + return { + iv: encryptionInfo.defaultConstantIv, + subsamples: null, + }; +}; + const decryptSample = async ( track: InternalTrack, sampleEncryption: SampleEncryptionInfo, diff --git a/test/browser/read-mp4.test.ts b/test/browser/read-mp4.test.ts new file mode 100644 index 0000000..de0aa4a --- /dev/null +++ b/test/browser/read-mp4.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from 'vitest'; +import { Input } from '../../src/input.js'; +import { UrlSource } from '../../src/source.js'; +import { ALL_FORMATS } from '../../src/input-format.js'; +import { AudioSampleSink } from '../../src/media-sink.js'; +import { assert } from '../../src/misc.js'; + +test('Encrypted MP4 without senc', async () => { + using input = new Input({ + source: new UrlSource('/639955605-b752f7ad-7ce8-43c2-8ae9-88e74c6ae696.mp4'), + formats: ALL_FORMATS, + formatOptions: { + isobmff: { + resolveKeyId: () => 'aabbccddeeff00112233445566778899', + }, + }, + }); + + const audioTrack = await input.getPrimaryAudioTrack(); + assert(audioTrack); + + // Test that it can decode + const sink = new AudioSampleSink(audioTrack); + using firstSample = await sink.getSample(0); + assert(firstSample); + expect(firstSample.timestamp).toBe(0); +}); diff --git a/test/public/639955605-b752f7ad-7ce8-43c2-8ae9-88e74c6ae696.mp4 b/test/public/639955605-b752f7ad-7ce8-43c2-8ae9-88e74c6ae696.mp4 new file mode 100755 index 0000000..d5c3b2a Binary files /dev/null and b/test/public/639955605-b752f7ad-7ce8-43c2-8ae9-88e74c6ae696.mp4 differ