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:
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
- ? ¤tMoof->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 = ¤tMoof->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();