2 * Copyright (C) 2007, 2008, 2009, 2010, 2011 Apple, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #include "MediaPlayerPrivateQuickTimeVisualContext.h"
32 #include "CookieJar.h"
33 #include "DocumentLoader.h"
35 #include "FrameView.h"
36 #include "GraphicsContext.h"
38 #include "MediaPlayerPrivateTaskTimer.h"
40 #include "QTCFDictionary.h"
41 #include "QTDecompressionSession.h"
43 #include "QTMovieTask.h"
44 #include "QTMovieVisualContext.h"
45 #include "ScrollView.h"
47 #include "SoftLinking.h"
48 #include "TimeRanges.h"
50 #include <AssertMacros.h>
51 #include <CoreGraphics/CGAffineTransform.h>
52 #include <CoreGraphics/CGContext.h>
53 #include <QuartzCore/CATransform3D.h>
55 #include <wtf/CurrentTime.h>
56 #include <wtf/HashSet.h>
57 #include <wtf/MainThread.h>
58 #include <wtf/MathExtras.h>
59 #include <wtf/StdLibExtras.h>
60 #include <wtf/text/StringBuilder.h>
61 #include <wtf/text/StringHash.h>
63 #if USE(ACCELERATED_COMPOSITING)
64 #include "PlatformCALayer.h"
65 #include "WKCAImageQueue.h"
72 static CGImageRef CreateCGImageFromPixelBuffer(QTPixelBuffer buffer);
73 static bool requiredDllsAvailable();
75 SOFT_LINK_LIBRARY(Wininet)
76 SOFT_LINK(Wininet, InternetSetCookieExW, DWORD, WINAPI, (LPCWSTR lpszUrl, LPCWSTR lpszCookieName, LPCWSTR lpszCookieData, DWORD dwFlags, DWORD_PTR dwReserved), (lpszUrl, lpszCookieName, lpszCookieData, dwFlags, dwReserved))
78 // Interface declaration for MediaPlayerPrivateQuickTimeVisualContext's QTMovieClient aggregate
79 class MediaPlayerPrivateQuickTimeVisualContext::MovieClient : public QTMovieClient {
81 MovieClient(MediaPlayerPrivateQuickTimeVisualContext* parent) : m_parent(parent) {}
82 virtual ~MovieClient() { m_parent = 0; }
83 virtual void movieEnded(QTMovie*);
84 virtual void movieLoadStateChanged(QTMovie*);
85 virtual void movieTimeChanged(QTMovie*);
87 MediaPlayerPrivateQuickTimeVisualContext* m_parent;
90 #if USE(ACCELERATED_COMPOSITING)
91 class MediaPlayerPrivateQuickTimeVisualContext::LayerClient : public PlatformCALayerClient {
93 LayerClient(MediaPlayerPrivateQuickTimeVisualContext* parent) : m_parent(parent) {}
94 virtual ~LayerClient() { m_parent = 0; }
97 virtual void platformCALayerLayoutSublayersOfLayer(PlatformCALayer*);
98 virtual bool platformCALayerRespondsToLayoutChanges() const { return true; }
100 virtual void platformCALayerAnimationStarted(CFTimeInterval beginTime) { }
101 virtual GraphicsLayer::CompositingCoordinatesOrientation platformCALayerContentsOrientation() const { return GraphicsLayer::CompositingCoordinatesBottomUp; }
102 virtual void platformCALayerPaintContents(GraphicsContext&, const IntRect& inClip) { }
103 virtual bool platformCALayerShowDebugBorders() const { return false; }
104 virtual bool platformCALayerShowRepaintCounter() const { return false; }
105 virtual int platformCALayerIncrementRepaintCount() { return 0; }
107 virtual bool platformCALayerContentsOpaque() const { return false; }
108 virtual bool platformCALayerDrawsContent() const { return false; }
109 virtual void platformCALayerLayerDidDisplay(PlatformLayer*) { }
111 MediaPlayerPrivateQuickTimeVisualContext* m_parent;
114 void MediaPlayerPrivateQuickTimeVisualContext::LayerClient::platformCALayerLayoutSublayersOfLayer(PlatformCALayer* layer)
117 ASSERT(m_parent->m_transformLayer == layer);
119 FloatSize parentSize = layer->bounds().size();
120 FloatSize naturalSize = m_parent->naturalSize();
122 // Calculate the ratio of these two sizes and use that ratio to scale the qtVideoLayer:
123 FloatSize ratio(parentSize.width() / naturalSize.width(), parentSize.height() / naturalSize.height());
127 m_parent->m_movie->getNaturalSize(videoWidth, videoHeight);
128 FloatRect videoBounds(0, 0, videoWidth * ratio.width(), videoHeight * ratio.height());
129 FloatPoint3D videoAnchor = m_parent->m_qtVideoLayer->anchorPoint();
131 // Calculate the new position based on the parent's size:
132 FloatPoint position(parentSize.width() * 0.5 - videoBounds.width() * (0.5 - videoAnchor.x()),
133 parentSize.height() * 0.5 - videoBounds.height() * (0.5 - videoAnchor.y()));
135 m_parent->m_qtVideoLayer->setBounds(videoBounds);
136 m_parent->m_qtVideoLayer->setPosition(position);
140 class MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient : public QTMovieVisualContextClient {
142 VisualContextClient(MediaPlayerPrivateQuickTimeVisualContext* parent) : m_parent(parent) {}
143 virtual ~VisualContextClient() { m_parent = 0; }
144 void imageAvailableForTime(const QTCVTimeStamp*);
145 static void retrieveCurrentImageProc(void*);
147 MediaPlayerPrivateQuickTimeVisualContext* m_parent;
150 PassOwnPtr<MediaPlayerPrivateInterface> MediaPlayerPrivateQuickTimeVisualContext::create(MediaPlayer* player)
152 return adoptPtr(new MediaPlayerPrivateQuickTimeVisualContext(player));
155 void MediaPlayerPrivateQuickTimeVisualContext::registerMediaEngine(MediaEngineRegistrar registrar)
158 registrar(create, getSupportedTypes, supportsType, 0, 0, 0);
161 MediaPlayerPrivateQuickTimeVisualContext::MediaPlayerPrivateQuickTimeVisualContext(MediaPlayer* player)
164 , m_seekTimer(this, &MediaPlayerPrivateQuickTimeVisualContext::seekTimerFired)
165 , m_visualContextTimer(this, &MediaPlayerPrivateQuickTimeVisualContext::visualContextTimerFired)
166 , m_networkState(MediaPlayer::Empty)
167 , m_readyState(MediaPlayer::HaveNothing)
168 , m_enabledTrackCount(0)
169 , m_totalTrackCount(0)
170 , m_hasUnsupportedTracks(false)
171 , m_startedPlaying(false)
172 , m_isStreaming(false)
174 , m_newFrameAvailable(false)
175 , m_movieClient(adoptPtr(new MediaPlayerPrivateQuickTimeVisualContext::MovieClient(this)))
176 #if USE(ACCELERATED_COMPOSITING)
177 , m_layerClient(adoptPtr(new MediaPlayerPrivateQuickTimeVisualContext::LayerClient(this)))
178 , m_movieTransform(CGAffineTransformIdentity)
180 , m_visualContextClient(adoptPtr(new MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient(this)))
181 , m_delayingLoad(false)
182 , m_privateBrowsing(false)
183 , m_preload(MediaPlayer::Auto)
187 MediaPlayerPrivateQuickTimeVisualContext::~MediaPlayerPrivateQuickTimeVisualContext()
189 tearDownVideoRendering();
190 cancelCallOnMainThread(&VisualContextClient::retrieveCurrentImageProc, this);
193 bool MediaPlayerPrivateQuickTimeVisualContext::supportsFullscreen() const
195 #if USE(ACCELERATED_COMPOSITING)
196 Document* document = m_player->mediaPlayerClient()->mediaPlayerOwningDocument();
197 if (document && document->settings())
198 return document->settings()->acceleratedCompositingEnabled();
203 PlatformMedia MediaPlayerPrivateQuickTimeVisualContext::platformMedia() const
206 p.type = PlatformMedia::QTMovieVisualContextType;
207 p.media.qtMovieVisualContext = m_visualContext.get();
210 #if USE(ACCELERATED_COMPOSITING)
212 PlatformLayer* MediaPlayerPrivateQuickTimeVisualContext::platformLayer() const
214 return m_transformLayer ? m_transformLayer->platformLayer() : 0;
218 String MediaPlayerPrivateQuickTimeVisualContext::rfc2616DateStringFromTime(CFAbsoluteTime time)
220 static const char* const dayStrings[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
221 static const char* const monthStrings[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
222 static const CFStringRef dateFormatString = CFSTR("%s, %02d %s %04d %02d:%02d:%02d GMT");
223 static CFTimeZoneRef gmtTimeZone;
225 gmtTimeZone = CFTimeZoneCopyDefault();
227 CFGregorianDate dateValue = CFAbsoluteTimeGetGregorianDate(time, gmtTimeZone);
228 if (!CFGregorianDateIsValid(dateValue, kCFGregorianAllUnits))
231 time = CFGregorianDateGetAbsoluteTime(dateValue, gmtTimeZone);
232 SInt32 day = CFAbsoluteTimeGetDayOfWeek(time, 0);
234 RetainPtr<CFStringRef> dateCFString(AdoptCF, CFStringCreateWithFormat(0, 0, dateFormatString, dayStrings[day - 1], dateValue.day,
235 monthStrings[dateValue.month - 1], dateValue.year, dateValue.hour, dateValue.minute, (int)dateValue.second));
236 return dateCFString.get();
239 static void addCookieParam(StringBuilder& cookieBuilder, const String& name, const String& value)
244 // If this isn't the first parameter added, terminate the previous one.
245 if (cookieBuilder.length())
246 cookieBuilder.append("; ");
248 // Add parameter name, and value if there is one.
249 cookieBuilder.append(name);
250 if (!value.isEmpty()) {
251 cookieBuilder.append('=');
252 cookieBuilder.append(value);
256 void MediaPlayerPrivateQuickTimeVisualContext::setUpCookiesForQuickTime(const String& url)
258 // WebCore loaded the page with the movie URL with CFNetwork but QuickTime will
259 // use WinINet to download the movie, so we need to copy any cookies needed to
260 // download the movie into WinInet before asking QuickTime to open it.
261 Document* document = m_player->mediaPlayerClient()->mediaPlayerOwningDocument();
262 Frame* frame = document ? document->frame() : 0;
263 if (!frame || !frame->page() || !frame->page()->cookieEnabled())
266 KURL movieURL = KURL(KURL(), url);
267 Vector<Cookie> documentCookies;
268 if (!getRawCookies(frame->document(), movieURL, documentCookies))
271 for (size_t ndx = 0; ndx < documentCookies.size(); ndx++) {
272 const Cookie& cookie = documentCookies[ndx];
274 if (cookie.name.isEmpty())
277 // Build up the cookie string with as much information as we can get so WinINet
278 // knows what to do with it.
279 StringBuilder cookieBuilder;
280 addCookieParam(cookieBuilder, cookie.name, cookie.value);
281 addCookieParam(cookieBuilder, "path", cookie.path);
283 addCookieParam(cookieBuilder, "expires", rfc2616DateStringFromTime(cookie.expires));
285 addCookieParam(cookieBuilder, "httpOnly", String());
286 cookieBuilder.append(';');
289 if (!cookie.domain.isEmpty()) {
290 StringBuilder urlBuilder;
292 urlBuilder.append(movieURL.protocol());
293 urlBuilder.append("://");
294 if (cookie.domain[0] == '.')
295 urlBuilder.append(cookie.domain.substring(1));
297 urlBuilder.append(cookie.domain);
298 if (cookie.path.length() > 1)
299 urlBuilder.append(cookie.path);
301 cookieURL = urlBuilder.toString();
303 cookieURL = movieURL;
305 InternetSetCookieExW(cookieURL.charactersWithNullTermination(), 0, cookieBuilder.toString().charactersWithNullTermination(), 0, 0);
309 static void disableComponentsOnce()
311 static bool sComponentsDisabled = false;
312 if (sComponentsDisabled)
314 sComponentsDisabled = true;
316 uint32_t componentsToDisable[][5] = {
317 {'eat ', 'TEXT', 'text', 0, 0},
318 {'eat ', 'TXT ', 'text', 0, 0},
319 {'eat ', 'utxt', 'text', 0, 0},
320 {'eat ', 'TEXT', 'tx3g', 0, 0},
323 for (size_t i = 0; i < WTF_ARRAY_LENGTH(componentsToDisable); ++i)
324 QTMovie::disableComponent(componentsToDisable[i]);
327 void MediaPlayerPrivateQuickTimeVisualContext::resumeLoad()
329 m_delayingLoad = false;
331 if (!m_movieURL.isEmpty())
332 loadInternal(m_movieURL);
335 void MediaPlayerPrivateQuickTimeVisualContext::load(const String& url)
339 if (m_preload == MediaPlayer::None) {
340 m_delayingLoad = true;
347 void MediaPlayerPrivateQuickTimeVisualContext::loadInternal(const String& url)
349 if (!QTMovie::initializeQuickTime()) {
350 // FIXME: is this the right error to return?
351 m_networkState = MediaPlayer::DecodeError;
352 m_player->networkStateChanged();
356 disableComponentsOnce();
358 // Initialize the task timer.
359 MediaPlayerPrivateTaskTimer::initialize();
361 if (m_networkState != MediaPlayer::Loading) {
362 m_networkState = MediaPlayer::Loading;
363 m_player->networkStateChanged();
365 if (m_readyState != MediaPlayer::HaveNothing) {
366 m_readyState = MediaPlayer::HaveNothing;
367 m_player->readyStateChanged();
371 setUpCookiesForQuickTime(url);
373 m_movie = adoptRef(new QTMovie(m_movieClient.get()));
375 m_movie->load(url.characters(), url.length(), m_player->preservesPitch());
376 m_movie->setVolume(m_player->volume());
379 void MediaPlayerPrivateQuickTimeVisualContext::prepareToPlay()
381 if (!m_movie || m_delayingLoad)
385 void MediaPlayerPrivateQuickTimeVisualContext::play()
389 m_startedPlaying = true;
392 m_visualContextTimer.startRepeating(1.0 / 30);
395 void MediaPlayerPrivateQuickTimeVisualContext::pause()
399 m_startedPlaying = false;
402 m_visualContextTimer.stop();
405 float MediaPlayerPrivateQuickTimeVisualContext::duration() const
409 return m_movie->duration();
412 float MediaPlayerPrivateQuickTimeVisualContext::currentTime() const
416 return m_movie->currentTime();
419 void MediaPlayerPrivateQuickTimeVisualContext::seek(float time)
426 if (time > duration())
430 if (maxTimeLoaded() >= m_seekTo)
433 m_seekTimer.start(0, 0.5f);
436 void MediaPlayerPrivateQuickTimeVisualContext::doSeek()
438 float oldRate = m_movie->rate();
441 m_movie->setCurrentTime(m_seekTo);
442 float timeAfterSeek = currentTime();
443 // restore playback only if not at end, othewise QTMovie will loop
444 if (oldRate && timeAfterSeek < duration())
445 m_movie->setRate(oldRate);
449 void MediaPlayerPrivateQuickTimeVisualContext::cancelSeek()
455 void MediaPlayerPrivateQuickTimeVisualContext::seekTimerFired(Timer<MediaPlayerPrivateQuickTimeVisualContext>*)
457 if (!m_movie || !seeking() || currentTime() == m_seekTo) {
460 m_player->timeChanged();
464 if (maxTimeLoaded() >= m_seekTo)
467 MediaPlayer::NetworkState state = networkState();
468 if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
471 m_player->timeChanged();
476 bool MediaPlayerPrivateQuickTimeVisualContext::paused() const
480 return (!m_movie->rate());
483 bool MediaPlayerPrivateQuickTimeVisualContext::seeking() const
487 return m_seekTo >= 0;
490 IntSize MediaPlayerPrivateQuickTimeVisualContext::naturalSize() const
496 m_movie->getNaturalSize(width, height);
497 #if USE(ACCELERATED_COMPOSITING)
498 CGSize originalSize = {width, height};
499 CGSize transformedSize = CGSizeApplyAffineTransform(originalSize, m_movieTransform);
500 return IntSize(abs(transformedSize.width), abs(transformedSize.height));
502 return IntSize(width, height);
506 bool MediaPlayerPrivateQuickTimeVisualContext::hasVideo() const
510 return m_movie->hasVideo();
513 bool MediaPlayerPrivateQuickTimeVisualContext::hasAudio() const
517 return m_movie->hasAudio();
520 void MediaPlayerPrivateQuickTimeVisualContext::setVolume(float volume)
524 m_movie->setVolume(volume);
527 void MediaPlayerPrivateQuickTimeVisualContext::setRate(float rate)
532 // Do not call setRate(...) unless we have started playing; otherwise
533 // QuickTime's VisualContext can get wedged waiting for a rate change
534 // call which will never come.
535 if (m_startedPlaying)
536 m_movie->setRate(rate);
539 void MediaPlayerPrivateQuickTimeVisualContext::setPreservesPitch(bool preservesPitch)
543 m_movie->setPreservesPitch(preservesPitch);
546 bool MediaPlayerPrivateQuickTimeVisualContext::hasClosedCaptions() const
550 return m_movie->hasClosedCaptions();
553 void MediaPlayerPrivateQuickTimeVisualContext::setClosedCaptionsVisible(bool visible)
557 m_movie->setClosedCaptionsVisible(visible);
560 PassRefPtr<TimeRanges> MediaPlayerPrivateQuickTimeVisualContext::buffered() const
562 RefPtr<TimeRanges> timeRanges = TimeRanges::create();
563 float loaded = maxTimeLoaded();
564 // rtsp streams are not buffered
565 if (!m_isStreaming && loaded > 0)
566 timeRanges->add(0, loaded);
567 return timeRanges.release();
570 float MediaPlayerPrivateQuickTimeVisualContext::maxTimeSeekable() const
572 // infinite duration means live stream
573 return !isfinite(duration()) ? 0 : maxTimeLoaded();
576 float MediaPlayerPrivateQuickTimeVisualContext::maxTimeLoaded() const
580 return m_movie->maxTimeLoaded();
583 unsigned MediaPlayerPrivateQuickTimeVisualContext::bytesLoaded() const
587 float dur = duration();
588 float maxTime = maxTimeLoaded();
591 return totalBytes() * maxTime / dur;
594 unsigned MediaPlayerPrivateQuickTimeVisualContext::totalBytes() const
598 return m_movie->dataSize();
601 void MediaPlayerPrivateQuickTimeVisualContext::cancelLoad()
603 if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
606 tearDownVideoRendering();
608 // Cancel the load by destroying the movie.
614 void MediaPlayerPrivateQuickTimeVisualContext::updateStates()
616 MediaPlayer::NetworkState oldNetworkState = m_networkState;
617 MediaPlayer::ReadyState oldReadyState = m_readyState;
619 long loadState = m_movie ? m_movie->loadState() : QTMovieLoadStateError;
621 if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) {
622 m_movie->disableUnsupportedTracks(m_enabledTrackCount, m_totalTrackCount);
623 if (m_player->inMediaDocument()) {
624 if (!m_enabledTrackCount || m_enabledTrackCount != m_totalTrackCount) {
625 // This is a type of media that we do not handle directly with a <video>
626 // element, eg. QuickTime VR, a movie with a sprite track, etc. Tell the
627 // MediaPlayerClient that we won't support it.
628 sawUnsupportedTracks();
631 } else if (!m_enabledTrackCount)
632 loadState = QTMovieLoadStateError;
635 // "Loaded" is reserved for fully buffered movies, never the case when streaming
636 if (loadState >= QTMovieLoadStateComplete && !m_isStreaming) {
637 m_networkState = MediaPlayer::Loaded;
638 m_readyState = MediaPlayer::HaveEnoughData;
639 } else if (loadState >= QTMovieLoadStatePlaythroughOK) {
640 m_readyState = MediaPlayer::HaveEnoughData;
641 } else if (loadState >= QTMovieLoadStatePlayable) {
642 // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967>
643 m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData;
644 } else if (loadState >= QTMovieLoadStateLoaded) {
645 m_readyState = MediaPlayer::HaveMetadata;
646 } else if (loadState > QTMovieLoadStateError) {
647 m_networkState = MediaPlayer::Loading;
648 m_readyState = MediaPlayer::HaveNothing;
650 if (m_player->inMediaDocument()) {
651 // Something went wrong in the loading of media within a standalone file.
652 // This can occur with chained ref movies that eventually resolve to a
653 // file we don't support.
654 sawUnsupportedTracks();
658 float loaded = maxTimeLoaded();
660 m_readyState = MediaPlayer::HaveNothing;
662 if (!m_enabledTrackCount)
663 m_networkState = MediaPlayer::FormatError;
665 // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692>
667 m_networkState = MediaPlayer::DecodeError;
669 m_readyState = MediaPlayer::HaveNothing;
673 if (isReadyForRendering() && !hasSetUpVideoRendering())
674 setUpVideoRendering();
677 m_readyState = MediaPlayer::HaveNothing;
679 if (m_networkState != oldNetworkState)
680 m_player->networkStateChanged();
681 if (m_readyState != oldReadyState)
682 m_player->readyStateChanged();
685 bool MediaPlayerPrivateQuickTimeVisualContext::isReadyForRendering() const
687 return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible();
690 void MediaPlayerPrivateQuickTimeVisualContext::sawUnsupportedTracks()
692 m_movie->setDisabled(true);
693 m_hasUnsupportedTracks = true;
694 m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player);
697 void MediaPlayerPrivateQuickTimeVisualContext::didEnd()
699 if (m_hasUnsupportedTracks)
702 m_startedPlaying = false;
705 m_player->timeChanged();
708 void MediaPlayerPrivateQuickTimeVisualContext::setSize(const IntSize& size)
710 if (m_hasUnsupportedTracks || !m_movie || m_size == size)
715 void MediaPlayerPrivateQuickTimeVisualContext::setVisible(bool visible)
717 if (m_hasUnsupportedTracks || !m_movie || m_visible == visible)
722 if (isReadyForRendering())
723 setUpVideoRendering();
725 tearDownVideoRendering();
728 void MediaPlayerPrivateQuickTimeVisualContext::paint(GraphicsContext* p, const IntRect& r)
730 MediaRenderingMode currentMode = currentRenderingMode();
732 if (currentMode == MediaRenderingNone)
735 if (currentMode == MediaRenderingSoftwareRenderer && !m_visualContext)
738 QTPixelBuffer buffer = m_visualContext->imageForTime(0);
739 if (buffer.pixelBufferRef()) {
740 #if USE(ACCELERATED_COMPOSITING)
741 if (m_qtVideoLayer) {
742 // We are probably being asked to render the video into a canvas, but
743 // there's a good chance the QTPixelBuffer is not ARGB and thus can't be
744 // drawn using CG. If so, fire up an ICMDecompressionSession and convert
745 // the current frame into something which can be rendered by CG.
746 if (!buffer.pixelFormatIs32ARGB() && !buffer.pixelFormatIs32BGRA()) {
747 // The decompression session will only decompress a specific pixelFormat
748 // at a specific width and height; if these differ, the session must be
749 // recreated with the new parameters.
750 if (!m_decompressionSession || !m_decompressionSession->canDecompress(buffer))
751 m_decompressionSession = QTDecompressionSession::create(buffer.pixelFormatType(), buffer.width(), buffer.height());
752 buffer = m_decompressionSession->decompress(buffer);
756 CGImageRef image = CreateCGImageFromPixelBuffer(buffer);
758 CGContextRef context = p->platformContext();
759 CGContextSaveGState(context);
760 CGContextTranslateCTM(context, r.x(), r.y());
761 CGContextTranslateCTM(context, 0, r.height());
762 CGContextScaleCTM(context, 1, -1);
763 CGContextDrawImage(context, CGRectMake(0, 0, r.width(), r.height()), image);
764 CGContextRestoreGState(context);
766 CGImageRelease(image);
768 paintCompleted(*p, r);
771 void MediaPlayerPrivateQuickTimeVisualContext::paintCompleted(GraphicsContext& context, const IntRect& rect)
773 m_newFrameAvailable = false;
776 void MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient::retrieveCurrentImageProc(void* refcon)
778 static_cast<MediaPlayerPrivateQuickTimeVisualContext*>(refcon)->retrieveCurrentImage();
781 void MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient::imageAvailableForTime(const QTCVTimeStamp* timeStamp)
783 // This call may come in on another thread, so marshall to the main thread first:
784 callOnMainThread(&retrieveCurrentImageProc, m_parent);
786 // callOnMainThread must be paired with cancelCallOnMainThread in the destructor,
787 // in case this object is deleted before the main thread request is handled.
790 void MediaPlayerPrivateQuickTimeVisualContext::visualContextTimerFired(Timer<MediaPlayerPrivateQuickTimeVisualContext>*)
792 if (m_visualContext && m_visualContext->isImageAvailableForTime(0))
793 retrieveCurrentImage();
796 static CFDictionaryRef QTCFDictionaryCreateWithDataCallback(CFAllocatorRef allocator, const UInt8* bytes, CFIndex length)
798 RetainPtr<CFDataRef> data(AdoptCF, CFDataCreateWithBytesNoCopy(allocator, bytes, length, kCFAllocatorNull));
802 return reinterpret_cast<CFDictionaryRef>(CFPropertyListCreateFromXMLData(allocator, data.get(), kCFPropertyListImmutable, 0));
805 static CGImageRef CreateCGImageFromPixelBuffer(QTPixelBuffer buffer)
807 #if USE(ACCELERATED_COMPOSITING)
808 CGDataProviderRef provider = 0;
809 CGColorSpaceRef colorSpace = 0;
810 CGImageRef image = 0;
812 size_t bitsPerComponent = 0;
813 size_t bitsPerPixel = 0;
814 CGImageAlphaInfo alphaInfo = kCGImageAlphaNone;
816 if (buffer.pixelFormatIs32BGRA()) {
817 bitsPerComponent = 8;
819 alphaInfo = (CGImageAlphaInfo)(kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little);
820 } else if (buffer.pixelFormatIs32ARGB()) {
821 bitsPerComponent = 8;
823 alphaInfo = (CGImageAlphaInfo)(kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big);
825 // All other pixel formats are currently unsupported:
826 ASSERT_NOT_REACHED();
829 CGDataProviderDirectAccessCallbacks callbacks = {
830 &QTPixelBuffer::dataProviderGetBytePointerCallback,
831 &QTPixelBuffer::dataProviderReleaseBytePointerCallback,
832 &QTPixelBuffer::dataProviderGetBytesAtPositionCallback,
833 &QTPixelBuffer::dataProviderReleaseInfoCallback,
836 // Colorspace should be device, so that Quartz does not have to do an extra render.
837 colorSpace = CGColorSpaceCreateDeviceRGB();
838 require(colorSpace, Bail);
840 provider = CGDataProviderCreateDirectAccess(buffer.pixelBufferRef(), buffer.dataSize(), &callbacks);
841 require(provider, Bail);
843 // CGDataProvider does not retain the buffer, but it will release it later, so do an extra retain here:
844 QTPixelBuffer::retainCallback(buffer.pixelBufferRef());
846 image = CGImageCreate(buffer.width(), buffer.height(), bitsPerComponent, bitsPerPixel, buffer.bytesPerRow(), colorSpace, alphaInfo, provider, 0, false, kCGRenderingIntentDefault);
849 // Once the image is created we can release our reference to the provider and the colorspace, they are retained by the image
851 CGDataProviderRelease(provider);
853 CGColorSpaceRelease(colorSpace);
862 void MediaPlayerPrivateQuickTimeVisualContext::retrieveCurrentImage()
864 if (!m_visualContext)
867 #if USE(ACCELERATED_COMPOSITING)
868 if (m_qtVideoLayer) {
870 QTPixelBuffer buffer = m_visualContext->imageForTime(0);
871 if (!buffer.pixelBufferRef())
874 PlatformCALayer* layer = m_qtVideoLayer.get();
876 if (!buffer.lockBaseAddress()) {
877 if (requiredDllsAvailable()) {
879 m_imageQueue = adoptPtr(new WKCAImageQueue(buffer.width(), buffer.height(), 30));
880 m_imageQueue->setFlags(WKCAImageQueue::Fill, WKCAImageQueue::Fill);
881 layer->setContents(m_imageQueue->get());
884 // Debug QuickTime links against a non-Debug version of CoreFoundation, so the
885 // CFDictionary attached to the CVPixelBuffer cannot be directly passed on into the
886 // CAImageQueue without being converted to a non-Debug CFDictionary. Additionally,
887 // old versions of QuickTime used a non-AAS CoreFoundation, so the types are not
888 // interchangable even in the release case.
889 RetainPtr<CFDictionaryRef> attachments(AdoptCF, QTCFDictionaryCreateCopyWithDataCallback(kCFAllocatorDefault, buffer.attachments(), &QTCFDictionaryCreateWithDataCallback));
890 CFTimeInterval imageTime = QTMovieVisualContext::currentHostTime();
892 m_imageQueue->collect();
894 uint64_t imageId = m_imageQueue->registerPixelBuffer(buffer.baseAddress(), buffer.dataSize(), buffer.bytesPerRow(), buffer.width(), buffer.height(), buffer.pixelFormatType(), attachments.get(), 0);
896 if (m_imageQueue->insertImage(imageTime, WKCAImageQueue::Buffer, imageId, WKCAImageQueue::Opaque | WKCAImageQueue::Flush, &QTPixelBuffer::imageQueueReleaseCallback, buffer.pixelBufferRef())) {
897 // Retain the buffer one extra time so it doesn't dissappear before CAImageQueue decides to release it:
898 QTPixelBuffer::retainCallback(buffer.pixelBufferRef());
902 CGImageRef image = CreateCGImageFromPixelBuffer(buffer);
903 layer->setContents(image);
904 CGImageRelease(image);
907 buffer.unlockBaseAddress();
908 layer->setNeedsCommit();
914 m_visualContext->task();
917 static HashSet<String> mimeTypeCache()
919 DEFINE_STATIC_LOCAL(HashSet<String>, typeCache, ());
920 static bool typeListInitialized = false;
922 if (!typeListInitialized) {
923 unsigned count = QTMovie::countSupportedTypes();
924 for (unsigned n = 0; n < count; n++) {
925 const UChar* character;
927 QTMovie::getSupportedType(n, character, len);
929 typeCache.add(String(character, len));
932 typeListInitialized = true;
938 static CFStringRef createVersionStringFromModuleName(LPCWSTR moduleName)
940 HMODULE module = GetModuleHandleW(moduleName);
944 wchar_t filePath[MAX_PATH] = {0};
945 if (!GetModuleFileNameW(module, filePath, MAX_PATH))
948 DWORD versionInfoSize = GetFileVersionInfoSizeW(filePath, 0);
949 if (!versionInfoSize)
952 CFStringRef versionString = 0;
953 void* versionInfo = calloc(versionInfoSize, sizeof(char));
954 if (GetFileVersionInfo(filePath, 0, versionInfoSize, versionInfo)) {
955 VS_FIXEDFILEINFO* fileInfo = 0;
956 UINT fileInfoLength = 0;
958 if (VerQueryValueW(versionInfo, L"\\", reinterpret_cast<LPVOID*>(&fileInfo), &fileInfoLength)) {
959 versionString = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%d.%d.%d.%d"),
960 HIWORD(fileInfo->dwFileVersionMS), LOWORD(fileInfo->dwFileVersionMS),
961 HIWORD(fileInfo->dwFileVersionLS), LOWORD(fileInfo->dwFileVersionLS));
966 return versionString;
969 static bool requiredDllsAvailable()
971 static bool s_prerequisitesChecked = false;
972 static bool s_prerequisitesSatisfied;
973 static const CFStringRef kMinQuartzCoreVersion = CFSTR("1.0.42.0");
974 static const CFStringRef kMinCoreVideoVersion = CFSTR("1.0.1.0");
976 if (s_prerequisitesChecked)
977 return s_prerequisitesSatisfied;
978 s_prerequisitesChecked = true;
979 s_prerequisitesSatisfied = false;
981 CFStringRef quartzCoreString = createVersionStringFromModuleName(L"QuartzCore");
982 if (!quartzCoreString)
983 quartzCoreString = createVersionStringFromModuleName(L"QuartzCore_debug");
985 CFStringRef coreVideoString = createVersionStringFromModuleName(L"CoreVideo");
986 if (!coreVideoString)
987 coreVideoString = createVersionStringFromModuleName(L"CoreVideo_debug");
989 s_prerequisitesSatisfied = (quartzCoreString && coreVideoString
990 && CFStringCompare(quartzCoreString, kMinQuartzCoreVersion, kCFCompareNumerically) != kCFCompareLessThan
991 && CFStringCompare(coreVideoString, kMinCoreVideoVersion, kCFCompareNumerically) != kCFCompareLessThan);
993 if (quartzCoreString)
994 CFRelease(quartzCoreString);
996 CFRelease(coreVideoString);
998 return s_prerequisitesSatisfied;
1001 void MediaPlayerPrivateQuickTimeVisualContext::getSupportedTypes(HashSet<String>& types)
1003 types = mimeTypeCache();
1006 bool MediaPlayerPrivateQuickTimeVisualContext::isAvailable()
1008 return QTMovie::initializeQuickTime();
1011 MediaPlayer::SupportsType MediaPlayerPrivateQuickTimeVisualContext::supportsType(const String& type, const String& codecs)
1013 // only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an
1014 // extended MIME type
1015 return mimeTypeCache().contains(type) ? (codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported) : MediaPlayer::IsNotSupported;
1018 void MediaPlayerPrivateQuickTimeVisualContext::MovieClient::movieEnded(QTMovie* movie)
1020 if (m_parent->m_hasUnsupportedTracks)
1023 m_parent->m_visualContextTimer.stop();
1025 ASSERT(m_parent->m_movie.get() == movie);
1029 void MediaPlayerPrivateQuickTimeVisualContext::MovieClient::movieLoadStateChanged(QTMovie* movie)
1031 if (m_parent->m_hasUnsupportedTracks)
1034 ASSERT(m_parent->m_movie.get() == movie);
1035 m_parent->updateStates();
1038 void MediaPlayerPrivateQuickTimeVisualContext::MovieClient::movieTimeChanged(QTMovie* movie)
1040 if (m_parent->m_hasUnsupportedTracks)
1043 ASSERT(m_parent->m_movie.get() == movie);
1044 m_parent->updateStates();
1045 m_parent->m_player->timeChanged();
1048 bool MediaPlayerPrivateQuickTimeVisualContext::hasSingleSecurityOrigin() const
1050 // We tell quicktime to disallow resources that come from different origins
1051 // so we all media is single origin.
1055 void MediaPlayerPrivateQuickTimeVisualContext::setPreload(MediaPlayer::Preload preload)
1057 m_preload = preload;
1058 if (m_delayingLoad && m_preload != MediaPlayer::None)
1062 float MediaPlayerPrivateQuickTimeVisualContext::mediaTimeForTimeValue(float timeValue) const
1065 if (m_readyState < MediaPlayer::HaveMetadata || !(timeScale = m_movie->timeScale()))
1068 long mediaTimeValue = lroundf(timeValue * timeScale);
1069 return static_cast<float>(mediaTimeValue) / timeScale;
1072 MediaPlayerPrivateQuickTimeVisualContext::MediaRenderingMode MediaPlayerPrivateQuickTimeVisualContext::currentRenderingMode() const
1075 return MediaRenderingNone;
1077 #if USE(ACCELERATED_COMPOSITING)
1079 return MediaRenderingMovieLayer;
1082 return m_visualContext ? MediaRenderingSoftwareRenderer : MediaRenderingNone;
1085 MediaPlayerPrivateQuickTimeVisualContext::MediaRenderingMode MediaPlayerPrivateQuickTimeVisualContext::preferredRenderingMode() const
1087 if (!m_player->frameView() || !m_movie)
1088 return MediaRenderingNone;
1090 #if USE(ACCELERATED_COMPOSITING)
1091 if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player))
1092 return MediaRenderingMovieLayer;
1095 return MediaRenderingSoftwareRenderer;
1098 void MediaPlayerPrivateQuickTimeVisualContext::setUpVideoRendering()
1100 MediaRenderingMode currentMode = currentRenderingMode();
1101 MediaRenderingMode preferredMode = preferredRenderingMode();
1103 #if !USE(ACCELERATED_COMPOSITING)
1104 ASSERT(preferredMode != MediaRenderingMovieLayer);
1107 if (currentMode == preferredMode && currentMode != MediaRenderingNone)
1110 if (currentMode != MediaRenderingNone)
1111 tearDownVideoRendering();
1113 if (preferredMode == MediaRenderingMovieLayer)
1114 createLayerForMovie();
1116 #if USE(ACCELERATED_COMPOSITING)
1117 if (currentMode == MediaRenderingMovieLayer || preferredMode == MediaRenderingMovieLayer)
1118 m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
1121 QTPixelBuffer::Type contextType = requiredDllsAvailable() && preferredMode == MediaRenderingMovieLayer ? QTPixelBuffer::ConfigureForCAImageQueue : QTPixelBuffer::ConfigureForCGImage;
1122 m_visualContext = QTMovieVisualContext::create(m_visualContextClient.get(), contextType);
1123 m_visualContext->setMovie(m_movie.get());
1126 void MediaPlayerPrivateQuickTimeVisualContext::tearDownVideoRendering()
1128 #if USE(ACCELERATED_COMPOSITING)
1130 destroyLayerForMovie();
1133 m_visualContext = 0;
1136 bool MediaPlayerPrivateQuickTimeVisualContext::hasSetUpVideoRendering() const
1138 #if USE(ACCELERATED_COMPOSITING)
1139 return m_qtVideoLayer || (currentRenderingMode() != MediaRenderingMovieLayer && m_visualContext);
1145 void MediaPlayerPrivateQuickTimeVisualContext::retrieveAndResetMovieTransform()
1147 #if USE(ACCELERATED_COMPOSITING)
1148 // First things first, reset the total movie transform so that
1149 // we can bail out early:
1150 m_movieTransform = CGAffineTransformIdentity;
1152 if (!m_movie || !m_movie->hasVideo())
1155 // This trick will only work on movies with a single video track,
1156 // so bail out early if the video contains more than one (or zero)
1158 QTTrackArray videoTracks = m_movie->videoTracks();
1159 if (videoTracks.size() != 1)
1162 QTTrack* track = videoTracks[0].get();
1165 CGAffineTransform movieTransform = m_movie->getTransform();
1166 if (!CGAffineTransformEqualToTransform(movieTransform, CGAffineTransformIdentity))
1167 m_movie->resetTransform();
1169 CGAffineTransform trackTransform = track->getTransform();
1170 if (!CGAffineTransformEqualToTransform(trackTransform, CGAffineTransformIdentity))
1171 track->resetTransform();
1173 // Multiply the two transforms together, taking care to
1174 // do so in the correct order, track * movie = final:
1175 m_movieTransform = CGAffineTransformConcat(trackTransform, movieTransform);
1179 void MediaPlayerPrivateQuickTimeVisualContext::createLayerForMovie()
1181 #if USE(ACCELERATED_COMPOSITING)
1182 ASSERT(supportsAcceleratedRendering());
1184 if (!m_movie || m_qtVideoLayer)
1187 // Create a PlatformCALayer which will transform the contents of the video layer
1188 // which is in m_qtVideoLayer.
1189 m_transformLayer = PlatformCALayer::create(PlatformCALayer::LayerTypeLayer, m_layerClient.get());
1190 if (!m_transformLayer)
1193 // Mark the layer as anchored in the top left.
1194 m_transformLayer->setAnchorPoint(FloatPoint3D());
1196 m_qtVideoLayer = PlatformCALayer::create(PlatformCALayer::LayerTypeLayer, 0);
1197 if (!m_qtVideoLayer)
1200 if (CGAffineTransformEqualToTransform(m_movieTransform, CGAffineTransformIdentity))
1201 retrieveAndResetMovieTransform();
1202 CGAffineTransform t = m_movieTransform;
1204 // Remove the translation portion of the transform, since we will always rotate about
1205 // the layer's center point. In our limited use-case (a single video track), this is
1208 m_qtVideoLayer->setTransform(CATransform3DMakeAffineTransform(t));
1211 m_qtVideoLayer->setName("Video layer");
1213 m_transformLayer->appendSublayer(m_qtVideoLayer.get());
1214 m_transformLayer->setNeedsLayout();
1215 // The layer will get hooked up via RenderLayerBacking::updateGraphicsLayerConfiguration().
1218 // Fill the newly created layer with image data, so we're not looking at
1219 // an empty layer until the next time a new image is available, which could
1220 // be a long time if we're paused.
1221 if (m_visualContext)
1222 retrieveCurrentImage();
1225 void MediaPlayerPrivateQuickTimeVisualContext::destroyLayerForMovie()
1227 #if USE(ACCELERATED_COMPOSITING)
1228 if (m_qtVideoLayer) {
1229 m_qtVideoLayer->removeFromSuperlayer();
1233 if (m_transformLayer)
1234 m_transformLayer = 0;
1237 m_imageQueue = nullptr;
1241 #if USE(ACCELERATED_COMPOSITING)
1242 bool MediaPlayerPrivateQuickTimeVisualContext::supportsAcceleratedRendering() const
1244 return isReadyForRendering();
1247 void MediaPlayerPrivateQuickTimeVisualContext::acceleratedRenderingStateChanged()
1249 // Set up or change the rendering path if necessary.
1250 setUpVideoRendering();
1253 void MediaPlayerPrivateQuickTimeVisualContext::setPrivateBrowsingMode(bool privateBrowsing)
1255 m_privateBrowsing = privateBrowsing;
1257 m_movie->setPrivateBrowsingMode(m_privateBrowsing);