Unreviewed, roll out http://trac.webkit.org/changeset/187972.
[WebKit-https.git] / Source / WebCore / platform / graphics / avfoundation / MediaPlayerPrivateAVFoundation.cpp
index 096ebb2..c5df5e9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2011-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
  *    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 COMPUTER, INC. ``AS IS'' AND ANY
+ * 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 COMPUTER, INC. OR
+ * 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
 
 #include "MediaPlayerPrivateAVFoundation.h"
 
-#include "ApplicationCacheHost.h"
 #include "DocumentLoader.h"
-#include "FrameView.h"
+#include "FloatConversion.h"
 #include "GraphicsContext.h"
-#include "GraphicsLayer.h"
-#include "KURL.h"
+#include "InbandTextTrackPrivateAVF.h"
+#include "InbandTextTrackPrivateClient.h"
+#include "URL.h"
 #include "Logging.h"
+#include "PlatformLayer.h"
+#include "PlatformTimeRanges.h"
+#include "Settings.h"
 #include "SoftLinking.h"
-#include "TimeRanges.h"
 #include <CoreMedia/CoreMedia.h>
-#include <wtf/UnusedParam.h>
-
-using namespace std;
+#include <runtime/DataView.h>
+#include <runtime/Uint16Array.h>
+#include <wtf/MainThread.h>
+#include <wtf/NeverDestroyed.h>
+#include <wtf/text/CString.h>
+#include <wtf/StringPrintStream.h>
 
 namespace WebCore {
 
-static const float invalidTime = -1.0f;
-
 MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation(MediaPlayer* player)
     : m_player(player)
+    , m_weakPtrFactory(this)
     , m_queuedNotifications()
     , m_queueMutex()
-    , m_mainThreadCallPending(false)
     , m_networkState(MediaPlayer::Empty)
     , m_readyState(MediaPlayer::HaveNothing)
     , m_preload(MediaPlayer::Auto)
-    , m_scaleFactor(1, 1)
-    , m_cachedMaxTimeLoaded(0)
-    , m_cachedMaxTimeSeekable(0)
-    , m_cachedDuration(invalidTime)
-    , m_reportedDuration(invalidTime)
-    , m_seekTo(invalidTime)
-    , m_requestedRate(1)
-    , m_delayCallbacks(false)
-    , m_havePreparedToPlay(false)
+    , m_cachedDuration(MediaTime::invalidTime())
+    , m_reportedDuration(MediaTime::invalidTime())
+    , m_maxTimeLoadedAtLastDidLoadingProgress(MediaTime::invalidTime())
+    , m_delayCallbacks(0)
+    , m_delayCharacteristicsChangedNotification(0)
+    , m_mainThreadCallPending(false)
     , m_assetIsPlayable(false)
     , m_visible(false)
-    , m_videoFrameHasDrawn(false)
     , m_loadingMetadata(false)
-    , m_delayingLoad(false)
     , m_isAllowedToRender(false)
     , m_cachedHasAudio(false)
     , m_cachedHasVideo(false)
@@ -76,6 +74,10 @@ MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation(MediaPlayer* play
     , m_ignoreLoadStateChanges(false)
     , m_haveReportedFirstVideoFrame(false)
     , m_playWhenFramesAvailable(false)
+    , m_inbandTrackConfigurationPending(false)
+    , m_characteristicsChanged(false)
+    , m_shouldMaintainAspectRatio(true)
+    , m_seeking(false)
 {
     LOG(Media, "MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation(%p)", this);
 }
@@ -83,15 +85,14 @@ MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation(MediaPlayer* play
 MediaPlayerPrivateAVFoundation::~MediaPlayerPrivateAVFoundation()
 {
     LOG(Media, "MediaPlayerPrivateAVFoundation::~MediaPlayerPrivateAVFoundation(%p)", this);
+    setIgnoreLoadStateChanges(true);
     cancelCallOnMainThread(mainThreadCallback, this);
 }
 
 MediaPlayerPrivateAVFoundation::MediaRenderingMode MediaPlayerPrivateAVFoundation::currentRenderingMode() const
 {
-#if USE(ACCELERATED_COMPOSITING)
     if (platformLayer())
         return MediaRenderingToLayer;
-#endif
 
     if (hasContextRenderer())
         return MediaRenderingToContext;
@@ -101,13 +102,11 @@ MediaPlayerPrivateAVFoundation::MediaRenderingMode MediaPlayerPrivateAVFoundatio
 
 MediaPlayerPrivateAVFoundation::MediaRenderingMode MediaPlayerPrivateAVFoundation::preferredRenderingMode() const
 {
-    if (!m_player->visible() || !m_player->frameView() || assetStatus() == MediaPlayerAVAssetStatusUnknown)
+    if (!m_player->visible() || assetStatus() == MediaPlayerAVAssetStatusUnknown)
         return MediaRenderingNone;
 
-#if USE(ACCELERATED_COMPOSITING)
-    if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player))
+    if (supportsAcceleratedRendering() && m_player->client().mediaPlayerRenderingCanBeAccelerated(m_player))
         return MediaRenderingToLayer;
-#endif
 
     return MediaRenderingToContext;
 }
@@ -119,6 +118,10 @@ void MediaPlayerPrivateAVFoundation::setUpVideoRendering()
 
     MediaRenderingMode currentMode = currentRenderingMode();
     MediaRenderingMode preferredMode = preferredRenderingMode();
+
+    if (preferredMode == MediaRenderingNone)
+        preferredMode = MediaRenderingToContext;
+
     if (currentMode == preferredMode && currentMode != MediaRenderingNone)
         return;
 
@@ -133,21 +136,17 @@ void MediaPlayerPrivateAVFoundation::setUpVideoRendering()
     case MediaRenderingToContext:
         createContextVideoRenderer();
         break;
-        
-#if USE(ACCELERATED_COMPOSITING)
+
     case MediaRenderingToLayer:
         createVideoLayer();
         break;
-#endif
     }
 
-#if USE(ACCELERATED_COMPOSITING)
     // If using a movie layer, inform the client so the compositing tree is updated.
     if (currentMode == MediaRenderingToLayer || preferredMode == MediaRenderingToLayer) {
         LOG(Media, "MediaPlayerPrivateAVFoundation::setUpVideoRendering(%p) - calling mediaPlayerRenderingModeChanged()", this);
-        m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
+        m_player->client().mediaPlayerRenderingModeChanged(m_player);
     }
-#endif
 }
 
 void MediaPlayerPrivateAVFoundation::tearDownVideoRendering()
