1/*
2 * Copyright (C) 2011 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(VIDEO) && USE(AVFOUNDATION)
29
30#include "MediaPlayerPrivateAVFoundation.h"
31
32#include "ApplicationCacheHost.h"
33#include "DocumentLoader.h"
34#include "FrameView.h"
35#include "GraphicsContext.h"
36#include "GraphicsLayer.h"
37#include "KURL.h"
38#include "Logging.h"
39#include "SoftLinking.h"
40#include "TimeRanges.h"
41#include <CoreMedia/CoreMedia.h>
42#include <wtf/UnusedParam.h>
43
44using namespace std;
45
46namespace WebCore {
47
48static const float invalidTime = -1.0f;
49
50MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation(MediaPlayer* player)
51    : m_player(player)
52    , m_queuedNotifications()
53    , m_queueMutex()
54    , m_mainThreadCallPending(false)
55    , m_networkState(MediaPlayer::Empty)
56    , m_readyState(MediaPlayer::HaveNothing)
57    , m_preload(MediaPlayer::Auto)
58    , m_scaleFactor(1, 1)
59    , m_cachedMaxTimeLoaded(0)
60    , m_cachedMaxTimeSeekable(0)
61    , m_cachedDuration(invalidTime)
62    , m_reportedDuration(invalidTime)
63    , m_seekTo(invalidTime)
64    , m_requestedRate(1)
65    , m_delayCallbacks(false)
66    , m_havePreparedToPlay(false)
67    , m_assetIsPlayable(false)
68    , m_visible(false)
69    , m_videoFrameHasDrawn(false)
70    , m_loadingMetadata(false)
71    , m_delayingLoad(false)
72    , m_isAllowedToRender(false)
73    , m_cachedHasAudio(false)
74    , m_cachedHasVideo(false)
75    , m_cachedHasCaptions(false)
76    , m_ignoreLoadStateChanges(false)
77    , m_haveReportedFirstVideoFrame(false)
78    , m_playWhenFramesAvailable(false)
79{
80    LOG(Media, "MediaPlayerPrivateAVFoundation::MediaPlayerPrivateAVFoundation(%p)", this);
81}
82
83MediaPlayerPrivateAVFoundation::~MediaPlayerPrivateAVFoundation()
84{
85    LOG(Media, "MediaPlayerPrivateAVFoundation::~MediaPlayerPrivateAVFoundation(%p)", this);
86    cancelCallOnMainThread(mainThreadCallback, this);
87}
88
89MediaPlayerPrivateAVFoundation::MediaRenderingMode MediaPlayerPrivateAVFoundation::currentRenderingMode() const
90{
91#if USE(ACCELERATED_COMPOSITING)
92    if (platformLayer())
93        return MediaRenderingToLayer;
94#endif
95
96    if (hasContextRenderer())
97        return MediaRenderingToContext;
98
99    return MediaRenderingNone;
100}
101
102MediaPlayerPrivateAVFoundation::MediaRenderingMode MediaPlayerPrivateAVFoundation::preferredRenderingMode() const
103{
104    if (!m_player->visible() || !m_player->frameView() || assetStatus() == MediaPlayerAVAssetStatusUnknown)
105        return MediaRenderingNone;
106
107#if USE(ACCELERATED_COMPOSITING)
108    if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player))
109        return MediaRenderingToLayer;
110#endif
111
112    return MediaRenderingToContext;
113}
114
115void MediaPlayerPrivateAVFoundation::setUpVideoRendering()
116{
117    if (!isReadyForVideoSetup())
118        return;
119
120    MediaRenderingMode currentMode = currentRenderingMode();
121    MediaRenderingMode preferredMode = preferredRenderingMode();
122    if (currentMode == preferredMode && currentMode != MediaRenderingNone)
123        return;
124
125    LOG(Media, "MediaPlayerPrivateAVFoundation::setUpVideoRendering(%p) - current mode = %d, preferred mode = %d",
126        this, static_cast<int>(currentMode), static_cast<int>(preferredMode));
127
128    if (currentMode != MediaRenderingNone)
129        tearDownVideoRendering();
130
131    switch (preferredMode) {
132    case MediaRenderingNone:
133    case MediaRenderingToContext:
134        createContextVideoRenderer();
135        break;
136
137#if USE(ACCELERATED_COMPOSITING)
138    case MediaRenderingToLayer:
139        createVideoLayer();
140        break;
141#endif
142    }
143
144#if USE(ACCELERATED_COMPOSITING)
145    // If using a movie layer, inform the client so the compositing tree is updated.
146    if (currentMode == MediaRenderingToLayer || preferredMode == MediaRenderingToLayer) {
147        LOG(Media, "MediaPlayerPrivateAVFoundation::setUpVideoRendering(%p) - calling mediaPlayerRenderingModeChanged()", this);
148        m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
149    }
150#endif
151}
152
153void MediaPlayerPrivateAVFoundation::tearDownVideoRendering()
154{
155    LOG(Media, "MediaPlayerPrivateAVFoundation::tearDownVideoRendering(%p)", this);
156
157    destroyContextVideoRenderer();
158
159#if USE(ACCELERATED_COMPOSITING)
160    if (platformLayer())
161        destroyVideoLayer();
162#endif
163}
164
165bool MediaPlayerPrivateAVFoundation::hasSetUpVideoRendering() const
166{
167    return hasLayerRenderer() || hasContextRenderer();
168}
169
170void MediaPlayerPrivateAVFoundation::resumeLoad()
171{
172    LOG(Media, "MediaPlayerPrivateAVFoundation::resumeLoad(%p)", this);
173
174    ASSERT(m_delayingLoad);
175    m_delayingLoad = false;
176
177    if (m_assetURL.length())
178        prepareToPlay();
179}
180
181void MediaPlayerPrivateAVFoundation::load(const String& url)
182{
183    LOG(Media, "MediaPlayerPrivateAVFoundation::load(%p)", this);
184
185    if (m_networkState != MediaPlayer::Loading) {
186        m_networkState = MediaPlayer::Loading;
187        m_player->networkStateChanged();
188    }
189    if (m_readyState != MediaPlayer::HaveNothing) {
190        m_readyState = MediaPlayer::HaveNothing;
191        m_player->readyStateChanged();
192    }
193
194    m_videoFrameHasDrawn = false;
195    m_assetURL = url;
196
197    // Don't do any more work if the url is empty.
198    if (!url.length())
199        return;
200
201    if (m_preload == MediaPlayer::None) {
202        LOG(Media, "MediaPlayerPrivateAVFoundation::load(%p) - preload==none so returning", this);
203        m_delayingLoad = true;
204        return;
205    }
206
207    prepareToPlay();
208}
209
210void MediaPlayerPrivateAVFoundation::playabilityKnown()
211{
212    LOG(Media, "MediaPlayerPrivateAVFoundation::playabilityKnown(%p)", this);
213
214    updateStates();
215    if (m_assetIsPlayable)
216        return;
217
218    // Nothing more to do if we already have all of the item's metadata.
219    if (assetStatus() > MediaPlayerAVAssetStatusLoading) {
220        LOG(Media, "MediaPlayerPrivateAVFoundation::playabilityKnown(%p) - all metadata loaded", this);
221        return;
222    }
223
224    // At this point we are supposed to load metadata. It is OK to ask the asset to load the same
225    // information multiple times, because if it has already been loaded the completion handler
226    // will just be called synchronously.
227    m_loadingMetadata = true;
228    beginLoadingMetadata();
229}
230
231void MediaPlayerPrivateAVFoundation::prepareToPlay()
232{
233    LOG(Media, "MediaPlayerPrivateAVFoundation::prepareToPlay(%p)", this);
234
235    m_preload = MediaPlayer::Auto;
236    if (m_havePreparedToPlay)
237        return;
238    m_havePreparedToPlay = true;
239
240    m_delayingLoad = false;
241#if ENABLE(OFFLINE_WEB_APPLICATIONS)
242    Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : 0;
243    ApplicationCacheHost* cacheHost = frame ? frame->loader()->documentLoader()->applicationCacheHost() : 0;
244    ApplicationCacheResource* resource = 0;
245    if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(m_assetURL), resource) && resource)
246        createAVPlayerForCacheResource(resource);
247    else
248#endif
249    createAVPlayerForURL(m_assetURL);
250    checkPlayability();
251}
252
253void MediaPlayerPrivateAVFoundation::play()
254{
255    LOG(Media, "MediaPlayerPrivateAVFoundation::play(%p)", this);
256
257    // If the file has video, don't request playback until the first frame of video is ready to display
258    // or the audio may start playing before we can render video.
259    if (!m_cachedHasVideo || hasAvailableVideoFrame())
260        platformPlay();
261    else
262        m_playWhenFramesAvailable = true;
263}
264
265void MediaPlayerPrivateAVFoundation::pause()
266{
267    LOG(Media, "MediaPlayerPrivateAVFoundation::pause(%p)", this);
268    m_playWhenFramesAvailable = false;
269    platformPause();
270}
271
272void MediaPlayerPrivateAVFoundation::paint(GraphicsContext*, const IntRect&)
273{
274    // This is the base class, only need to remember that a frame has been drawn.
275    m_videoFrameHasDrawn = true;
276}
277
278float MediaPlayerPrivateAVFoundation::duration() const
279{
280    if (!metaDataAvailable())
281        return 0;
282
283    if (m_cachedDuration == invalidTime) {
284        m_cachedDuration = platformDuration();
285        LOG(Media, "MediaPlayerPrivateAVFMac::duration(%p) - caching %f", this, m_cachedDuration);
286    }
287
288    return m_cachedDuration;
289}
290
291void MediaPlayerPrivateAVFoundation::seek(float time)
292{
293    LOG(Media, "MediaPlayerPrivateAVFoundation::seek(%p) - seeking to %f", this, time);
294    if (!metaDataAvailable())
295        return;
296
297    if (time > duration())
298        time = duration();
299
300    m_seekTo = time;
301
302    seekToTime(time);
303}
304
305void MediaPlayerPrivateAVFoundation::setRate(float rate)
306{
307    LOG(Media, "MediaPlayerPrivateAVFoundation::setRate(%p) - seting to %f", this, rate);
308    m_requestedRate = rate;
309
310    updateRate();
311}
312
313bool MediaPlayerPrivateAVFoundation::paused() const
314{
315    if (!metaDataAvailable())
316        return true;
317
318    return rate() == 0;
319}
320
321bool MediaPlayerPrivateAVFoundation::seeking() const
322{
323    if (!metaDataAvailable())
324        return false;
325
326    return m_seekTo != invalidTime;
327}
328
329IntSize MediaPlayerPrivateAVFoundation::naturalSize() const
330{
331    if (!metaDataAvailable())
332        return IntSize();
333
334    // In spite of the name of this method, return the natural size transformed by the
335    // initial movie scale because the spec says intrinsic size is:
336    //
337    //    ... the dimensions of the resource in CSS pixels after taking into account the resource's
338    //    dimensions, aspect ratio, clean aperture, resolution, and so forth, as defined for the
339    //    format used by the resource
340
341    return m_cachedNaturalSize;
342}
343
344void MediaPlayerPrivateAVFoundation::setNaturalSize(IntSize size)
345{
346    IntSize oldSize = m_cachedNaturalSize;
347    m_cachedNaturalSize = size;
348    if (oldSize != m_cachedNaturalSize)
349        m_player->sizeChanged();
350}
351
352PassRefPtr<TimeRanges> MediaPlayerPrivateAVFoundation::buffered() const
353{
354    if (!m_cachedLoadedTimeRanges)
355        m_cachedLoadedTimeRanges = platformBufferedTimeRanges();
356
357    return m_cachedLoadedTimeRanges->copy();
358}
359
360float MediaPlayerPrivateAVFoundation::maxTimeSeekable() const
361{
362    if (!metaDataAvailable())
363        return 0;
364
365    if (!m_cachedMaxTimeSeekable)
366        m_cachedMaxTimeSeekable = platformMaxTimeSeekable();
367
368    LOG(Media, "MediaPlayerPrivateAVFoundation::maxTimeSeekable(%p) - returning %f", this, m_cachedMaxTimeSeekable);
369    return m_cachedMaxTimeSeekable;
370}
371
372float MediaPlayerPrivateAVFoundation::maxTimeLoaded() const
373{
374    if (!metaDataAvailable())
375        return 0;
376
377    if (!m_cachedMaxTimeLoaded)
378        m_cachedMaxTimeLoaded = platformMaxTimeLoaded();
379
380    return m_cachedMaxTimeLoaded;
381}
382
383unsigned MediaPlayerPrivateAVFoundation::bytesLoaded() const
384{
385    float dur = duration();
386    if (!dur)
387        return 0;
388    unsigned loaded = totalBytes() * maxTimeLoaded() / dur;
389    LOG(Media, "MediaPlayerPrivateAVFoundation::bytesLoaded(%p) - returning %i", this, loaded);
390    return loaded;
391}
392
393bool MediaPlayerPrivateAVFoundation::isReadyForVideoSetup() const
394{
395    return m_isAllowedToRender && m_readyState >= MediaPlayer::HaveMetadata && m_player->visible();
396}
397
398void MediaPlayerPrivateAVFoundation::prepareForRendering()
399{
400    if (m_isAllowedToRender)
401        return;
402    m_isAllowedToRender = true;
403
404    setUpVideoRendering();
405
406    if (currentRenderingMode() == MediaRenderingToLayer || preferredRenderingMode() == MediaRenderingToLayer)
407        m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
408}
409
410bool MediaPlayerPrivateAVFoundation::supportsFullscreen() const
411{
412#if ENABLE(FULLSCREEN_API)
413    return true;
414#else
415    // FIXME: WebVideoFullscreenController assumes a QTKit/QuickTime media engine
416    return false;
417#endif
418}
419
420void MediaPlayerPrivateAVFoundation::updateStates()
421{
422    MediaPlayer::NetworkState oldNetworkState = m_networkState;
423    MediaPlayer::ReadyState oldReadyState = m_readyState;
424
425    LOG(Media, "MediaPlayerPrivateAVFoundation::updateStates(%p) - entering with networkState = %i, readyState = %i",
426        this, static_cast<int>(m_networkState), static_cast<int>(m_readyState));
427
428    if (m_loadingMetadata)
429        m_networkState = MediaPlayer::Loading;
430    else {
431        // -loadValuesAsynchronouslyForKeys:completionHandler: has invoked its handler; test status of keys and determine state.
432        AVAssetStatus avAssetStatus = assetStatus();
433        ItemStatus itemStatus = playerItemStatus();
434
435        m_assetIsPlayable = (avAssetStatus == MediaPlayerAVAssetStatusPlayable);
436        if (m_readyState < MediaPlayer::HaveMetadata && avAssetStatus > MediaPlayerAVAssetStatusLoading) {
437            if (m_assetIsPlayable) {
438                if (itemStatus == MediaPlayerAVPlayerItemStatusUnknown) {
439                    if (avAssetStatus == MediaPlayerAVAssetStatusFailed || m_preload > MediaPlayer::MetaData) {
440                        // We may have a playable asset that doesn't support inspection prior to playback; go ahead
441                        // and create the AVPlayerItem now. When the AVPlayerItem becomes ready to play, we will
442                        // have access to its metadata. Or we may have been asked to become ready to play immediately.
443                        m_networkState = MediaPlayer::Loading;
444                        prepareToPlay();
445                    } else
446                        m_networkState = MediaPlayer::Idle;
447                }
448                if (avAssetStatus == MediaPlayerAVAssetStatusLoaded)
449                    m_readyState = MediaPlayer::HaveMetadata;
450            } else {
451                // FIX ME: fetch the error associated with the @"playable" key to distinguish between format
452                // and network errors.
453                m_networkState = MediaPlayer::FormatError;
454            }
455        }
456
457        if (avAssetStatus >= MediaPlayerAVAssetStatusLoaded && itemStatus > MediaPlayerAVPlayerItemStatusUnknown) {
458            if (seeking())
459                m_readyState = m_readyState >= MediaPlayer::HaveMetadata ? MediaPlayer::HaveMetadata : MediaPlayer::HaveNothing;
460            else {
461                float maxLoaded = maxTimeLoaded();
462                switch (itemStatus) {
463                case MediaPlayerAVPlayerItemStatusUnknown:
464                    break;
465                case MediaPlayerAVPlayerItemStatusFailed:
466                    m_networkState = MediaPlayer::DecodeError;
467                    break;
468                case MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp:
469                    m_readyState = MediaPlayer::HaveEnoughData;
470                    break;
471                case MediaPlayerAVPlayerItemStatusReadyToPlay:
472                case MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty:
473                case MediaPlayerAVPlayerItemStatusPlaybackBufferFull:
474                    if (maxLoaded > currentTime())
475                        m_readyState = MediaPlayer::HaveFutureData;
476                    else
477                        m_readyState = MediaPlayer::HaveCurrentData;
478                    break;
479                }
480
481                if (itemStatus >= MediaPlayerAVPlayerItemStatusReadyToPlay)
482                    m_networkState = (maxLoaded == duration()) ? MediaPlayer::Loaded : MediaPlayer::Loading;
483            }
484        }
485    }
486
487    if (isReadyForVideoSetup() && currentRenderingMode() != preferredRenderingMode())
488        setUpVideoRendering();
489
490    if (!m_haveReportedFirstVideoFrame && m_cachedHasVideo && hasAvailableVideoFrame()) {
491        m_haveReportedFirstVideoFrame = true;
492        m_player->firstVideoFrameAvailable();
493    }
494
495    if (m_networkState != oldNetworkState)
496        m_player->networkStateChanged();
497
498    if (m_readyState != oldReadyState)
499        m_player->readyStateChanged();
500
501    if (m_playWhenFramesAvailable && hasAvailableVideoFrame()) {
502        m_playWhenFramesAvailable = false;
503        platformPlay();
504    }
505
506    LOG(Media, "MediaPlayerPrivateAVFoundation::updateStates(%p) - exiting with networkState = %i, readyState = %i",
507        this, static_cast<int>(m_networkState), static_cast<int>(m_readyState));
508}
509
510void MediaPlayerPrivateAVFoundation::setSize(const IntSize&)
511{
512}
513
514void MediaPlayerPrivateAVFoundation::setVisible(bool visible)
515{
516    if (m_visible == visible)
517        return;
518
519    m_visible = visible;
520    if (visible)
521        setUpVideoRendering();
522    else
523        tearDownVideoRendering();
524}
525
526bool MediaPlayerPrivateAVFoundation::hasAvailableVideoFrame() const
527{
528    if (currentRenderingMode() == MediaRenderingToLayer)
529        return videoLayerIsReadyToDisplay();
530
531    // When using the software renderer we hope someone will signal that a frame is available so we might as well
532    // wait until we know that a frame has been drawn.
533    return m_videoFrameHasDrawn;
534}
535
536void MediaPlayerPrivateAVFoundation::acceleratedRenderingStateChanged()
537{
538    // Set up or change the rendering path if necessary.
539    setUpVideoRendering();
540}
541
542void MediaPlayerPrivateAVFoundation::metadataLoaded()
543{
544    m_loadingMetadata = false;
545    updateStates();
546}
547
548void MediaPlayerPrivateAVFoundation::loadStateChanged()
549{
550    if (m_ignoreLoadStateChanges)
551        return;
552    updateStates();
553}
554
555void MediaPlayerPrivateAVFoundation::rateChanged()
556{
557    updateStates();
558    m_player->rateChanged();
559}
560
561void MediaPlayerPrivateAVFoundation::loadedTimeRangesChanged()
562{
563    m_cachedLoadedTimeRanges = 0;
564    m_cachedMaxTimeLoaded = 0;
565    updateStates();
566
567    // For some media files, reported duration is estimated and updated as media is loaded
568    // so report duration changed when the estimate is upated.
569    float dur = duration();
570    if (dur != m_reportedDuration) {
571        if (m_reportedDuration != invalidTime)
572            m_player->durationChanged();
573        m_reportedDuration = dur;
574    }
575}
576
577void MediaPlayerPrivateAVFoundation::seekableTimeRangesChanged()
578{
579    m_cachedMaxTimeSeekable = 0;
580}
581
582void MediaPlayerPrivateAVFoundation::timeChanged(double time)
583{
584    LOG(Media, "MediaPlayerPrivateAVFoundation::timeChanged(%p) - time = %f", this, time);
585
586    if (m_seekTo == invalidTime)
587        return;
588
589    // AVFoundation may call our observer more than once during a seek, and we can't currently tell
590    // if we will be able to seek to an exact time, so assume that we are done seeking if we are
591    // "close enough" to the seek time.
592    const double smallSeekDelta = 1.0 / 100;
593
594    float currentRate = rate();
595    if ((currentRate > 0 && time >= m_seekTo) || (currentRate < 0 && time <= m_seekTo) || (abs(m_seekTo - time) <= smallSeekDelta)) {
596        m_seekTo = invalidTime;
597        updateStates();
598        m_player->timeChanged();
599    }
600}
601
602void MediaPlayerPrivateAVFoundation::seekCompleted(bool finished)
603{
604    LOG(Media, "MediaPlayerPrivateAVFoundation::seekCompleted(%p) - finished = %d", this, finished);
605
606    if (finished)
607        m_seekTo = invalidTime;
608}
609
610void MediaPlayerPrivateAVFoundation::didEnd()
611{
612    // Hang onto the current time and use it as duration from now on since we are definitely at
613    // the end of the movie. Do this because the initial duration is sometimes an estimate.
614    float now = currentTime();
615    if (now > 0)
616        m_cachedDuration = now;
617
618    updateStates();
619    m_player->timeChanged();
620}
621
622void MediaPlayerPrivateAVFoundation::repaint()
623{
624    m_videoFrameHasDrawn = true;
625    m_player->repaint();
626}
627
628MediaPlayer::MovieLoadType MediaPlayerPrivateAVFoundation::movieLoadType() const
629{
630    if (!metaDataAvailable() || assetStatus() == MediaPlayerAVAssetStatusUnknown)
631        return MediaPlayer::Unknown;
632
633    if (isinf(duration()))
634        return MediaPlayer::LiveStream;
635
636    return MediaPlayer::Download;
637}
638
639void MediaPlayerPrivateAVFoundation::setPreload(MediaPlayer::Preload preload)
640{
641    m_preload = preload;
642    if (m_delayingLoad && m_preload != MediaPlayer::None)
643        resumeLoad();
644}
645
646void MediaPlayerPrivateAVFoundation::setDelayCallbacks(bool delay)
647{
648    MutexLocker lock(m_queueMutex);
649    if (delay)
650        ++m_delayCallbacks;
651    else {
652        ASSERT(m_delayCallbacks);
653        --m_delayCallbacks;
654    }
655}
656
657void MediaPlayerPrivateAVFoundation::mainThreadCallback(void* context)
658{
659    LOG(Media, "MediaPlayerPrivateAVFoundation::mainThreadCallback(%p)", context);
660    MediaPlayerPrivateAVFoundation* player = static_cast<MediaPlayerPrivateAVFoundation*>(context);
661    player->clearMainThreadPendingFlag();
662    player->dispatchNotification();
663}
664
665void MediaPlayerPrivateAVFoundation::clearMainThreadPendingFlag()
666{
667    MutexLocker lock(m_queueMutex);
668    m_mainThreadCallPending = false;
669}
670
671void MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(Notification::Type type, double time)
672{
673    scheduleMainThreadNotification(Notification(type, time));
674}
675
676void MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(Notification::Type type, bool finished)
677{
678    scheduleMainThreadNotification(Notification(type, finished));
679}
680
681void MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(Notification notification)
682{
683    LOG(Media, "MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(%p) - notification %d", this, static_cast<int>(notification.type()));
684    m_queueMutex.lock();
685
686    // It is important to always process the properties in the order that we are notified,
687    // so always go through the queue because notifications happen on different threads.
688    m_queuedNotifications.append(notification);
689
690    bool delayDispatch = m_delayCallbacks || !isMainThread();
691    if (delayDispatch && !m_mainThreadCallPending) {
692        m_mainThreadCallPending = true;
693        callOnMainThread(mainThreadCallback, this);
694    }
695
696    m_queueMutex.unlock();
697
698    if (delayDispatch) {
699        LOG(Media, "MediaPlayerPrivateAVFoundation::scheduleMainThreadNotification(%p) - early return", this);
700        return;
701    }
702
703    dispatchNotification();
704}
705
706void MediaPlayerPrivateAVFoundation::dispatchNotification()
707{
708    ASSERT(isMainThread());
709
710    Notification notification = Notification();
711    {
712        MutexLocker lock(m_queueMutex);
713
714        if (m_queuedNotifications.isEmpty())
715            return;
716
717        if (!m_delayCallbacks) {
718            // Only dispatch one notification callback per invocation because they can cause recursion.
719            notification = m_queuedNotifications.first();
720            m_queuedNotifications.remove(0);
721        }
722
723        if (!m_queuedNotifications.isEmpty() && !m_mainThreadCallPending)
724            callOnMainThread(mainThreadCallback, this);
725
726        if (!notification.isValid())
727            return;
728    }
729
730    LOG(Media, "MediaPlayerPrivateAVFoundation::dispatchNotification(%p) - dispatching %d", this, static_cast<int>(notification.type()));
731
732    switch (notification.type()) {
733    case Notification::ItemDidPlayToEndTime:
734        didEnd();
735        break;
736    case Notification::ItemTracksChanged:
737        tracksChanged();
738        break;
739    case Notification::ItemStatusChanged:
740        loadStateChanged();
741        break;
742    case Notification::ItemSeekableTimeRangesChanged:
743        seekableTimeRangesChanged();
744        loadStateChanged();
745        break;
746    case Notification::ItemLoadedTimeRangesChanged:
747        loadedTimeRangesChanged();
748        loadStateChanged();
749        break;
750    case Notification::ItemPresentationSizeChanged:
751        sizeChanged();
752        break;
753    case Notification::ItemIsPlaybackLikelyToKeepUpChanged:
754        loadStateChanged();
755        break;
756    case Notification::ItemIsPlaybackBufferEmptyChanged:
757        loadStateChanged();
758        break;
759    case Notification::ItemIsPlaybackBufferFullChanged:
760        loadStateChanged();
761        break;
762    case Notification::PlayerRateChanged:
763        rateChanged();
764        break;
765    case Notification::PlayerTimeChanged:
766        timeChanged(notification.time());
767        break;
768    case Notification::SeekCompleted:
769        seekCompleted(notification.finished());
770        break;
771    case Notification::AssetMetadataLoaded:
772        metadataLoaded();
773        break;
774    case Notification::AssetPlayabilityKnown:
775        playabilityKnown();
776        break;
777    case Notification::None:
778        ASSERT_NOT_REACHED();
779        break;
780    }
781}
782
783} // namespace WebCore
784
785#endif
786