tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit c2d456b45faa46ecd3e1c9991f4c892198876a5d
parent 659d342a62d7638e011d13199e58c6b2690f60a3
Author: Arnaud Bienner <arnaud.bienner@gmail.com>
Date:   Sun, 23 Nov 2025 19:56:03 +0000

Bug 1505675 - fMP4: parse senc box and use it to retrieve crypto info by default. r=karlt,media-playback-reviewers

Still use auxiliary data (saio/saiz), but as fallback in case senc is not present or couldn't be parsed.

Differential Revision: https://phabricator.services.mozilla.com/D272640

Diffstat:
Mdom/media/mp4/Atom.h | 2+-
Mdom/media/mp4/MoofParser.cpp | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mdom/media/mp4/MoofParser.h | 36++++++++++++++++++++++++++++++++----
Mdom/media/mp4/SampleIterator.cpp | 87+++++++++++++++++++++++--------------------------------------------------------
Mdom/media/mp4/SampleIterator.h | 2+-
Mdom/media/mp4/SinfParser.h | 2+-
Atesting/web-platform/tests/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-saio.https.html | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-senc.https.html | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtesting/web-platform/tests/encrypted-media/content/content-metadata.js | 10++++++++--
Mtesting/web-platform/tests/encrypted-media/util/testmediasource.js | 12++++++++++++
10 files changed, 370 insertions(+), 75 deletions(-)