@@ -156,10 +155,8 @@ void MediaPlayerPrivateAVFoundation::tearDownVideoRendering()
 
     destroyContextVideoRenderer();
 
-#if USE(ACCELERATED_COMPOSITING)
     if (platformLayer())
         destroyVideoLayer();
-#endif
 }
 
 bool MediaPlayerPrivateAVFoundation::hasSetUpVideoRendering() const
@@ -167,51 +164,34 @@ bool MediaPlayerPrivateAVFoundation::hasSetUpVideoRendering() const
     return hasLayerRenderer() || hasContextRenderer();
 }
 
-void MediaPlayerPrivateAVFoundation::resumeLoad()
-{
-    LOG(Media, "MediaPlayerPrivateAVFoundation::resumeLoad(%p)", this);
-
-    ASSERT(m_delayingLoad);
-    m_delayingLoad = false;
-
-    if (m_assetURL.length())
-        prepareToPlay();
-}
-
 void MediaPlayerPrivateAVFoundation::load(const String& url)
 {
     LOG(Media, "MediaPlayerPrivateAVFoundation::load(%p)", this);
 
-    if (m_networkState != MediaPlayer::Loading) {
-        m_networkState = MediaPlayer::Loading;
-        m_player->networkStateChanged();
-    }
-    if (m_readyState != MediaPlayer::HaveNothing) {
-        m_readyState = MediaPlayer::HaveNothing;
-        m_player->readyStateChanged();
-    }
+    setNetworkState(m_preload == MediaPlayer::None ? MediaPlayer::Idle : MediaPlayer::Loading);
+    setReadyState(MediaPlayer::HaveNothing);
 
-    m_videoFrameHasDrawn = false;
     m_assetURL = url;
 
     // Don't do any more work if the url is empty.
     if (!url.length())
         return;
 
-    if (m_preload == MediaPlayer::None) {
-        LOG(Media, "MediaPlayerPrivateAVFoundation::load(%p) - preload==none so returning", this);
-        m_delayingLoad = true;
-        return;
-    }
+    setPreload(m_preload);
+}
 
-    prepareToPlay();
+#if ENABLE(MEDIA_SOURCE)
+void MediaPlayerPrivateAVFoundation::load(const String&, MediaSourcePrivateClient*)
+{
+    setNetworkState(MediaPlayer::FormatError);
 }
+#endif
+
 
 void MediaPlayerPrivateAVFoundation::playabilityKnown()
 {
     LOG(Media, "MediaPlayerPrivateAVFoundation::playabilityKnown(%p)", this);
 
-    updateStates();
     if (m_assetIsPlayable)
         return;
 
@@ -232,22 +212,7 @@ void MediaPlayerPrivateAVFoundation::prepareToPlay()
 {
     LOG(Media, "MediaPlayerPrivateAVFoundation::prepareToPlay(%p)", this);
 
-    m_preload = MediaPlayer::Auto;
-    if (m_havePreparedToPlay)
-        return;
-    m_havePreparedToPlay = true;
-
-    m_delayingLoad = false;
-#if ENABLE(OFFLINE_WEB_APPLICATIONS)
-    Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : 0;
-    ApplicationCacheHost* cacheHost = frame ? frame->loader()->documentLoader()->applicationCacheHost() : 0;
-    ApplicationCacheResource* resource = 0;
-    if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(m_assetURL), resource) && resource)
-        createAVPlayerForCacheResource(resource);
-    else
-#endif    
-    createAVPlayerForURL(m_assetURL);
-    checkPlayability();
+    setPreload(MediaPlayer::Auto);
 }
 
 void MediaPlayerPrivateAVFoundation::play()
@@ -258,8 +223,10 @@ void MediaPlayerPrivateAVFoundation::play()
     // or the audio may start playing before we can render video.
     if (!m_cachedHasVideo || hasAvailableVideoFrame())
         platformPlay();
-    else
+    else {
+        LOG(Media, "MediaPlayerPrivateAVFoundation::play(%p) - waiting for first video frame", this);
         m_playWhenFramesAvailable = true;
+    }
 }
 
 void MediaPlayerPrivateAVFoundation::pause()
@@ -269,48 +236,53 @@ void MediaPlayerPrivateAVFoundation::pause()
     platformPause();
 }
 
-void MediaPlayerPrivateAVFoundation::paint(GraphicsContext*, const IntRect&)
+MediaTime MediaPlayerPrivateAVFoundation::durationMediaTime() const
 {
-    // This is the base class, only need to remember that a frame has been drawn.
-    m_videoFrameHasDrawn = true;
-}
+    if (m_cachedDuration.isValid())
+        return m_cachedDuration;
 
-float MediaPlayerPrivateAVFoundation::duration() const
-{
-    if (!metaDataAvailable())
-        return 0;
-
-    if (m_cachedDuration == invalidTime) {
-        m_cachedDuration = platformDuration();
-        LOG(Media, "MediaPlayerPrivateAVFMac::duration(%p) - caching %f", this, m_cachedDuration);
-    }
+    MediaTime duration = platformDuration();
+    if (!duration || duration.isInvalid())
+        return MediaTime::zeroTime();
 
+    m_cachedDuration = duration;
+    LOG(Media, "MediaPlayerPrivateAVFoundation::duration(%p) - caching %s", this, toString(m_cachedDuration).utf8().data());
     return m_cachedDuration;
 }
 
-void MediaPlayerPrivateAVFoundation::seek(float time)
+void MediaPlayerPrivateAVFoundation::seek(const MediaTime& time)
 {
+    seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime());
+}
+
+void MediaPlayerPrivateAVFoundation::seekWithTolerance(const MediaTime& mediaTime, const MediaTime& negativeTolerance, const MediaTime& positiveTolerance)
+{
+    MediaTime time = mediaTime;
+
+    if (m_seeking) {
+        LOG(Media, "MediaPlayerPrivateAVFoundation::seekWithTolerance(%p) - save pending seek", this);
+        m_pendingSeek = [this, time, negativeTolerance, positiveTolerance]() {
+            seekWithTolerance(time, negativeTolerance, positiveTolerance);
+        };
+        return;
+    }
+    m_seeking = true;
+
     if (!metaDataAvailable())
         return;
 
-    if (time > duration())
-        time = duration();
+    if (time > durationMediaTime())
+        time = durationMediaTime();
 
-    if (currentTime() == time)
+    if (currentMediaTime() == time)
         return;
 
-    LOG(Media, "MediaPlayerPrivateAVFoundation::seek(%p) - seeking to %f", this, time);
-    m_seekTo = time;
+    if (currentTextTrack())
+        currentTextTrack()->beginSeeking();
 
-    seekToTime(time);
-}
+    LOG(Media, "MediaPlayerPrivateAVFoundation::seek(%p) - seeking to %s", this, toString(time).utf8().data());
 
