[iOS] add missing QuickTime plug-in replacement API
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 3 Apr 2014 15:46:05 +0000 (15:46 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 3 Apr 2014 15:46:05 +0000 (15:46 +0000)
https://bugs.webkit.org/show_bug.cgi?id=131042

Reviewed by Dean Jackson.

Rename QuickTimePluginReplacement.cpp to QuickTimePluginReplacement.mm to make it possible
to use the ObjC JSC API.
* Modules/plugins/QuickTimePluginReplacement.cpp:
* Modules/plugins/QuickTimePluginReplacement.h:
(WebCore::QuickTimePluginReplacement::parentElement):
* Modules/plugins/QuickTimePluginReplacement.idl:

* Modules/plugins/QuickTimePluginReplacement.js:
(Replacement.prototype.timedMetadataUpdates): Implement.
(Replacement.prototype.accessLog): Ditto.
(Replacement.prototype.errorLog): Ditto.

Use the JSC ObjC API to create a JavaScript object from an array of AVMetadataItems. The
JSC ObjC API supports basic NSTypes, but an AVMetadataItem can also contain NSData which
the existing plug-in returns as base-64 encoded data, so create wrappers for NSDictionary
and NSArray.
* Modules/plugins/QuickTimePluginReplacement.mm: Copied from Source/WebCore/Modules/plugins/QuickTimePluginReplacement.cpp.
(WebCore::QuickTimePluginReplacement::ensureReplacementScriptInjected): Disambiguate with "JSC::"
(WebCore::QuickTimePluginReplacement::installReplacement): Ditto.
(WebCore::jsValueWithDataInContext): Create JSValue* from NSData.
(WebCore::jsValueWithArrayInContext): Create JSValue* from NSArray.
(WebCore::jsValueWithDictionaryInContext): Create JSValue* from NSDictionary.
(WebCore::jsValueWithValueInContext): Create JSValue* from basic NSTypes plus AVMetadataItem
    and NSData.
(WebCore::jsValueWithAVMetadataItemInContext): Create JSValue* from AVMetadataItem.
(WebCore::JSQuickTimePluginReplacement::timedMetaData): Script interface.
(WebCore::JSQuickTimePluginReplacement::accessLog): Ditto.
(WebCore::JSQuickTimePluginReplacement::errorLog): Ditto.

* WebCore.xcodeproj/project.pbxproj: QuickTimePluginReplacement.cpp -> QuickTimePluginReplacement.mm.

* platform/graphics/MediaPlayer.cpp:
(WebCore::MediaPlayer::timedMetadata): iOS only accessor.
(WebCore::MediaPlayer::accessLog): Ditto.
(WebCore::MediaPlayer::errorLog): Ditto.
* platform/graphics/MediaPlayer.h:
* platform/graphics/MediaPlayerPrivate.h:

* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
(WebCore::MediaPlayerPrivateAVFoundationObjC::timedMetadata):
(WebCore::MediaPlayerPrivateAVFoundationObjC::accessLog):
(WebCore::MediaPlayerPrivateAVFoundationObjC::errorLog):
(WebCore::MediaPlayerPrivateAVFoundationObjC::metadataDidArrive):
(WebCore::itemKVOProperties):
(-[WebCoreAVFMovieObserver observeValueForKeyPath:ofObject:change:context:]):

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

12 files changed:
Source/WebCore/ChangeLog
Source/WebCore/Modules/plugins/QuickTimePluginReplacement.cpp [deleted file]
Source/WebCore/Modules/plugins/QuickTimePluginReplacement.h
Source/WebCore/Modules/plugins/QuickTimePluginReplacement.idl
Source/WebCore/Modules/plugins/QuickTimePluginReplacement.js
Source/WebCore/Modules/plugins/QuickTimePluginReplacement.mm [new file with mode: 0644]
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/platform/graphics/MediaPlayer.cpp
Source/WebCore/platform/graphics/MediaPlayer.h
Source/WebCore/platform/graphics/MediaPlayerPrivate.h
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm

index 13ca05e..0ef0239 100644 (file)
@@ -1,3 +1,57 @@
+2014-04-03  Eric Carlson  <eric.carlson@apple.com>
+
+        [iOS] add missing QuickTime plug-in replacement API
+        https://bugs.webkit.org/show_bug.cgi?id=131042
+
+        Reviewed by Dean Jackson.
+
+        Rename QuickTimePluginReplacement.cpp to QuickTimePluginReplacement.mm to make it possible
+        to use the ObjC JSC API.
+        * Modules/plugins/QuickTimePluginReplacement.cpp:
+        * Modules/plugins/QuickTimePluginReplacement.h:
+        (WebCore::QuickTimePluginReplacement::parentElement):
+        * Modules/plugins/QuickTimePluginReplacement.idl:
+
+        * Modules/plugins/QuickTimePluginReplacement.js:
+        (Replacement.prototype.timedMetadataUpdates): Implement.
+        (Replacement.prototype.accessLog): Ditto.
+        (Replacement.prototype.errorLog): Ditto.
+
+        Use the JSC ObjC API to create a JavaScript object from an array of AVMetadataItems. The
+        JSC ObjC API supports basic NSTypes, but an AVMetadataItem can also contain NSData which 
+        the existing plug-in returns as base-64 encoded data, so create wrappers for NSDictionary
+        and NSArray.
+        * Modules/plugins/QuickTimePluginReplacement.mm: Copied from Source/WebCore/Modules/plugins/QuickTimePluginReplacement.cpp.
+        (WebCore::QuickTimePluginReplacement::ensureReplacementScriptInjected): Disambiguate with "JSC::"
+        (WebCore::QuickTimePluginReplacement::installReplacement): Ditto.
+        (WebCore::jsValueWithDataInContext): Create JSValue* from NSData.
+        (WebCore::jsValueWithArrayInContext): Create JSValue* from NSArray.
+        (WebCore::jsValueWithDictionaryInContext): Create JSValue* from NSDictionary.
+        (WebCore::jsValueWithValueInContext): Create JSValue* from basic NSTypes plus AVMetadataItem
+            and NSData.
+        (WebCore::jsValueWithAVMetadataItemInContext): Create JSValue* from AVMetadataItem.
+        (WebCore::JSQuickTimePluginReplacement::timedMetaData): Script interface.
+        (WebCore::JSQuickTimePluginReplacement::accessLog): Ditto.
+        (WebCore::JSQuickTimePluginReplacement::errorLog): Ditto.
+
+        * WebCore.xcodeproj/project.pbxproj: QuickTimePluginReplacement.cpp -> QuickTimePluginReplacement.mm.
+
+        * platform/graphics/MediaPlayer.cpp:
+        (WebCore::MediaPlayer::timedMetadata): iOS only accessor.
+        (WebCore::MediaPlayer::accessLog): Ditto.
+        (WebCore::MediaPlayer::errorLog): Ditto.
+        * platform/graphics/MediaPlayer.h:
+        * platform/graphics/MediaPlayerPrivate.h:
+
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::timedMetadata):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::accessLog):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::errorLog):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::metadataDidArrive):
+        (WebCore::itemKVOProperties):
+        (-[WebCoreAVFMovieObserver observeValueForKeyPath:ofObject:change:context:]):
+
 2014-04-03  Andrei Bucur  <abucur@adobe.com>
 
         [CSS Regions] Include region range information when printing the render tree
