[EME] Support FPS-over-HLS in the Modern EME API
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 12 Dec 2017 23:12:20 +0000 (23:12 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 12 Dec 2017 23:12:20 +0000 (23:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=180707

Reviewed by Eric Carlson.

Add support for the "skd" initDataType, where the initData is the URI provided in the
EXT-X-KEY tag in a HLS manifest:

* platform/graphics/avfoundation/CDMFairPlayStreaming.cpp:
(WebCore::CDMPrivateFairPlayStreaming::sinfName):
(WebCore::CDMPrivateFairPlayStreaming::skdName):
(WebCore::extractSinfData):
(WebCore::CDMPrivateFairPlayStreaming::sanitizeSkd):
(WebCore::CDMPrivateFairPlayStreaming::extractKeyIDsSkd):
(WebCore::validInitDataTypes):
(WebCore::CDMFactory::platformRegisterFactories):
(WebCore::CDMPrivateFairPlayStreaming::supportsInitDataType const):
(WebCore::CDMPrivateFairPlayStreaming::supportsConfiguration const):
(WebCore::CDMPrivateFairPlayStreaming::supportsInitData const):
(WebCore::sinfName): Deleted.

Add support for creating a AVContentKeyRequest from a skd key URI rather than from
initialization data, and for extracting keyIDs from the AVContentKeyRequest identifier.

* platform/graphics/avfoundation/CDMFairPlayStreaming.h:
* platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.h:
* platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.mm:
(WebCore::CDMInstanceFairPlayStreamingAVFObjC::keyIDs):
(WebCore::CDMInstanceFairPlayStreamingAVFObjC::requestLicense):
(WebCore::CDMInstanceFairPlayStreamingAVFObjC::updateLicense):
(WebCore::CDMInstanceFairPlayStreamingAVFObjC::didProvideRequest):

Add support for AVContentKeySession to MediaPlayerPrivateAVFoundationObjC, and for emitting
initializationData messages when encountering a loading request for a "skd" URI.

* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
(WebCore::MediaPlayerPrivateAVFoundationObjC::shouldWaitForLoadingOfResource):
(WebCore::MediaPlayerPrivateAVFoundationObjC::cdmInstanceAttached):
(WebCore::MediaPlayerPrivateAVFoundationObjC::cdmInstanceDetached):
(WebCore::MediaPlayerPrivateAVFoundationObjC::attemptToDecryptWithInstance):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@225813 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/avfoundation/CDMFairPlayStreaming.cpp
Source/WebCore/platform/graphics/avfoundation/CDMFairPlayStreaming.h
Source/WebCore/platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.h
Source/WebCore/platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.mm
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm

index 5b091eb..b891774 100644 (file)
@@ -1,3 +1,47 @@
+2017-12-12  Jer Noble  <jer.noble@apple.com>
+
+        [EME] Support FPS-over-HLS in the Modern EME API
+        https://bugs.webkit.org/show_bug.cgi?id=180707
+
+        Reviewed by Eric Carlson.
+
+        Add support for the "skd" initDataType, where the initData is the URI provided in the 
+        EXT-X-KEY tag in a HLS manifest:
+
+        * platform/graphics/avfoundation/CDMFairPlayStreaming.cpp:
+        (WebCore::CDMPrivateFairPlayStreaming::sinfName):
+        (WebCore::CDMPrivateFairPlayStreaming::skdName):
+        (WebCore::extractSinfData):
+        (WebCore::CDMPrivateFairPlayStreaming::sanitizeSkd):
+        (WebCore::CDMPrivateFairPlayStreaming::extractKeyIDsSkd):
+        (WebCore::validInitDataTypes):
+        (WebCore::CDMFactory::platformRegisterFactories):
+        (WebCore::CDMPrivateFairPlayStreaming::supportsInitDataType const):
+        (WebCore::CDMPrivateFairPlayStreaming::supportsConfiguration const):
+        (WebCore::CDMPrivateFairPlayStreaming::supportsInitData const):
+        (WebCore::sinfName): Deleted.
+
+        Add support for creating a AVContentKeyRequest from a skd key URI rather than from
+        initialization data, and for extracting keyIDs from the AVContentKeyRequest identifier.
+
+        * platform/graphics/avfoundation/CDMFairPlayStreaming.h:
+        * platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.h:
+        * platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.mm:
+        (WebCore::CDMInstanceFairPlayStreamingAVFObjC::keyIDs):
+        (WebCore::CDMInstanceFairPlayStreamingAVFObjC::requestLicense):
+        (WebCore::CDMInstanceFairPlayStreamingAVFObjC::updateLicense):
+        (WebCore::CDMInstanceFairPlayStreamingAVFObjC::didProvideRequest):
+
+        Add support for AVContentKeySession to MediaPlayerPrivateAVFoundationObjC, and for emitting
+        initializationData messages when encountering a loading request for a "skd" URI.
+
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::shouldWaitForLoadingOfResource):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::cdmInstanceAttached):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::cdmInstanceDetached):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::attemptToDecryptWithInstance):
+
 2017-12-12  Antoine Quint  <graouts@apple.com>
 
         [Web Animations] Expose promises on Animation interface
