Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm (158744 => 158745)
--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm 2013-11-06 15:54:08 UTC (rev 158744)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm 2013-11-06 16:15:16 UTC (rev 158745)
@@ -57,6 +57,7 @@
#import <runtime/Uint32Array.h>
#import <runtime/Uint8Array.h>
#import <wtf/CurrentTime.h>
+#import <wtf/Functional.h>
#import <wtf/text/CString.h>
#import <AVFoundation/AVFoundation.h>
@@ -248,6 +249,14 @@
, m_loaderDelegate(adoptNS([[WebCoreAVFLoaderDelegate alloc] initWithCallback:this]))
#endif
, m_currentTrack(0)
+ , m_cachedDuration(MediaPlayer::invalidTime())
+ , m_cachedRate(0)
+ , m_pendingStatusChanges(0)
+ , m_cachedItemStatus(MediaPlayerAVPlayerItemStatusDoesNotExist)
+ , m_cachedLikelyToKeepUp(false)
+ , m_cachedBufferEmpty(false)
+ , m_cachedBufferFull(false)
+ , m_cachedHasEnabledAudio(false)
{
#if ENABLE(ENCRYPTED_MEDIA_V2)
playerToPrivateMap().set(player, this);
@@ -304,6 +313,17 @@
[m_avPlayer.get() removeObserver:m_objcObserver.get() forKeyPath:@"rate"];
m_avPlayer = nil;
}
+
+ // Reset cached properties
+ m_pendingStatusChanges = 0;
+ m_cachedItemStatus = MediaPlayerAVPlayerItemStatusDoesNotExist;
+ m_cachedSeekableRanges = nullptr;
+ m_cachedLoadedRanges = nullptr;
+ m_cachedTracks = nullptr;
+ m_cachedHasEnabledAudio = false;
+ m_cachedPresentationSize = FloatSize();
+ m_cachedDuration = 0;
+
setIgnoreLoadStateChanges(false);
}
@@ -452,7 +472,7 @@
setDelayCallbacks(true);
m_avPlayer = adoptNS([[AVPlayer alloc] init]);
- [m_avPlayer.get() addObserver:m_objcObserver.get() forKeyPath:@"rate" options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayer];
+ [m_avPlayer.get() addObserver:m_objcObserver.get() forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:(void *)MediaPlayerAVFoundationObservationContextPlayer];
#if HAVE(AVFOUNDATION_MEDIA_SELECTION_GROUP) && HAVE(AVFOUNDATION_LEGIBLE_OUTPUT_SUPPORT)
[m_avPlayer.get() setAppliesMediaSelectionCriteriaAutomatically:YES];
@@ -478,8 +498,9 @@
[[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(didEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:m_avPlayerItem.get()];
+ NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionPrior;
for (NSString *keyName in itemKVOProperties())
- [m_avPlayerItem.get() addObserver:m_objcObserver.get() forKeyPath:keyName options:nil context:(void *)MediaPlayerAVFoundationObservationContextPlayerItem];
+ [m_avPlayerItem.get() addObserver:m_objcObserver.get() forKeyPath:keyName options:options context:(void *)MediaPlayerAVFoundationObservationContextPlayerItem];
if (m_avPlayer)
[m_avPlayer.get() replaceCurrentItemWithPlayerItem:m_avPlayerItem.get()];
@@ -525,16 +546,15 @@
if (!m_avPlayerItem)
return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusDoesNotExist;
- AVPlayerItemStatus status = [m_avPlayerItem.get() status];
- if (status == AVPlayerItemStatusUnknown)
+ if (m_cachedItemStatus == AVPlayerItemStatusUnknown)
return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusUnknown;
- if (status == AVPlayerItemStatusFailed)
+ if (m_cachedItemStatus == AVPlayerItemStatusFailed)
return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusFailed;
- if ([m_avPlayerItem.get() isPlaybackLikelyToKeepUp])
+ if (m_cachedLikelyToKeepUp)
return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackLikelyToKeepUp;
- if ([m_avPlayerItem.get() isPlaybackBufferFull])
+ if (m_cachedBufferFull)
return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferFull;
- if ([m_avPlayerItem.get() isPlaybackBufferEmpty])
+ if (m_cachedBufferEmpty)
return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusPlaybackBufferEmpty;
return MediaPlayerPrivateAVFoundation::MediaPlayerAVPlayerItemStatusReadyToPlay;
@@ -570,6 +590,7 @@
return;
setDelayCallbacks(true);
+ m_cachedRate = requestedRate();
[m_avPlayer.get() setRate:requestedRate()];
setDelayCallbacks(false);
}
@@ -581,6 +602,7 @@
return;
setDelayCallbacks(true);
+ m_cachedRate = 0;
[m_avPlayer.get() setRate:nil];
setDelayCallbacks(false);
}
@@ -658,6 +680,7 @@
void MediaPlayerPrivateAVFoundationObjC::updateRate()
{
setDelayCallbacks(true);
+ m_cachedRate = requestedRate();
[m_avPlayer.get() setRate:requestedRate()];
setDelayCallbacks(false);
}
@@ -667,7 +690,7 @@
if (!metaDataAvailable())
return 0;
- return [m_avPlayer.get() rate];
+ return m_cachedRate;
}
PassRefPtr<TimeRanges> MediaPlayerPrivateAVFoundationObjC::platformBufferedTimeRanges() const
@@ -677,8 +700,7 @@
if (!m_avPlayerItem)
return timeRanges.release();
- NSArray *loadedRanges = [m_avPlayerItem.get() loadedTimeRanges];
- for (NSValue *thisRangeValue in loadedRanges) {
+ for (NSValue *thisRangeValue in m_cachedLoadedRanges.get()) {
CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
if (CMTIMERANGE_IS_VALID(timeRange) && !CMTIMERANGE_IS_EMPTY(timeRange)) {
float rangeStart = narrowPrecisionToFloat(CMTimeGetSeconds(timeRange.start));
@@ -691,13 +713,12 @@
double MediaPlayerPrivateAVFoundationObjC::platformMinTimeSeekable() const
{
- NSArray *seekableRanges = [m_avPlayerItem.get() seekableTimeRanges];
- if (!seekableRanges || ![seekableRanges count])
+ if (!m_cachedSeekableRanges || ![m_cachedSeekableRanges count])
return 0;
double minTimeSeekable = std::numeric_limits<double>::infinity();
bool hasValidRange = false;
- for (NSValue *thisRangeValue in seekableRanges) {
+ for (NSValue *thisRangeValue in m_cachedSeekableRanges.get()) {
CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
continue;
@@ -712,12 +733,11 @@
double MediaPlayerPrivateAVFoundationObjC::platformMaxTimeSeekable() const
{
- NSArray *seekableRanges = [m_avPlayerItem.get() seekableTimeRanges];
- if (!seekableRanges)
- return 0;
+ if (!m_cachedSeekableRanges)
+ m_cachedSeekableRanges = [m_avPlayerItem seekableTimeRanges];
double maxTimeSeekable = 0;
- for (NSValue *thisRangeValue in seekableRanges) {
+ for (NSValue *thisRangeValue in m_cachedSeekableRanges.get()) {
CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
continue;
@@ -731,12 +751,19 @@
float MediaPlayerPrivateAVFoundationObjC::platformMaxTimeLoaded() const
{
- NSArray *loadedRanges = [m_avPlayerItem.get() loadedTimeRanges];
- if (!loadedRanges)
+#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED <= 1080
+ // AVFoundation on Mountain Lion will occasionally not send a KVO notification
+ // when loadedTimeRanges changes when there is no video output. In that case
+ // update the cached value explicitly.
+ if (!hasLayerRenderer() && !hasContextRenderer())
+ m_cachedLoadedRanges = [m_avPlayerItem loadedTimeRanges];
+#endif
+
+ if (!m_cachedLoadedRanges)
return 0;
float maxTimeLoaded = 0;
- for (NSValue *thisRangeValue in loadedRanges) {
+ for (NSValue *thisRangeValue in m_cachedLoadedRanges.get()) {
CMTimeRange timeRange = [thisRangeValue CMTimeRangeValue];
if (!CMTIMERANGE_IS_VALID(timeRange) || CMTIMERANGE_IS_EMPTY(timeRange))
continue;
@@ -755,9 +782,8 @@
return 0;
long long totalMediaSize = 0;
- NSArray *tracks = [m_avAsset.get() tracks];
- for (AVAssetTrack *thisTrack in tracks)
- totalMediaSize += [thisTrack totalSampleDataLength];
+ for (AVPlayerItemTrack *thisTrack in m_cachedTracks.get())
+ totalMediaSize += [[thisTrack assetTrack] totalSampleDataLength];
return static_cast<unsigned>(totalMediaSize);
}
@@ -1032,8 +1058,7 @@
} else {
bool hasVideo = false;
bool hasAudio = false;
- NSArray *tracks = [m_avPlayerItem.get() tracks];
- for (AVPlayerItemTrack *track in tracks) {
+ for (AVPlayerItemTrack *track in m_cachedTracks.get()) {
if ([track isEnabled]) {
AVAssetTrack *assetTrack = [track assetTrack];
if ([[assetTrack mediaType] isEqualToString:AVMediaTypeVideo])
@@ -1131,12 +1156,12 @@
void MediaPlayerPrivateAVFoundationObjC::updateAudioTracks()
{
- determineChangedTracksFromNewTracksAndOldItems([m_avPlayerItem tracks], AVMediaTypeAudio, m_audioTracks, &AudioTrackPrivateAVFObjC::create, player(), &MediaPlayer::removeAudioTrack, &MediaPlayer::addAudioTrack);
+ determineChangedTracksFromNewTracksAndOldItems(m_cachedTracks.get(), AVMediaTypeAudio, m_audioTracks, &AudioTrackPrivateAVFObjC::create, player(), &MediaPlayer::removeAudioTrack, &MediaPlayer::addAudioTrack);
}
void MediaPlayerPrivateAVFoundationObjC::updateVideoTracks()
{
- determineChangedTracksFromNewTracksAndOldItems([m_avPlayerItem tracks], AVMediaTypeVideo, m_videoTracks, &VideoTrackPrivateAVFObjC::create, player(), &MediaPlayer::removeVideoTrack, &MediaPlayer::addVideoTrack);
+ determineChangedTracksFromNewTracksAndOldItems(m_cachedTracks.get(), AVMediaTypeVideo, m_videoTracks, &VideoTrackPrivateAVFObjC::create, player(), &MediaPlayer::removeVideoTrack, &MediaPlayer::addVideoTrack);
}
#endif // ENABLE(VIDEO_TRACK)
@@ -1145,22 +1170,21 @@
if (!m_avAsset)
return;
- NSArray *tracks = [m_avAsset.get() tracks];
-
// Some assets don't report track properties until they are completely ready to play, but we
// want to report a size as early as possible so use presentationSize when an asset has no tracks.
- if (m_avPlayerItem && ![tracks count]) {
- setNaturalSize(IntSize([m_avPlayerItem.get() presentationSize]));
+ if (m_avPlayerItem && ![m_cachedTracks count]) {
+ setNaturalSize(IntSize(m_cachedPresentationSize));
return;
}
// AVAsset's 'naturalSize' property only considers the movie's first video track, so we need to compute
// the union of all visual track rects.
CGRect trackUnionRect = CGRectZero;
- for (AVAssetTrack *track in tracks) {
- CGSize trackSize = [track naturalSize];
+ for (AVPlayerItemTrack *track in m_cachedTracks.get()) {
+ AVAssetTrack* assetTrack = [track assetTrack];
+ CGSize trackSize = [assetTrack naturalSize];
CGRect trackRect = CGRectMake(0, 0, trackSize.width, trackSize.height);
- trackUnionRect = CGRectUnion(trackUnionRect, CGRectApplyAffineTransform(trackRect, [track preferredTransform]));
+ trackUnionRect = CGRectUnion(trackUnionRect, CGRectApplyAffineTransform(trackRect, [assetTrack preferredTransform]));
}
// The movie is always displayed at 0,0 so move the track rect to the origin before using width and height.
@@ -1462,8 +1486,7 @@
#endif
Vector<RefPtr<InbandTextTrackPrivateAVF>> removedTextTracks = m_textTracks;
- NSArray *tracks = [m_avPlayerItem.get() tracks];
- for (AVPlayerItemTrack *playerItemTrack in tracks) {
+ for (AVPlayerItemTrack *playerItemTrack in m_cachedTracks.get()) {
AVAssetTrack *assetTrack = [playerItemTrack assetTrack];
if (![[assetTrack mediaType] isEqualToString:AVMediaTypeClosedCaption])
@@ -1627,6 +1650,110 @@
return m_languageOfPrimaryAudioTrack;
}
+void MediaPlayerPrivateAVFoundationObjC::playerItemStatusDidChange(int status)
+{
+ m_cachedItemStatus = status;
+
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpWillChange()
+{
+ m_pendingStatusChanges++;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpDidChange(bool likelyToKeepUp)
+{
+ m_cachedLikelyToKeepUp = likelyToKeepUp;
+
+ ASSERT(m_pendingStatusChanges);
+ if (!--m_pendingStatusChanges)
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyWillChange()
+{
+ m_pendingStatusChanges++;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyDidChange(bool bufferEmpty)
+{
+ m_cachedBufferEmpty = bufferEmpty;
+
+ ASSERT(m_pendingStatusChanges);
+ if (!--m_pendingStatusChanges)
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackBufferFullWillChange()
+{
+ m_pendingStatusChanges++;
+}
+
+void MediaPlayerPrivateAVFoundationObjC::playbackBufferFullDidChange(bool bufferFull)
+{
+ m_cachedBufferFull = bufferFull;
+
+ ASSERT(m_pendingStatusChanges);
+ if (!--m_pendingStatusChanges)
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange(NSArray* seekableRanges)
+{
+ m_cachedSeekableRanges = seekableRanges;
+
+ seekableTimeRangesChanged();
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::loadedTimeRangesDidChange(NSArray* loadedRanges)
+{
+ m_cachedLoadedRanges = loadedRanges;
+
+ loadedTimeRangesChanged();
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::tracksDidChange(NSArray* tracks)
+{
+ m_cachedTracks = tracks;
+
+ tracksChanged();
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::hasEnabledAudioDidChange(bool hasEnabledAudio)
+{
+ m_cachedHasEnabledAudio = hasEnabledAudio;
+
+ tracksChanged();
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::presentationSizeDidChange(FloatSize size)
+{
+ m_cachedPresentationSize = size;
+
+ sizeChanged();
+ updateStates();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::durationDidChange(double duration)
+{
+ m_cachedDuration = duration;
+
+ invalidateCachedDuration();
+}
+
+void MediaPlayerPrivateAVFoundationObjC::rateDidChange(double rate)
+{
+ m_cachedRate = rate;
+
+ updateStates();
+ rateChanged();
+}
+
NSArray* assetMetadataKeyNames()
{
static NSArray* keys;
@@ -1717,46 +1844,63 @@
- (void)observeValueForKeyPath:keyPath ofObject:(id)object change:(NSDictionary *)change context:(MediaPlayerAVFoundationObservationContext)context
{
- UNUSED_PARAM(change);
+ UNUSED_PARAM(object);
+ id newValue = [change valueForKey:NSKeyValueChangeNewKey];
LOG(Media, "WebCoreAVFMovieObserver:observeValueForKeyPath(%p) - keyPath = %s", self, [keyPath UTF8String]);
if (!m_callback)
return;
- if (context == MediaPlayerAVFoundationObservationContextPlayerItem) {
+ bool willChange = [[change valueForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
+
+ WTF::Function<void ()> function;
+
+ if (context == MediaPlayerAVFoundationObservationContextPlayerItem && willChange) {
+ if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpWillChange, m_callback);
+ else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyWillChange, m_callback);
+ else if ([keyPath isEqualToString:@"playbackBufferFull"])
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackBufferFullWillChange, m_callback);
+ }
+
+ if (context == MediaPlayerAVFoundationObservationContextPlayerItem && !willChange) {
// A value changed for an AVPlayerItem
if ([keyPath isEqualToString:@"status"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemStatusChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playerItemStatusDidChange, m_callback, [newValue intValue]);
else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackLikelyToKeepUpChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackLikelyToKeepUpDidChange, m_callback, [newValue boolValue]);
else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackBufferEmptyChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackBufferEmptyDidChange, m_callback, [newValue boolValue]);
else if ([keyPath isEqualToString:@"playbackBufferFull"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemIsPlaybackBufferFullChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::playbackBufferFullDidChange, m_callback, [newValue boolValue]);
else if ([keyPath isEqualToString:@"asset"])
- m_callback->setAsset([object asset]);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::setAsset, m_callback, newValue);
else if ([keyPath isEqualToString:@"loadedTimeRanges"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemLoadedTimeRangesChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::loadedTimeRangesDidChange, m_callback, newValue);
else if ([keyPath isEqualToString:@"seekableTimeRanges"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemSeekableTimeRangesChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesDidChange, m_callback, newValue);
else if ([keyPath isEqualToString:@"tracks"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemTracksChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::tracksDidChange, m_callback, newValue);
else if ([keyPath isEqualToString:@"hasEnabledAudio"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemTracksChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::hasEnabledAudioDidChange, m_callback, [newValue boolValue]);
else if ([keyPath isEqualToString:@"presentationSize"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::ItemPresentationSizeChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::presentationSizeDidChange, m_callback, FloatSize([newValue sizeValue]));
else if ([keyPath isEqualToString:@"duration"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::DurationChanged);
-
- return;
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::durationDidChange, m_callback, [newValue doubleValue]);
}
- if (context == MediaPlayerAVFoundationObservationContextPlayer) {
+ if (context == MediaPlayerAVFoundationObservationContextPlayer && !willChange) {
// A value changed for an AVPlayer.
if ([keyPath isEqualToString:@"rate"])
- m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification::PlayerRateChanged);
+ function = WTF::bind(&MediaPlayerPrivateAVFoundationObjC::rateDidChange, m_callback, [newValue doubleValue]);
}
+
+ if (function.isNull())
+ return;
+
+ m_callback->scheduleMainThreadNotification(MediaPlayerPrivateAVFoundation::Notification(function));
}
#if HAVE(AVFOUNDATION_MEDIA_SELECTION_GROUP)