• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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#import "config.h"
27
28#if ENABLE(VIDEO) && USE(AVFOUNDATION)
29
30#import "MediaPlayerPrivateAVFoundationObjC.h"
31
32#import "ApplicationCacheResource.h"
33#import "BlockExceptions.h"
34#import "FloatConversion.h"
35#import "FrameView.h"
36#import "FloatConversion.h"
37#import "GraphicsContext.h"
38#import "KURL.h"
39#import "Logging.h"
40#import "SoftLinking.h"
41#import "TimeRanges.h"
42#import "WebCoreSystemInterface.h"
43#import <objc/objc-runtime.h>
44#import <wtf/UnusedParam.h>
45
46#import <CoreMedia/CoreMedia.h>
47#import <AVFoundation/AVFoundation.h>
48
49SOFT_LINK_FRAMEWORK(AVFoundation)
50SOFT_LINK_FRAMEWORK(CoreMedia)
51
52SOFT_LINK(CoreMedia, CMTimeCompare, int32_t, (CMTime time1, CMTime time2), (time1, time2))
53SOFT_LINK(CoreMedia, CMTimeMakeWithSeconds, CMTime, (Float64 seconds, int32_t preferredTimeScale), (seconds, preferredTimeScale))
54SOFT_LINK(CoreMedia, CMTimeGetSeconds, Float64, (CMTime time), (time))
55SOFT_LINK(CoreMedia, CMTimeRangeGetEnd, CMTime, (CMTimeRange range), (range))
56
57SOFT_LINK_CLASS(AVFoundation, AVPlayer)
58SOFT_LINK_CLASS(AVFoundation, AVPlayerItem)
59SOFT_LINK_CLASS(AVFoundation, AVPlayerLayer)
60SOFT_LINK_CLASS(AVFoundation, AVURLAsset)
61SOFT_LINK_CLASS(AVFoundation, AVAssetImageGenerator)
62
63SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicVisual, NSString *)
64SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicAudible, NSString *)
65SOFT_LINK_POINTER(AVFoundation, AVMediaTypeClosedCaption, NSString *)
66SOFT_LINK_POINTER(AVFoundation, AVPlayerItemDidPlayToEndTimeNotification, NSString *)
67SOFT_LINK_POINTER(AVFoundation, AVAssetImageGeneratorApertureModeCleanAperture, NSString *)
68
69SOFT_LINK_CONSTANT(CoreMedia, kCMTimeZero, CMTime)
70
71#define AVPlayer getAVPlayerClass()
72#define AVPlayerItem getAVPlayerItemClass()
73#define AVPlayerLayer getAVPlayerLayerClass()
74#define AVURLAsset getAVURLAssetClass()
75#define AVAssetImageGenerator getAVAssetImageGeneratorClass()
76
77#define AVMediaCharacteristicVisual getAVMediaCharacteristicVisual()
78#define AVMediaCharacteristicAudible getAVMediaCharacteristicAudible()
79#define AVMediaTypeClosedCaption getAVMediaTypeClosedCaption()
80#define AVPlayerItemDidPlayToEndTimeNotification getAVPlayerItemDidPlayToEndTimeNotification()
81#define AVAssetImageGeneratorApertureModeCleanAperture getAVAssetImageGeneratorApertureModeCleanAperture()
82
83#define kCMTimeZero getkCMTimeZero()
84
85using namespace WebCore;
86using namespace std;
87
88enum MediaPlayerAVFoundationObservationContext {
89    MediaPlayerAVFoundationObservationContextPlayerItem,
90    MediaPlayerAVFoundationObservationContextPlayer
91};
92
93@interface WebCoreAVFMovieObserver : NSObject
94{
95    MediaPlayerPrivateAVFoundationObjC* m_callback;
96    int m_delayCallbacks;
97}
98-(id)initWithCallback:(MediaPlayerPrivateAVFoundationObjC*)callback;
99-(void)disconnect;
100-(void)playableKnown;
101-(void)metadataLoaded;
102-(void)timeChanged:(double)time;
103-(void)seekCompleted:(BOOL)finished;
104-(void)didEnd:(NSNotification *)notification;
105-(void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary *)change context:(MediaPlayerAVFoundationObservationContext)context;
106@end
107
108namespace WebCore {
109
110static NSArray *assetMetadataKeyNames();
111static NSArray *itemKVOProperties();
112
113#if !LOG_DISABLED
114static const char *boolString(bool val)
115{
116    return val ? "true" : "false";
117}
118#endif
119
120static const float invalidTime = -1.0f;
121
122MediaPlayerPrivateInterface* MediaPlayerPrivateAVFoundationObjC::create(MediaPlayer* player)
123{
124    return new MediaPlayerPrivateAVFoundationObjC(player);
125}
126
127void MediaPlayerPrivateAVFoundationObjC::registerMediaEngine(MediaEngineRegistrar registrar)
128{
129    if (isAvailable())
130        registrar(create, getSupportedTypes, supportsType, 0, 0, 0);
131}
132
133MediaPlayerPrivateAVFoundationObjC::MediaPlayerPrivateAVFoundationObjC(MediaPlayer* player)
134    : MediaPlayerPrivateAVFoundation(player)
135    , m_objcObserver(AdoptNS, [[WebCoreAVFMovieObserver alloc] initWithCallback:this])
136    , m_timeObserver(0)
137{
138}
139
140MediaPlayerPrivateAVFoundationObjC::~MediaPlayerPrivateAVFoundationObjC()
141{
142    cancelLoad();
143    [m_objcObserver.get() disconnect];
144}
145
146void MediaPlayerPrivateAVFoundationObjC::cancelLoad()
147{
148    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::cancelLoad(%p)", this);
149    tearDownVideoRendering();
150
151    [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
152
153    // Tell our observer to do nothing when our cancellation of pending loading calls its completion handler.
154    setIgnoreLoadStateChanges(true);
155    if (m_avAsset) {
156        [m_avAsset.get() cancelLoading];
157        m_avAsset = nil;
158    }
159    if (m_avPlayerItem) {
160        for (NSString *keyName in itemKVOProperties())
161            [m_avPlayerItem.get() removeObserver:m_objcObserver.get() forKeyPath:keyName];
162
163        m_avPlayerItem = nil;
164    }
165    if (m_avPlayer) {
166        if (m_timeObserver)
167            [m_avPlayer.get() removeTimeObserver:m_timeObserver];
168        [m_avPlayer.get() removeObserver:m_objcObserver.get() forKeyPath:@"rate"];
169        m_avPlayer = nil;
170    }
171    setIgnoreLoadStateChanges(false);
172}
173
174bool MediaPlayerPrivateAVFoundationObjC::hasLayerRenderer() const
175{
176    return m_videoLayer;
177}
178
179bool MediaPlayerPrivateAVFoundationObjC::hasContextRenderer() const
180{
181    return m_imageGenerator;
182}
183
184void MediaPlayerPrivateAVFoundationObjC::createContextVideoRenderer()
185{
186    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createContextVideoRenderer(%p)", this);
187
188    if (!m_avAsset || m_imageGenerator)
189        return;
190
191    m_imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:m_avAsset.get()];
192
193    [m_imageGenerator.get() setApertureMode:AVAssetImageGeneratorApertureModeCleanAperture];
194    [m_imageGenerator.get() setAppliesPreferredTrackTransform:YES];
195
196    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createImageGenerator(%p) - returning %p", this, m_imageGenerator.get());
197}
198
199void MediaPlayerPrivateAVFoundationObjC::destroyContextVideoRenderer()
200{
201    if (!m_imageGenerator)
202        return;
203
204    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::destroyContextVideoRenderer(%p) - destroying  %p", this, m_imageGenerator.get());
205
206    m_imageGenerator = 0;
207}
208
209void MediaPlayerPrivateAVFoundationObjC::createVideoLayer()
210{
211    if (!m_avPlayer)
212        return;
213
214    if (!m_videoLayer) {
215        m_videoLayer.adoptNS([[AVPlayerLayer alloc] init]);
216        [m_videoLayer.get() setPlayer:m_avPlayer.get()];
217        LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createVideoLayer(%p) - returning %p", this, m_videoLayer.get());
218    }
219}
220
221void MediaPlayerPrivateAVFoundationObjC::destroyVideoLayer()
222{
223    if (!m_videoLayer)
224        return;
225
226    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::destroyVideoLayer(%p) - destroying", this, m_videoLayer.get());
227
228    [m_videoLayer.get() setPlayer:nil];
229
230    m_videoLayer = 0;
231}
232
233bool MediaPlayerPrivateAVFoundationObjC::videoLayerIsReadyToDisplay() const
234{
235    return (m_videoLayer && [m_videoLayer.get() isReadyForDisplay]);
236}
237
238void MediaPlayerPrivateAVFoundationObjC::createAVPlayerForURL(const String& url)
239{
240    setDelayCallbacks(true);
241
242    if (!m_avAsset) {
243        NSURL *cocoaURL = KURL(ParsedURLString, url);
244        m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:cocoaURL options:nil]);
245    }
246
247    createAVPlayer();
248}
249
250#if ENABLE(OFFLINE_WEB_APPLICATIONS)
251void MediaPlayerPrivateAVFoundationObjC::createAVPlayerForCacheResource(ApplicationCacheResource* resource)
252{
253    // AVFoundation can't open arbitrary data pointers, so if this ApplicationCacheResource doesn't
254    // have a valid local path, just open the resource's original URL.
255    if (resource->path().isEmpty()) {
256        createAVPlayerForURL(resource->url());
257        return;
258    }
259
260    setDelayCallbacks(true);
261
262    if (!m_avAsset) {
263        NSURL* localURL = [NSURL fileURLWithPath:resource->path()];
264        m_avAsset.adoptNS([[AVURLAsset alloc] initWithURL:localURL options:nil]);
265    }
266
267    createAVPlayer();
268}
269#endif
270
271void MediaPlayerPrivateAVFoundationObjC::createAVPlayer()
272{
273    if (!m_avPlayer) {
274        m_avPlayer.adoptNS([[AVPlayer alloc] init]);
275
276        [m_avPlayer.get() addObserver:m_objcObserver.get() forKeyPath:@"rate" options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayer];
277
278        // Add a time observer, ask to be called infrequently because we don't really want periodic callbacks but
279        // our observer will also be called whenever a seek happens.
280        const double veryLongInterval = 60*60*60*24*30;
281        WebCoreAVFMovieObserver *observer = m_objcObserver.get();
282        m_timeObserver = [m_avPlayer.get() addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(veryLongInterval, 10) queue:nil usingBlock:^(CMTime time){
283            [observer timeChanged:CMTimeGetSeconds(time)];
284        }];
285    }
286
287    if (!m_avPlayerItem) {
288        // Create the player item so we can media data.
289        m_avPlayerItem.adoptNS([[AVPlayerItem alloc] initWithAsset:m_avAsset.get()]);
290
291        [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()selector:@selector(didEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:m_avPlayerItem.get()];
292
293        for (NSString *keyName in itemKVOProperties())
294            [m_avPlayerItem.get() addObserver:m_objcObserver.get() forKeyPath:keyName options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayerItem];
295
296        [m_avPlayer.get() replaceCurrentItemWithPlayerItem:m_avPlayerItem.get()];
297    }
298
299    setDelayCallbacks(false);
300}
301
302void MediaPlayerPrivateAVFoundationObjC::checkPlayability()
303{
304    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::checkPlayability(%p)", this);
305
306    [m_avAsset.get() loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:@"playable"] completionHandler:^{
307        [m_objcObserver.get() playableKnown];
308    }];
309}
310
311void MediaPlayerPrivateAVFoundationObjC::beginLoadingMetadata()
312{
313    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::playabilityKnown(%p) - requesting metadata loading", this);
314    [m_avAsset.get() loadValuesAsynchronouslyForKeys:[assetMetadataKeyNames() retain] completionHandler:^{
315        [m_objcObserver.get() metadataLoaded];
316    }];
317}
318
319MediaPlayerPrivateAVFoundation::ItemStatus MediaPlayerPrivateAVFoundationObjC::playerItemStatus() const
320{
321    if (!m_avPlayerItem)
322        return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusUnknown;
323
324    AVPlayerItemStatus status = [m_avPlayerItem.get() status];
325    if (status == AVPlayerItemStatusUnknown)
326        return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusUnknown;
327    if (status == AVPlayerItemStatusFailed)
328        return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusFailed;
329    if ([m_avPlayerItem.get() isPlaybackLikelyToKeepUp])
330        return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp;
331    if (buffered()->contain(duration()))
332        return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferFull;
333    if (buffered()->contain(currentTime()))
334        return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty;
335
336    return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusReadyToPlay;
337}
338
339PlatformMedia MediaPlayerPrivateAVFoundationObjC::platformMedia() const
340{
341    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::platformMedia(%p)", this);
342    PlatformMedia pm;
343    pm.type = PlatformMedia::AVFoundationMediaPlayerType;
344    pm.media.avfMediaPlayer = m_avPlayer.get();
345    return pm;
346}
347
348PlatformLayer* MediaPlayerPrivateAVFoundationObjC::platformLayer() const
349{
350    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::platformLayer(%p)", this);
351    return m_videoLayer.get();
352}
353
354void MediaPlayerPrivateAVFoundationObjC::platformPlay()
355{
356    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::platformPlay(%p)", this);
357    if (!metaDataAvailable())
358        return;
359
360    setDelayCallbacks(true);
361    [m_avPlayer.get() setRate:requestedRate()];
362    setDelayCallbacks(false);
363}
364
365void MediaPlayerPrivateAVFoundationObjC::platformPause()
366{
367    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::platformPause(%p)", this);
368    if (!metaDataAvailable())
369        return;
370
371    setDelayCallbacks(true);
372    [m_avPlayer.get() setRate:nil];
373    setDelayCallbacks(false);
374}
375
376float MediaPlayerPrivateAVFoundationObjC::platformDuration() const
377{
378    if (!metaDataAvailable() || !m_avPlayerItem)
379        return 0;
380
381    float duration;
382    CMTime cmDuration = [m_avPlayerItem.get() duration];
383    if (CMTIME_IS_NUMERIC(cmDuration))
384        duration = narrowPrecisionToFloat(CMTimeGetSeconds(cmDuration));
385    else if (CMTIME_IS_INDEFINITE(cmDuration))
386        duration = numeric_limits<float>::infinity();
387    else {
388        LOG(Media, "MediaPlayerPrivateAVFoundationObjC::duration(%p) - invalid duration, returning 0", this);
389        return 0;
390    }
391
392    return duration;
393}
394
395float MediaPlayerPrivateAVFoundationObjC::currentTime() const
396{
397    if (!metaDataAvailable() || !m_avPlayerItem)
398        return 0;
399
400    CMTime itemTime = [m_avPlayerItem.get() currentTime];
401    if (CMTIME_IS_NUMERIC(itemTime))
402        return narrowPrecisionToFloat(CMTimeGetSeconds(itemTime));
403
404    return 0;
405}
406
407void MediaPlayerPrivateAVFoundationObjC::seekToTime(float time)
408{
409    // setCurrentTime generates several event callbacks, update afterwards.
410    setDelayCallbacks(true);
411
412    WebCoreAVFMovieObserver *observer = m_objcObserver.get();
413    [m_avPlayerItem.get() seekToTime:CMTimeMakeWithSeconds(time, 600) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
414        [observer seekCompleted:finished];
415    }];
416
417    setDelayCallbacks(false);
418}
419
420void MediaPlayerPrivateAVFoundationObjC::setVolume(float volume)
421{
422    if (!metaDataAvailable())
423        return;
424
425    [m_avPlayer.get() setVolume:volume];
426}
427
428void MediaPlayerPrivateAVFoundationObjC::setClosedCaptionsVisible(bool closedCaptionsVisible)
429{
430    if (!metaDataAvailable())
431        return;
432
433    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::setClosedCaptionsVisible(%p) - setting to %s", this, boolString(closedCaptionsVisible));
434    [m_avPlayer.get() setClosedCaptionDisplayEnabled:closedCaptionsVisible];
435}
436
437void MediaPlayerPrivateAVFoundationObjC::updateRate()
438{
439    setDelayCallbacks(true);
440    [m_avPlayer.get() setRate:requestedRate()];
441    setDelayCallbacks(false);
442}
443
444float MediaPlayerPrivateAVFoundationObjC::rate() const
445{
446    if (!metaDataAvailable())
447        return 0;
448
449    return [m_avPlayer.get() rate];
450}
451
452PassRefPtr<TimeRanges> MediaPlayerPrivateAVFoundationObjC::platformBufferedTimeRanges() const
453{
454    RefPtr<TimeRanges> timeRanges = TimeRanges::create();
455
456    if (!m_avPlayerItem)
457        return timeRanges.release();
458
459    NSArray *loadedRanges = [m_avPlayerItem.get() loadedTimeRanges];
460    for (NSValue *thisRangeValue in loadedRanges) {
461        CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
462        if (CMTIMERANGE_IS_VALID(timeRange) && !CMTIMERANGE_IS_EMPTY(timeRange)) {
463            float rangeStart = narrowPrecisionToFloat(CMTimeGetSeconds(timeRange.start));
464            float rangeEnd = narrowPrecisionToFloat(CMTimeGetSeconds(CMTimeRangeGetEnd(timeRange)));
465            timeRanges->add(rangeStart, rangeEnd);
466        }
467    }
468    return timeRanges.release();
469}
470
471float MediaPlayerPrivateAVFoundationObjC::platformMaxTimeSeekable() const
472{
473    NSArray *seekableRanges = [m_avPlayerItem.get() seekableTimeRanges];
474    if (!seekableRanges)
475        return 0;
476
477    float maxTimeSeekable = 0;
478    for (NSValue *thisRangeValue in seekableRanges) {
479        CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
480        if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
481            continue;
482
483        float endOfRange = narrowPrecisionToFloat(CMTimeGetSeconds(CMTimeRangeGetEnd(timeRange)));
484        if (maxTimeSeekable < endOfRange)
485            maxTimeSeekable = endOfRange;
486    }
487    return maxTimeSeekable;
488}
489
490float MediaPlayerPrivateAVFoundationObjC::platformMaxTimeLoaded() const
491{
492    NSArray *loadedRanges = [m_avPlayerItem.get() loadedTimeRanges];
493    if (!loadedRanges)
494        return 0;
495
496    float maxTimeLoaded = 0;
497    for (NSValue *thisRangeValue in loadedRanges) {
498        CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
499        if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
500            continue;
501
502        float endOfRange = narrowPrecisionToFloat(CMTimeGetSeconds(CMTimeRangeGetEnd(timeRange)));
503        if (maxTimeLoaded < endOfRange)
504            maxTimeLoaded = endOfRange;
505    }
506
507    return maxTimeLoaded;
508}
509
510unsigned MediaPlayerPrivateAVFoundationObjC::totalBytes() const
511{
512    if (!metaDataAvailable())
513        return 0;
514
515    long long totalMediaSize = 0;
516    NSArray *tracks = [m_avAsset.get() tracks];
517    for (AVAssetTrack *thisTrack in tracks)
518        totalMediaSize += [thisTrack totalSampleDataLength];
519
520    return static_cast<unsigned>(totalMediaSize);
521}
522
523void MediaPlayerPrivateAVFoundationObjC::setAsset(id asset)
524{
525    m_avAsset = asset;
526}
527
528MediaPlayerPrivateAVFoundation::AVAssetStatus MediaPlayerPrivateAVFoundationObjC::assetStatus() const
529{
530    if (!m_avAsset)
531        return MediaPlayerAVAssetStatusUnknown;
532
533    for (NSString *keyName in assetMetadataKeyNames()) {
534        AVKeyValueStatus keyStatus = [m_avAsset.get() statusOfValueForKey:keyName error:nil];
535        if (keyStatus < AVKeyValueStatusLoaded)
536            return MediaPlayerAVAssetStatusLoading;// At least one key is not loaded yet.
537
538        if (keyStatus == AVKeyValueStatusFailed)
539            return MediaPlayerAVAssetStatusFailed; // At least one key could not be loaded.
540        if (keyStatus == AVKeyValueStatusCancelled)
541            return MediaPlayerAVAssetStatusCancelled; // Loading of at least one key was cancelled.
542    }
543
544    if ([[m_avAsset.get() valueForKey:@"playable"] boolValue])
545        return MediaPlayerAVAssetStatusPlayable;
546
547    return MediaPlayerAVAssetStatusLoaded;
548}
549
550void MediaPlayerPrivateAVFoundationObjC::paintCurrentFrameInContext(GraphicsContext* context, const IntRect& rect)
551{
552    if (!metaDataAvailable() || context->paintingDisabled())
553        return;
554
555    paint(context, rect);
556}
557
558void MediaPlayerPrivateAVFoundationObjC::paint(GraphicsContext* context, const IntRect& rect)
559{
560    if (!metaDataAvailable() || context->paintingDisabled())
561        return;
562
563    setDelayCallbacks(true);
564    BEGIN_BLOCK_OBJC_EXCEPTIONS;
565
566    RetainPtr<CGImageRef> image = createImageForTimeInRect(currentTime(), rect);
567    if (image) {
568        context->save();
569        context->translate(rect.x(), rect.y() + rect.height());
570        context->scale(FloatSize(1.0f, -1.0f));
571        context->setImageInterpolationQuality(InterpolationLow);
572        IntRect paintRect(IntPoint(0, 0), IntSize(rect.width(), rect.height()));
573        CGContextDrawImage(context->platformContext(), CGRectMake(0, 0, paintRect.width(), paintRect.height()), image.get());
574        context->restore();
575        image = 0;
576    }
577
578    END_BLOCK_OBJC_EXCEPTIONS;
579    setDelayCallbacks(false);
580
581    MediaPlayerPrivateAVFoundation::paint(context, rect);
582}
583
584static HashSet<String> mimeTypeCache()
585{
586    DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
587    static bool typeListInitialized = false;
588
589    if (typeListInitialized)
590        return cache;
591    typeListInitialized = true;
592
593    NSArray *types = [AVURLAsset audiovisualMIMETypes];
594    for (NSString *mimeType in types)
595        cache.add(mimeType);
596
597    return cache;
598}
599
600RetainPtr<CGImageRef> MediaPlayerPrivateAVFoundationObjC::createImageForTimeInRect(float time, const IntRect& rect)
601{
602    if (!m_imageGenerator)
603        createContextVideoRenderer();
604    ASSERT(m_imageGenerator);
605
606#if !LOG_DISABLED
607    double start = WTF::currentTime();
608#endif
609
610    [m_imageGenerator.get() setMaximumSize:CGSize(rect.size())];
611    CGImageRef image = [m_imageGenerator.get() copyCGImageAtTime:CMTimeMakeWithSeconds(time, 600) actualTime:nil error:nil];
612
613#if !LOG_DISABLED
614    double duration = WTF::currentTime() - start;
615    LOG(Media, "MediaPlayerPrivateAVFoundationObjC::createImageForTimeInRect(%p) - creating image took %.4f", this, narrowPrecisionToFloat(duration));
616#endif
617
618    return image;
619}
620
621void MediaPlayerPrivateAVFoundationObjC::getSupportedTypes(HashSet<String>& supportedTypes)
622{
623    supportedTypes = mimeTypeCache();
624}
625
626MediaPlayer::SupportsType MediaPlayerPrivateAVFoundationObjC::supportsType(const String& type, const String& codecs)
627{
628    if (!mimeTypeCache().contains(type))
629        return MediaPlayer::IsNotSupported;
630
631    // The spec says:
632    // "Implementors are encouraged to return "maybe" unless the type can be confidently established as being supported or not."
633    if (codecs.isEmpty())
634        return MediaPlayer::MayBeSupported;
635
636    NSString *typeString = [NSString stringWithFormat:@"%@; codecs=\"%@\"", (NSString *)type, (NSString *)codecs];
637    return [AVURLAsset isPlayableExtendedMIMEType:typeString] ? MediaPlayer::IsSupported : MediaPlayer::MayBeSupported;;
638}
639
640bool MediaPlayerPrivateAVFoundationObjC::isAvailable()
641{
642    return true;
643}
644
645float MediaPlayerPrivateAVFoundationObjC::mediaTimeForTimeValue(float timeValue) const
646{
647    if (!metaDataAvailable())
648        return timeValue;
649
650    // FIXME - impossible to implement until rdar://8721510 is fixed.
651    return timeValue;
652}
653
654void MediaPlayerPrivateAVFoundationObjC::tracksChanged()
655{
656    // This is called whenever the tracks collection changes so cache hasVideo and hasAudio since we get
657    // asked about those fairly fequently.
658    setHasVideo([[m_avAsset.get() tracksWithMediaCharacteristic:AVMediaCharacteristicVisual] count]);
659    setHasAudio([[m_avAsset.get() tracksWithMediaCharacteristic:AVMediaCharacteristicAudible] count]);
660    setHasClosedCaptions([[m_avAsset.get() tracksWithMediaType:AVMediaTypeClosedCaption] count]);
661
662    sizeChanged();
663}
664
665void MediaPlayerPrivateAVFoundationObjC::sizeChanged()
666{
667    NSArray *tracks = [m_avAsset.get() tracks];
668
669    // Some assets don't report track properties until they are completely ready to play, but we
670    // want to report a size as early as possible so use presentationSize when an asset has no tracks.
671    if (![tracks count]) {
672        setNaturalSize(IntSize([m_avPlayerItem.get() presentationSize]));
673        return;
674    }
675
676    // AVAsset's 'naturalSize' property only considers the movie's first video track, so we need to compute
677    // the union of all visual track rects.
678    CGRect trackUnionRect = CGRectZero;
679    for (AVAssetTrack *track in tracks) {
680        CGSize trackSize = [track naturalSize];
681        CGRect trackRect = CGRectMake(0, 0, trackSize.width, trackSize.height);
682        trackUnionRect = CGRectUnion(trackUnionRect, CGRectApplyAffineTransform(trackRect, [track preferredTransform]));
683    }
684
685    // The movie is always displayed at 0,0 so move the track rect to the origin before using width and height.
686    trackUnionRect = CGRectOffset(trackUnionRect, trackUnionRect.origin.x, trackUnionRect.origin.y);
687
688    // Also look at the asset's preferred transform so we account for a movie matrix.
689    CGSize naturalSize = CGSizeApplyAffineTransform(trackUnionRect.size, [m_avAsset.get() preferredTransform]);
690
691    // Cache the natural size (setNaturalSize will notify the player if it has changed).
692    setNaturalSize(IntSize(naturalSize));
693}
694
695NSArray* assetMetadataKeyNames()
696{
697    static NSArray* keys;
698    if (!keys) {
699        keys = [[NSArray alloc] initWithObjects:@"duration",
700                    @"naturalSize",
701                    @"preferredTransform",
702                    @"preferredVolume",
703                    @"preferredRate",
704                    @"playable",
705                    @"tracks",
706                   nil];
707    }
708    return keys;
709}
710
711NSArray* itemKVOProperties()
712{
713    static NSArray* keys;
714    if (!keys) {
715        keys = [[NSArray alloc] initWithObjects:@"presentationSize",
716                @"status",
717                @"asset",
718                @"tracks",
719                @"seekableTimeRanges",
720                @"loadedTimeRanges",
721                @"playbackLikelyToKeepUp",
722                @"playbackBufferFull",
723                @"playbackBufferEmpty",
724                nil];
725    }
726    return keys;
727}
728
729} // namespace WebCore
730
731@implementation WebCoreAVFMovieObserver
732
733- (id)initWithCallback:(MediaPlayerPrivateAVFoundationObjC*)callback
734{
735    m_callback = callback;
736    return [super init];
737}
738
739- (void)disconnect
740{
741    [NSObject cancelPreviousPerformRequestsWithTarget:self];
742    m_callback = 0;
743}
744
745- (void)metadataLoaded
746{
747    if (!m_callback)
748        return;
749
750    m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::AssetMetadataLoaded);
751}
752
753- (void)playableKnown
754{
755    if (!m_callback)
756        return;
757
758    m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::AssetPlayabilityKnown);
759}
760
761- (void)timeChanged:(double)time
762{
763    if (!m_callback)
764        return;
765
766    m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::PlayerTimeChanged, time);
767}
768
769- (void)seekCompleted:(BOOL)finished
770{
771    if (!m_callback)
772        return;
773
774    m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::SeekCompleted, static_cast<bool>(finished));
775}
776
777- (void)didEnd:(NSNotification *)unusedNotification
778{
779    UNUSED_PARAM(unusedNotification);
780    if (!m_callback)
781        return;
782    m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemDidPlayToEndTime);
783}
784
785- (void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary *)change context:(MediaPlayerAVFoundationObservationContext)context
786{
787    UNUSED_PARAM(change);
788
789    LOG(Media, "WebCoreAVFMovieObserver:observeValueForKeyPath(%p) - keyPath = %s", self, [keyPath UTF8String]);
790
791    if (!m_callback)
792        return;
793
794    if (context == MediaPlayerAVFoundationObservationContextPlayerItem) {
795        // A value changed for an AVPlayerItem
796        if ([keyPath isEqualToString:@"status"])
797            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemStatusChanged);
798        else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
799            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackLikelyToKeepUpChanged);
800        else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
801            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackBufferEmptyChanged);
802        else if ([keyPath isEqualToString:@"playbackBufferFull"])
803            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackBufferFullChanged);
804        else if ([keyPath isEqualToString:@"asset"])
805            m_callback->setAsset([object asset]);
806        else if ([keyPath isEqualToString:@"loadedTimeRanges"])
807            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemLoadedTimeRangesChanged);
808        else if ([keyPath isEqualToString:@"seekableTimeRanges"])
809            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemSeekableTimeRangesChanged);
810        else if ([keyPath isEqualToString:@"tracks"])
811            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemTracksChanged);
812        else if ([keyPath isEqualToString:@"presentationSize"])
813            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemPresentationSizeChanged);
814
815        return;
816    }
817
818    if (context == MediaPlayerAVFoundationObservationContextPlayer) {
819        // A value changed for an AVPlayer.
820        if ([keyPath isEqualToString:@"rate"])
821            m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::PlayerRateChanged);
822}
823}
824
825@end
826
827#endif
828