diff --git a/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.cpp b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.cpp
deleted file mode 100644 (file)
index 1acc6bb..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
- */
-
-#include "config.h"
-
-#if ENABLE(MEDIA_CONTROLS_SCRIPT)
-
-#include "QuickTimePluginReplacement.h"
-
-#include "Event.h"
-#include "HTMLPlugInElement.h"
-#include "HTMLVideoElement.h"
-#include "JSDOMBinding.h"
-#include "JSDOMGlobalObject.h"
-#include "JSHTMLVideoElement.h"
-#include "JSQuickTimePluginReplacement.h"
-#include "Logging.h"
-#include "MainFrame.h"
-#include "Page.h"
-#include "RenderElement.h"
-#include "ScriptController.h"
-#include "ScriptSourceCode.h"
-#include "UserAgentScripts.h"
-#include <JavaScriptCore/APICast.h>
-#include <JavaScriptCore/JSBase.h>
-#include <JavaScriptCore/JSCJSValueInlines.h>
-
-using namespace JSC;
-
-namespace WebCore {
-
-static String quickTimePluginReplacementScript()
-{
-    DEPRECATED_DEFINE_STATIC_LOCAL(String, script, (QuickTimePluginReplacementJavaScript, sizeof(QuickTimePluginReplacementJavaScript)));
-    return script;
-}
-
-void QuickTimePluginReplacement::registerPluginReplacement(PluginReplacementRegistrar registrar)
-{
-    registrar(ReplacementPlugin(create, supportsMimeType, supportsFileExtension));
-}
-
-PassRefPtr<PluginReplacement> QuickTimePluginReplacement::create(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
-{
-    return adoptRef(new QuickTimePluginReplacement(plugin, paramNames, paramValues));
-}
-
-bool QuickTimePluginReplacement::supportsMimeType(const String& mimeType)
-{
-    static const char* types[] = {
-        "application/vnd.apple.mpegurl", "application/x-mpegurl", "audio/3gpp", "audio/3gpp2", "audio/aac", "audio/aiff",
-        "audio/amr", "audio/basic", "audio/mp3", "audio/mp4", "audio/mpeg", "audio/mpeg3", "audio/mpegurl", "audio/scpls",
-        "audio/wav", "audio/x-aac", "audio/x-aiff", "audio/x-caf", "audio/x-m4a", "audio/x-m4b", "audio/x-m4p",
-        "audio/x-m4r", "audio/x-mp3", "audio/x-mpeg", "audio/x-mpeg3", "audio/x-mpegurl", "audio/x-scpls", "audio/x-wav",
-        "video/3gpp", "video/3gpp2", "video/mp4", "video/quicktime", "video/x-m4v"
-    };
-    DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, typeHash, ());
-    if (!typeHash.size()) {
-        for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i)
-            typeHash.add(types[i]);
-    }
-
-    return typeHash.contains(mimeType);
-}
-
-bool QuickTimePluginReplacement::supportsFileExtension(const String& extension)
-{
-    static const char* extensions[] = {
-        "3g2", "3gp", "3gp2", "3gpp", "aac", "adts", "aif", "aifc", "aiff", "AMR", "au", "bwf", "caf", "cdda", "m3u",
-        "m3u8", "m4a", "m4b", "m4p", "m4r", "m4v", "mov", "mp3", "mp3", "mp4", "mpeg", "mpg", "mqv", "pls", "qt",
-        "snd", "swa", "ts", "ulw", "wav"
-    };
-    DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, extensionHash, ());
-    if (!extensionHash.size()) {
-        for (size_t i = 0; i < WTF_ARRAY_LENGTH(extensions); ++i)
-            extensionHash.add(extensions[i]);
-    }
-
-    return extensionHash.contains(extension);
-}
-
-QuickTimePluginReplacement::QuickTimePluginReplacement(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
-    :PluginReplacement()
-    , m_parentElement(&plugin)
-    , m_names(paramNames)
-    , m_values(paramValues)
-    , m_scriptObject(nullptr)
-{
-}
-
-QuickTimePluginReplacement::~QuickTimePluginReplacement()
-{
-    m_parentElement = nullptr;
-    m_scriptObject = nullptr;
-    m_mediaElement = nullptr;
-}
-
-RenderPtr<RenderElement> QuickTimePluginReplacement::createElementRenderer(HTMLPlugInElement& plugin, PassRef<RenderStyle> style)
-{
-    ASSERT_UNUSED(plugin, m_parentElement == &plugin);
-
-    if (m_mediaElement)
-        return m_mediaElement->createElementRenderer(std::move(style));
-
-    return nullptr;
-}
-
-DOMWrapperWorld& QuickTimePluginReplacement::isolatedWorld()
-{
-    static DOMWrapperWorld& isolatedWorld = *DOMWrapperWorld::create(JSDOMWindow::commonVM()).leakRef();
-    return isolatedWorld;
-}
-
-bool QuickTimePluginReplacement::ensureReplacementScriptInjected()
-{
-    Page* page = m_parentElement->document().page();
-    if (!page)
-        return false;
-    
-    DOMWrapperWorld& world = isolatedWorld();
-    ScriptController& scriptController = page->mainFrame().script();
-    JSDOMGlobalObject* globalObject = jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
-    ExecState* exec = globalObject->globalExec();
-    
-    JSValue replacementFunction = globalObject->get(exec, Identifier(exec, "createPluginReplacement"));
-    if (replacementFunction.isFunction())
-        return true;
-    
-    scriptController.evaluateInWorld(ScriptSourceCode(quickTimePluginReplacementScript()), world);
-    if (exec->hadException()) {
-        LOG(Plugins, "%p - Exception when evaluating QuickTime plugin replacement script", this);
-        exec->clearException();
-        return false;
-    }
-    
-    return true;
-}
-
-bool QuickTimePluginReplacement::installReplacement(ShadowRoot* root)
-{
-    Page* page = m_parentElement->document().page();
-
-    if (!ensureReplacementScriptInjected())
-        return false;
-
-    DOMWrapperWorld& world = isolatedWorld();
-    ScriptController& scriptController = page->mainFrame().script();
-    JSDOMGlobalObject* globalObject = jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
-    ExecState* exec = globalObject->globalExec();
-    JSLockHolder lock(exec);
-    
-    // Lookup the "createPluginReplacement" function.
-    JSValue replacementFunction = globalObject->get(exec, Identifier(exec, "createPluginReplacement"));
-    if (replacementFunction.isUndefinedOrNull())
-        return false;
-    JSObject* replacementObject = replacementFunction.toObject(exec);
-    CallData callData;
-    CallType callType = replacementObject->methodTable()->getCallData(replacementObject, callData);
-    if (callType == CallTypeNone)
-        return false;
-
-    MarkedArgumentBuffer argList;
-    argList.append(toJS(exec, globalObject, root));
-    argList.append(toJS(exec, globalObject, m_parentElement));
-    argList.append(toJS(exec, globalObject, this));
-    argList.append(toJS<String>(exec, globalObject, m_names));
-    argList.append(toJS<String>(exec, globalObject, m_values));
-    JSValue replacement = call(exec, replacementObject, callType, callData, globalObject, argList);
-    if (exec->hadException()) {
-        exec->clearException();
-        return false;
-    }
-
-    // Get the <video> created to replace the plug-in.
-    JSValue value = replacement.get(exec, Identifier(exec, "video"));
-    if (!exec->hadException() && !value.isUndefinedOrNull())
-        m_mediaElement = toHTMLVideoElement(value);
-
-    if (!m_mediaElement) {
-        LOG(Plugins, "%p - Failed to find <video> element created by QuickTime plugin replacement script.", this);
-        exec->clearException();
-        return false;
-    }
-
-    // Get the scripting interface.
-    value = replacement.get(exec, Identifier(exec, "scriptObject"));
-    if (!exec->hadException() && !value.isUndefinedOrNull())
-        m_scriptObject = value.toObject(exec);
-
-    if (!m_scriptObject) {
-        LOG(Plugins, "%p - Failed to find script object created by QuickTime plugin replacement.", this);
-        exec->clearException();
-        return false;
-    }
-
-    return true;
-}
-
-unsigned long long QuickTimePluginReplacement::movieSize() const
-{
-    if (m_mediaElement)
-        return m_mediaElement->fileSize();
-
-    return 0;
-}
-
-void QuickTimePluginReplacement::postEvent(const String& eventName)
-{
-    Ref<HTMLPlugInElement> protect(*m_parentElement);
-    RefPtr<Event> event = Event::create(eventName, false, true);
-    m_parentElement->dispatchEvent(event.get());
-}
-
-}
-#endif
index 02a2d66..93fe912 100644 (file)
@@ -55,6 +55,8 @@ public:
     virtual bool willCreateRenderer() override { return m_mediaElement; }
     virtual RenderPtr<RenderElement> createElementRenderer(HTMLPlugInElement&, PassRef<RenderStyle>) override;
 
