/* * Copyright (C) 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #if ENABLE(VIDEO) && PLATFORM(IOS) #import "MediaPlayerPrivateIOS.h" #import "GraphicsLayerCA.h" #import "InbandTextTrackPrivateAVFIOS.h" #import "Logging.h" #import "PlatformTextTrack.h" #import "TextTrackRepresentation.h" #import "TimeRanges.h" #import "URL.h" #import "WAKAppKitStubs.h" #import "WebCoreThreadRun.h" #import #import #import using namespace WebCore; using namespace std; @interface WebCoreMediaPlayerNotificationHelper : NSObject { MediaPlayerPrivateIOS* _client; BOOL _deferredPropertiesScheduled; } - (id)initWithClient:(MediaPlayerPrivateIOS *)client; - (void)disconnect; - (void)cancelPendingRequests; - (void)delayNotification:(int)notification; - (void)deliverNotification:(NSNumber *)notification; - (void)schedulePrepareToPlayWithOptionalDelay:(NSNumber *)shouldDelay; - (void)scheduleDeferredPropertiesWithOptionalDelay:(NSNumber *)shouldDelay; - (void)pluginElementInBandAlternateTextTracksDidChange:(NSArray *)tracks; - (void)pluginElementDidSelectTextTrack:(NSString *)trackId; - (void)pluginElementDidOutputAttributedStrings:(NSArray *)strings nativeSampleBuffers:(NSArray *)buffers forTime:(NSTimeInterval)time; @end // Private selectors implemented by the media player proxy object returned by the plug-in. @interface NSObject (WebMediaPlayerProxy_Extras) - (void)_attributeChanged:(NSString *)name value:(NSString *)value; - (BOOL)_readyForPlayback; @end namespace WebCore { const int kRequiredHelperInterfaceVersion = 1; static NSString * const DeferredPropertyControlsKey = @"controls"; static NSString * const DeferredPropertyPosterKey = @"poster"; static NSString * const DeferredPropertySrcKey = @"src"; static NSString * const DeferredPlayKey = @"play"; static NSString * const DeferredPropertySelectedTrackKey = @"selectedtrack"; static NSString * const DeferredPropertyOutOfBandTracksKey = @"outofbandtracks"; #if ENABLE(IOS_AIRPLAY) static NSString * const DeferredWirelessVideoPlaybackDisabled = @"wirelessVideoPlaybackDisabled"; #endif static NSString * const TextTrackDisplayNameKey = @"displayname"; static NSString * const TextTrackLocaleIdentifierKey = @"localeidentifier"; static NSString * const TextTrackSubtitlesKey = @"subtitles"; static NSString * const TextTrackCaptionsKey = @"captions"; static NSString * const TextTrackTypeKey = @"type"; static NSString * const TextTrackIDKey = @"id"; const int TextTrackOffMenuItemID = -1; const int TextTrackAutoMenuItemID = -2; MediaPlayerPrivateIOS::PlatformTextTrackMenuInterfaceIOS::PlatformTextTrackMenuInterfaceIOS(MediaPlayerPrivateIOS* owner) : m_client(nullptr) , m_owner(owner) { } MediaPlayerPrivateIOS::PlatformTextTrackMenuInterfaceIOS::~PlatformTextTrackMenuInterfaceIOS() { m_owner = nullptr; m_client = nullptr; } PassOwnPtr MediaPlayerPrivateIOS::create(MediaPlayer* player) { return adoptPtr(new MediaPlayerPrivateIOS(player)); } void MediaPlayerPrivateIOS::registerMediaEngine(MediaEngineRegistrar registrar) { if (isAvailable()) registrar(create, getSupportedTypes, supportsType, 0, 0, 0); } MediaPlayerPrivateIOS::MediaPlayerPrivateIOS(MediaPlayer* player) : m_mediaPlayer(player) , m_objcHelper(adoptNS([[WebCoreMediaPlayerNotificationHelper alloc] initWithClient:this])) , m_networkState(MediaPlayer::Empty) , m_readyState(MediaPlayer::HaveNothing) #if ENABLE(VIDEO_TRACK) , m_currentTrack(nullptr) #endif , m_delayCallbacks(0) , m_changingVolume(0) , m_bytesLoadedAtLastDidLoadingProgress(0) , m_requestedRate(0) , m_bufferingState(Empty) , m_visible(false) , m_usingNetwork(false) , m_inFullScreen(false) , m_shouldPrepareToPlay(false) , m_preparingToPlay(false) , m_pauseRequested(false) { } MediaPlayerPrivateIOS::~MediaPlayerPrivateIOS() { clearTextTracks(); [m_objcHelper.get() disconnect]; if ([m_mediaPlayerHelper.get() respondsToSelector:@selector(_setDelegate:)]) [m_mediaPlayerHelper.get() _setDelegate:nil]; [m_mediaPlayerHelper.get() _disconnect]; } void MediaPlayerPrivateIOS::setMediaPlayerProxy(WebMediaPlayerProxy *proxy) { if (m_mediaPlayerHelper) [m_mediaPlayerHelper.get() _disconnect]; m_mediaPlayerHelper = nullptr; if (proxy) { // don't try to use a helper if the interface version is too low if ([proxy _interfaceVersion] < kRequiredHelperInterfaceVersion) { NSLog(@"Media player helper interface version too low, exiting."); return; } m_mediaPlayerHelper = adoptNS(static_cast([proxy retain])); if ([m_mediaPlayerHelper.get() respondsToSelector:@selector(_setDelegate:)]) [m_mediaPlayerHelper.get() _setDelegate:m_objcHelper.get()]; acceleratedRenderingStateChanged(); } processPendingRequests(); } void MediaPlayerPrivateIOS::load(const String& url) { NSURL *nsURL = URL(ParsedURLString, url); if (!m_mediaPlayerHelper) { addDeferredRequest(DeferredPropertySrcKey, [nsURL absoluteString]); return; } [m_deferredProperties.get() removeObjectForKey:DeferredPropertySrcKey]; setDelayCallbacks(true); { if (m_networkState != MediaPlayer::Loading) { m_networkState = MediaPlayer::Loading; m_mediaPlayer->networkStateChanged(); } if (m_readyState != MediaPlayer::HaveNothing) { m_readyState = MediaPlayer::HaveNothing; m_mediaPlayer->readyStateChanged(); } m_pauseRequested = false; m_preparingToPlay = false; [m_mediaPlayerHelper.get() _cancelLoad]; [m_mediaPlayerHelper.get() _load:nsURL]; } setDelayCallbacks(false); } void MediaPlayerPrivateIOS::cancelLoad() { [m_objcHelper.get() cancelPendingRequests]; [m_deferredProperties.get() removeAllObjects]; if (!m_mediaPlayerHelper) return; [m_mediaPlayerHelper.get() _cancelLoad]; } void MediaPlayerPrivateIOS::addDeferredRequest(NSString *name, id value) { if (!m_deferredProperties) m_deferredProperties = adoptNS([[NSMutableDictionary alloc] init]); [m_deferredProperties.get() setObject:value ? value : @"" forKey:name]; } void MediaPlayerPrivateIOS::processDeferredRequests() { if (![m_deferredProperties.get() count]) return; setDelayCallbacks(true); { RetainPtr localProperties = adoptNS([[NSDictionary alloc] initWithDictionary:m_deferredProperties.get()]); [m_deferredProperties.get() removeAllObjects]; // Always set the src first. id value = [localProperties.get() objectForKey:DeferredPropertySrcKey]; if (value) load(value); for (NSString *name in localProperties.get()) { value = [localProperties.get() objectForKey:name]; LOG(Media, "MediaPlayerPrivateIOS::processDeferredRequests(%p) - processing %s", this, [name UTF8String]); if ([name isEqualToString:DeferredPropertyPosterKey]) setPoster(value); else if ([name isEqualToString:DeferredPropertyControlsKey]) setControls([value boolValue]); else if ([name isEqualToString:DeferredPlayKey]) play(); else if ([name isEqualToString:DeferredPropertyOutOfBandTracksKey]) setOutOfBandTextTracks(value); else if ([name isEqualToString:DeferredPropertySelectedTrackKey]) setSelectedTextTrack(value); #if ENABLE(IOS_AIRPLAY) else if ([name isEqualToString:DeferredWirelessVideoPlaybackDisabled]) setWirelessVideoPlaybackDisabled([value boolValue]); #endif else if (![name isEqualToString:DeferredPropertySrcKey]) attributeChanged(name, value); } } setDelayCallbacks(false); } void MediaPlayerPrivateIOS::setPoster(const String& url) { setDelayCallbacks(true); { NSURL *nsURL = URL(ParsedURLString, url); if (m_mediaPlayerHelper) { [m_deferredProperties.get() removeObjectForKey:DeferredPropertyPosterKey]; [m_mediaPlayerHelper.get() _setPoster:nsURL]; } else addDeferredRequest(DeferredPropertyPosterKey, url); } setDelayCallbacks(false); } void MediaPlayerPrivateIOS::setControls(bool controls) { setDelayCallbacks(true); { if (m_mediaPlayerHelper) { [m_deferredProperties.get() removeObjectForKey:DeferredPropertyControlsKey]; [m_mediaPlayerHelper.get() _setControls:controls]; } else addDeferredRequest(DeferredPropertyControlsKey, [NSNumber numberWithBool:controls]); } setDelayCallbacks(false); } void MediaPlayerPrivateIOS::processPendingRequests() { if (m_shouldPrepareToPlay && !m_preparingToPlay) [m_objcHelper.get() schedulePrepareToPlayWithOptionalDelay:[NSNumber numberWithBool:YES]]; if ([m_deferredProperties.get() count]) [m_objcHelper.get() scheduleDeferredPropertiesWithOptionalDelay:[NSNumber numberWithBool:YES]]; m_shouldPrepareToPlay = false; } void MediaPlayerPrivateIOS::prepareToPlay() { if (!m_mediaPlayerHelper) { // Not hooked up to the plug-in yet, remember this request. m_shouldPrepareToPlay = true; return; } if (m_preparingToPlay || m_readyState >= MediaPlayer::HaveFutureData) return; m_shouldPrepareToPlay = false; m_preparingToPlay = true; setDelayCallbacks(true); [m_mediaPlayerHelper.get() _prepareForPlayback]; setDelayCallbacks(false); } void MediaPlayerPrivateIOS::play() { if (!m_mediaPlayerHelper) { addDeferredRequest(DeferredPlayKey, nil); return; } m_pauseRequested = false; setDelayCallbacks(true); [m_mediaPlayerHelper.get() _play]; setDelayCallbacks(false); } void MediaPlayerPrivateIOS::pause() { if (!m_mediaPlayerHelper) return; m_pauseRequested = true; setDelayCallbacks(true); [m_mediaPlayerHelper.get() _pause]; setDelayCallbacks(false); } float MediaPlayerPrivateIOS::duration() const { if (!m_mediaPlayerHelper) return 0; float duration = static_cast([m_mediaPlayerHelper.get() _duration]); return duration == -1 ? numeric_limits::infinity() : duration; } float MediaPlayerPrivateIOS::currentTime() const { if (!m_mediaPlayerHelper) return 0; return static_cast([m_mediaPlayerHelper.get() _currentTime]); } void MediaPlayerPrivateIOS::seek(float time) { if (!m_mediaPlayerHelper) return; float duration = this->duration(); [m_mediaPlayerHelper.get() _setCurrentTime:(time > duration ? duration : time)]; } void MediaPlayerPrivateIOS::setEndTime(float time) { if (!m_mediaPlayerHelper) return; [m_mediaPlayerHelper.get() _setEndTime:time]; } bool MediaPlayerPrivateIOS::paused() const { if (!m_mediaPlayerHelper) return true; return !rate(); } bool MediaPlayerPrivateIOS::seeking() const { if (!m_mediaPlayerHelper) return false; return [m_mediaPlayerHelper.get() _seeking]; } IntSize MediaPlayerPrivateIOS::naturalSize() const { if (!m_mediaPlayerHelper) return IntSize(); NSSize size = [m_mediaPlayerHelper.get() _naturalSize]; return IntSize(size); } bool MediaPlayerPrivateIOS::hasVideo() const { if (!m_mediaPlayerHelper) return false; return [m_mediaPlayerHelper.get() _hasVideo]; } bool MediaPlayerPrivateIOS::hasAudio() const { if (!m_mediaPlayerHelper) return false; return [m_mediaPlayerHelper.get() _hasAudio]; } float MediaPlayerPrivateIOS::volume() const { if (!m_mediaPlayerHelper) return false; return [m_mediaPlayerHelper.get() _volume]; } void MediaPlayerPrivateIOS::setVolume(float inVolume) { if (!m_mediaPlayerHelper || m_changingVolume) return; [m_mediaPlayerHelper.get() _setVolume:inVolume]; } float MediaPlayerPrivateIOS::rate() const { if (!m_mediaPlayerHelper) return false; return [m_mediaPlayerHelper.get() _rate]; } void MediaPlayerPrivateIOS::setRate(float inRate) { if (!m_mediaPlayerHelper) return; m_requestedRate = inRate; [m_mediaPlayerHelper.get() _setRate:inRate]; } void MediaPlayerPrivateIOS::setMuted(bool inMute) { if (!m_mediaPlayerHelper) return; [m_mediaPlayerHelper.get() _setMuted:inMute]; } int MediaPlayerPrivateIOS::dataRate() const { if (!m_mediaPlayerHelper) return 0; return [m_mediaPlayerHelper.get() _dataRate]; } MediaPlayer::NetworkState MediaPlayerPrivateIOS::networkState() const { return m_networkState; } MediaPlayer::ReadyState MediaPlayerPrivateIOS::readyState() const { return m_readyState; } float MediaPlayerPrivateIOS::maxTimeBuffered() const { if (!m_mediaPlayerHelper) return 0; return [m_mediaPlayerHelper.get() _maxTimeBuffered]; } bool MediaPlayerPrivateIOS::didLoadingProgress() const { if (!m_mediaPlayerHelper) return false; unsigned currentBytesLoaded = [m_mediaPlayerHelper.get() _bytesLoaded]; bool didLoadingProgress = currentBytesLoaded != m_bytesLoadedAtLastDidLoadingProgress; m_bytesLoadedAtLastDidLoadingProgress = currentBytesLoaded; return didLoadingProgress; } bool MediaPlayerPrivateIOS::totalBytesKnown() const { return totalBytes() > 0; } unsigned MediaPlayerPrivateIOS::totalBytes() const { if (!m_mediaPlayerHelper) return 0; return [m_mediaPlayerHelper.get() _totalBytes]; } float MediaPlayerPrivateIOS::maxTimeSeekable() const { if (!m_mediaPlayerHelper) return 0.0f; return [m_mediaPlayerHelper.get() _maxTimeSeekable]; } PassRefPtr MediaPlayerPrivateIOS::buffered() const { RefPtr timeRanges = TimeRanges::create(); if (!m_mediaPlayerHelper) return timeRanges.release(); NSArray *ranges = [m_mediaPlayerHelper.get() _bufferedTimeRanges]; if (!ranges) return timeRanges.release(); float timeRange[2]; int count = [ranges count]; for (int i = 0; i < count; ++i) { NSData *range = [ranges objectAtIndex:i]; [range getBytes:timeRange length:(NSUInteger)sizeof(timeRange)]; timeRanges->add(timeRange[0], timeRange[1]); } return timeRanges.release(); } void MediaPlayerPrivateIOS::setSize(const IntSize&) { // FIXME: Do we need to do anything here? } void MediaPlayerPrivateIOS::setVisible(bool visible) { // FIXME: Do we need to do anything more here? if (m_visible != visible) m_visible = visible; } void MediaPlayerPrivateIOS::paint(GraphicsContext*, const IntRect&) { // Nothing to do here. } static HashSet mimeTypeCache() { static NeverDestroyed> typeCache; static bool typeListInitialized = false; if (typeListInitialized) return typeCache; typeListInitialized = true; // FIXME: Should get these from the helper, but we don't have an instance when this is called. static const char* typeList[] = { "application/x-mpegurl", "application/vnd.apple.mpegurl", "audio/3gpp", "audio/3gpp2", "audio/aac", "audio/aiff", "audio/amr", "audio/basic", "audio/mp3", "audio/mp4", "audio/mpeg", "audio/mpegurl", "audio/mpeg3", "audio/scpls", "audio/wav", "audio/x-aac", "audio/x-aiff", "audio/x-caf", "audio/x-m4a", "audio/x-m4b", "audio/x-m4p", "audio/x-m4r", "audio/x-mp3", "audio/x-mpeg", "audio/x-mpeg3", "audio/x-mpegurl", "audio/x-scpls", "audio/x-wav", "video/3gpp", "video/3gpp2", "video/mp4", "video/quicktime", "video/x-m4v", // FIXME: This is added to restore old behavior for: // Embedded videos don't play on spiegel.de (.html file redirects) // However, a better fix is tracked by: //