-void MediaPlayerPrivateAVFoundation::setRate(float rate)
-{
-    LOG(Media, "MediaPlayerPrivateAVFoundation::setRate(%p) - seting to %f", this, rate);
-    m_requestedRate = rate;
-
-    updateRate();
+    seekToTime(time, negativeTolerance, positiveTolerance);
 }
 
 bool MediaPlayerPrivateAVFoundation::paused() const
@@ -326,10 +298,10 @@ bool MediaPlayerPrivateAVFoundation::seeking() const
     if (!metaDataAvailable())
         return false;
 
-    return m_seekTo != invalidTime;
+    return m_seeking;
 }
 
-IntSize MediaPlayerPrivateAVFoundation::naturalSize() const
+FloatSize MediaPlayerPrivateAVFoundation::naturalSize() const
 {
     if (!metaDataAvailable())
         return IntSize();
@@ -344,38 +316,123 @@ IntSize MediaPlayerPrivateAVFoundation::naturalSize() const
     return m_cachedNaturalSize;
 }
 
-void MediaPlayerPrivateAVFoundation::setNaturalSize(IntSize size)
+void MediaPlayerPrivateAVFoundation::setNaturalSize(FloatSize size)
 {
-    IntSize oldSize = m_cachedNaturalSize;
+    LOG(Media, "MediaPlayerPrivateAVFoundation:setNaturalSize(%p) - size = %f x %f", this, size.width(), size.height());
+
+    FloatSize oldSize = m_cachedNaturalSize;
     m_cachedNaturalSize = size;
     if (oldSize != m_cachedNaturalSize)
         m_player->sizeChanged();
 }
 
-PassRefPtr<TimeRanges> MediaPlayerPrivateAVFoundation::buffered() const
+void MediaPlayerPrivateAVFoundation::setHasVideo(bool b)
+{
+    if (m_cachedHasVideo != b) {
+        m_cachedHasVideo = b;
+        characteristicsChanged();
+    }
+}
+
+void MediaPlayerPrivateAVFoundation::setHasAudio(bool b)
+{
+    if (m_cachedHasAudio != b) {
+        m_cachedHasAudio = b;
+        characteristicsChanged();
+    }
+}
+
+void MediaPlayerPrivateAVFoundation::setHasClosedCaptions(bool b)
+{
+    if (m_cachedHasCaptions != b) {
+        m_cachedHasCaptions = b;
+        characteristicsChanged();
+    }
+}
+
+void MediaPlayerPrivateAVFoundation::setNetworkState(MediaPlayer::NetworkState state)
+{
+    if (state == m_networkState)
+        return;
+
+    m_networkState = state;
+    m_player->networkStateChanged();
+}
+
+void MediaPlayerPrivateAVFoundation::setReadyState(MediaPlayer::ReadyState state)
+{
+    if (state == m_readyState)
+        return;
+
+    m_readyState = state;
+    m_player->readyStateChanged();
+}
+
+void MediaPlayerPrivateAVFoundation::characteristicsChanged()
+{
+    if (m_delayCharacteristicsChangedNotification) {
+        m_characteristicsChanged = true;
+        return;
+    }
+
+    m_characteristicsChanged = false;
+    m_player->characteristicChanged();
+}
+
+void MediaPlayerPrivateAVFoundation::setDelayCharacteristicsChangedNotification(bool delay)
+{
+    if (delay) {
+        m_delayCharacteristicsChangedNotification++;
+        return;
+    }
+    
+    ASSERT(m_delayCharacteristicsChangedNotification);
+    m_delayCharacteristicsChangedNotification--;
+    if (!m_delayCharacteristicsChangedNotification && m_characteristicsChanged)
+        characteristicsChanged();
+}
+
+std::unique_ptr<PlatformTimeRanges> MediaPlayerPrivateAVFoundation::buffered() const
 {
     if (!m_cachedLoadedTimeRanges)
         m_cachedLoadedTimeRanges = platformBufferedTimeRanges();
 
-    return m_cachedLoadedTimeRanges->copy();
+    return std::make_unique<PlatformTimeRanges>(*m_cachedLoadedTimeRanges);
 }
 
-float MediaPlayerPrivateAVFoundation::maxTimeSeekable() const
+MediaTime MediaPlayerPrivateAVFoundation::maxMediaTimeSeekable() const
 {
     if (!metaDataAvailable())
-        return 0;
+        return MediaTime::zeroTime();
 
     if (!m_cachedMaxTimeSeekable)
         m_cachedMaxTimeSeekable = platformMaxTimeSeekable();
 
-    LOG(Media, "MediaPlayerPrivateAVFoundation::maxTimeSeekable(%p) - returning %f", this, m_cachedMaxTimeSeekable);
+    LOG(Media, "MediaPlayerPrivateAVFoundation::maxTimeSeekable(%p) - returning %s", this, toString(m_cachedMaxTimeSeekable).utf8().data());
     return m_cachedMaxTimeSeekable;   
 }
 
-float MediaPlayerPrivateAVFoundation::maxTimeLoaded() const
+MediaTime MediaPlayerPrivateAVFoundation::minMediaTimeSeekable() const
 {
     if (!metaDataAvailable())
-        return 0;
+        return MediaTime::zeroTime();
+
+    if (!m_cachedMinTimeSeekable)
+        m_cachedMinTimeSeekable = platformMinTimeSeekable();
+
+    LOG(Media, "MediaPlayerPrivateAVFoundation::minTimeSeekable(%p) - returning %s", this, toString(m_cachedMinTimeSeekable).utf8().data());
+    return m_cachedMinTimeSeekable;
+}
+
+double MediaPlayerPrivateAVFoundation::requestedRate() const
+{
+    return m_player->requestedRate();
+}
+
+MediaTime MediaPlayerPrivateAVFoundation::maxTimeLoaded() const
+{
+    if (!metaDataAvailable())
+        return MediaTime::zeroTime();
 
     if (!m_cachedMaxTimeLoaded)
         m_cachedMaxTimeLoaded = platformMaxTimeLoaded();
@@ -383,19 +440,23 @@ float MediaPlayerPrivateAVFoundation::maxTimeLoaded() const
     return m_cachedMaxTimeLoaded;   
 }
 