+    HTMLVideoElement* parentElement() { return m_mediaElement.get(); }
+
     unsigned long long movieSize() const;
     void postEvent(const String&);
 
index 07173d5..d1e9fcb 100644 (file)
@@ -29,5 +29,8 @@
     JSGenerateToJSObject,
 ] interface QuickTimePluginReplacement {
     readonly attribute unsigned long long movieSize;
+    [CustomGetter] readonly attribute any timedMetaData;
+    [CustomGetter] readonly attribute any accessLog;
+    [CustomGetter] readonly attribute any errorLog;
     void postEvent(DOMString eventName);
 };
index 9ffd2a7..ed2f906 100644 (file)
@@ -322,19 +322,28 @@ Replacement.prototype = {
     
     timedMetadataUpdates: function()
     {
-        // FIXME: not implemented yet.
+        try {
+            return this.host.timedMetaData;
+        } catch(e) { }
+
         return null;
     },
     
     accessLog: function()
     {
-        // FIXME: not implemented yet.
+        try {
+            return this.host.accessLog;
+        } catch(e) { }
+
         return null;
     },
     
     errorLog: function()
     {
-        // FIXME: not implemented yet.
+        try {
+            return this.host.errorLog;
+        } catch(e) { }
+
         return null;
     },
 };
diff --git a/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.mm b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.mm
new file mode 100644 (file)
index 0000000..c940e6a
--- /dev/null
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2013-2014 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#import "config.h"
+
+#if ENABLE(MEDIA_CONTROLS_SCRIPT)
+
+#import "QuickTimePluginReplacement.h"
+
+#import "Event.h"
+#import "HTMLPlugInElement.h"
+#import "HTMLVideoElement.h"
+#import "JSDOMBinding.h"
+#import "JSDOMGlobalObject.h"
+#import "JSHTMLVideoElement.h"
+#import "JSQuickTimePluginReplacement.h"
+#import "Logging.h"
+#import "MainFrame.h"
+#import "Page.h"
+#import "RenderElement.h"
+#import "ScriptController.h"
+#import "ScriptSourceCode.h"
+#import "SoftLinking.h"
+#import "UserAgentScripts.h"
+#import <objc/runtime.h>
+#import <AVFoundation/AVFoundation.h>
+#import <CoreMedia/CoreMedia.h>
+#import <Foundation/NSString.h>
+#import <JavaScriptCore/JavaScriptCore.h>
+#import <JavaScriptCore/APICast.h>
+#import <wtf/text/Base64.h>
+
+SOFT_LINK_FRAMEWORK_OPTIONAL(CoreMedia)
+SOFT_LINK(CoreMedia, CMTimeCopyAsDictionary, CFDictionaryRef, (CMTime time, CFAllocatorRef allocator), (time, allocator))
+
+typedef AVMetadataItem AVMetadataItemType;
+SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
+SOFT_LINK_CLASS(AVFoundation, AVMetadataItem)
+#define AVMetadataItem getAVMetadataItemClass()
+
+namespace WebCore {
+
+#if PLATFORM(IOS)
+static JSValue *jsValueWithValueInContext(id, JSContext *);
+static JSValue *jsValueWithAVMetadataItemInContext(AVMetadataItemType *, JSContext *);
+#endif
+
+static String quickTimePluginReplacementScript()
+{
+    DEPRECATED_DEFINE_STATIC_LOCAL(String, script, (QuickTimePluginReplacementJavaScript, sizeof(QuickTimePluginReplacementJavaScript)));
+    return script;
+}
+
+void QuickTimePluginReplacement::registerPluginReplacement(PluginReplacementRegistrar registrar)
+{
+    registrar(ReplacementPlugin(create, supportsMimeType, supportsFileExtension));
+}
+
+PassRefPtr<PluginReplacement> QuickTimePluginReplacement::create(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
+{
+    return adoptRef(new QuickTimePluginReplacement(plugin, paramNames, paramValues));
+}
+
+bool QuickTimePluginReplacement::supportsMimeType(const String& mimeType)
+{
+    static const char* types[] = {
+        "application/vnd.apple.mpegurl", "application/x-mpegurl", "audio/3gpp", "audio/3gpp2", "audio/aac", "audio/aiff",
+        "audio/amr", "audio/basic", "audio/mp3", "audio/mp4", "audio/mpeg", "audio/mpeg3", "audio/mpegurl", "audio/scpls",
+        "audio/wav", "audio/x-aac", "audio/x-aiff", "audio/x-caf", "audio/x-m4a", "audio/x-m4b", "audio/x-m4p",
+        "audio/x-m4r", "audio/x-mp3", "audio/x-mpeg", "audio/x-mpeg3", "audio/x-mpegurl", "audio/x-scpls", "audio/x-wav",
+        "video/3gpp", "video/3gpp2", "video/mp4", "video/quicktime", "video/x-m4v"
+    };
+    DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, typeHash, ());
+    if (!typeHash.size()) {
+        for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i)
+            typeHash.add(types[i]);
+    }
+
+    return typeHash.contains(mimeType);
+}
+
+bool QuickTimePluginReplacement::supportsFileExtension(const String& extension)
+{
+    static const char* extensions[] = {
+        "3g2", "3gp", "3gp2", "3gpp", "aac", "adts", "aif", "aifc", "aiff", "AMR", "au", "bwf", "caf", "cdda", "m3u",
+        "m3u8", "m4a", "m4b", "m4p", "m4r", "m4v", "mov", "mp3", "mp3", "mp4", "mpeg", "mpg", "mqv", "pls", "qt",
+        "snd", "swa", "ts", "ulw", "wav"
+    };
+    DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, extensionHash, ());
+    if (!extensionHash.size()) {
+        for (size_t i = 0; i < WTF_ARRAY_LENGTH(extensions); ++i)
+            extensionHash.add(extensions[i]);
+    }
+
+    return extensionHash.contains(extension);
+}
+
+QuickTimePluginReplacement::QuickTimePluginReplacement(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
+    :PluginReplacement()
+    , m_parentElement(&plugin)
+    , m_names(paramNames)
+    , m_values(paramValues)
+    , m_scriptObject(nullptr)
+{
+}
+
+QuickTimePluginReplacement::~QuickTimePluginReplacement()
+{
+    m_parentElement = nullptr;
+    m_scriptObject = nullptr;
+    m_mediaElement = nullptr;
+}
+
+RenderPtr<RenderElement> QuickTimePluginReplacement::createElementRenderer(HTMLPlugInElement& plugin, PassRef<RenderStyle> style)
+{
+    ASSERT_UNUSED(plugin, m_parentElement == &plugin);
+
+    if (m_mediaElement)
+        return m_mediaElement->createElementRenderer(std::move(style));
+
+    return nullptr;
+}
+
+DOMWrapperWorld& QuickTimePluginReplacement::isolatedWorld()
+{
+    static DOMWrapperWorld& isolatedWorld = *DOMWrapperWorld::create(JSDOMWindow::commonVM()).leakRef();
+    return isolatedWorld;
+}
+
+bool QuickTimePluginReplacement::ensureReplacementScriptInjected()
+{
+    Page* page = m_parentElement->document().page();
+    if (!page)
+        return false;
+    
+    DOMWrapperWorld& world = isolatedWorld();
+    ScriptController& scriptController = page->mainFrame().script();
+    JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
+    JSC::ExecState* exec = globalObject->globalExec();
+    
+    JSC::JSValue replacementFunction = globalObject->get(exec, JSC::Identifier(exec, "createPluginReplacement"));
+    if (replacementFunction.isFunction())
+        return true;
+    
+    scriptController.evaluateInWorld(ScriptSourceCode(quickTimePluginReplacementScript()), world);
+    if (exec->hadException()) {
+        LOG(Plugins, "%p - Exception when evaluating QuickTime plugin replacement script", this);
+        exec->clearException();
+        return false;
+    }
+    
+    return true;
+}
+
+bool QuickTimePluginReplacement::installReplacement(ShadowRoot* root)
+{
+    Page* page = m_parentElement->document().page();
+
+    if (!ensureReplacementScriptInjected())
+        return false;
+
+    DOMWrapperWorld& world = isolatedWorld();
+    ScriptController& scriptController = page->mainFrame().script();
+    JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
+    JSC::ExecState* exec = globalObject->globalExec();
+    JSC::JSLockHolder lock(exec);
+    
+    // Lookup the "createPluginReplacement" function.
+    JSC::JSValue replacementFunction = globalObject->get(exec, JSC::Identifier(exec, "createPluginReplacement"));
+    if (replacementFunction.isUndefinedOrNull())
+        return false;
+    JSC::JSObject* replacementObject = replacementFunction.toObject(exec);
+    JSC::CallData callData;
+    JSC::CallType callType = replacementObject->methodTable()->getCallData(replacementObject, callData);
+    if (callType == JSC::CallTypeNone)
+        return false;
+
+    JSC::MarkedArgumentBuffer argList;
+    argList.append(toJS(exec, globalObject, root));
+    argList.append(toJS(exec, globalObject, m_parentElement));
+    argList.append(toJS(exec, globalObject, this));
+    argList.append(toJS<String>(exec, globalObject, m_names));
+    argList.append(toJS<String>(exec, globalObject, m_values));
+    JSC::JSValue replacement = call(exec, replacementObject, callType, callData, globalObject, argList);
+    if (exec->hadException()) {
+        exec->clearException();
+        return false;
+    }
+
+    // Get the <video> created to replace the plug-in.
+    JSC::JSValue value = replacement.get(exec, JSC::Identifier(exec, "video"));
+    if (!exec->hadException() && !value.isUndefinedOrNull())
+        m_mediaElement = toHTMLVideoElement(value);
+
+    if (!m_mediaElement) {
+        LOG(Plugins, "%p - Failed to find <video> element created by QuickTime plugin replacement script.", this);
+        exec->clearException();
+        return false;
+    }
+
+    // Get the scripting interface.
+    value = replacement.get(exec, JSC::Identifier(exec, "scriptObject"));
+    if (!exec->hadException() && !value.isUndefinedOrNull())
+        m_scriptObject = value.toObject(exec);
+
+    if (!m_scriptObject) {
+        LOG(Plugins, "%p - Failed to find script object created by QuickTime plugin replacement.", this);
+        exec->clearException();
+        return false;
+    }
+
+    return true;
+}
+
+unsigned long long QuickTimePluginReplacement::movieSize() const
+{
+    if (m_mediaElement)
+        return m_mediaElement->fileSize();
+
+    return 0;
+}
+
+void QuickTimePluginReplacement::postEvent(const String& eventName)
+{
+    Ref<HTMLPlugInElement> protect(*m_parentElement);
+    RefPtr<Event> event = Event::create(eventName, false, true);
+    m_parentElement->dispatchEvent(event.get());
+}
+
+#if PLATFORM(IOS)
+static JSValue *jsValueWithDataInContext(NSData *data, const String& mimeType, JSContext *context)
+{
+    Vector<char> base64Data;
+    base64Encode([data bytes], [data length], base64Data);
+
+    String data64;
+    if (!mimeType.isEmpty())
+        data64 = "data:" + mimeType + ";base64," + base64Data;
+    else
+        data64 = "data:text/plain;base64," + base64Data;
+
+    return [JSValue valueWithObject:(id)data64.createCFString().get() inContext:context];
+}
+
+static JSValue *jsValueWithArrayInContext(NSArray *array, JSContext *context)
+{
+    JSValueRef exception = 0;
+    JSValue *result = [JSValue valueWithNewArrayInContext:context];
+    JSObjectRef resultObject = JSValueToObject([context JSGlobalContextRef], [result JSValueRef], &exception);
+    if (exception)
+        return [JSValue valueWithUndefinedInContext:context];
+
+    NSUInteger count = [array count];
+    for (NSUInteger i = 0; i < count; ++i) {
+        JSValue *value = jsValueWithValueInContext([array objectAtIndex:i], context);
+        if (!value)
+            continue;
+
+        JSObjectSetPropertyAtIndex([context JSGlobalContextRef], resultObject, (unsigned)i, [value JSValueRef], &exception);
+        if (exception)
+            continue;
+    }
+
+    return result;
+}
+
+
+static JSValue *jsValueWithDictionaryInContext(NSDictionary *dictionary, JSContext *context)
+{
+    JSValueRef exception = 0;
+    JSValue *result = [JSValue valueWithNewObjectInContext:context];
+    JSObjectRef resultObject = JSValueToObject([context JSGlobalContextRef], [result JSValueRef], &exception);
+    if (exception)
+        return [JSValue valueWithUndefinedInContext:context];
+
+    for (id key in [dictionary keyEnumerator]) {
+        if (![key isKindOfClass:[NSString class]])
+            continue;
+
+        JSValue *value = jsValueWithValueInContext([dictionary objectForKey:key], context);
+        if (!value)
+            continue;
+
+        JSStringRef name = JSStringCreateWithCFString((CFStringRef)key);
+        JSObjectSetProperty([context JSGlobalContextRef], resultObject, name, [value JSValueRef], 0, &exception);
+        if (exception)
+            continue;
+    }
+
+    return result;
+}
+
+static JSValue *jsValueWithValueInContext(id value, JSContext *context)
+{
+    if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])
+        return [JSValue valueWithObject:value inContext:context];
+    else if ([value isKindOfClass:[NSLocale class]])
+        return [JSValue valueWithObject:[value localeIdentifier] inContext:context];
+    else if ([value isKindOfClass:[NSDictionary class]])
+        return jsValueWithDictionaryInContext(value, context);
+    else if ([value isKindOfClass:[NSArray class]])
+        return jsValueWithArrayInContext(value, context);
+    else if ([value isKindOfClass:[NSData class]])
+        return jsValueWithDataInContext(value, emptyString(), context);
+    else if ([value isKindOfClass:[AVMetadataItem class]])
+        return jsValueWithAVMetadataItemInContext(value, context);
+
+    return nil;
+}
+
+static JSValue *jsValueWithAVMetadataItemInContext(AVMetadataItemType *item, JSContext *context)
+{
+    NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithDictionary:[item extraAttributes]];
+
+    if (item.keySpace)
+        [dictionary setObject:item.keySpace forKey:@"keyspace"];
+
+    if (item.key)
+        [dictionary setObject:item.key forKey:@"key"];
+
+    if (item.locale)
+        [dictionary setObject:item.locale forKey:@"locale"];
+
+    if (CMTIME_IS_VALID(item.time)) {
+        CFDictionaryRef timeDict = CMTimeCopyAsDictionary(item.time, kCFAllocatorDefault);
+
+        if (timeDict) {
+            [dictionary setObject:(id)timeDict forKey:@"timestamp"];
+            CFRelease(timeDict);
+        }
+    }
+    
+    if (item.value) {
+        id value = item.value;
+        NSString *mimeType = [[item extraAttributes] objectForKey:@"MIMEtype"];
+        if ([value isKindOfClass:[NSData class]] && mimeType) {
+            Vector<char> base64Data;
+            base64Encode([value bytes], [value length], base64Data);
+            String data64 = "data:" + String(mimeType) + ";base64," + base64Data;
+            [dictionary setObject:(id)data64.createCFString().get() forKey:@"value"];
+        } else
+            [dictionary setObject:value forKey:@"value"];
+    }
+
+    return jsValueWithDictionaryInContext(dictionary, context);
+}
+#endif
+
+JSC::JSValue JSQuickTimePluginReplacement::timedMetaData(JSC::ExecState* exec) const
+{
+#if PLATFORM(IOS)
+    HTMLVideoElement* parent = impl().parentElement();
+    if (!parent || !parent->player())
+        return JSC::jsNull();
+
+    Frame* frame = parent->document().frame();
+    if (!frame)
+        return JSC::jsNull();
+
+    NSArray *metaData = parent->player()->timedMetadata();
+    if (!metaData)
+        return JSC::jsNull();
+
+    JSContext *jsContext = frame->script().javaScriptContext();
+    JSValue *metaDataValue = jsValueWithValueInContext(metaData, jsContext);
+    
+    return toJS(exec, [metaDataValue JSValueRef]);
+#else
+    UNUSED_PARAM(exec);
+    return JSC::jsNull();
+#endif
+}
+
+JSC::JSValue JSQuickTimePluginReplacement::accessLog(JSC::ExecState* exec) const
+{
+#if PLATFORM(IOS)
+    HTMLVideoElement* parent = impl().parentElement();
+    if (!parent || !parent->player())
+        return JSC::jsNull();
+
+    Frame* frame = parent->document().frame();
+    if (!frame)
+        return JSC::jsNull();
+
+    JSValue *dictionary = [JSValue valueWithNewObjectInContext:frame->script().javaScriptContext()];
+    String accessLogString = parent->player()->accessLog();
+    [dictionary setValue:static_cast<NSString *>(accessLogString) forProperty:(NSString *)CFSTR("extendedLog")];
+
+    return toJS(exec, [dictionary JSValueRef]);
+#else
+    UNUSED_PARAM(exec);
+    return JSC::jsNull();
+#endif
+}
+
+JSC::JSValue JSQuickTimePluginReplacement::errorLog(JSC::ExecState* exec) const
+{
+#if PLATFORM(IOS)
+    HTMLVideoElement* parent = impl().parentElement();
+    if (!parent || !parent->player())
+        return JSC::jsNull();
+
+    Frame* frame = parent->document().frame();
+    if (!frame)
+        return JSC::jsNull();
+
+    JSValue *dictionary = [JSValue valueWithNewObjectInContext:frame->script().javaScriptContext()];
+    String errorLogString = parent->player()->errorLog();
+    [dictionary setValue:static_cast<NSString *>(errorLogString) forProperty:(NSString *)CFSTR("extendedLog")];
+
+    return toJS(exec, [dictionary JSValueRef]);
+#else
+    UNUSED_PARAM(exec);
+    return JSC::jsNull();
+#endif
+}
+
+}
+
+#endif
index edba9a5..01e2ced 100644 (file)
                07277E5417D018CC0015534D /* JSMediaStreamTrackEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07277E4817D018CC0015534D /* JSMediaStreamTrackEvent.cpp */; };
                07277E5517D018CC0015534D /* JSMediaStreamTrackEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 07277E4917D018CC0015534D /* JSMediaStreamTrackEvent.h */; settings = {ATTRIBUTES = (Private, ); }; };
                072AE1E5183C0741000A5988 /* PluginReplacement.h in Headers */ = {isa = PBXBuildFile; fileRef = 072AE1DF183C0741000A5988 /* PluginReplacement.h */; };