index 3ffbb65..e569401 100644 (file)
@@ -62,12 +62,18 @@ static const Vector<FourCC>& validFairPlayStreamingSchemes()
     return validSchemes;
 }
 
-static const String& sinfName()
+const AtomicString& CDMPrivateFairPlayStreaming::sinfName()
 {
-    static NeverDestroyed<String> sinf { MAKE_STATIC_STRING_IMPL("sinf") };
+    static NeverDestroyed<AtomicString> sinf { MAKE_STATIC_STRING_IMPL("sinf") };
     return sinf;
 }
 
+const AtomicString& CDMPrivateFairPlayStreaming::skdName()
+{
+    static NeverDestroyed<AtomicString> skd { MAKE_STATIC_STRING_IMPL("skd") };
+    return skd;
+}
+
 static Vector<Ref<SharedBuffer>> extractSinfData(const SharedBuffer& buffer)
 {
     // JSON of the format: "{ sinf: [ <base64-encoded-string> ] }"
@@ -84,7 +90,7 @@ static Vector<Ref<SharedBuffer>> extractSinfData(const SharedBuffer& buffer)
         return { };
 
     RefPtr<JSON::Array> sinfArray;
-    if (!object->getArray(sinfName(), sinfArray))
+    if (!object->getArray(CDMPrivateFairPlayStreaming::sinfName(), sinfArray))
         return { };
 
     Vector<Ref<SharedBuffer>> sinfs;
@@ -174,12 +180,34 @@ RefPtr<SharedBuffer> CDMPrivateFairPlayStreaming::sanitizeSinf(const SharedBuffe
     return buffer.copy();
 }
 
+RefPtr<SharedBuffer> CDMPrivateFairPlayStreaming::sanitizeSkd(const SharedBuffer& buffer)
+{
+    UNUSED_PARAM(buffer);
+    notImplemented();
+    return buffer.copy();
+}
+
+Vector<Ref<SharedBuffer>> CDMPrivateFairPlayStreaming::extractKeyIDsSkd(const SharedBuffer& buffer)
+{
+    // In the 'skd' scheme, the init data is the key ID.
+    Vector<Ref<SharedBuffer>> keyIDs;
+    keyIDs.append(buffer.copy());
+    return keyIDs;
+}
+
+static const HashSet<AtomicString>& validInitDataTypes()
+{
+    static NeverDestroyed<HashSet<AtomicString>> validTypes = HashSet<AtomicString>({ CDMPrivateFairPlayStreaming::sinfName(), CDMPrivateFairPlayStreaming::skdName() });
+    return validTypes;
+}
+
 void CDMFactory::platformRegisterFactories(Vector<CDMFactory*>& factories)
 {
     factories.append(&CDMFactoryClearKey::singleton());
     factories.append(&CDMFactoryFairPlayStreaming::singleton());
 
-    InitDataRegistry::shared().registerInitDataType(sinfName(), { CDMPrivateFairPlayStreaming::sanitizeSinf, CDMPrivateFairPlayStreaming::extractKeyIDsSinf });
+    InitDataRegistry::shared().registerInitDataType(CDMPrivateFairPlayStreaming::sinfName(), { CDMPrivateFairPlayStreaming::sanitizeSinf, CDMPrivateFairPlayStreaming::extractKeyIDsSinf });
+    InitDataRegistry::shared().registerInitDataType(CDMPrivateFairPlayStreaming::skdName(), { CDMPrivateFairPlayStreaming::sanitizeSkd, CDMPrivateFairPlayStreaming::extractKeyIDsSkd });
 }
 
 CDMFactoryFairPlayStreaming& CDMFactoryFairPlayStreaming::singleton()
@@ -211,12 +239,12 @@ CDMPrivateFairPlayStreaming::~CDMPrivateFairPlayStreaming() = default;
 
 bool CDMPrivateFairPlayStreaming::supportsInitDataType(const AtomicString& initDataType) const
 {
-    return initDataType == sinfName();
+    return validInitDataTypes().contains(initDataType);
 }
 
 bool CDMPrivateFairPlayStreaming::supportsConfiguration(const CDMKeySystemConfiguration& configuration) const
 {
-    if (!configuration.initDataTypes.contains(sinfName()))
+    if (!WTF::anyOf(configuration.initDataTypes, [] (auto& initDataType) { return validInitDataTypes().contains(initDataType); }))
         return false;
 
 #if HAVE(AVCONTENTKEYSESSION)
@@ -331,9 +359,17 @@ bool CDMPrivateFairPlayStreaming::supportsInitData(const AtomicString& initDataT
     if (!supportsInitDataType(initDataType))
         return false;
 
-    return WTF::anyOf(extractSchemeAndKeyIdFromSinf(initData), [](auto& result) {
-        return validFairPlayStreamingSchemes().contains(result.first);
-    });
+    if (initDataType == sinfName()) {
+        return WTF::anyOf(extractSchemeAndKeyIdFromSinf(initData), [](auto& result) {
+            return validFairPlayStreamingSchemes().contains(result.first);
+        });
+    }
+
+    if (initDataType == skdName())
+        return true;
+
+    ASSERT_NOT_REACHED();
+    return false;
 }
 
 RefPtr<SharedBuffer> CDMPrivateFairPlayStreaming::sanitizeResponse(const SharedBuffer& response) const
index 7f4c43b..d34f72c 100644 (file)
@@ -65,8 +65,13 @@ public:
     RefPtr<SharedBuffer> sanitizeResponse(const SharedBuffer&) const override;
     std::optional<String> sanitizeSessionId(const String&) const override;
 
+    static const AtomicString& sinfName();
     static Vector<Ref<SharedBuffer>> extractKeyIDsSinf(const SharedBuffer&);
     static RefPtr<SharedBuffer> sanitizeSinf(const SharedBuffer&);
+
+    static const AtomicString& skdName();
+    static Vector<Ref<SharedBuffer>> extractKeyIDsSkd(const SharedBuffer&);
+    static RefPtr<SharedBuffer> sanitizeSkd(const SharedBuffer&);
 };
 
 }
