Skip to content

Commit

Permalink
[Cocoa] Encrypted video does not play when not in the DOM
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=248008
rdar://102446042

Reviewed by Eric Carlson.

WebCoreDecompressionSession isn't capable (by design) of decoding encrypted content. When
a video element is not in the DOM, a decompression session is created to allow the element
to be painted into a canvas. But since decoding encrypted samples will always fail, always
create an AVSampleBufferDisplayLayer if any SourceBuffer has an enabled, protected video track.

* LayoutTests/http/tests/media/fairplay/fps-mse-play-while-not-in-dom-expected.txt: Added.
* LayoutTests/http/tests/media/fairplay/fps-mse-play-while-not-in-dom.html: Added.
* Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h:
* Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::shouldEnsureLayer const):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::needsVideoLayerChanged):
* Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h:
* Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
(WebCore::SourceBufferPrivateAVFObjC::didProvideContentKeyRequestInitializationDataForTrackID):
(WebCore::SourceBufferPrivateAVFObjC::needsVideoLayer const):
(WebCore::SourceBufferPrivateAVFObjC::trackDidChangeSelected):
(WebCore::SourceBufferPrivateAVFObjC::canEnqueueSample):

Canonical link: https://commits.webkit.org/256805@main
  • Loading branch information
jernoble committed Nov 17, 2022
1 parent d2862d5 commit f651a6b
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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 Header
Created sourceBuffer
FETCH: content/elementary-stream-video-header-keyid-2.m4v OK
EVENT(encrypted)
EVENT(message)
PROMISE: licenseResponse resolved
PROMISE: session.update() resolved
EVENT(updateend)
-
Appending Encrypted Video Payload
FETCH: content/elementary-stream-video-payload.m4v OK
EVENT(updateend)
RUN(video.play())
EVENT(playing)
END OF TEST

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<title>fps-mse-play-while-not-in-dom</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() {
window.video = document.createElement('video');
let keys = await startEME({video: video, capabilities: [{
initDataTypes: ['sinf'],
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 Header');

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

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

await fetchAndAppend(sourceBuffer, 'content/elementary-stream-video-payload.m4v');

run('video.play()');
await waitForEventWithTimeout(video, 'playing', 10000, 'Did not play in time');
}
</script>
</head>
<body>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ class MediaPlayerPrivateMediaSourceAVFObjC
const Vector<ContentType>& mediaContentTypesRequiringHardwareSupport() const;
bool shouldCheckHardwareSupport() const;

void needsVideoLayerChanged();

#if !RELEASE_LOG_DISABLED
const Logger& logger() const final { return m_logger.get(); }
const char* logClassName() const override { return "MediaPlayerPrivateMediaSourceAVFObjC"; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,10 @@ void getSupportedTypes(HashSet<String, ASCIICaseInsensitiveHash>& types) const f
{
#if HAVE(AVSAMPLEBUFFERDISPLAYLAYER_COPYDISPLAYEDPIXELBUFFER)
return isCopyDisplayedPixelBufferAvailable() && [&] {
if (m_mediaSourcePrivate && anyOf(m_mediaSourcePrivate->sourceBuffers(), [] (auto& sourceBuffer) {
return sourceBuffer->needsVideoLayer();
}))
return true;
if (m_sampleBufferDisplayLayer)
return !CGRectIsEmpty([m_sampleBufferDisplayLayer bounds]);
return !m_player->playerContentBoxRect().isEmpty();
Expand Down Expand Up @@ -1242,6 +1246,11 @@ void getSupportedTypes(HashSet<String, ASCIICaseInsensitiveHash>& types) const f
return m_player->shouldCheckHardwareSupport();
}

void MediaPlayerPrivateMediaSourceAVFObjC::needsVideoLayerChanged()
{
updateDisplayLayerAndDecompressionSession();
}

void MediaPlayerPrivateMediaSourceAVFObjC::setReadyState(MediaPlayer::ReadyState readyState)
{
if (m_readyState == readyState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ class SourceBufferPrivateAVFObjC final
FloatSize naturalSize();

uint64_t protectedTrackID() const { return m_protectedTrackID; }
bool needsVideoLayer() const;

AVStreamDataParser* streamDataParser() const;
void setCDMSession(CDMSessionMediaSourceAVFObjC*);
void setCDMInstance(CDMInstance*);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ static void bufferWasConsumedCallback(CMNotificationCenterRef, const void* liste

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

m_waitingForKey = true;
player->waitingForKeyChanged();
Expand All @@ -610,6 +611,21 @@ static void bufferWasConsumedCallback(CMNotificationCenterRef, const void* liste
UNUSED_PARAM(hasSessionSemaphore);
}

bool SourceBufferPrivateAVFObjC::needsVideoLayer() const
{
if (m_protectedTrackID == notFound)
return false;

if (m_enabledVideoTrackID != m_protectedTrackID)
return false;

// When video content is protected and keys are assigned through
// the renderers, decoding content through decompression sessions
// will fail. In this scenario, ask the player to create a layer
// instead.
return sampleBufferRenderersSupportKeySession();
}

void SourceBufferPrivateAVFObjC::append(Ref<SharedBuffer>&& data)
{
ALWAYS_LOG(LOGIDENTIFIER, "data length = ", data->size());
Expand Down Expand Up @@ -876,6 +892,9 @@ static void bufferWasConsumedCallback(CMNotificationCenterRef, const void* liste
}
}

if (auto* player = this->player())
player->needsVideoLayerChanged();

m_mediaSource->hasSelectedVideoChanged(*this);
}

Expand Down Expand Up @@ -1235,6 +1254,10 @@ static void bufferWasConsumedCallback(CMNotificationCenterRef, const void* liste
if (!m_cdmInstance)
return false;

// DecompressionSessions doesn't support encrypted media.
if (!m_displayLayer)
return false;

// if sample is encrypted, and keyIDs match the current set of keyIDs: enqueue sample.
auto findResult = m_currentTrackIDs.find(trackID);
if (findResult != m_currentTrackIDs.end() && findResult->value == sample.keyIDs())
Expand Down

0 comments on commit f651a6b

Please sign in to comment.