-               072AE1E6183C0741000A5988 /* QuickTimePluginReplacement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 072AE1E0183C0741000A5988 /* QuickTimePluginReplacement.cpp */; };
+               072AE1E6183C0741000A5988 /* QuickTimePluginReplacement.mm in Sources */ = {isa = PBXBuildFile; fileRef = 072AE1E0183C0741000A5988 /* QuickTimePluginReplacement.mm */; };
                072AE1E8183C0741000A5988 /* QuickTimePluginReplacement.h in Headers */ = {isa = PBXBuildFile; fileRef = 072AE1E2183C0741000A5988 /* QuickTimePluginReplacement.h */; };
                072C8B11131C518600A4FCE9 /* MediaPlayerPrivateAVFoundation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 076F0D0912B8192700C26AA4 /* MediaPlayerPrivateAVFoundation.cpp */; };
                072CA86116CB4DC3008AE131 /* CaptionUserPreferences.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 072CA86016CB4DC3008AE131 /* CaptionUserPreferences.cpp */; };
                0729B14E17CFCCA0004F1D60 /* MediaStreamCenterMac.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MediaStreamCenterMac.cpp; sourceTree = "<group>"; };
                0729B14F17CFCCA0004F1D60 /* MediaStreamCenterMac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaStreamCenterMac.h; sourceTree = "<group>"; };
                072AE1DF183C0741000A5988 /* PluginReplacement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PluginReplacement.h; path = plugins/PluginReplacement.h; sourceTree = "<group>"; };
-               072AE1E0183C0741000A5988 /* QuickTimePluginReplacement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = QuickTimePluginReplacement.cpp; path = plugins/QuickTimePluginReplacement.cpp; sourceTree = "<group>"; };
+               072AE1E0183C0741000A5988 /* QuickTimePluginReplacement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = QuickTimePluginReplacement.mm; path = plugins/QuickTimePluginReplacement.mm; sourceTree = "<group>"; };
                072AE1E1183C0741000A5988 /* QuickTimePluginReplacement.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = QuickTimePluginReplacement.css; path = plugins/QuickTimePluginReplacement.css; sourceTree = "<group>"; };
                072AE1E2183C0741000A5988 /* QuickTimePluginReplacement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QuickTimePluginReplacement.h; path = plugins/QuickTimePluginReplacement.h; sourceTree = "<group>"; };
                072AE1E3183C0741000A5988 /* QuickTimePluginReplacement.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = QuickTimePluginReplacement.idl; path = plugins/QuickTimePluginReplacement.idl; sourceTree = "<group>"; };
                        isa = PBXGroup;
                        children = (
                                072AE1DF183C0741000A5988 /* PluginReplacement.h */,
