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