index 19ea579..b23ed4e 100644 (file)
@@ -81,6 +81,8 @@ private:
     WeakPtr<CDMInstanceFairPlayStreamingAVFObjC> createWeakPtr() { return m_weakPtrFactory.createWeakPtr(*this); }
     bool isLicenseTypeSupported(LicenseType) const;
 
+    Vector<Ref<SharedBuffer>> keyIDs();
+
     WeakPtrFactory<CDMInstanceFairPlayStreamingAVFObjC> m_weakPtrFactory;
     RefPtr<SharedBuffer> m_serverCertificate;
     bool m_persistentStateAllowed { true };
index 01572c1..b9e59e3 100644 (file)
@@ -225,7 +225,20 @@ bool CDMInstanceFairPlayStreamingAVFObjC::isLicenseTypeSupported(LicenseType lic
     }
 }
 
-void CDMInstanceFairPlayStreamingAVFObjC::requestLicense(LicenseType licenseType, const AtomicString&, Ref<SharedBuffer>&& initData, LicenseCallback callback)
+Vector<Ref<SharedBuffer>> CDMInstanceFairPlayStreamingAVFObjC::keyIDs()
+{
+    // FIXME(rdar://problem/35597141): use the future AVContentKeyRequest keyID property, rather than parsing it out of the init
+    // data, to get the keyID.
+    if ([m_request.get().identifier isKindOfClass:[NSString class]])
+        return Vector<Ref<SharedBuffer>>::from(SharedBuffer::create([(NSString *)m_request.get().identifier dataUsingEncoding:NSUTF8StringEncoding]));
+    if ([m_request.get().identifier isKindOfClass:[NSData class]])
+        return Vector<Ref<SharedBuffer>>::from(SharedBuffer::create((NSData *)m_request.get().identifier));
+    if (m_request.get().initializationData)
+        return CDMPrivateFairPlayStreaming::extractKeyIDsSinf(SharedBuffer::create(m_request.get().initializationData));
+    return { };
+}
+
+void CDMInstanceFairPlayStreamingAVFObjC::requestLicense(LicenseType licenseType, const AtomicString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback callback)
 {
     if (!isLicenseTypeSupported(licenseType)) {
         callback(SharedBuffer::create(), emptyString(), false, Failed);
@@ -237,9 +250,20 @@ void CDMInstanceFairPlayStreamingAVFObjC::requestLicense(LicenseType licenseType
         return;
     }
 
-    m_requestLicenseCallback = WTFMove(callback);
+    RetainPtr<NSString> identifier;
+    RetainPtr<NSData> initializationData;
+
+    if (initDataType == CDMPrivateFairPlayStreaming::sinfName())
+        initializationData = initData->createNSData();
+    else if (initDataType == CDMPrivateFairPlayStreaming::skdName())
+        identifier = adoptNS([[NSString alloc] initWithData:initData->createNSData().get() encoding:NSUTF8StringEncoding]);
+    else {
+        callback(SharedBuffer::create(), emptyString(), false, Failed);
+        return;
+    }
 
-    [m_session processContentKeyRequestWithIdentifier:nil initializationData:initData->createNSData().get() options:nil];
+    m_requestLicenseCallback = WTFMove(callback);
+    [m_session processContentKeyRequestWithIdentifier:identifier.get() initializationData:initializationData.get() options:nil];
 }
 
 static bool isEqual(const SharedBuffer& data, const String& value)
@@ -276,9 +300,7 @@ void CDMInstanceFairPlayStreamingAVFObjC::updateLicense(const String&, LicenseTy
         callback(false, std::nullopt, std::nullopt, std::nullopt, Failed);
         return;
     }
-    // FIXME(rdar://problem/35597141): use the future AVContentKeyRequest keyID property, rather than parsing it out of the init
-    // data, to get the keyID.
-    Vector<Ref<SharedBuffer>> keyIDs = CDMPrivateFairPlayStreaming::extractKeyIDsSinf(SharedBuffer::create(m_request.get().initializationData));
+    Vector<Ref<SharedBuffer>> keyIDs = this->keyIDs();
     if (keyIDs.isEmpty()) {
         callback(false, std::nullopt, std::nullopt, std::nullopt, Failed);
         return;
@@ -400,7 +422,8 @@ void CDMInstanceFairPlayStreamingAVFObjC::didProvideRequest(AVContentKeyRequest
         return;
 
     RetainPtr<NSData> appIdentifier = m_serverCertificate ? m_serverCertificate->createNSData() : nullptr;
-    Vector<Ref<SharedBuffer>> keyIDs = CDMPrivateFairPlayStreaming::extractKeyIDsSinf(SharedBuffer::create(request.initializationData));
+    Vector<Ref<SharedBuffer>> keyIDs = this->keyIDs();
+
     if (keyIDs.isEmpty()) {
         m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
         m_requestLicenseCallback = nullptr;
index d452a17..2222c94 100644 (file)
@@ -63,6 +63,7 @@ namespace WebCore {
 
 class AudioSourceProviderAVFObjC;
 class AudioTrackPrivateAVFObjC;
+class CDMInstanceFairPlayStreamingAVFObjC;
 class CDMSessionAVFoundationObjC;
 class InbandMetadataTextTrackPrivateAVF;
 class InbandTextTrackPrivateAVFObjC;
@@ -153,6 +154,12 @@ public:
     void removeSession(LegacyCDMSession&);
 #endif
 
+#if ENABLE(ENCRYPTED_MEDIA)
+    void cdmInstanceAttached(CDMInstance&) final;
+    void cdmInstanceDetached(CDMInstance&) final;
+    void attemptToDecryptWithInstance(CDMInstance&) final;
+#endif
+
     WeakPtr<MediaPlayerPrivateAVFoundationObjC> createWeakPtr() { return m_weakPtrFactory.createWeakPtr(*this); }
 
 private:
@@ -397,6 +404,9 @@ private:
 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
     WeakPtr<CDMSessionAVFoundationObjC> m_session;
 #endif
+#if ENABLE(ENCRYPTED_MEDIA) && HAVE(AVCONTENTKEYSESSION)
+    RefPtr<CDMInstanceFairPlayStreamingAVFObjC> m_cdmInstance;
+#endif
 
     mutable RetainPtr<NSArray> m_cachedSeekableRanges;
     mutable RetainPtr<NSArray> m_cachedLoadedRanges;
index 820e274..37811a8 100644 (file)
@@ -34,6 +34,7 @@
 #import "AudioSourceProviderAVFObjC.h"
 #import "AudioTrackPrivateAVFObjC.h"
 #import "AuthenticationChallenge.h"
+#import "CDMInstanceFairPlayStreamingAVFObjC.h"
 #import "CDMSessionAVFoundationObjC.h"
 #import "Cookie.h"
 #import "DeprecatedGlobalSettings.h"
@@ -54,6 +55,7 @@
 #import "PlatformTimeRanges.h"
 #import "SecurityOrigin.h"
 #import "SerializedPlatformRepresentationMac.h"
+#import "SharedBuffer.h"
 #import "TextEncoding.h"
 #import "TextTrackRepresentation.h"
 #import "TextureCacheCV.h"
@@ -1766,8 +1768,9 @@ bool MediaPlayerPrivateAVFoundationObjC::shouldWaitForLoadingOfResource(AVAssetR
     String scheme = [[[avRequest request] URL] scheme];
     String keyURI = [[[avRequest request] URL] absoluteString];
 
-#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)
     if (scheme == "skd") {
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
         // Create an initData with the following layout:
         // [4 bytes: keyURI size], [keyURI size bytes: keyURI]
         unsigned keyURISize = keyURI.length() * sizeof(UChar);
@@ -1782,11 +1785,20 @@ bool MediaPlayerPrivateAVFoundationObjC::shouldWaitForLoadingOfResource(AVAssetR
         RefPtr<Uint8Array> initData = Uint8Array::create(WTFMove(initDataBuffer), 0, byteLength);
         if (!player()->keyNeeded(initData.get()))
             return false;
-
+#endif
         m_keyURIToRequestMap.set(keyURI, avRequest);
+#if ENABLE(ENCRYPTED_MEDIA) && HAVE(AVCONTENTKEYSESSION)
+        if (m_cdmInstance)
+            return false;
+
+        RetainPtr<NSData> keyURIData = [keyURI dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
+        auto keyURIBuffer = SharedBuffer::create(keyURIData.get());
+        player()->initializationDataEncountered(ASCIILiteral("skd"), keyURIBuffer->tryCreateArrayBuffer());
+#endif
         return true;
     }
 
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
     if (scheme == "clearkey") {
         String keyID = [[[avRequest request] URL] resourceSpecifier];
         auto encodedKeyId = UTF8Encoding().encode(keyID, UnencodableHandling::URLEncodedEntities);
@@ -1807,6 +1819,7 @@ bool MediaPlayerPrivateAVFoundationObjC::shouldWaitForLoadingOfResource(AVAssetR
         return true;
     }
 #endif
+#endif
 
     RefPtr<WebCoreAVFResourceLoader> resourceLoader = WebCoreAVFResourceLoader::create(this, avRequest);
     m_resourceLoaderMap.add(avRequest, resourceLoader);
@@ -2477,6 +2490,46 @@ void MediaPlayerPrivateAVFoundationObjC::outputObscuredDueToInsufficientExternal
 
 #endif
 
+#if ENABLE(ENCRYPTED_MEDIA)
+void MediaPlayerPrivateAVFoundationObjC::cdmInstanceAttached(CDMInstance& instance)
+{
+#if HAVE(AVCONTENTKEYSESSION)
+    if (!is<CDMInstanceFairPlayStreamingAVFObjC>(instance))
+        return;
+
+    auto& fpsInstance = downcast<CDMInstanceFairPlayStreamingAVFObjC>(instance);
+    if (&fpsInstance == m_cdmInstance)
+        return;
+
+    if (m_cdmInstance)
+        cdmInstanceDetached(*m_cdmInstance);
+
+    m_cdmInstance = &fpsInstance;
+    [m_cdmInstance->contentKeySession() addContentKeyRecipient:m_avAsset.get()];
+#else
+    UNUSED_PARAM(instance);
+#endif
+}
+
+void MediaPlayerPrivateAVFoundationObjC::cdmInstanceDetached(CDMInstance& instance)
+{
+#if HAVE(AVCONTENTKEYSESSION)
+    ASSERT_UNUSED(instance, m_cdmInstance && m_cdmInstance == &instance);
+    [m_cdmInstance->contentKeySession() removeContentKeyRecipient:m_avAsset.get()];
+    m_cdmInstance = nullptr;
+#else
+    UNUSED_PARAM(instance);
+#endif
+}
+
+void MediaPlayerPrivateAVFoundationObjC::attemptToDecryptWithInstance(CDMInstance&)
+{
+    auto keyURIToRequestMap = WTFMove(m_keyURIToRequestMap);
+    for (auto& request : keyURIToRequestMap.values())
+        [request finishLoading];
+}
+#endif
+
 #if !HAVE(AVFOUNDATION_LEGIBLE_OUTPUT_SUPPORT)
 
 void MediaPlayerPrivateAVFoundationObjC::processLegacyClosedCaptionsTracks()