-                               072AE1E0183C0741000A5988 /* QuickTimePluginReplacement.cpp */,
+                               072AE1E0183C0741000A5988 /* QuickTimePluginReplacement.mm */,
                                072AE1E1183C0741000A5988 /* QuickTimePluginReplacement.css */,
                                072AE1E2183C0741000A5988 /* QuickTimePluginReplacement.h */,
                                072AE1E3183C0741000A5988 /* QuickTimePluginReplacement.idl */,
                                E4D687770ED7AE3D006EA978 /* PurgeableBufferMac.cpp in Sources */,
                                550A0BC9085F6039007353D6 /* QualifiedName.cpp in Sources */,
                                442AF7AA102CDDEA008FD4D3 /* QuickLook.mm in Sources */,
-                               072AE1E6183C0741000A5988 /* QuickTimePluginReplacement.cpp in Sources */,
+                               072AE1E6183C0741000A5988 /* QuickTimePluginReplacement.mm in Sources */,
                                379E371613736A6600B9E919 /* QuotedPrintable.cpp in Sources */,
                                5A574F28131DB96D00471B88 /* QuotesData.cpp in Sources */,
                                F55B3DCB1251F12D003EF269 /* RadioInputType.cpp in Sources */,
index b4ea65d..d07a6b5 100644 (file)
@@ -640,6 +640,21 @@ void MediaPlayer::setVideoFullscreenGravity(MediaPlayer::VideoGravity gravity)
 {
     m_private->setVideoFullscreenGravity(gravity);
 }