-unsigned MediaPlayerPrivateAVFoundation::bytesLoaded() const
+bool MediaPlayerPrivateAVFoundation::didLoadingProgress() const
 {
-    float dur = duration();
-    if (!dur)
-        return 0;
-    unsigned loaded = totalBytes() * maxTimeLoaded() / dur;
-    LOG(Media, "MediaPlayerPrivateAVFoundation::bytesLoaded(%p) - returning %i", this, loaded);
-    return loaded;
+    if (!durationMediaTime())
+        return false;
+    MediaTime currentMaxTimeLoaded = maxTimeLoaded();
+    bool didLoadingProgress = currentMaxTimeLoaded != m_maxTimeLoadedAtLastDidLoadingProgress;
+    m_maxTimeLoadedAtLastDidLoadingProgress = currentMaxTimeLoaded;
+
+    return didLoadingProgress;
 }
 
 bool MediaPlayerPrivateAVFoundation::isReadyForVideoSetup() const
 {
-    return m_isAllowedToRender && m_readyState >= MediaPlayer::HaveMetadata && m_player->visible();
+    // AVFoundation will not return true for firstVideoFrameAvailable until
+    // an AVPlayerLayer has been added to the AVPlayerItem, so allow video setup
+    // here if a video track to trigger allocation of a AVPlayerLayer.
+    return (m_isAllowedToRender || m_cachedHasVideo) && m_readyState >= MediaPlayer::HaveMetadata && m_player->visible();
 }
 
 void MediaPlayerPrivateAVFoundation::prepareForRendering()
@@ -407,7 +468,7 @@ void MediaPlayerPrivateAVFoundation::prepareForRendering()
     setUpVideoRendering();
 
     if (currentRenderingMode() == MediaRenderingToLayer || preferredRenderingMode() == MediaRenderingToLayer)
-        m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
+        m_player->client().mediaPlayerRenderingModeChanged(m_player);
 }
 
 bool MediaPlayerPrivateAVFoundation::supportsFullscreen() const
@@ -416,78 +477,81 @@ bool MediaPlayerPrivateAVFoundation::supportsFullscreen() const
     return true;
 #else
     // FIXME: WebVideoFullscreenController assumes a QTKit/QuickTime media engine