diff --git a/dom/media/mp4/Atom.h b/dom/media/mp4/Atom.h @@ -10,7 +10,7 @@ namespace mozilla { class Atom { public: Atom() : mValid(false) {} - virtual bool IsValid() { return mValid; } + virtual bool IsValid() const { return mValid; } protected: bool mValid; diff --git a/dom/media/mp4/MoofParser.cpp b/dom/media/mp4/MoofParser.cpp @@ -122,6 +122,15 @@ MediaByteRange MoofParser::FirstCompleteMediaSegment() { return MediaByteRange(); } +const CencSampleEncryptionInfoEntry* MoofParser::GetSampleEncryptionEntry( + size_t aMoof, size_t aSample) const { + if (aMoof >= mMoofs.Length()) { + return nullptr; + } + return mMoofs[aMoof].GetSampleEncryptionEntry( + aSample, &mTrackSampleToGroupEntries, &mTrackSampleEncryptionInfoEntries); +} + DDLoggedTypeDeclNameAndBase(BlockingStream, ByteStream); class BlockingStream : public ByteStream, @@ -421,7 +430,7 @@ class CtsComparator { }; Moof::Moof(Box& aBox, const TrackParseMode& aTrackParseMode, Trex& aTrex, - Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf, + Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, const Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio, nsTArray<TrackEndCts>& aTracksEndCts) : mRange(aBox.Range()), @@ -548,7 +557,11 @@ Moof::Moof(Box& aBox, const TrackParseMode& aTrackParseMode, Trex& aTrex, MP4Interval<TimeUnit>(ctsOrder[0]->mCompositionRange.start, ctsOrder.LastElement()->mCompositionRange.end); } - ProcessCencAuxInfo(aSinf.mDefaultEncryptionType); + // No need to retrieve auxiliary encryption data if we have a senc box: we + // won't use it in SampleIterator::GetNext() + if (!mSencValid) { + ProcessCencAuxInfo(aSinf.mDefaultEncryptionType); + } } LOG_DEBUG(Moof, "Done."); } @@ -645,9 +658,76 @@ bool Moof::ProcessCencAuxInfo(AtomType aScheme) { return true; } +const CencSampleEncryptionInfoEntry* Moof::GetSampleEncryptionEntry( + size_t aSample, + const FallibleTArray<SampleToGroupEntry>* aTrackSampleToGroupEntries, + const FallibleTArray<CencSampleEncryptionInfoEntry>* + aTrackSampleEncryptionInfoEntries) const { + const SampleToGroupEntry* sampleToGroupEntry = nullptr; + + // Default to using the sample to group entries for the fragment, otherwise + // fall back to the sample to group entries for the track. + const FallibleTArray<SampleToGroupEntry>* sampleToGroupEntries = + mFragmentSampleToGroupEntries.Length() != 0 + ? &mFragmentSampleToGroupEntries + : aTrackSampleToGroupEntries; + + if (!sampleToGroupEntries) { + return nullptr; + } + + uint32_t seen = 0; + + for (const SampleToGroupEntry& entry : *sampleToGroupEntries) { + if (seen + entry.mSampleCount > aSample) { + sampleToGroupEntry = &entry; + break; + } + seen += entry.mSampleCount; + } + + // ISO-14496-12 Section 8.9.2.3 and 8.9.4 : group description index + // (1) ranges from 1 to the number of sample group entries in the track + // level SampleGroupDescription Box, or (2) takes the value 0 to + // indicate that this sample is a member of no group, in this case, the + // sample is associated with the default values specified in + // TrackEncryption Box, or (3) starts at 0x10001, i.e. the index value + // 1, with the value 1 in the top 16 bits, to reference fragment-local + // SampleGroupDescription Box. + + // According to the spec, ISO-14496-12, the sum of the sample counts in this + // box should be equal to the total number of samples, and, if less, the + // reader should behave as if an extra SampleToGroupEntry existed, with + // groupDescriptionIndex 0. + + if (!sampleToGroupEntry || sampleToGroupEntry->mGroupDescriptionIndex == 0) { + return nullptr; + } + + const FallibleTArray<CencSampleEncryptionInfoEntry>* entries = + aTrackSampleEncryptionInfoEntries; + + uint32_t groupIndex = sampleToGroupEntry->mGroupDescriptionIndex; + + // If the first bit is set to a one, then we should use the sample group + // descriptions from the fragment. + if (groupIndex > SampleToGroupEntry::kFragmentGroupDescriptionIndexBase) { + groupIndex -= SampleToGroupEntry::kFragmentGroupDescriptionIndexBase; + entries = &mFragmentSampleEncryptionInfoEntries; + } + + if (!entries) { + return nullptr; + } + + // The group_index is one based. + return groupIndex > entries->Length() ? nullptr + : &entries->ElementAt(groupIndex - 1); +} + void Moof::ParseTraf(Box& aBox, const TrackParseMode& aTrackParseMode, Trex& aTrex, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, - Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio) { + const Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio) { LOG_DEBUG( Traf, "Starting, aTrackParseMode=%s, track#=%" PRIu32 @@ -712,9 +792,10 @@ void Moof::ParseTraf(Box& aBox, const TrackParseMode& aTrackParseMode, mTfhd.mTrackId); return; } - // Now search for TRUN boxes. + // Second pass: search for trun boxes and senc boxes. uint64_t decodeTime = tfdt.IsValid() ? tfdt.mBaseMediaDecodeTime : *aDecodeTime; + Box sencBox; for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) { if (box.IsType("trun")) { if (ParseTrun(box, aMvhd, aMdhd, aEdts, &decodeTime, aIsAudio).isOk()) { @@ -724,8 +805,23 @@ void Moof::ParseTraf(Box& aBox, const TrackParseMode& aTrackParseMode, mValid = false; return; } + } else if (box.IsType("senc")) { + LOG_DEBUG(Moof, "Found senc box"); + sencBox = box; + } + } + + // senc box found: parse it. + // We need to parse senc boxes in another pass because we need potential sgpd + // and sbgp boxes to have been parsed, as they might override the IV size and + // as such the size of senc entries. + // trun box shall have been parsed as well, so mIndex has been filled. + if (sencBox.IsAvailable()) { + if (ParseSenc(sencBox, aSinf).isErr()) [[unlikely]] { + LOG_WARN(Moof, "ParseSenc failed"); } } + *aDecodeTime = decodeTime; LOG_DEBUG(Traf, "Done, setting aDecodeTime=%." PRIu64 ".", decodeTime); } @@ -737,6 +833,82 @@ void Moof::FixRounding(const Moof& aMoof) { } } +Result<Ok, nsresult> Moof::ParseSenc(Box& aBox, const Sinf& aSinf) { + // If we already had a senc box, ignore following ones + // Not sure how likely this could be in real life + if (mSencValid) [[unlikely]] { + LOG_WARN(Moof, "Already found a valid senc box, ignoring new one"); + return Ok(); + } + + BoxReader reader(aBox); + const uint8_t version = MOZ_TRY(reader->ReadU8()); + const uint32_t flags = MOZ_TRY(reader->ReadU24()); + const uint32_t sampleCount = MOZ_TRY(reader->ReadU32()); + // ISO/IEC 23001-7 ยง7.2: + // "sample_count is the number of protected samples in the containing track or + // track fragment. This value SHALL be either zero (0) or the total number of + // samples in the track or track fragment." + if (sampleCount == 0) { + LOG_DEBUG(Moof, "senc box has 0 sample_count"); + // Though having sample_count = 0 seems to be compliant, return without + // error but don't set mSencValid to true in case there is another senc box + // or saio/saiz auxiliary data + return Ok(); + } + if (sampleCount != mIndex.Length()) { + LOG_ERROR(Moof, "Invalid sample count in senc box: expecting %zu, got %d\n", + mIndex.Length(), sampleCount); + return Err(NS_ERROR_FAILURE); + } + + if (version == 0) { + for (size_t i = 0; i < sampleCount; ++i) { + Sample& sample = mIndex[i]; + const CencSampleEncryptionInfoEntry* sampleInfo = + GetSampleEncryptionEntry(i); + uint8_t ivSize = sampleInfo ? sampleInfo->mIVSize : aSinf.mDefaultIVSize; + if (!reader->ReadArray(sample.mIV, ivSize)) { + return Err(MediaResult::Logged( + NS_ERROR_DOM_MEDIA_DEMUXER_ERR, + RESULT_DETAIL("sample InitializationVector error"), + gMediaDemuxerLog)); + } + // Clear arrays, to be safe, in the (unlikely and invalid) case we started + // to parse a previous senc box but it failed halfway. + sample.mPlainSizes.Clear(); + sample.mEncryptedSizes.Clear(); + const bool useSubSampleEncryption = flags & 0x02; + if (useSubSampleEncryption) { + uint16_t subsampleCount = MOZ_TRY(reader->ReadU16()); + for (uint16_t i = 0; i < subsampleCount; ++i) { + uint16_t bytesOfClearData = MOZ_TRY(reader->ReadU16()); + uint32_t bytesOfProtectedData = MOZ_TRY(reader->ReadU32()); + sample.mPlainSizes.AppendElement(bytesOfClearData); + sample.mEncryptedSizes.AppendElement(bytesOfProtectedData); + } + } else { + // No UseSubSampleEncryption flag means the entire sample is encrypted. + sample.mPlainSizes.AppendElement(0); + sample.mEncryptedSizes.AppendElement(sample.mByteRange.Length()); + } + } + } else if (version == 1) { + // TODO + LOG_ERROR(Senc, "version %d not supported yet", version); + return Err(NS_ERROR_FAILURE); + } else if (version == 2) { + // TODO + LOG_ERROR(Senc, "version %d not supported yet", version); + return Err(NS_ERROR_FAILURE); + } else { + LOG_ERROR(Senc, "Unknown version %d", version); + return Err(NS_ERROR_FAILURE); + } + mSencValid = true; + return Ok(); +} + Result<Ok, nsresult> Moof::ParseTrun(Box& aBox, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, uint64_t* aDecodeTime, bool aIsAudio) { diff --git a/dom/media/mp4/MoofParser.h b/dom/media/mp4/MoofParser.h @@ -122,7 +122,7 @@ class Edts : public Atom { public: Edts() : mMediaStart(0), mEmptyOffset(0) {} explicit Edts(Box& aBox); - virtual bool IsValid() override { + virtual bool IsValid() const override { // edts is optional return true; } @@ -136,6 +136,14 @@ class Edts : public Atom { struct Sample { mozilla::MediaByteRange mByteRange; + // Crypto information coming from senc box: shall be used first + CopyableTArray<uint8_t> mIV; + CopyableTArray<uint32_t> mPlainSizes; + // The number of encrypted bytes in each subsample. The nth element in the + // array is the number of encrypted bytes at the start of the nth subsample. + CopyableTArray<uint32_t> mEncryptedSizes; + // Crypto information coming from saio box: shall be used if no senc + // information is present mozilla::MediaByteRange mCencRange; media::TimeUnit mDecodeTime; MP4Interval<media::TimeUnit> mCompositionRange; @@ -239,12 +247,25 @@ using TrackParseMode = Variant<ParseAllTracks, uint32_t>; class Moof final : public Atom { public: Moof(Box& aBox, const TrackParseMode& aTrackParseMode, Trex& aTrex, - Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf, + Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, const Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio, nsTArray<TrackEndCts>& aTracksEndCts); - bool GetAuxInfo(AtomType aType, FallibleTArray<MediaByteRange>* aByteRanges); void FixRounding(const Moof& aMoof); + // Retrieve CencSampleEncryptionInfoEntry for a given sample number. + // Optionally, you can provide track's group boxes (sbgp): they will be used + // if the moof fragment does not contain a sbgp box. + const CencSampleEncryptionInfoEntry* GetSampleEncryptionEntry( + size_t aSample, + const FallibleTArray<SampleToGroupEntry>* aTrackSampleToGroupEntries = + nullptr, + const FallibleTArray<CencSampleEncryptionInfoEntry>* + aTrackSampleEncryptionInfoEntries = nullptr) const; + + // Returns true if a senc box has been found, successfully parsed, and + // contains crypto info + bool SencIsValid() const { return mSencValid; } + mozilla::MediaByteRange mRange; mozilla::MediaByteRange mMdatRange; MP4Interval<media::TimeUnit> mTimeRange; @@ -262,12 +283,13 @@ class Moof final : public Atom { private: // aDecodeTime is updated to the end of the parsed TRAF on return. void ParseTraf(Box& aBox, const TrackParseMode& aTrackParseMode, Trex& aTrex, - Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf, + Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, const Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio); // aDecodeTime is updated to the end of the parsed TRUN on return. Result<Ok, nsresult> ParseTrun(Box& aBox, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, uint64_t* aDecodeTime, bool aIsAudio); + Result<Ok, nsresult> ParseSenc(Box& aBox, const Sinf& aSinf); // Process the sample auxiliary information used by common encryption. // aScheme is used to select the appropriate auxiliary information and should // be set based on the encryption scheme used by the track being processed. @@ -275,7 +297,10 @@ class Moof final : public Atom { // from that standard. I.e. this function is used to handle up auxiliary // information from the cenc and cbcs schemes. bool ProcessCencAuxInfo(AtomType aScheme); + bool GetAuxInfo(AtomType aType, FallibleTArray<MediaByteRange>* aByteRanges); + media::TimeUnit mMaxRoundingError; + bool mSencValid = false; }; DDLoggedTypeDeclName(MoofParser); @@ -331,6 +356,9 @@ class MoofParser : public DecoderDoctorLifeLogger<MoofParser> { MediaByteRange FirstCompleteMediaSegment(); MediaByteRange FirstCompleteMediaHeader(); + const CencSampleEncryptionInfoEntry* GetSampleEncryptionEntry( + size_t moofNumber, size_t aMoof) const; + mozilla::MediaByteRange mInitRange; RefPtr<ByteStream> mSource; uint64_t mOffset; diff --git a/dom/media/mp4/SampleIterator.cpp b/dom/media/mp4/SampleIterator.cpp @@ -154,12 +154,12 @@ Result<already_AddRefed<MediaRawData>, MediaResult> SampleIterator::GetNext() { return sample.forget(); } + const nsTArray<Moof>& moofs = moofParser->Moofs(); + const Moof* currentMoof = &moofs[mCurrentMoof]; // We need to check if this moof has init data the CDM expects us to surface. // This should happen when handling the first sample, even if that sample // isn't encrypted (samples later in the moof may be). if (mCurrentSample == 0) { - const nsTArray<Moof>& moofs = moofParser->Moofs(); - const Moof* currentMoof = &moofs[mCurrentMoof]; if (!currentMoof->mPsshes.IsEmpty()) { // This Moof contained crypto init data. Report that. We only report // the init data on the Moof's first sample, to avoid reporting it more @@ -189,7 +189,7 @@ Result<already_AddRefed<MediaRawData>, MediaResult> SampleIterator::GetNext() { "Sample should not already have a key ID"); MOZ_ASSERT(writer->mCrypto.mConstantIV.IsEmpty(), "Sample should not already have a constant IV"); - CencSampleEncryptionInfoEntry* sampleInfo = GetSampleEncryptionEntry(); + const CencSampleEncryptionInfoEntry* sampleInfo = GetSampleEncryptionEntry(); if (sampleInfo) { // Use sample group information if present, this supersedes track level // information. @@ -209,7 +209,8 @@ Result<already_AddRefed<MediaRawData>, MediaResult> SampleIterator::GetNext() { } if ((writer->mCrypto.mIVSize == 0 && writer->mCrypto.mConstantIV.IsEmpty()) || - (writer->mCrypto.mIVSize != 0 && s->mCencRange.IsEmpty())) { + (writer->mCrypto.mIVSize != 0 && + (s->mCencRange.IsEmpty() && !currentMoof->SencIsValid()))) { // If mIVSize == 0, this indicates that a constant IV is in use, thus we // should have a non empty constant IV. Alternatively if IV size is non // zero, we should have an IV for this sample, which we need to look up @@ -219,8 +220,21 @@ Result<already_AddRefed<MediaRawData>, MediaResult> SampleIterator::GetNext() { RESULT_DETAIL("Crypto IV size inconsistent"), gMediaDemuxerLog)); } - // Parse auxiliary information if present - if (!s->mCencRange.IsEmpty()) { + // Retrieve encryption information + // This information might come from two places: the senc box, or the + // auxiliary data (indicated by saio and saiz boxes) + // Try to use senc information first, and fallback to auxiliary data if not + // present + if (currentMoof->SencIsValid()) { + if (writer->mCrypto.mIVSize != s->mIV.Length()) { + return Err(MediaResult::Logged( + NS_ERROR_DOM_MEDIA_DEMUXER_ERR, + RESULT_DETAIL("Inconsistent crypto IV size"), gMediaDemuxerLog)); + } + writer->mCrypto.mIV = s->mIV; + writer->mCrypto.mPlainSizes = s->mPlainSizes; + writer->mCrypto.mEncryptedSizes = s->mEncryptedSizes; + } else if (!s->mCencRange.IsEmpty()) { // The size comes from an 8 bit field AutoTArray<uint8_t, 256> cencAuxInfo; cencAuxInfo.SetLength(s->mCencRange.Length()); @@ -292,61 +306,10 @@ SampleDescriptionEntry* SampleIterator::GetSampleDescriptionEntry() { return &sampleDescriptions[sampleDescriptionIndex]; } -CencSampleEncryptionInfoEntry* SampleIterator::GetSampleEncryptionEntry() { - nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs(); - Moof* currentMoof = &moofs[mCurrentMoof]; - SampleToGroupEntry* sampleToGroupEntry = nullptr; - - // Default to using the sample to group entries for the fragment, otherwise - // fall back to the sample to group entries for the track. - FallibleTArray<SampleToGroupEntry>* sampleToGroupEntries = - currentMoof->mFragmentSampleToGroupEntries.Length() != 0 - ? &currentMoof->mFragmentSampleToGroupEntries - : &mIndex->mMoofParser->mTrackSampleToGroupEntries; - - uint32_t seen = 0; - - for (SampleToGroupEntry& entry : *sampleToGroupEntries) { - if (seen + entry.mSampleCount > mCurrentSample) { - sampleToGroupEntry = &entry; - break; - } - seen += entry.mSampleCount; - } - - // ISO-14496-12 Section 8.9.2.3 and 8.9.4 : group description index - // (1) ranges from 1 to the number of sample group entries in the track - // level SampleGroupDescription Box, or (2) takes the value 0 to - // indicate that this sample is a member of no group, in this case, the - // sample is associated with the default values specified in - // TrackEncryption Box, or (3) starts at 0x10001, i.e. the index value - // 1, with the value 1 in the top 16 bits, to reference fragment-local - // SampleGroupDescription Box. - - // According to the spec, ISO-14496-12, the sum of the sample counts in this - // box should be equal to the total number of samples, and, if less, the - // reader should behave as if an extra SampleToGroupEntry existed, with - // groupDescriptionIndex 0. - - if (!sampleToGroupEntry || sampleToGroupEntry->mGroupDescriptionIndex == 0) { - return nullptr; - } - - FallibleTArray<CencSampleEncryptionInfoEntry>* entries = - &mIndex->mMoofParser->mTrackSampleEncryptionInfoEntries; - - uint32_t groupIndex = sampleToGroupEntry->mGroupDescriptionIndex; - - // If the first bit is set to a one, then we should use the sample group - // descriptions from the fragment. - if (groupIndex > SampleToGroupEntry::kFragmentGroupDescriptionIndexBase) { - groupIndex -= SampleToGroupEntry::kFragmentGroupDescriptionIndexBase; - entries = &currentMoof->mFragmentSampleEncryptionInfoEntries; - } - - // The group_index is one based. - return groupIndex > entries->Length() ? nullptr - : &entries->ElementAt(groupIndex - 1); +const CencSampleEncryptionInfoEntry* SampleIterator::GetSampleEncryptionEntry() + const { + return mIndex->mMoofParser->GetSampleEncryptionEntry(mCurrentMoof, + mCurrentSample); } Result<CryptoScheme, nsCString> SampleIterator::GetEncryptionScheme() { @@ -381,7 +344,7 @@ Result<CryptoScheme, nsCString> SampleIterator::GetEncryptionScheme() { "indicates encryption, but could not find associated sinf box.")); } - CencSampleEncryptionInfoEntry* sampleInfo = GetSampleEncryptionEntry(); + const CencSampleEncryptionInfoEntry* sampleInfo = GetSampleEncryptionEntry(); if (sampleInfo && !sampleInfo->mIsEncrypted) { // May not have sample encryption info, but if we do, it should match other // metadata. diff --git a/dom/media/mp4/SampleIterator.h b/dom/media/mp4/SampleIterator.h @@ -44,7 +44,7 @@ class SampleIterator { // Gets the sample description entry for the current moof, or nullptr if // called without a valid current moof. SampleDescriptionEntry* GetSampleDescriptionEntry(); - CencSampleEncryptionInfoEntry* GetSampleEncryptionEntry(); + const CencSampleEncryptionInfoEntry* GetSampleEncryptionEntry() const; // Determines the encryption scheme in use for the current sample. If the // the scheme cannot be unambiguously determined, will return an error with diff --git a/dom/media/mp4/SinfParser.h b/dom/media/mp4/SinfParser.h @@ -22,7 +22,7 @@ class Sinf : public Atom { mDefaultSkipByteBlock(0) {} explicit Sinf(Box& aBox); - bool IsValid() override { + bool IsValid() const override { return !!mDefaultEncryptionType && // Should have an encryption scheme (mDefaultIVSize > 0 || // and either a default IV size mDefaultConstantIV.Length() > 0); // or a constant IV. diff --git a/testing/web-platform/tests/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-saio.https.html b/testing/web-platform/tests/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-saio.https.html @@ -0,0 +1,57 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Encrypted Media Extensions: Successful Playback, Temporary session with Clear Key, mp4, multiple keys for audio/video, with no saio boxes</title> + <link rel="help" href="https://w3c.github.io/encrypted-media/"> + + <!-- Web Platform Test Harness scripts --> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + + <!-- Helper scripts for Encrypted Media Extensions tests --> + <script src=/encrypted-media/util/utils.js></script> + <script src=/encrypted-media/util/testmediasource.js></script> + <script src=/encrypted-media/util/utf8.js></script> + + <!-- Content metadata --> + <script src=/encrypted-media/content/content-metadata.js></script> + + <!-- Message handler for Clear Key keysystem --> + <script src=/encrypted-media/util/clearkey-messagehandler.js></script> + + <!-- The script for this specific test --> + <script src=/encrypted-media/scripts/playback-temporary.js></script> + + </head> + <body> + <div id='log'></div> + + <div id='video'> + <video id="videoelement" width="200px"></video> + </div> + + <script> + var contentitem = content['mp4-av-multikey'], + handler = new MessageHandler( 'org.w3.clearkey', contentitem ), + config = { video: document.getElementById('videoelement'), + keysystem: 'org.w3.clearkey', + messagehandler: handler.messagehandler, + audioPath: contentitem.audio.path, + videoPath: contentitem.video.path, + audioType: contentitem.audio.type, + videoType: contentitem.video.type, + // Replace 'saio' text by ' aio', and 'saiz' by ' aiz' + // to make those boxes unrecognized: 'senc' boxes shall + // be used instead + audioBlankBytes: contentitem.audio.saioStarts.concat(contentitem.audio.saizStarts), + videoBlankBytes: contentitem.video.saioStarts.concat(contentitem.video.saizStarts), + initDataType: 'keyids', + initData: getInitData(contentitem,'keyids'), + testcase: 'multikey audio/video' }; + + + runTest(config); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-senc.https.html b/testing/web-platform/tests/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-senc.https.html @@ -0,0 +1,57 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Encrypted Media Extensions: Successful Playback, Temporary session with Clear Key, mp4, multiple keys for audio/video, with no senc boxes</title> + <link rel="help" href="https://w3c.github.io/encrypted-media/"> + + <!-- Web Platform Test Harness scripts --> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + + <!-- Helper scripts for Encrypted Media Extensions tests --> + <script src=/encrypted-media/util/utils.js></script> + <script src=/encrypted-media/util/testmediasource.js></script> + <script src=/encrypted-media/util/utf8.js></script> + + <!-- Content metadata --> + <script src=/encrypted-media/content/content-metadata.js></script> + + <!-- Message handler for Clear Key keysystem --> + <script src=/encrypted-media/util/clearkey-messagehandler.js></script> + + <!-- The script for this specific test --> + <script src=/encrypted-media/scripts/playback-temporary.js></script> + + </head> + <body> + <div id='log'></div> + + <div id='video'> + <video id="videoelement" width="200px"></video> + </div> + + <script> + var contentitem = content['mp4-av-multikey'], + handler = new MessageHandler( 'org.w3.clearkey', contentitem ), + config = { video: document.getElementById('videoelement'), + keysystem: 'org.w3.clearkey', + messagehandler: handler.messagehandler, + audioPath: contentitem.audio.path, + videoPath: contentitem.video.path, + audioType: contentitem.audio.type, + videoType: contentitem.video.type, + // Replace 'senc' text by ' enc' to make those boxes + // unrecognized: auxiliary data ('saio' boxes) shall be + // used instead + audioBlankBytes: contentitem.audio.sencStarts, + videoBlankBytes: contentitem.video.sencStarts, + initDataType: 'keyids', + initData: getInitData(contentitem,'keyids'), + testcase: 'multikey audio/video' }; + + + runTest(config); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/encrypted-media/content/content-metadata.js b/testing/web-platform/tests/encrypted-media/content/content-metadata.js @@ -52,9 +52,15 @@ content = addMemberListToObject( { associatedInitData: true, // indicates that initData for one key causes other keys to be returned as well audio: { type: 'audio/mp4;codecs="mp4a.40.2"', path: '/encrypted-media/content/audio_aac-lc_128k_enc_dashinit.mp4', - sinfStart: 0x02aa }, + sinfStart: 0x02aa, + saizStarts: [0x081b, 0x9065, 0x1150a], + saioStarts: [0x0802, 0x906e, 0x11523], + sencStarts: [0x9c3, 0x9226] }, video : { type: 'video/mp4;codecs="avc1.4d401e"', - path: '/encrypted-media/content/video_512x288_h264-360k_enc_dashinit.mp4' }, + path: '/encrypted-media/content/video_512x288_h264-360k_enc_dashinit.mp4', + saizStarts: [0x840, 0x18031, 0x2ebad], + saioStarts: [0x889, 0x1804a, 0x2ebc6], + sencStarts: [0x2ec62] }, keys : [ { kid: [ 0xad, 0x13, 0xf9, 0xea, 0x2b, 0xe6, 0x98, 0xb8, 0x75, 0xf5, 0x04, 0xa8, 0xe3, 0xcc, 0xea, 0x64 ], key: [ 0xbe, 0x7d, 0xf8, 0xa3, 0x66, 0x7a, 0x6a, 0x8f, 0xd5, 0x64, 0xd0, 0xed, 0x81, 0x33, 0x9a, 0x95 ], initDataType: 'cenc', diff --git a/testing/web-platform/tests/encrypted-media/util/testmediasource.js b/testing/web-platform/tests/encrypted-media/util/testmediasource.js @@ -12,6 +12,18 @@ function testmediasource(config) { Promise.all(fetches).then(function(resources) { config.audioMedia = resources[0]; config.videoMedia = resources[1]; + if (Object.hasOwn(config, 'audioBlankBytes')) { + audioMedia = new Uint8Array(config.audioMedia); + for (const pos of config.audioBlankBytes) { + audioMedia[pos] = ' '.charCodeAt(); + } + } + if (Object.hasOwn(config, 'videoBlankBytes')) { + videoMedia = new Uint8Array(config.videoMedia); + for (const pos of config.videoBlankBytes) { + videoMedia[pos] = ' '.charCodeAt(); + } + } // Create media source var source = new MediaSource();