+
+NSArray* MediaPlayer::timedMetadata() const
+{
+    return m_private->timedMetadata();
+}
+
+String MediaPlayer::accessLog() const
+{
+    return m_private->accessLog();
+}
+
+String MediaPlayer::errorLog() const
+{
+    return m_private->errorLog();
+}
 #endif
 
 MediaPlayer::NetworkState MediaPlayer::networkState()
index c634646..8dfe946 100644 (file)
@@ -62,6 +62,7 @@
 
 OBJC_CLASS AVAsset;
 OBJC_CLASS AVPlayer;
+OBJC_CLASS NSArray;
 OBJC_CLASS QTMovie;
 
 class AVCFPlayer;
@@ -308,7 +309,12 @@ public:
     void setVideoFullscreenFrame(FloatRect);
     enum VideoGravity { VideoGravityResize, VideoGravityResizeAspect, VideoGravityResizeAspectFill };
     void setVideoFullscreenGravity(VideoGravity);
+
+    NSArray *timedMetadata() const;
+    String accessLog() const;
+    String errorLog() const;
 #endif
+
     IntSize naturalSize();
     bool hasVideo() const;
     bool hasAudio() const;
index 6fb96ab..ab55740 100644 (file)
@@ -58,7 +58,12 @@ public:
     virtual void setVideoFullscreenLayer(PlatformLayer*) { }
     virtual void setVideoFullscreenFrame(FloatRect) { }
     virtual void setVideoFullscreenGravity(MediaPlayer::VideoGravity) { }