+#if PLATFORM(IOS)
+    if (Settings::avKitEnabled())
+        return true;
+#endif
     return false;
 #endif
 }
 
 void MediaPlayerPrivateAVFoundation::updateStates()
 {
-    MediaPlayer::NetworkState oldNetworkState = m_networkState;
-    MediaPlayer::ReadyState oldReadyState = m_readyState;
+    if (m_ignoreLoadStateChanges)
+        return;
 
-    LOG(Media, "MediaPlayerPrivateAVFoundation::updateStates(%p) - entering with networkState = %i, readyState = %i", 
-        this, static_cast<int>(m_networkState), static_cast<int>(m_readyState));
+    MediaPlayer::NetworkState newNetworkState = m_networkState;
+    MediaPlayer::ReadyState newReadyState = m_readyState;
 
     if (m_loadingMetadata)
-        m_networkState = MediaPlayer::Loading;
+        newNetworkState = MediaPlayer::Loading;
     else {
         // -loadValuesAsynchronouslyForKeys:completionHandler: has invoked its handler; test status of keys and determine state.
-        AVAssetStatus avAssetStatus = assetStatus();
+        AssetStatus assetStatus = this->assetStatus();
         ItemStatus itemStatus = playerItemStatus();
         
-        m_assetIsPlayable = (avAssetStatus == MediaPlayerAVAssetStatusPlayable);
-        if (m_readyState < MediaPlayer::HaveMetadata && avAssetStatus > MediaPlayerAVAssetStatusLoading) {
+        m_assetIsPlayable = (assetStatus == MediaPlayerAVAssetStatusPlayable);
+        if (m_readyState < MediaPlayer::HaveMetadata && assetStatus > MediaPlayerAVAssetStatusLoading) {
             if (m_assetIsPlayable) {
-                if (itemStatus == MediaPlayerAVPlayerItemStatusUnknown) {
-                    if (avAssetStatus == MediaPlayerAVAssetStatusFailed || m_preload > MediaPlayer::MetaData) {
-                        // We may have a playable asset that doesn't support inspection prior to playback; go ahead 
-                        // and create the AVPlayerItem now. When the AVPlayerItem becomes ready to play, we will 
-                        // have access to its metadata. Or we may have been asked to become ready to play immediately.
-                        m_networkState = MediaPlayer::Loading;
+                if (assetStatus >= MediaPlayerAVAssetStatusLoaded)
+                    newReadyState = MediaPlayer::HaveMetadata;
+                if (itemStatus <= MediaPlayerAVPlayerItemStatusUnknown) {
+                    if (assetStatus == MediaPlayerAVAssetStatusFailed || m_preload > MediaPlayer::MetaData || isLiveStream()) {
+                        // The asset is playable but doesn't support inspection prior to playback (eg. streaming files),
+                        // or we are supposed to prepare for playback immediately, so create the player item now.
+                        newNetworkState = MediaPlayer::Loading;
                         prepareToPlay();
                     } else
-                        m_networkState = MediaPlayer::Idle;
+                        newNetworkState = MediaPlayer::Idle;
                 }
-                if (avAssetStatus == MediaPlayerAVAssetStatusLoaded)
-                    m_readyState = MediaPlayer::HaveMetadata;
             } else {
                 // FIX ME: fetch the error associated with the @"playable" key to distinguish between format 
                 // and network errors.
-                m_networkState = MediaPlayer::FormatError;
+                newNetworkState = MediaPlayer::FormatError;
             }
         }
         
-        if (avAssetStatus >= MediaPlayerAVAssetStatusLoaded && itemStatus > MediaPlayerAVPlayerItemStatusUnknown) {
-            if (seeking())
-                m_readyState = m_readyState >= MediaPlayer::HaveMetadata ? MediaPlayer::HaveMetadata : MediaPlayer::HaveNothing;
-            else {
-                float maxLoaded = maxTimeLoaded();
-                switch (itemStatus) {
-                case MediaPlayerAVPlayerItemStatusUnknown:
-                    break;
-                case MediaPlayerAVPlayerItemStatusFailed:
-                    m_networkState = MediaPlayer::DecodeError;
-                    break;
-                case MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp:
-                    m_readyState = MediaPlayer::HaveEnoughData;
-                    break;
-                case MediaPlayerAVPlayerItemStatusReadyToPlay:
-                case MediaPlayerAVPlayerItemStatusPlaybackBufferFull:
-                    // If the readyState is already HaveEnoughData, don't go lower because of this state change.
-                    if (m_readyState == MediaPlayer::HaveEnoughData)
-                        break;
-
-                case MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty:
-                    if (maxLoaded > currentTime())
-                        m_readyState = MediaPlayer::HaveFutureData;
-                    else
-                        m_readyState = MediaPlayer::HaveCurrentData;
-                    break;
-                }
-
-                if (itemStatus >= MediaPlayerAVPlayerItemStatusReadyToPlay)
-                    m_networkState = (maxLoaded == duration()) ? MediaPlayer::Loaded : MediaPlayer::Loading;
+        if (assetStatus >= MediaPlayerAVAssetStatusLoaded && itemStatus > MediaPlayerAVPlayerItemStatusUnknown) {
+            switch (itemStatus) {
+            case MediaPlayerAVPlayerItemStatusDoesNotExist:
+            case MediaPlayerAVPlayerItemStatusUnknown:
+            case MediaPlayerAVPlayerItemStatusFailed:
+                break;
+
+            case MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp:
+            case MediaPlayerAVPlayerItemStatusPlaybackBufferFull:
+                // If the status becomes PlaybackBufferFull, loading stops and the status will not
+                // progress to LikelyToKeepUp. Set the readyState to  HAVE_ENOUGH_DATA, on the
+                // presumption that if the playback buffer is full, playback will probably not stall.
+                newReadyState = MediaPlayer::HaveEnoughData;
+                break;
+
+            case MediaPlayerAVPlayerItemStatusReadyToPlay:
+                if (m_readyState != MediaPlayer::HaveEnoughData && maxTimeLoaded() > currentMediaTime())
+                    newReadyState = MediaPlayer::HaveFutureData;
+                break;
+
+            case MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty:
+                newReadyState = MediaPlayer::HaveCurrentData;
+                break;
             }
+
+            if (itemStatus == MediaPlayerAVPlayerItemStatusPlaybackBufferFull)
+                newNetworkState = MediaPlayer::Idle;
+            else if (itemStatus == MediaPlayerAVPlayerItemStatusFailed)
+                newNetworkState = MediaPlayer::DecodeError;
+            else if (itemStatus != MediaPlayerAVPlayerItemStatusPlaybackBufferFull && itemStatus >= MediaPlayerAVPlayerItemStatusReadyToPlay)
+                newNetworkState = (maxTimeLoaded() == durationMediaTime()) ? MediaPlayer::Loaded : MediaPlayer::Loading;
         }
     }
 
@@ -495,23 +559,26 @@ void MediaPlayerPrivateAVFoundation::updateStates()
         setUpVideoRendering();
 
     if (!m_haveReportedFirstVideoFrame && m_cachedHasVideo && hasAvailableVideoFrame()) {
+        if (m_readyState < MediaPlayer::HaveCurrentData)
+            newReadyState = MediaPlayer::HaveCurrentData;
         m_haveReportedFirstVideoFrame = true;
         m_player->firstVideoFrameAvailable();
     }
 
-    if (m_networkState != oldNetworkState)
-        m_player->networkStateChanged();
+#if !LOG_DISABLED
+    if (m_networkState != newNetworkState || m_readyState != newReadyState) {
+        LOG(Media, "MediaPlayerPrivateAVFoundation::updateStates(%p) - entered with networkState = %i, readyState = %i,  exiting with networkState = %i, readyState = %i",
+            this, static_cast<int>(m_networkState), static_cast<int>(m_readyState), static_cast<int>(newNetworkState), static_cast<int>(newReadyState));
+    }
+#endif
 
-    if (m_readyState != oldReadyState)
-        m_player->readyStateChanged();
+    setNetworkState(newNetworkState);
+    setReadyState(newReadyState);
 
     if (m_playWhenFramesAvailable && hasAvailableVideoFrame()) {
         m_playWhenFramesAvailable = false;
         platformPlay();
     }
-
-    LOG(Media, "MediaPlayerPrivateAVFoundation::updateStates(%p) - exiting with networkState = %i, readyState = %i", 
-        this, static_cast<int>(m_networkState), static_cast<int>(m_readyState));
 }
 
 void MediaPlayerPrivateAVFoundation::setSize(const IntSize&) 
@@ -530,105 +597,110 @@ void MediaPlayerPrivateAVFoundation::setVisible(bool visible)
     platformSetVisible(visible);
 }
 
-bool MediaPlayerPrivateAVFoundation::hasAvailableVideoFrame() const
-{
-    if (currentRenderingMode() == MediaRenderingToLayer)
-        return videoLayerIsReadyToDisplay();
-
-    // When using the software renderer we hope someone will signal that a frame is available so we might as well
-    // wait until we know that a frame has been drawn.
-    return m_videoFrameHasDrawn;
-}
-
 void MediaPlayerPrivateAVFoundation::acceleratedRenderingStateChanged()
 {
     // Set up or change the rendering path if necessary.
     setUpVideoRendering();
 }
 
-void MediaPlayerPrivateAVFoundation::metadataLoaded()
+void MediaPlayerPrivateAVFoundation::setShouldMaintainAspectRatio(bool maintainAspectRatio)
 {
-    m_loadingMetadata = false;
-    updateStates();
+    if (maintainAspectRatio == m_shouldMaintainAspectRatio)
+        return;
+
+    m_shouldMaintainAspectRatio = maintainAspectRatio;
+    updateVideoLayerGravity();
 }
 
-void MediaPlayerPrivateAVFoundation::loadStateChanged()
+void MediaPlayerPrivateAVFoundation::metadataLoaded()
 {
-    if (m_ignoreLoadStateChanges)
-        return;
-    updateStates();
+    m_loadingMetadata = false;
+    tracksChanged();
 }
 
 void MediaPlayerPrivateAVFoundation::rateChanged()
 {
-    updateStates();
+#if ENABLE(WIRELESS_PLAYBACK_TARGET) && PLATFORM(IOS)
+    if (isCurrentPlaybackTargetWireless())
+        m_player->handlePlaybackCommand(rate() ? PlatformMediaSession::PlayCommand : PlatformMediaSession::PauseCommand);
+#endif
+
     m_player->rateChanged();
 }
 
 void MediaPlayerPrivateAVFoundation::loadedTimeRangesChanged()
 {
-    m_cachedLoadedTimeRanges = 0;
-    m_cachedMaxTimeLoaded = 0;
-    updateStates();
-
-    // For some media files, reported duration is estimated and updated as media is loaded
-    // so report duration changed when the estimate is upated.
-    float dur = duration();
-    if (dur != m_reportedDuration) {
-        if (m_reportedDuration != invalidTime)
-            m_player->durationChanged();
-        m_reportedDuration = dur;
-    }
+    m_cachedLoadedTimeRanges = nullptr;
+    m_cachedMaxTimeLoaded = MediaTime::zeroTime();
+    invalidateCachedDuration();
 }
 
 void MediaPlayerPrivateAVFoundation::seekableTimeRangesChanged()
 {
-    m_cachedMaxTimeSeekable = 0;
+    m_cachedMaxTimeSeekable = MediaTime::zeroTime();
+    m_cachedMinTimeSeekable = MediaTime::zeroTime();
 }
 
-void MediaPlayerPrivateAVFoundation::timeChanged(double time)
+void MediaPlayerPrivateAVFoundation::timeChanged(const MediaTime& time)
 {
-    LOG(Media, "MediaPlayerPrivateAVFoundation::timeChanged(%p) - time = %f", this, time);
-
-    if (m_seekTo == invalidTime)
-        return;
-
-    // AVFoundation may call our observer more than once during a seek, and we can't currently tell
-    // if we will be able to seek to an exact time, so assume that we are done seeking if we are
-    // "close enough" to the seek time.
-    const double smallSeekDelta = 1.0 / 100;
-
-    float currentRate = rate();
-    if ((currentRate > 0 && time >= m_seekTo) || (currentRate < 0 && time <= m_seekTo) || (abs(m_seekTo - time) <= smallSeekDelta)) {
-        m_seekTo = invalidTime;
-        updateStates();
-        m_player->timeChanged();
-    }
+    LOG(Media, "MediaPlayerPrivateAVFoundation::timeChanged(%p) - time = %s", this, toString(time).utf8().data());
+    UNUSED_PARAM(time);
 }
 
 void MediaPlayerPrivateAVFoundation::seekCompleted(bool finished)
 {
     LOG(Media, "MediaPlayerPrivateAVFoundation::seekCompleted(%p) - finished = %d", this, finished);
-    
-    if (finished)
-        m_seekTo = invalidTime;
+    UNUSED_PARAM(finished);
+
+    m_seeking = false;
+
+    std::function<void()> pendingSeek;
+    std::swap(pendingSeek, m_pendingSeek);
+
+    if (pendingSeek) {
+        LOG(Media, "MediaPlayerPrivateAVFoundation::seekCompleted(%p) - issuing pending seek", this);
+        pendingSeek();
+        return;
+    }
+
+    if (currentTextTrack())
+        currentTextTrack()->endSeeking();
+
+    updateStates();
+    m_player->timeChanged();
 }
 
 void MediaPlayerPrivateAVFoundation::didEnd()
 {
     // Hang onto the current time and use it as duration from now on since we are definitely at
     // the end of the movie. Do this because the initial duration is sometimes an estimate.
-    float now = currentTime();
-    if (now > 0)
+    MediaTime now = currentMediaTime();
+    if (now > MediaTime::zeroTime())
         m_cachedDuration = now;
 
     updateStates();
     m_player->timeChanged();
 }
 
+void MediaPlayerPrivateAVFoundation::invalidateCachedDuration()
+{
+    LOG(Media, "MediaPlayerPrivateAVFoundation::invalidateCachedDuration(%p)", this);
+    
+    m_cachedDuration = MediaTime::invalidTime();
+
+    // For some media files, reported duration is estimated and updated as media is loaded
+    // so report duration changed when the estimate is upated.
+    MediaTime duration = this->durationMediaTime();
+    if (duration != m_reportedDuration) {
+        if (m_reportedDuration.isValid())
+            m_player->durationChanged();
+        m_reportedDuration = duration;
+    }
+    
+}
+
 void MediaPlayerPrivateAVFoundation::repaint()
 {
-    m_videoFrameHasDrawn = true;
     m_player->repaint();
 }
 
@@ -646,11 +718,27 @@ MediaPlayer::MovieLoadType MediaPlayerPrivateAVFoundation::movieLoadType() const
 void MediaPlayerPrivateAVFoundation::setPreload(MediaPlayer::Preload preload)
 {
     m_preload = preload;
-    if (m_delayingLoad && m_preload != MediaPlayer::None)
-        resumeLoad();
+    if (!m_assetURL.length())
+        return;
+
+    setDelayCallbacks(true);
+
+    if (m_preload >= MediaPlayer::MetaData && assetStatus() == MediaPlayerAVAssetStatusDoesNotExist) {
+        createAVAssetForURL(m_assetURL);
+        checkPlayability();
+    }
+
+    // Don't force creation of the player and player item unless we already know that the asset is playable. If we aren't
+    // there yet, or if we already know it is not playable, creating them now won't help.
+    if (m_preload == MediaPlayer::Auto && m_assetIsPlayable) {
+        createAVPlayerItem();
+        createAVPlayer();
+    }
+
+    setDelayCallbacks(false);
 }
 
-void MediaPlayerPrivateAVFoundation::setDelayCallbacks(bool delay)
+void MediaPlayerPrivateAVFoundation::setDelayCallbacks(bool delay) const
 {
     MutexLocker lock(m_queueMutex);
     if (delay)
@@ -675,7 +763,7 @@ void MediaPlayerPrivateAVFoundation::clearMainThreadPendingFlag()
     m_mainThreadCallPending = false;
 }
 
-void MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(Notification::Type type, double time)
+void MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(Notification::Type type, const MediaTime& time)
 {
     scheduleMainThreadNotification(Notification(type, time));
 }
@@ -685,16 +773,36 @@ void MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(Notification
     scheduleMainThreadNotification(Notification(type, finished));
 }
 
+#if !LOG_DISABLED
+static const char* notificationName(MediaPlayerPrivateAVFoundation::Notification& notification)
+{
+#define DEFINE_TYPE_STRING_CASE(type) case MediaPlayerPrivateAVFoundation::Notification::type: return #type;
+    switch (notification.type()) {
+    FOR_EACH_MEDIAPLAYERPRIVATEAVFOUNDATION_NOTIFICATION_TYPE(DEFINE_TYPE_STRING_CASE)
+    case MediaPlayerPrivateAVFoundation::Notification::FunctionType: return "FunctionType";
+    default: ASSERT_NOT_REACHED(); return "";
+    }
+#undef DEFINE_TYPE_STRING_CASE
+}
+#endif // !LOG_DISABLED
+    
+
 void MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(Notification notification)
 {
-    LOG(Media, "MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(%p) - notification %d", this, static_cast<int>(notification.type()));
+    if (notification.type() != Notification::FunctionType)
+        LOG(Media, "MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(%p) - notification %s", this, notificationName(notification));
+
     m_queueMutex.lock();
 
-    // It is important to always process the properties in the order that we are notified, 
+    // It is important to always process the properties in the order that we are notified,
     // so always go through the queue because notifications happen on different threads.
     m_queuedNotifications.append(notification);
 
+#if OS(WINDOWS)
+    bool delayDispatch = true;
+#else
     bool delayDispatch = m_delayCallbacks || !isMainThread();
+#endif
     if (delayDispatch && !m_mainThreadCallPending) {
         m_mainThreadCallPending = true;
         callOnMainThread(mainThreadCallback, this);
@@ -703,7 +811,8 @@ void MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(Notification
     m_queueMutex.unlock();
 
     if (delayDispatch) {
-        LOG(Media, "MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(%p) - early return", this);
+        if (notification.type() != Notification::FunctionType)
+            LOG(Media, "MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(%p) - early return", this);
         return;
     }
 
@@ -729,12 +838,13 @@ void MediaPlayerPrivateAVFoundation::dispatchNotification()
         
         if (!m_queuedNotifications.isEmpty() && !m_mainThreadCallPending)
             callOnMainThread(mainThreadCallback, this);
-        
+
         if (!notification.isValid())
             return;
     }
 
-    LOG(Media, "MediaPlayerPrivateAVFoundation::dispatchNotification(%p) - dispatching %d", this, static_cast<int>(notification.type()));
+    if (notification.type() != Notification::FunctionType)
+        LOG(Media, "MediaPlayerPrivateAVFoundation::dispatchNotification(%p) - dispatching %s", this, notificationName(notification));
 
     switch (notification.type()) {
     case Notification::ItemDidPlayToEndTime:
@@ -742,31 +852,34 @@ void MediaPlayerPrivateAVFoundation::dispatchNotification()
         break;
     case Notification::ItemTracksChanged:
         tracksChanged();
+        updateStates();
         break;
     case Notification::ItemStatusChanged:
-        loadStateChanged();
+        updateStates();
         break;
     case Notification::ItemSeekableTimeRangesChanged:
         seekableTimeRangesChanged();
-        loadStateChanged();
+        updateStates();
         break;
     case Notification::ItemLoadedTimeRangesChanged:
         loadedTimeRangesChanged();
-        loadStateChanged();
+        updateStates();
         break;
     case Notification::ItemPresentationSizeChanged:
         sizeChanged();
+        updateStates();
         break;
     case Notification::ItemIsPlaybackLikelyToKeepUpChanged:
-        loadStateChanged();
+        updateStates();
         break;
     case Notification::ItemIsPlaybackBufferEmptyChanged:
-        loadStateChanged();
+        updateStates();
         break;
     case Notification::ItemIsPlaybackBufferFullChanged:
-        loadStateChanged();
+        updateStates();
         break;
     case Notification::PlayerRateChanged:
+        updateStates();
         rateChanged();
         break;
     case Notification::PlayerTimeChanged:
@@ -777,16 +890,265 @@ void MediaPlayerPrivateAVFoundation::dispatchNotification()
         break;
     case Notification::AssetMetadataLoaded:
         metadataLoaded();
+        updateStates();
         break;
     case Notification::AssetPlayabilityKnown:
+        updateStates();
         playabilityKnown();
         break;
+    case Notification::DurationChanged:
+        invalidateCachedDuration();
+        break;
+    case Notification::ContentsNeedsDisplay:
+        contentsNeedsDisplay();
+        break;
+    case Notification::InbandTracksNeedConfiguration:
+        m_inbandTrackConfigurationPending = false;
+        configureInbandTracks();
+        break;
+    case Notification::FunctionType:
+        notification.function()();
+        break;
+    case Notification::TargetIsWirelessChanged:
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+        playbackTargetIsWirelessChanged();
+#endif
+        break;
+
     case Notification::None:
         ASSERT_NOT_REACHED();
         break;
     }
 }
 
+void MediaPlayerPrivateAVFoundation::configureInbandTracks()
+{
+    RefPtr<InbandTextTrackPrivateAVF> trackToEnable;
+    
+#if ENABLE(AVF_CAPTIONS)
+    synchronizeTextTrackState();
+#endif
+
+    // AVFoundation can only emit cues for one track at a time, so enable the first track that is showing, or the first that
+    // is hidden if none are showing. Otherwise disable all tracks.
+    for (unsigned i = 0; i < m_textTracks.size(); ++i) {
+        RefPtr<InbandTextTrackPrivateAVF> track = m_textTracks[i];
+        if (track->mode() == InbandTextTrackPrivate::Showing) {
+            trackToEnable = track;
+            break;
+        }
+        if (track->mode() == InbandTextTrackPrivate::Hidden)
+            trackToEnable = track;
+    }
+
+    setCurrentTextTrack(trackToEnable.get());
+}
+
+void MediaPlayerPrivateAVFoundation::trackModeChanged()
+{
+    if (m_inbandTrackConfigurationPending)
+        return;
+    m_inbandTrackConfigurationPending = true;
+    scheduleMainThreadNotification(Notification::InbandTracksNeedConfiguration);
+}
+
+void MediaPlayerPrivateAVFoundation::clearTextTracks()
+{
+    for (unsigned i = 0; i < m_textTracks.size(); ++i) {
+        RefPtr<InbandTextTrackPrivateAVF> track = m_textTracks[i];
+        player()->removeTextTrack(track);
+        track->disconnect();
+    }
+    m_textTracks.clear();
+}
+
+void MediaPlayerPrivateAVFoundation::processNewAndRemovedTextTracks(const Vector<RefPtr<InbandTextTrackPrivateAVF>>& removedTextTracks)
+{
+    if (removedTextTracks.size()) {
+        for (unsigned i = 0; i < m_textTracks.size(); ++i) {
+            if (!removedTextTracks.contains(m_textTracks[i]))
+                continue;
+            
+            player()->removeTextTrack(removedTextTracks[i].get());
+            m_textTracks.remove(i);
+        }
+    }
+
+    unsigned inBandCount = 0;
+    for (unsigned i = 0; i < m_textTracks.size(); ++i) {
+        RefPtr<InbandTextTrackPrivateAVF> track = m_textTracks[i];
+
+#if ENABLE(AVF_CAPTIONS)
+        if (track->textTrackCategory() == InbandTextTrackPrivateAVF::OutOfBand)
+            continue;
+#endif
+
+        track->setTextTrackIndex(inBandCount);
+        ++inBandCount;
+        if (track->hasBeenReported())
+            continue;
+        
+        track->setHasBeenReported(true);
+        player()->addTextTrack(track.get());
+    }
+    LOG(Media, "MediaPlayerPrivateAVFoundation::processNewAndRemovedTextTracks(%p) - found %lu text tracks", this, m_textTracks.size());
+}
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+void MediaPlayerPrivateAVFoundation::playbackTargetIsWirelessChanged()
+{
+    if (m_player)
+        m_player->currentPlaybackTargetIsWirelessChanged();
+}
+#endif
+
+#if ENABLE(ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA_V2)
+bool MediaPlayerPrivateAVFoundation::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<JSC::DataView> initDataView = JSC::DataView::create(initDataBuffer, 0, initDataBuffer->byteLength());
+    uint32_t offset = 0;
+    bool status = true;
+
+    uint32_t keyURILength = initDataView->get<uint32_t>(offset, true, &status);
+    offset += 4;
+    if (!status || offset + keyURILength > initData->length())
+        return false;
+
+    RefPtr<Uint16Array> keyURIArray = Uint16Array::create(initDataBuffer, offset, keyURILength);
+    if (!keyURIArray)
+        return false;
+
+    keyURI = String(reinterpret_cast<UChar*>(keyURIArray->data()), keyURILength / sizeof(unsigned short));
+    offset += keyURILength;
+
+    uint32_t keyIDLength = initDataView->get<uint32_t>(offset, true, &status);
+    offset += 4;
+    if (!status || offset + keyIDLength > initData->length())
+        return false;
+
+    RefPtr<Uint16Array> keyIDArray = Uint16Array::create(initDataBuffer, offset, keyIDLength);
+    if (!keyIDArray)
+        return false;
+
+    keyID = String(reinterpret_cast<UChar*>(keyIDArray->data()), keyIDLength / sizeof(unsigned short));
+    offset += keyIDLength;
+
+    uint32_t certificateLength = initDataView->get<uint32_t>(offset, true, &status);
+    offset += 4;
+    if (!status || offset + certificateLength > initData->length())
+        return false;
+
+    certificate = Uint8Array::create(initDataBuffer, offset, certificateLength);
+    if (!certificate)
+        return false;
+
+    return true;
+}
+#endif
+
+URL MediaPlayerPrivateAVFoundation::resolvedURL() const
+{
+    if (!m_assetURL.length())
+        return URL();
+
+    return URL(ParsedURLString, m_assetURL);
+}
+
+bool MediaPlayerPrivateAVFoundation::canSaveMediaData() const
+{
+    URL url = resolvedURL();
+
+    if (url.isLocalFile())
+        return true;
+
+    if (!url.protocolIsInHTTPFamily())
+        return false;
+
+    if (isLiveStream())
+        return false;
+
+    return true;
+}
+
+bool MediaPlayerPrivateAVFoundation::isUnsupportedMIMEType(const String& type)
+{
+    String lowerCaseType = type.convertToASCIILowercase();
+
+    // AVFoundation will return non-video MIME types which it claims to support, but which we
+    // do not support in the <video> element. Reject all non video/, audio/, and application/ types.
+    if (!lowerCaseType.startsWith("video/") && !lowerCaseType.startsWith("audio/") && !lowerCaseType.startsWith("application/"))
+        return true;
+
+    // Reject types we know AVFoundation does not support that sites commonly ask about.
+    if (lowerCaseType == "video/webm" || lowerCaseType == "audio/webm" || lowerCaseType == "video/x-webm")
+        return true;
+
+    if (lowerCaseType == "video/x-flv")
+        return true;
+
+    if (lowerCaseType == "audio/ogg" || lowerCaseType == "video/ogg" || lowerCaseType == "application/ogg")
+        return true;
+
+    if (lowerCaseType == "video/h264")
+        return true;
+
+    return false;
+}
+
+const HashSet<String>& MediaPlayerPrivateAVFoundation::staticMIMETypeList()
+{
+    static NeverDestroyed<HashSet<String>> cache = []() {
+        HashSet<String> types;
+
+        static const char* typeNames[] = {
+            "application/vnd.apple.mpegurl",
+            "application/x-mpegurl",
+            "audio/3gpp",
+            "audio/aac",
+            "audio/aacp",
+            "audio/aiff",
+            "audio/basic",
+            "audio/mp3",
+            "audio/mp4",
+            "audio/mpeg",
+            "audio/mpeg3",
+            "audio/mpegurl",
+            "audio/mpg",
+            "audio/wav",
+            "audio/wave",
+            "audio/x-aac",
+            "audio/x-aiff",
+            "audio/x-m4a",
+            "audio/x-mpegurl",
+            "audio/x-wav",
+            "video/3gpp",
+            "video/3gpp2",
+            "video/mp4",
+            "video/mpeg",
+            "video/mpeg2",
+            "video/mpg",
+            "video/quicktime",
+            "video/x-m4v",
+            "video/x-mpeg",
+            "video/x-mpg",
+        };
+        for (size_t i = 0; i < WTF_ARRAY_LENGTH(typeNames); ++i)
+            types.add(typeNames[i]);
+
+        return types;
+    }();
+
+    return cache;
+}
+
 } // namespace WebCore
 
 #endif