/* * Copyright (C) 2007, 2008, 2009, 2010, 2011 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. */ #include "config.h" #if ENABLE(VIDEO) #include "MediaPlayerPrivateQuickTimeVisualContext.h" #include "ApplicationCacheHost.h" #include "ApplicationCacheResource.h" #include "Cookie.h" #include "CookieJar.h" #include "DocumentLoader.h" #include "Frame.h" #include "FrameView.h" #include "GraphicsContext.h" #include "KURL.h" #include "MediaPlayerPrivateTaskTimer.h" #include "QTCFDictionary.h" #include "QTDecompressionSession.h" #include "QTMovie.h" #include "QTMovieTask.h" #include "QTMovieVisualContext.h" #include "ScrollView.h" #include "Settings.h" #include "SoftLinking.h" #include "TimeRanges.h" #include "Timer.h" #include #include #include #include #include #include #include #include #include #include #include #include #if USE(ACCELERATED_COMPOSITING) #include "PlatformCALayer.h" #include "WKCAImageQueue.h" #endif using namespace std; namespace WebCore { static CGImageRef CreateCGImageFromPixelBuffer(QTPixelBuffer buffer); static bool requiredDllsAvailable(); SOFT_LINK_LIBRARY(Wininet) SOFT_LINK(Wininet, InternetSetCookieExW, DWORD, WINAPI, (LPCWSTR lpszUrl, LPCWSTR lpszCookieName, LPCWSTR lpszCookieData, DWORD dwFlags, DWORD_PTR dwReserved), (lpszUrl, lpszCookieName, lpszCookieData, dwFlags, dwReserved)) // Interface declaration for MediaPlayerPrivateQuickTimeVisualContext's QTMovieClient aggregate class MediaPlayerPrivateQuickTimeVisualContext::MovieClient : public QTMovieClient { public: MovieClient(MediaPlayerPrivateQuickTimeVisualContext* parent) : m_parent(parent) {} virtual ~MovieClient() { m_parent = 0; } virtual void movieEnded(QTMovie*); virtual void movieLoadStateChanged(QTMovie*); virtual void movieTimeChanged(QTMovie*); private: MediaPlayerPrivateQuickTimeVisualContext* m_parent; }; #if USE(ACCELERATED_COMPOSITING) class MediaPlayerPrivateQuickTimeVisualContext::LayerClient : public PlatformCALayerClient { public: LayerClient(MediaPlayerPrivateQuickTimeVisualContext* parent) : m_parent(parent) {} virtual ~LayerClient() { m_parent = 0; } private: virtual void platformCALayerLayoutSublayersOfLayer(PlatformCALayer*); virtual bool platformCALayerRespondsToLayoutChanges() const { return true; } virtual void platformCALayerAnimationStarted(CFTimeInterval beginTime) { } virtual GraphicsLayer::CompositingCoordinatesOrientation platformCALayerContentsOrientation() const { return GraphicsLayer::CompositingCoordinatesBottomUp; } virtual void platformCALayerPaintContents(GraphicsContext&, const IntRect& inClip) { } virtual bool platformCALayerShowDebugBorders() const { return false; } virtual bool platformCALayerShowRepaintCounter() const { return false; } virtual int platformCALayerIncrementRepaintCount() { return 0; } virtual bool platformCALayerContentsOpaque() const { return false; } virtual bool platformCALayerDrawsContent() const { return false; } virtual void platformCALayerLayerDidDisplay(PlatformLayer*) { } MediaPlayerPrivateQuickTimeVisualContext* m_parent; }; void MediaPlayerPrivateQuickTimeVisualContext::LayerClient::platformCALayerLayoutSublayersOfLayer(PlatformCALayer* layer) { ASSERT(m_parent); ASSERT(m_parent->m_transformLayer == layer); FloatSize parentSize = layer->bounds().size(); FloatSize naturalSize = m_parent->naturalSize(); // Calculate the ratio of these two sizes and use that ratio to scale the qtVideoLayer: FloatSize ratio(parentSize.width() / naturalSize.width(), parentSize.height() / naturalSize.height()); int videoWidth = 0; int videoHeight = 0; m_parent->m_movie->getNaturalSize(videoWidth, videoHeight); FloatRect videoBounds(0, 0, videoWidth * ratio.width(), videoHeight * ratio.height()); FloatPoint3D videoAnchor = m_parent->m_qtVideoLayer->anchorPoint(); // Calculate the new position based on the parent's size: FloatPoint position(parentSize.width() * 0.5 - videoBounds.width() * (0.5 - videoAnchor.x()), parentSize.height() * 0.5 - videoBounds.height() * (0.5 - videoAnchor.y())); m_parent->m_qtVideoLayer->setBounds(videoBounds); m_parent->m_qtVideoLayer->setPosition(position); } #endif class MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient : public QTMovieVisualContextClient { public: VisualContextClient(MediaPlayerPrivateQuickTimeVisualContext* parent) : m_parent(parent) {} virtual ~VisualContextClient() { m_parent = 0; } void imageAvailableForTime(const QTCVTimeStamp*); static void retrieveCurrentImageProc(void*); private: MediaPlayerPrivateQuickTimeVisualContext* m_parent; }; MediaPlayerPrivateInterface* MediaPlayerPrivateQuickTimeVisualContext::create(MediaPlayer* player) { return new MediaPlayerPrivateQuickTimeVisualContext(player); } void MediaPlayerPrivateQuickTimeVisualContext::registerMediaEngine(MediaEngineRegistrar registrar) { if (isAvailable()) registrar(create, getSupportedTypes, supportsType, 0, 0, 0); } MediaPlayerPrivateQuickTimeVisualContext::MediaPlayerPrivateQuickTimeVisualContext(MediaPlayer* player) : m_player(player) , m_seekTo(-1) , m_seekTimer(this, &MediaPlayerPrivateQuickTimeVisualContext::seekTimerFired) , m_visualContextTimer(this, &MediaPlayerPrivateQuickTimeVisualContext::visualContextTimerFired) , m_networkState(MediaPlayer::Empty) , m_readyState(MediaPlayer::HaveNothing) , m_enabledTrackCount(0) , m_totalTrackCount(0) , m_hasUnsupportedTracks(false) , m_startedPlaying(false) , m_isStreaming(false) , m_visible(false) , m_newFrameAvailable(false) , m_movieClient(new MediaPlayerPrivateQuickTimeVisualContext::MovieClient(this)) #if USE(ACCELERATED_COMPOSITING) , m_layerClient(new MediaPlayerPrivateQuickTimeVisualContext::LayerClient(this)) , m_movieTransform(CGAffineTransformIdentity) #endif , m_visualContextClient(new MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient(this)) , m_delayingLoad(false) , m_privateBrowsing(false) , m_preload(MediaPlayer::Auto) { } MediaPlayerPrivateQuickTimeVisualContext::~MediaPlayerPrivateQuickTimeVisualContext() { tearDownVideoRendering(); cancelCallOnMainThread(&VisualContextClient::retrieveCurrentImageProc, this); } bool MediaPlayerPrivateQuickTimeVisualContext::supportsFullscreen() const { #if USE(ACCELERATED_COMPOSITING) Document* document = m_player->mediaPlayerClient()->mediaPlayerOwningDocument(); if (document && document->settings()) return document->settings()->acceleratedCompositingEnabled(); #endif return false; } PlatformMedia MediaPlayerPrivateQuickTimeVisualContext::platformMedia() const { PlatformMedia p; p.type = PlatformMedia::QTMovieVisualContextType; p.media.qtMovieVisualContext = m_visualContext.get(); return p; } #if USE(ACCELERATED_COMPOSITING) PlatformLayer* MediaPlayerPrivateQuickTimeVisualContext::platformLayer() const { return m_transformLayer ? m_transformLayer->platformLayer() : 0; } #endif String MediaPlayerPrivateQuickTimeVisualContext::rfc2616DateStringFromTime(CFAbsoluteTime time) { static const char* const dayStrings[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; static const char* const monthStrings[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const CFStringRef dateFormatString = CFSTR("%s, %02d %s %04d %02d:%02d:%02d GMT"); static CFTimeZoneRef gmtTimeZone; if (!gmtTimeZone) gmtTimeZone = CFTimeZoneCopyDefault(); CFGregorianDate dateValue = CFAbsoluteTimeGetGregorianDate(time, gmtTimeZone); if (!CFGregorianDateIsValid(dateValue, kCFGregorianAllUnits)) return String(); time = CFGregorianDateGetAbsoluteTime(dateValue, gmtTimeZone); SInt32 day = CFAbsoluteTimeGetDayOfWeek(time, 0); RetainPtr dateCFString(AdoptCF, CFStringCreateWithFormat(0, 0, dateFormatString, dayStrings[day - 1], dateValue.day, monthStrings[dateValue.month - 1], dateValue.year, dateValue.hour, dateValue.minute, (int)dateValue.second)); return dateCFString.get(); } static void addCookieParam(StringBuilder& cookieBuilder, const String& name, const String& value) { if (name.isEmpty()) return; // If this isn't the first parameter added, terminate the previous one. if (cookieBuilder.length()) cookieBuilder.append("; "); // Add parameter name, and value if there is one. cookieBuilder.append(name); if (!value.isEmpty()) { cookieBuilder.append('='); cookieBuilder.append(value); } } void MediaPlayerPrivateQuickTimeVisualContext::setUpCookiesForQuickTime(const String& url) { // WebCore loaded the page with the movie URL with CFNetwork but QuickTime will // use WinINet to download the movie, so we need to copy any cookies needed to // download the movie into WinInet before asking QuickTime to open it. Document* document = m_player->mediaPlayerClient()->mediaPlayerOwningDocument(); Frame* frame = document ? document->frame() : 0; if (!frame || !frame->page() || !frame->page()->cookieEnabled()) return; KURL movieURL = KURL(KURL(), url); Vector documentCookies; if (!getRawCookies(frame->document(), movieURL, documentCookies)) return; for (size_t ndx = 0; ndx < documentCookies.size(); ndx++) { const Cookie& cookie = documentCookies[ndx]; if (cookie.name.isEmpty()) continue; // Build up the cookie string with as much information as we can get so WinINet // knows what to do with it. StringBuilder cookieBuilder; addCookieParam(cookieBuilder, cookie.name, cookie.value); addCookieParam(cookieBuilder, "path", cookie.path); if (cookie.expires) addCookieParam(cookieBuilder, "expires", rfc2616DateStringFromTime(cookie.expires)); if (cookie.httpOnly) addCookieParam(cookieBuilder, "httpOnly", String()); cookieBuilder.append(';'); String cookieURL; if (!cookie.domain.isEmpty()) { StringBuilder urlBuilder; urlBuilder.append(movieURL.protocol()); urlBuilder.append("://"); if (cookie.domain[0] == '.') urlBuilder.append(cookie.domain.substring(1)); else urlBuilder.append(cookie.domain); if (cookie.path.length() > 1) urlBuilder.append(cookie.path); cookieURL = urlBuilder.toString(); } else cookieURL = movieURL; InternetSetCookieExW(cookieURL.charactersWithNullTermination(), 0, cookieBuilder.toString().charactersWithNullTermination(), 0, 0); } } static void disableComponentsOnce() { static bool sComponentsDisabled = false; if (sComponentsDisabled) return; sComponentsDisabled = true; uint32_t componentsToDisable[][5] = { {'eat ', 'TEXT', 'text', 0, 0}, {'eat ', 'TXT ', 'text', 0, 0}, {'eat ', 'utxt', 'text', 0, 0}, {'eat ', 'TEXT', 'tx3g', 0, 0}, }; for (size_t i = 0; i < WTF_ARRAY_LENGTH(componentsToDisable); ++i) QTMovie::disableComponent(componentsToDisable[i]); } void MediaPlayerPrivateQuickTimeVisualContext::resumeLoad() { m_delayingLoad = false; if (!m_movieURL.isEmpty()) loadInternal(m_movieURL); } void MediaPlayerPrivateQuickTimeVisualContext::load(const String& url) { m_movieURL = url; if (m_preload == MediaPlayer::None) { m_delayingLoad = true; return; } loadInternal(url); } void MediaPlayerPrivateQuickTimeVisualContext::loadInternal(const String& url) { if (!QTMovie::initializeQuickTime()) { // FIXME: is this the right error to return? m_networkState = MediaPlayer::DecodeError; m_player->networkStateChanged(); return; } disableComponentsOnce(); // Initialize the task timer. MediaPlayerPrivateTaskTimer::initialize(); if (m_networkState != MediaPlayer::Loading) { m_networkState = MediaPlayer::Loading; m_player->networkStateChanged(); } if (m_readyState != MediaPlayer::HaveNothing) { m_readyState = MediaPlayer::HaveNothing; m_player->readyStateChanged(); } cancelSeek(); setUpCookiesForQuickTime(url); m_movie = adoptRef(new QTMovie(m_movieClient.get())); #if ENABLE(OFFLINE_WEB_APPLICATIONS) Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : 0; ApplicationCacheHost* cacheHost = frame ? frame->loader()->documentLoader()->applicationCacheHost() : 0; ApplicationCacheResource* resource = 0; if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource) && resource && !resource->path().isEmpty()) m_movie->load(resource->path().characters(), resource->path().length(), m_player->preservesPitch()); else #endif m_movie->load(url.characters(), url.length(), m_player->preservesPitch()); m_movie->setVolume(m_player->volume()); } void MediaPlayerPrivateQuickTimeVisualContext::prepareToPlay() { if (!m_movie || m_delayingLoad) resumeLoad(); } void MediaPlayerPrivateQuickTimeVisualContext::play() { if (!m_movie) return; m_startedPlaying = true; m_movie->play(); m_visualContextTimer.startRepeating(1.0 / 30); } void MediaPlayerPrivateQuickTimeVisualContext::pause() { if (!m_movie) return; m_startedPlaying = false; m_movie->pause(); m_visualContextTimer.stop(); } float MediaPlayerPrivateQuickTimeVisualContext::duration() const { if (!m_movie) return 0; return m_movie->duration(); } float MediaPlayerPrivateQuickTimeVisualContext::currentTime() const { if (!m_movie) return 0; return m_movie->currentTime(); } void MediaPlayerPrivateQuickTimeVisualContext::seek(float time) { cancelSeek(); if (!m_movie) return; if (time > duration()) time = duration(); m_seekTo = time; if (maxTimeLoaded() >= m_seekTo) doSeek(); else m_seekTimer.start(0, 0.5f); } void MediaPlayerPrivateQuickTimeVisualContext::doSeek() { float oldRate = m_movie->rate(); if (oldRate) m_movie->setRate(0); m_movie->setCurrentTime(m_seekTo); float timeAfterSeek = currentTime(); // restore playback only if not at end, othewise QTMovie will loop if (oldRate && timeAfterSeek < duration()) m_movie->setRate(oldRate); cancelSeek(); } void MediaPlayerPrivateQuickTimeVisualContext::cancelSeek() { m_seekTo = -1; m_seekTimer.stop(); } void MediaPlayerPrivateQuickTimeVisualContext::seekTimerFired(Timer*) { if (!m_movie || !seeking() || currentTime() == m_seekTo) { cancelSeek(); updateStates(); m_player->timeChanged(); return; } if (maxTimeLoaded() >= m_seekTo) doSeek(); else { MediaPlayer::NetworkState state = networkState(); if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) { cancelSeek(); updateStates(); m_player->timeChanged(); } } } bool MediaPlayerPrivateQuickTimeVisualContext::paused() const { if (!m_movie) return true; return (!m_movie->rate()); } bool MediaPlayerPrivateQuickTimeVisualContext::seeking() const { if (!m_movie) return false; return m_seekTo >= 0; } IntSize MediaPlayerPrivateQuickTimeVisualContext::naturalSize() const { if (!m_movie) return IntSize(); int width; int height; m_movie->getNaturalSize(width, height); #if USE(ACCELERATED_COMPOSITING) CGSize originalSize = {width, height}; CGSize transformedSize = CGSizeApplyAffineTransform(originalSize, m_movieTransform); return IntSize(abs(transformedSize.width), abs(transformedSize.height)); #else return IntSize(width, height); #endif } bool MediaPlayerPrivateQuickTimeVisualContext::hasVideo() const { if (!m_movie) return false; return m_movie->hasVideo(); } bool MediaPlayerPrivateQuickTimeVisualContext::hasAudio() const { if (!m_movie) return false; return m_movie->hasAudio(); } void MediaPlayerPrivateQuickTimeVisualContext::setVolume(float volume) { if (!m_movie) return; m_movie->setVolume(volume); } void MediaPlayerPrivateQuickTimeVisualContext::setRate(float rate) { if (!m_movie) return; // Do not call setRate(...) unless we have started playing; otherwise // QuickTime's VisualContext can get wedged waiting for a rate change // call which will never come. if (m_startedPlaying) m_movie->setRate(rate); } void MediaPlayerPrivateQuickTimeVisualContext::setPreservesPitch(bool preservesPitch) { if (!m_movie) return; m_movie->setPreservesPitch(preservesPitch); } bool MediaPlayerPrivateQuickTimeVisualContext::hasClosedCaptions() const { if (!m_movie) return false; return m_movie->hasClosedCaptions(); } void MediaPlayerPrivateQuickTimeVisualContext::setClosedCaptionsVisible(bool visible) { if (!m_movie) return; m_movie->setClosedCaptionsVisible(visible); } PassRefPtr MediaPlayerPrivateQuickTimeVisualContext::buffered() const { RefPtr timeRanges = TimeRanges::create(); float loaded = maxTimeLoaded(); // rtsp streams are not buffered if (!m_isStreaming && loaded > 0) timeRanges->add(0, loaded); return timeRanges.release(); } float MediaPlayerPrivateQuickTimeVisualContext::maxTimeSeekable() const { // infinite duration means live stream return !isfinite(duration()) ? 0 : maxTimeLoaded(); } float MediaPlayerPrivateQuickTimeVisualContext::maxTimeLoaded() const { if (!m_movie) return 0; return m_movie->maxTimeLoaded(); } unsigned MediaPlayerPrivateQuickTimeVisualContext::bytesLoaded() const { if (!m_movie) return 0; float dur = duration(); float maxTime = maxTimeLoaded(); if (!dur) return 0; return totalBytes() * maxTime / dur; } unsigned MediaPlayerPrivateQuickTimeVisualContext::totalBytes() const { if (!m_movie) return 0; return m_movie->dataSize(); } void MediaPlayerPrivateQuickTimeVisualContext::cancelLoad() { if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded) return; tearDownVideoRendering(); // Cancel the load by destroying the movie. m_movie.clear(); updateStates(); } void MediaPlayerPrivateQuickTimeVisualContext::updateStates() { MediaPlayer::NetworkState oldNetworkState = m_networkState; MediaPlayer::ReadyState oldReadyState = m_readyState; long loadState = m_movie ? m_movie->loadState() : QTMovieLoadStateError; if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) { m_movie->disableUnsupportedTracks(m_enabledTrackCount, m_totalTrackCount); if (m_player->inMediaDocument()) { if (!m_enabledTrackCount || m_enabledTrackCount != m_totalTrackCount) { // This is a type of media that we do not handle directly with a