+
+    virtual NSArray *timedMetadata() const { return 0; }
+    virtual String accessLog() const { return emptyString(); }
+    virtual String errorLog() const { return emptyString(); }
 #endif
+
     virtual void play() = 0;
     virtual void pause() = 0;    
 
index 4789521..a72c160 100644 (file)
@@ -108,6 +108,7 @@ public:
     void presentationSizeDidChange(FloatSize);
     void durationDidChange(double);
     void rateDidChange(double);
+    void metadataDidArrive(RetainPtr<NSArray>);
 
 #if HAVE(AVFOUNDATION_VIDEO_OUTPUT)
     void outputMediaDataWillChange(AVPlayerItemVideoOutput*);
@@ -152,7 +153,12 @@ private:
     virtual void setVideoFullscreenLayer(PlatformLayer*);
     virtual void setVideoFullscreenFrame(FloatRect);
     virtual void setVideoFullscreenGravity(MediaPlayer::VideoGravity);
+
+    virtual NSArray *timedMetadata() const override;
+    virtual String accessLog() const;
+    virtual String errorLog() const;
 #endif
+
     virtual bool supportsAcceleratedRendering() const { return true; }
     virtual float mediaTimeForTimeValue(float) const;
     virtual double maximumDurationToCacheMediaTime() const { return 5; }
