Skip to content

Commit

Permalink
Cherry-pick 12dd6bd. rdar://122030589
Browse files Browse the repository at this point in the history
    [Cocoa] EME with encrypted MPEG2-TS fails to begin playback
    https://bugs.webkit.org/show_bug.cgi?id=268948
    rdar://122030589

    Reviewed by Andy Estes.

    When encrypted samples are parsed from containers by AVStreamDataParser, those samples have
    their associated initialization data attached to their CMFormatDescription as extensions.
    WebKit then reads those initialization data from the format description to determine the
    keyID needed to decrypt that key. For MPEG2-TS files, the initialization data is stored
    in a different key/value pair. Explicitly support this initData type as 'mpts'.

    * LayoutTests/http/tests/media/fairplay/content/elementary-stream-video-keyid-1.ts: Added.
    * LayoutTests/http/tests/media/fairplay/fps-mse-unmuxed-mpts.html: Added.
    * Source/WebCore/platform/graphics/avfoundation/CDMFairPlayStreaming.cpp:
    (WebCore::CDMPrivateFairPlayStreaming::extractKeyIDsSinf):
    (WebCore::CDMPrivateFairPlayStreaming::mptsName):
    (WebCore::CDMPrivateFairPlayStreaming::extractKeyIDsMpts):
    (WebCore::CDMPrivateFairPlayStreaming::sanitizeMpts):
    (WebCore::CDMPrivateFairPlayStreaming::mptsKeyIDs):
    (WebCore::validInitDataTypes):
    (WebCore::CDMFactory::platformRegisterFactories):
    (WebCore::CDMPrivateFairPlayStreaming::supportsInitData const):
    * Source/WebCore/platform/graphics/avfoundation/CDMFairPlayStreaming.h:
    * Source/WebCore/platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.mm:
    (WebCore::keyIDsForRequest):
    (WebCore::CDMInstanceSessionFairPlayStreamingAVFObjC::requestLicense):
    * Source/WebCore/platform/graphics/avfoundation/objc/MediaSampleAVFObjC.mm:
    (WebCore::MediaSampleAVFObjC::commonInit):
    * Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
    (WebCore::SourceBufferPrivateAVFObjC::didProvideContentKeyRequestInitializationDataForTrackID):

    Canonical link: https://commits.webkit.org/274351@main

Identifier: 272448.541@safari-7618.1.15.10-branch
  • Loading branch information
xeenon authored and MyahCobbs committed Feb 9, 2024
1 parent 0c6c107 commit 3c3f438
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 19 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

PROMISE: requestMediaKeySystemAccess resolved
PROMISE: createMediaKeys resolved
FETCH: resources/cert.der OK
PROMISE: keys.setServerCertificate resolved
PROMISE: setMediaKeys() resolved
Created mediaSource
EVENT(sourceopen)
-
Appending Encrypted Video Payload
Created sourceBuffer
FETCH: content/elementary-stream-video-keyid-1.ts OK
EVENT(encrypted)
EVENT(message)
PROMISE: licenseResponse resolved
PROMISE: session.update() resolved
EVENT(updateend)
-
Playing video
EVENT(seeked)
Promise resolved OK
END OF TEST

48 changes: 48 additions & 0 deletions LayoutTests/http/tests/media/fairplay/fps-mse-unmuxed-mpts.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>fps-mse-unmuxed-same-key</title>
<script src=../../../media-resources/video-test.js></script>
<script src=support.js></script>
<script src="eme2016.js"></script>
<script>
window.addEventListener('load', async event => {
startTest().then(endTest).catch(failTest);
});

