diff options
Diffstat (limited to 'Source/WebCore/platform/graphics/avfoundation/objc')
2 files changed, 265 insertions, 2 deletions
diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h index d44a30c73..e70db1a98 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h +++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h @@ -29,8 +29,9 @@ #if ENABLE(VIDEO) && USE(AVFOUNDATION) #include "MediaPlayerPrivateAVFoundation.h" +#include <wtf/HashMap.h> -OBJC_CLASS AVAsset; +OBJC_CLASS AVURLAsset; OBJC_CLASS AVPlayer; OBJC_CLASS AVPlayerItem; OBJC_CLASS AVPlayerItemVideoOutput; @@ -38,6 +39,11 @@ OBJC_CLASS AVPlayerLayer; OBJC_CLASS AVAssetImageGenerator; OBJC_CLASS WebCoreAVFMovieObserver; +#if ENABLE(ENCRYPTED_MEDIA) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 +OBJC_CLASS WebCoreAVFLoaderDelegate; +OBJC_CLASS AVAssetResourceLoadingRequest; +#endif + #ifndef __OBJC__ typedef struct objc_object *id; #endif @@ -56,6 +62,10 @@ public: void setAsset(id); virtual void tracksChanged(); +#if ENABLE(ENCRYPTED_MEDIA) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 + bool shouldWaitForLoadingOfResource(AVAssetResourceLoadingRequest*); +#endif + private: MediaPlayerPrivateAVFoundationObjC(MediaPlayer*); @@ -63,6 +73,10 @@ private: static PassOwnPtr<MediaPlayerPrivateInterface> create(MediaPlayer*); static void getSupportedTypes(HashSet<String>& types); static MediaPlayer::SupportsType supportsType(const String& type, const String& codecs, const KURL&); +#if ENABLE(ENCRYPTED_MEDIA) + static MediaPlayer::SupportsType extendedSupportsType(const String& type, const String& codecs, const String& keySystem, const KURL&); +#endif + static bool isAvailable(); virtual void cancelLoad(); @@ -124,7 +138,13 @@ private: void paintWithVideoOutput(GraphicsContext*, const IntRect&); #endif - RetainPtr<AVAsset> m_avAsset; +#if ENABLE(ENCRYPTED_MEDIA) + virtual MediaPlayer::MediaKeyException addKey(const String&, const unsigned char*, unsigned, const unsigned char*, unsigned, const String&); + virtual MediaPlayer::MediaKeyException generateKeyRequest(const String&, const unsigned char*, unsigned); + virtual MediaPlayer::MediaKeyException cancelKeyRequest(const String&, const String&); +#endif + + RetainPtr<AVURLAsset> m_avAsset; RetainPtr<AVPlayer> m_avPlayer; RetainPtr<AVPlayerItem> m_avPlayerItem; RetainPtr<AVPlayerLayer> m_videoLayer; @@ -139,6 +159,12 @@ private: RetainPtr<AVPlayerItemVideoOutput> m_videoOutput; RetainPtr<CVPixelBufferRef> m_lastImage; #endif + +#if ENABLE(ENCRYPTED_MEDIA) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 + RetainPtr<WebCoreAVFLoaderDelegate> m_loaderDelegate; + HashMap<String, RetainPtr<AVAssetResourceLoadingRequest> > m_keyURIToRequestMap; + HashMap<String, RetainPtr<AVAssetResourceLoadingRequest> > m_sessionIDToRequestMap; +#endif }; } diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm index cf75d0cf6..5d9ddd9cb 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm @@ -30,6 +30,7 @@ #import "MediaPlayerPrivateAVFoundationObjC.h" #import "BlockExceptions.h" +#import "DataView.h" #import "FloatConversion.h" #import "FrameView.h" #import "FloatConversion.h" @@ -39,9 +40,13 @@ #import "SecurityOrigin.h" #import "SoftLinking.h" #import "TimeRanges.h" +#import "UUID.h" #import "WebCoreSystemInterface.h" #import <objc/objc-runtime.h> #import <wtf/UnusedParam.h> +#import <wtf/Uint8Array.h> +#import <wtf/Uint16Array.h> +#import <wtf/Uint32Array.h> #import <CoreMedia/CoreMedia.h> #import <AVFoundation/AVFoundation.h> @@ -112,6 +117,25 @@ enum MediaPlayerAVFoundationObservationContext { -(void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary *)change context:(MediaPlayerAVFoundationObservationContext)context; @end +#if ENABLE(ENCRYPTED_MEDIA) +@interface WebCoreAVFLoaderDelegate : NSObject<AVAssetResourceLoaderDelegate> { + MediaPlayerPrivateAVFoundationObjC* m_callback; +} +- (id)initWithCallback:(MediaPlayerPrivateAVFoundationObjC*)callback; +- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest; +@end + +static dispatch_queue_t globalLoaderDelegateQueue() +{ + static dispatch_queue_t globalQueue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + globalQueue = dispatch_queue_create("WebCoreAVFLoaderDelegate queue", DISPATCH_QUEUE_SERIAL); + }); + return globalQueue; +} +#endif + namespace WebCore { static NSArray *assetMetadataKeyNames(); @@ -132,7 +156,11 @@ PassOwnPtr<MediaPlayerPrivateInterface> MediaPlayerPrivateAVFoundationObjC::crea void MediaPlayerPrivateAVFoundationObjC::registerMediaEngine(MediaEngineRegistrar registrar) { if (isAvailable()) +#if ENABLE(ENCRYPTED_MEDIA) + registrar(create, getSupportedTypes, extendedSupportsType, 0, 0, 0); +#else registrar(create, getSupportedTypes, supportsType, 0, 0, 0); +#endif } MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC(MediaPlayer* player) @@ -140,6 +168,9 @@ MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC(MediaPlay , m_objcObserver(AdoptNS, [[WebCoreAVFMovieObserver alloc] initWithCallback:this]) , m_videoFrameHasDrawn(false) , m_haveCheckedPlayability(false) +#if ENABLE(ENCRYPTED_MEDIA) + , m_loaderDelegate(AdoptNS, [[WebCoreAVFLoaderDelegate alloc] initWithCallback:this]) +#endif { } @@ -304,6 +335,10 @@ void MediaPlayerPrivateAVFoundationObjC::createAVAssetForURL(const String& url) NSURL *cocoaURL = KURL(ParsedURLString, url); m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:cocoaURL options:options.get()]); +#if ENABLE(ENCRYPTED_MEDIA) + [[m_avAsset.get() resourceLoader] setDelegate:m_loaderDelegate.get() queue:globalLoaderDelegateQueue()]; +#endif + m_haveCheckedPlayability = false; setDelayCallbacks(false); @@ -725,6 +760,59 @@ MediaPlayer::SupportsType MediaPlayerPrivateAVFoundationObjC::supportsType(const return [AVURLAsset isPlayableExtendedMIMEType:typeString] ? MediaPlayer::IsSupported : MediaPlayer::MayBeSupported;; } +#if ENABLE(ENCRYPTED_MEDIA) +static bool keySystemIsSupported(const String& keySystem) +{ + if (equalIgnoringCase(keySystem, "com.apple.lskd") || equalIgnoringCase(keySystem, "com.apple.lskd.1_0")) + return true; + + return false; +} + +MediaPlayer::SupportsType MediaPlayerPrivateAVFoundationObjC::extendedSupportsType(const String& type, const String& codecs, const String& keySystem, const KURL& url) +{ + // From: <http://dvcs.w3.org/hg/html-media/raw-file/eme-v0.1b/encrypted-media/encrypted-media.html#dom-canplaytype> + // In addition to the steps in the current specification, this method must run the following steps: + + // 1. Check whether the Key System is supported with the specified container and codec type(s) by following the steps for the first matching condition from the following list: + // If keySystem is null, continue to the next step. + if (keySystem.isNull() || keySystem.isEmpty()) + return supportsType(type, codecs, url); + + // If keySystem contains an unrecognized or unsupported Key System, return the empty string + if (!keySystemIsSupported(keySystem)) + return MediaPlayer::IsNotSupported; + + // If the Key System specified by keySystem does not support decrypting the container and/or codec specified in the rest of the type string. + // (AVFoundation does not provide an API which would allow us to determine this, so this is a no-op) + + // 2. Return "maybe" or "probably" as appropriate per the existing specification of canPlayType(). + return supportsType(type, codecs, url); +} + +bool MediaPlayerPrivateAVFoundationObjC::shouldWaitForLoadingOfResource(AVAssetResourceLoadingRequest* avRequest) +{ + String keyURI = [[[avRequest request] URL] absoluteString]; + + // Create an initData with the following layout: + // [4 bytes: keyURI size], [keyURI size bytes: keyURI] + unsigned keyURISize = keyURI.length() * sizeof(UChar); + RefPtr<ArrayBuffer> initDataBuffer = ArrayBuffer::create(4 + keyURISize, 1); + RefPtr<DataView> initDataView = DataView::create(initDataBuffer, 0, initDataBuffer->byteLength()); + ExceptionCode ec = 0; + initDataView->setUint32(0, keyURISize, true, ec); + + RefPtr<Uint16Array> keyURIArray = Uint16Array::create(initDataBuffer, 4, keyURI.length()); + keyURIArray->setRange(keyURI.characters(), keyURI.length() / sizeof(unsigned char), 0); + + if (!player()->keyNeeded("com.apple.lskd", emptyString(), static_cast<const unsigned char*>(initDataBuffer->data()), initDataBuffer->byteLength())) + return false; + + m_keyURIToRequestMap.set(keyURI, avRequest); + return true; +} +#endif + bool MediaPlayerPrivateAVFoundationObjC::isAvailable() { return AVFoundationLibrary() && CoreMediaLibrary(); @@ -897,6 +985,133 @@ void MediaPlayerPrivateAVFoundationObjC::paintWithVideoOutput(GraphicsContext* c #endif +#if ENABLE(ENCRYPTED_MEDIA) + +static bool extractKeyURIKeyIDAndCertificateFromInitData(Uint8Array* initData, String& keyURI, String& keyID, RefPtr<Uint8Array>& certificate) +{ + // initData should have the following layout: + // [4 bytes: keyURI length][N bytes: keyURI][4 bytes: contentID length], [N bytes: contentID], [4 bytes: certificate length][N bytes: certificate] + if (initData->byteLength() < 4) + return false; + + RefPtr<ArrayBuffer> initDataBuffer = initData->buffer(); + + // Use a DataView to read uint32 values from the buffer, as Uint32Array requires the reads be aligned on 4-byte boundaries. + RefPtr<DataView> initDataView = DataView::create(initDataBuffer, 0, initDataBuffer->byteLength()); + uint32_t offset = 0; + ExceptionCode ec = 0; + + uint32_t keyURILength = initDataView->getUint32(offset, true, ec); + offset += 4; + if (ec || offset + keyURILength > initData->length()) + return false; + + RefPtr<Uint16Array> keyURIArray = Uint16Array::create(initDataBuffer, offset, keyURILength); + if (!keyURIArray) + return false; + + keyURI = String(keyURIArray->data(), keyURILength / sizeof(unsigned short)); + offset += keyURILength; + + uint32_t keyIDLength = initDataView->getUint32(offset, true, ec); + offset += 4; + if (ec || offset + keyIDLength > initData->length()) + return false; + + RefPtr<Uint16Array> keyIDArray = Uint16Array::create(initDataBuffer, offset, keyIDLength); + if (!keyIDArray) + return false; + + keyID = String(keyIDArray->data(), keyIDLength / sizeof(unsigned short)); + offset += keyIDLength; + + uint32_t certificateLength = initDataView->getUint32(offset, true, ec); + offset += 4; + if (ec || offset + certificateLength > initData->length()) + return false; + + certificate = Uint8Array::create(initDataBuffer, offset, certificateLength); + if (!certificate) + return false; + + return true; +} + +MediaPlayer::MediaKeyException MediaPlayerPrivateAVFoundationObjC::generateKeyRequest(const String& keySystem, const unsigned char* initDataPtr, unsigned initDataLength) +{ + if (!keySystemIsSupported(keySystem)) + return MediaPlayer::KeySystemNotSupported; + + RefPtr<Uint8Array> initData = Uint8Array::create(initDataPtr, initDataLength); + String keyURI; + String keyID; + RefPtr<Uint8Array> certificate; + if (!extractKeyURIKeyIDAndCertificateFromInitData(initData.get(), keyURI, keyID, certificate)) + return MediaPlayer::InvalidPlayerState; + + if (!m_keyURIToRequestMap.contains(keyURI)) + return MediaPlayer::InvalidPlayerState; + + String sessionID = createCanonicalUUIDString(); + + RetainPtr<AVAssetResourceLoadingRequest> avRequest = m_keyURIToRequestMap.get(keyURI); + + RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:certificate->baseAddress() length:certificate->byteLength()]); + NSString* assetStr = keyID; + RetainPtr<NSData> assetID = [NSData dataWithBytes: [assetStr cStringUsingEncoding:NSUTF8StringEncoding] length:[assetStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; + NSError* error = 0; + RetainPtr<NSData> keyRequest = [avRequest.get() streamingContentKeyRequestDataForApp:certificateData.get() contentIdentifier:assetID.get() options:nil error:&error]; + + if (!keyRequest) { + NSError* underlyingError = [[error userInfo] objectForKey:NSUnderlyingErrorKey]; + player()->keyError(keySystem, sessionID, MediaPlayerClient::DomainError, [underlyingError code]); + return MediaPlayer::NoError; + } + + RefPtr<ArrayBuffer> keyRequestBuffer = ArrayBuffer::create([keyRequest.get() bytes], [keyRequest.get() length]); + RefPtr<Uint8Array> keyRequestArray = Uint8Array::create(keyRequestBuffer, 0, keyRequestBuffer->byteLength()); + player()->keyMessage(keySystem, sessionID, keyRequestArray->data(), keyRequestArray->byteLength()); + + // Move ownership of the AVAssetResourceLoadingRequestfrom the keyIDToRequestMap to the sessionIDToRequestMap: + m_sessionIDToRequestMap.set(sessionID, avRequest); + m_keyURIToRequestMap.remove(keyURI); + + return MediaPlayer::NoError; +} + +MediaPlayer::MediaKeyException MediaPlayerPrivateAVFoundationObjC::addKey(const String& keySystem, const unsigned char* keyPtr, unsigned keyLength, const unsigned char* initDataPtr, unsigned initDataLength, const String& sessionID) +{ + if (!keySystemIsSupported(keySystem)) + return MediaPlayer::KeySystemNotSupported; + + if (!m_sessionIDToRequestMap.contains(sessionID)) + return MediaPlayer::InvalidPlayerState; + + RetainPtr<AVAssetResourceLoadingRequest> avRequest = m_sessionIDToRequestMap.get(sessionID); + RetainPtr<NSData> keyData = adoptNS([[NSData alloc] initWithBytes:keyPtr length:keyLength]); + [avRequest.get() finishLoadingWithResponse:nil data:keyData.get() redirect:nil]; + m_sessionIDToRequestMap.remove(sessionID); + + player()->keyAdded(keySystem, sessionID); + + UNUSED_PARAM(initDataPtr); + UNUSED_PARAM(initDataLength); + return MediaPlayer::NoError; +} + +MediaPlayer::MediaKeyException MediaPlayerPrivateAVFoundationObjC::cancelKeyRequest(const String& keySystem, const String& sessionID) +{ + if (!keySystemIsSupported(keySystem)) + return MediaPlayer::KeySystemNotSupported; + + if (!m_sessionIDToRequestMap.contains(sessionID)) + return MediaPlayer::InvalidPlayerState; + + m_sessionIDToRequestMap.remove(sessionID); + return MediaPlayer::NoError; +} +#endif + NSArray* assetMetadataKeyNames() { static NSArray* keys; @@ -1024,4 +1239,26 @@ NSArray* itemKVOProperties() @end +#if ENABLE(ENCRYPTED_MEDIA) +@implementation WebCoreAVFLoaderDelegate + +- (id)initWithCallback:(MediaPlayerPrivateAVFoundationObjC*)callback +{ + m_callback = callback; + return [super init]; +} + +- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest +{ + UNUSED_PARAM(resourceLoader); + dispatch_async(dispatch_get_main_queue(), ^{ + if (!m_callback->shouldWaitForLoadingOfResource(loadingRequest)) + [loadingRequest finishLoadingWithError:nil]; + }); + return TRUE; +} + +@end +#endif + #endif |