@@ -301,6 +307,7 @@ private:
     mutable RetainPtr<NSArray> m_cachedSeekableRanges;
     mutable RetainPtr<NSArray> m_cachedLoadedRanges;
     RetainPtr<NSArray> m_cachedTracks;
+    RetainPtr<NSArray> m_currentMetaData;
     FloatSize m_cachedPresentationSize;
     double m_cachedDuration;
     double m_cachedRate;
index eb6534d..dbb3c01 100644 (file)
@@ -98,6 +98,8 @@
 @end
 #endif
 
+typedef AVMetadataItem AVMetadataItemType;
+
 SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
 SOFT_LINK_FRAMEWORK_OPTIONAL(CoreMedia)
 SOFT_LINK_FRAMEWORK_OPTIONAL(CoreImage)
@@ -132,6 +134,8 @@ SOFT_LINK_CLASS(AVFoundation, AVPlayerItemVideoOutput)
 SOFT_LINK_CLASS(AVFoundation, AVPlayerLayer)
 SOFT_LINK_CLASS(AVFoundation, AVURLAsset)
 SOFT_LINK_CLASS(AVFoundation, AVAssetImageGenerator)
+SOFT_LINK_CLASS(AVFoundation, AVMetadataItem)
+
 SOFT_LINK_CLASS(CoreImage, CIContext)
 SOFT_LINK_CLASS(CoreImage, CIImage)
 
@@ -156,6 +160,7 @@ SOFT_LINK_CONSTANT(CoreMedia, kCMTimeZero, CMTime)
 #define AVPlayerLayer getAVPlayerLayerClass()
 #define AVURLAsset getAVURLAssetClass()
 #define AVAssetImageGenerator getAVAssetImageGeneratorClass()
+#define AVMetadataItem getAVMetadataItemClass()
 
 #define AVMediaCharacteristicVisual getAVMediaCharacteristicVisual()
 #define AVMediaCharacteristicAudible getAVMediaCharacteristicAudible()
@@ -847,6 +852,35 @@ void MediaPlayerPrivateAVFoundationObjC::setVideoFullscreenGravity(MediaPlayer::
 
     [m_videoLayer setVideoGravity:videoGravity];
 }
+
+NSArray *MediaPlayerPrivateAVFoundationObjC::timedMetadata() const
+{
+    if (m_currentMetaData)
+        return m_currentMetaData.get();
+    return nil;
+}
+
+String MediaPlayerPrivateAVFoundationObjC::accessLog() const
+{
+    if (!m_avPlayerItem)
+        return emptyString();
+    
+    AVPlayerItemAccessLog *log = [m_avPlayerItem.get() accessLog];
+    RetainPtr<NSString> logString = adoptNS([[NSString alloc] initWithData:[log extendedLogData] encoding:[log extendedLogDataStringEncoding]]);
+
+    return logString.get();
+}
+
+String MediaPlayerPrivateAVFoundationObjC::errorLog() const
+{
+    if (!m_avPlayerItem)
+        return emptyString();
+
+    AVPlayerItemErrorLog *log = [m_avPlayerItem.get() errorLog];
+    RetainPtr<NSString> logString = adoptNS([[NSString alloc] initWithData:[log extendedLogData] encoding:[log extendedLogDataStringEncoding]]);
+
+    return logString.get();
+}
 #endif
 
 void MediaPlayerPrivateAVFoundationObjC::platformSetVisible(bool isVisible)
@@ -2192,6 +2226,14 @@ void MediaPlayerPrivateAVFoundationObjC::loadedTimeRangesDidChange(RetainPtr<NSA
     updateStates();
 }
 
+void MediaPlayerPrivateAVFoundationObjC::metadataDidArrive(RetainPtr<NSArray> metadata)
+{
+    if (!metadata || [metadata isKindOfClass:[NSNull class]])
+        return;
+
+    m_currentMetaData = metadata;
+}
+
 void MediaPlayerPrivateAVFoundationObjC::tracksDidChange(RetainPtr<NSArray> tracks)
 {
     m_cachedTracks = tracks;
@@ -2270,6 +2312,7 @@ NSArray* itemKVOProperties()
                 @"playbackBufferEmpty",
                 @"duration",
                 @"hasEnabledAudio",
+                @"timedMetadata",
                 nil];
     }
     return keys;
@@ -2357,6 +2400,8 @@ NSArray* itemKVOProperties()
             function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::presentationSizeDidChange, m_callback, FloatSize([newValue sizeValue]));
         else if ([keyPath isEqualToString:@"duration"])
             function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::durationDidChange, m_callback, CMTimeGetSeconds([newValue CMTimeValue]));
+        else if ([keyPath isEqualToString:@"timedMetadata"] && newValue)
+            function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::metadataDidArrive, m_callback, RetainPtr<NSArray>(newValue));
     }
 
     if (context == MediaPlayerAVFoundationObservationContextPlayer && !willChange) {