async function startTest() {
let video = document.querySelector('video');
let keys = await startEME({video: video, setMediaKeys: true, capabilities: [{
initDataTypes: ['mpts'],
audioCapabilities: [{ contentType: 'audio/mp4', robustness: '' }],
videoCapabilities: [{ contentType: 'video/mp4', robustness: '' }],
distinctiveIdentifier: 'not-allowed',
persistentState: 'not-allowed',
sessionTypes: ['temporary'],
}]});

let mediaSource = new MediaSource;
video.srcObject = mediaSource;
consoleWrite('Created mediaSource');
await waitFor(mediaSource, 'sourceopen');

consoleWrite('-');
consoleWrite('Appending Encrypted Video Payload');

let {sourceBuffer: sourceBuffer, session: session} = await createBufferAppendAndWaitForEncrypted(video, mediaSource, keys, 'video/mp4', 'content/elementary-stream-video-keyid-1.ts');

consoleWrite('-');
consoleWrite('Playing video');

mediaSource.duration = sourceBuffer.buffered.end(0);
video.currentTime = sourceBuffer.buffered.start(0);
await waitFor(video, 'seeked');

await shouldResolve(video.play());
}
</script>
</head>
<body>
<video controls width="480"></video>
</body>
</html>
13 changes: 13 additions & 0 deletions LayoutTests/platform/mac/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -2580,3 +2580,16 @@ webkit.org/b/265957 fullscreen/full-screen-layer-dump.html [ Failure ]
# SVG Animations Failures on WPT Import
imported/w3c/web-platform-tests/svg/animations/reinserting-svg-into-document.html [ Failure ]
imported/w3c/web-platform-tests/svg/animations/use-animate-display-none-symbol-2.html [ Pass Failure ]

webkit.org/b/266996 imported/w3c/web-platform-tests/css/css-writing-modes/mongolian-orientation-002.html [ ImageOnlyFailure ]

fast/gradients/conic-stop-with-offset-zero-in-middle.html [ ImageOnlyFailure ]

webkit.org/b/267352 [ Monterey ] imported/w3c/web-platform-tests/mathml/relations/css-styling/padding-border-margin/margin-003.html [ Failure ]

webkit.org/b/267612 [ Ventura+ Release x86_64 ] fast/canvas/image-buffer-backend-variants.html [ Pass Failure ]

webkit.org/b/268947 [ Ventura+ ] imported/w3c/web-platform-tests/mathml/relations/css-styling/floats/not-floating-001.html [ Failure ]

# Enable after rdar://120859525 is available on bots
http/tests/media/fairplay/fps-mse-unmuxed-mpts.html [ Skip ]
20 changes: 20 additions & 0 deletions Source/WTF/wtf/PlatformHave.h
Original file line number Diff line number Diff line change
Expand Up @@ -1701,3 +1701,23 @@
&& (PLATFORM(IOS) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170400)
#define HAVE_MARKETPLACE_KIT 1
#endif

#if !defined(HAVE_XPC_API) \
&& (PLATFORM(MAC) \
|| ((PLATFORM(IOS) || PLATFORM(MACCATALYST)) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170400))
#define HAVE_XPC_API 1
#endif

#if !defined(HAVE_BROWSER_ENGINE_SUPPORTING_API) \
&& ((PLATFORM(MAC) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 140400) \
|| ((PLATFORM(IOS) || PLATFORM(MACCATALYST)) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170400) \
|| (PLATFORM(APPLETV) && __TV_OS_VERSION_MAX_ALLOWED >= 170400))
#define HAVE_BROWSER_ENGINE_SUPPORTING_API 1
#endif

#if !defined(HAVE_FAIRPLAYSTREAMING_MTPS_INITDATA) \
&& ((PLATFORM(IOS) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170400) \
|| (PLATFORM(MAC) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 140400) \
|| (PLATFORM(VISION) && __VISION_OS_VERSION_MAX_ALLOWED >= 10100))
#define HAVE_FAIRPLAYSTREAMING_MTPS_INITDATA 1
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ std::optional<Vector<Ref<SharedBuffer>>> CDMPrivateFairPlayStreaming::extractKey
{
Vector<Ref<SharedBuffer>> keyIDs;
auto results = extractSchemeAndKeyIdFromSinf(buffer);
if (results.isEmpty())
return std::nullopt;

for (auto& result : results) {
if (validFairPlayStreamingSchemes().contains(result.first))
Expand Down Expand Up @@ -200,13 +202,70 @@ std::optional<Vector<Ref<SharedBuffer>>> CDMPrivateFairPlayStreaming::extractKey
return Vector { buffer.makeContiguous() };
}

#if HAVE(FAIRPLAYSTREAMING_MTPS_INITDATA)
const AtomString& CDMPrivateFairPlayStreaming::mptsName()
{
static MainThreadNeverDestroyed<const AtomString> mpts { MAKE_STATIC_STRING_IMPL("mpts") };
return mpts;
}

std::optional<Vector<Ref<SharedBuffer>>> CDMPrivateFairPlayStreaming::extractKeyIDsMpts(const SharedBuffer& buffer)
{
// JSON of the format: "{ "codc" : [integer], "mtyp" : [integer], "cont" : "mpts"} }"
if (buffer.size() > std::numeric_limits<unsigned>::max())
return { };
String json { buffer.makeContiguous()->data(), static_cast<unsigned>(buffer.size()) };

auto value = JSON::Value::parseJSON(json);
if (!value)
return { };

auto object = value->asObject();
if (!object)
return { };

auto contValue = object->getString("cont"_s);
if (contValue != "mpts"_s)
return { };

auto codcValue = object->getInteger("codc"_s);
if (!codcValue)
return { };

auto mtypValue = object->getInteger("mtyp"_s);
if (!mtypValue)
return { };

return mptsKeyIDs();
}

RefPtr<SharedBuffer> CDMPrivateFairPlayStreaming::sanitizeMpts(const SharedBuffer& buffer)
{
UNUSED_PARAM(buffer);
notImplemented();
return buffer.makeContiguous();
}

const Vector<Ref<SharedBuffer>>& CDMPrivateFairPlayStreaming::mptsKeyIDs() {
static NeverDestroyed<Vector<Ref<SharedBuffer>>> mptsKeyID = [] {
Vector<uint8_t> keyData { std::initializer_list<uint8_t> { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } };
Ref<SharedBuffer> keyBuffer = SharedBuffer::create(WTFMove(keyData));
return Vector { 1, WTFMove(keyBuffer) };
}();
return mptsKeyID;
}
#endif

static const MemoryCompactLookupOnlyRobinHoodHashSet<AtomString>& validInitDataTypes()
{
static NeverDestroyed<MemoryCompactLookupOnlyRobinHoodHashSet<AtomString>> validTypes(std::initializer_list<AtomString> {
CDMPrivateFairPlayStreaming::sinfName(),
CDMPrivateFairPlayStreaming::skdName(),
#if HAVE(FAIRPLAYSTREAMING_CENC_INITDATA)
InitDataRegistry::cencName(),
#endif
#if HAVE(FAIRPLAYSTREAMING_MTPS_INITDATA)
CDMPrivateFairPlayStreaming::mptsName(),
#endif
});
return validTypes;
Expand All @@ -221,6 +280,9 @@ void CDMFactory::platformRegisterFactories(Vector<CDMFactory*>& factories)
std::call_once(onceFlag, [] {
InitDataRegistry::shared().registerInitDataType(CDMPrivateFairPlayStreaming::sinfName(), { CDMPrivateFairPlayStreaming::sanitizeSinf, CDMPrivateFairPlayStreaming::extractKeyIDsSinf });
InitDataRegistry::shared().registerInitDataType(CDMPrivateFairPlayStreaming::skdName(), { CDMPrivateFairPlayStreaming::sanitizeSkd, CDMPrivateFairPlayStreaming::extractKeyIDsSkd });
#if HAVE(FAIRPLAYSTREAMING_MTPS_INITDATA)
InitDataRegistry::shared().registerInitDataType(CDMPrivateFairPlayStreaming::mptsName(), { CDMPrivateFairPlayStreaming::sanitizeMpts, CDMPrivateFairPlayStreaming::extractKeyIDsMpts });
#endif
});
}

Expand Down Expand Up @@ -415,6 +477,11 @@ bool CDMPrivateFairPlayStreaming::supportsInitData(const AtomString& initDataTyp
if (initDataType == skdName())
return true;

#if HAVE(FAIRPLAYSTREAMING_MTPS_INITDATA)
if (initDataType == mptsName())
return true;
#endif

ASSERT_NOT_REACHED();
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ class CDMPrivateFairPlayStreaming final : public CDMPrivate {
static std::optional<Vector<Ref<SharedBuffer>>> extractKeyIDsSkd(const SharedBuffer&);
static RefPtr<SharedBuffer> sanitizeSkd(const SharedBuffer&);

#if HAVE(FAIRPLAYSTREAMING_MTPS_INITDATA)
static const AtomString& mptsName();
static std::optional<Vector<Ref<SharedBuffer>>> extractKeyIDsMpts(const SharedBuffer&);
static RefPtr<SharedBuffer> sanitizeMpts(const SharedBuffer&);
static const Vector<Ref<SharedBuffer>>& mptsKeyIDs();
#endif

static const Vector<FourCC>& validFairPlayStreamingSchemes();

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,10 @@ static Keys keyIDsForRequest(AVContentKeyRequest* request)
if (request.initializationData) {
if (auto sinfKeyIDs = CDMPrivateFairPlayStreaming::extractKeyIDsSinf(SharedBuffer::create(request.initializationData)))
return WTFMove(sinfKeyIDs.value());
#if HAVE(FAIRPLAYSTREAMING_MTPS_INITDATA)
if (auto mptsKeyIDs = CDMPrivateFairPlayStreaming::extractKeyIDsMpts(SharedBuffer::create(request.initializationData)))
return WTFMove(mptsKeyIDs.value());
#endif
}
return { };
}
Expand Down Expand Up @@ -813,6 +817,10 @@ static Keys keyIDsForRequest(const Request& requests)
auto psshString = base64EncodeToString(initData->makeContiguous()->data(), initData->size());
initializationData = [NSJSONSerialization dataWithJSONObject:@{ @"pssh": (NSString*)psshString } options:NSJSONWritingPrettyPrinted error:nil];
}
#endif
#if HAVE(FAIRPLAYSTREAMING_MTPS_INITDATA)
else if (initDataType == CDMPrivateFairPlayStreaming::mptsName())
initializationData = initData->makeContiguous()->createNSData();
#endif
else {
ERROR_LOG(LOGIDENTIFIER, " false, initDataType not suppported");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#import "config.h"
#import "MediaSampleAVFObjC.h"

#import "CDMFairPlayStreaming.h"
#import "CVUtilities.h"
#import "ISOTrackEncryptionBox.h"
#import "PixelBuffer.h"
Expand Down Expand Up @@ -76,10 +77,10 @@
if (CMTIME_IS_INVALID(presentationTime))
presentationTime = PAL::CMSampleBufferGetPresentationTimeStamp(m_sample.get());
m_presentationTime = PAL::toMediaTime(presentationTime);

auto decodeTime = PAL::CMSampleBufferGetDecodeTimeStamp(m_sample.get());
m_decodeTime = !CMTIME_IS_INVALID(decodeTime) ? PAL::toMediaTime(decodeTime) : m_presentationTime;

auto duration = PAL::CMSampleBufferGetOutputDuration(m_sample.get());
if (CMTIME_IS_INVALID(duration))
duration = PAL::CMSampleBufferGetDuration(m_sample.get());
Expand All @@ -89,23 +90,33 @@
auto getKeyIDs = [](CMFormatDescriptionRef description) -> Vector<Ref<SharedBuffer>> {
if (!description)
return { };
auto trackEncryptionData = static_cast<CFDataRef>(PAL::CMFormatDescriptionGetExtension(description, CFSTR("CommonEncryptionTrackEncryptionBox")));
if (!trackEncryptionData)
return { };
if (auto trackEncryptionData = static_cast<CFDataRef>(PAL::CMFormatDescriptionGetExtension(description, CFSTR("CommonEncryptionTrackEncryptionBox")))) {
// AVStreamDataParser will attach the 'tenc' box to each sample, not including the leading
// size and boxType data. Extract the 'tenc' box and use that box to derive the sample's
// keyID.
auto length = CFDataGetLength(trackEncryptionData);
auto ptr = (void*)(CFDataGetBytePtr(trackEncryptionData));
auto destructorFunction = createSharedTask<void(void*)>([data = WTFMove(trackEncryptionData)] (void*) { UNUSED_PARAM(data); });
auto trackEncryptionDataBuffer = ArrayBuffer::create(JSC::ArrayBufferContents(ptr, length, std::nullopt, WTFMove(destructorFunction)));

ISOTrackEncryptionBox trackEncryptionBox;
auto trackEncryptionView = JSC::DataView::create(WTFMove(trackEncryptionDataBuffer), 0, length);
if (!trackEncryptionBox.parseWithoutTypeAndSize(trackEncryptionView))
return { };
return { SharedBuffer::create(trackEncryptionBox.defaultKID()) };
}

// AVStreamDataParser will attach the 'tenc' box to each sample, not including the leading
// size and boxType data. Extract the 'tenc' box and use that box to derive the sample's
// keyID.
auto length = CFDataGetLength(trackEncryptionData);
auto ptr = (void*)(CFDataGetBytePtr(trackEncryptionData));
auto destructorFunction = createSharedTask<void(void*)>([data = WTFMove(trackEncryptionData)] (void*) { UNUSED_PARAM(data); });
auto trackEncryptionDataBuffer = ArrayBuffer::create(JSC::ArrayBufferContents(ptr, length, std::nullopt, WTFMove(destructorFunction)));

ISOTrackEncryptionBox trackEncryptionBox;
auto trackEncryptionView = JSC::DataView::create(WTFMove(trackEncryptionDataBuffer), 0, length);
if (!trackEncryptionBox.parseWithoutTypeAndSize(trackEncryptionView))
return { };
return { SharedBuffer::create(trackEncryptionBox.defaultKID()) };
#if HAVE(FAIRPLAYSTREAMING_MTPS_INITDATA)
if (auto transportStreamData = static_cast<CFDataRef>(PAL::CMFormatDescriptionGetExtension(description, CFSTR("TransportStreamEncryptionInitData")))) {
// AVStreamDataParser will attach a JSON transport stream encryption
// description object to each sample. Use a static keyID in this case
// as MPEG2-TS encryption dose not specify a particular keyID in the
// stream.
return CDMPrivateFairPlayStreaming::mptsKeyIDs();
}
#endif

return { };
};
m_keyIDs = getKeyIDs(PAL::CMSampleBufferGetFormatDescription(m_sample.get()));
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,13 @@ static inline bool shouldAddContentKeyRecipients()

#if ENABLE(ENCRYPTED_MEDIA) && HAVE(AVCONTENTKEYSESSION)
auto keyIDs = CDMPrivateFairPlayStreaming::extractKeyIDsSinf(*m_initData);
AtomString initDataType = CDMPrivateFairPlayStreaming::sinfName();
#if HAVE(FAIRPLAYSTREAMING_MTPS_INITDATA)
if (!keyIDs) {
keyIDs = CDMPrivateFairPlayStreaming::extractKeyIDsMpts(*m_initData);
initDataType = CDMPrivateFairPlayStreaming::mptsName();
}
#endif
if (!keyIDs)
return;

Expand All @@ -320,7 +327,7 @@ static inline bool shouldAddContentKeyRecipients()
}

m_keyIDs = WTFMove(keyIDs.value());
player->initializationDataEncountered("sinf"_s, m_initData->tryCreateArrayBuffer());
player->initializationDataEncountered(initDataType, m_initData->tryCreateArrayBuffer());
player->needsVideoLayerChanged();

m_waitingForKey = true;
Expand Down

0 comments on commit 3c3f438

Please sign in to comment.