| /* |
| * Copyright (C) 2007, 2008, 2009, 2010 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 "MediaPlayerPrivateQuickTimeWin.h" |
| |
| #include "Cookie.h" |
| #include "CookieJar.h" |
| #include "Frame.h" |
| #include "FrameView.h" |
| #include "GraphicsContext.h" |
| #include "KURL.h" |
| #include "QTMovieWin.h" |
| #include "ScrollView.h" |
| #include "SoftLinking.h" |
| #include "StringBuilder.h" |
| #include "StringHash.h" |
| #include "TimeRanges.h" |
| #include "Timer.h" |
| #include <Wininet.h> |
| #include <wtf/CurrentTime.h> |
| #include <wtf/HashSet.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/StdLibExtras.h> |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| #include "GraphicsLayerCACF.h" |
| #include "WKCACFLayer.h" |
| #endif |
| |
| #if DRAW_FRAME_RATE |
| #include "Font.h" |
| #include "FrameView.h" |
| #include "Frame.h" |
| #include "Document.h" |
| #include "RenderObject.h" |
| #include "RenderStyle.h" |
| #include "Windows.h" |
| #endif |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| 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)) |
| |
| MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) |
| { |
| return new MediaPlayerPrivate(player); |
| } |
| |
| void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) |
| { |
| if (isAvailable()) |
| registrar(create, getSupportedTypes, supportsType); |
| } |
| |
| MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) |
| : m_player(player) |
| , m_seekTo(-1) |
| , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired) |
| , 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) |
| #if DRAW_FRAME_RATE |
| , m_frameCountWhilePlaying(0) |
| , m_timeStartedPlaying(0) |
| , m_timeStoppedPlaying(0) |
| #endif |
| { |
| } |
| |
| MediaPlayerPrivate::~MediaPlayerPrivate() |
| { |
| tearDownVideoRendering(); |
| } |
| |
| bool MediaPlayerPrivate::supportsFullscreen() const |
| { |
| return true; |
| } |
| |
| PlatformMedia MediaPlayerPrivate::platformMedia() const |
| { |
| PlatformMedia p; |
| p.qtMovie = reinterpret_cast<QTMovie*>(m_qtMovie.get()); |
| return p; |
| } |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| PlatformLayer* MediaPlayerPrivate::platformLayer() const |
| { |
| return m_qtVideoLayer ? m_qtVideoLayer->platformLayer() : 0; |
| } |
| #endif |
| |
| class TaskTimer : TimerBase { |
| public: |
| static void initialize(); |
| |
| private: |
| static void setTaskTimerDelay(double); |
| static void stopTaskTimer(); |
| |
| void fired(); |
| |
| static TaskTimer* s_timer; |
| }; |
| |
| TaskTimer* TaskTimer::s_timer = 0; |
| |
| void TaskTimer::initialize() |
| { |
| if (s_timer) |
| return; |
| |
| s_timer = new TaskTimer; |
| |
| QTMovieWin::setTaskTimerFuncs(setTaskTimerDelay, stopTaskTimer); |
| } |
| |
| void TaskTimer::setTaskTimerDelay(double delayInSeconds) |
| { |
| ASSERT(s_timer); |
| |
| s_timer->startOneShot(delayInSeconds); |
| } |
| |
| void TaskTimer::stopTaskTimer() |
| { |
| ASSERT(s_timer); |
| |
| s_timer->stop(); |
| } |
| |
| void TaskTimer::fired() |
| { |
| QTMovieWin::taskTimerFired(); |
| } |
| |
| String MediaPlayerPrivate::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<CFStringRef> 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 MediaPlayerPrivate::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. |
| Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : 0; |
| if (!frame || !frame->page() || !frame->page()->cookieEnabled()) |
| return; |
| |
| KURL movieURL = KURL(KURL(), url); |
| Vector<Cookie> 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); |
| } |
| } |
| |
| void MediaPlayerPrivate::load(const String& url) |
| { |
| if (!QTMovieWin::initializeQuickTime()) { |
| // FIXME: is this the right error to return? |
| m_networkState = MediaPlayer::DecodeError; |
| m_player->networkStateChanged(); |
| return; |
| } |
| |
| // Initialize the task timer. |
| TaskTimer::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_qtMovie.set(new QTMovieWin(this)); |
| m_qtMovie->load(url.characters(), url.length(), m_player->preservesPitch()); |
| m_qtMovie->setVolume(m_player->volume()); |
| m_qtMovie->setVisible(m_player->visible()); |
| } |
| |
| void MediaPlayerPrivate::play() |
| { |
| if (!m_qtMovie) |
| return; |
| m_startedPlaying = true; |
| #if DRAW_FRAME_RATE |
| m_frameCountWhilePlaying = 0; |
| #endif |
| |
| m_qtMovie->play(); |
| } |
| |
| void MediaPlayerPrivate::pause() |
| { |
| if (!m_qtMovie) |
| return; |
| m_startedPlaying = false; |
| #if DRAW_FRAME_RATE |
| m_timeStoppedPlaying = WTF::currentTime(); |
| #endif |
| m_qtMovie->pause(); |
| } |
| |
| float MediaPlayerPrivate::duration() const |
| { |
| if (!m_qtMovie) |
| return 0; |
| return m_qtMovie->duration(); |
| } |
| |
| float MediaPlayerPrivate::currentTime() const |
| { |
| if (!m_qtMovie) |
| return 0; |
| return m_qtMovie->currentTime(); |
| } |
| |
| void MediaPlayerPrivate::seek(float time) |
| { |
| cancelSeek(); |
| |
| if (!m_qtMovie) |
| return; |
| |
| if (time > duration()) |
| time = duration(); |
| |
| m_seekTo = time; |
| if (maxTimeLoaded() >= m_seekTo) |
| doSeek(); |
| else |
| m_seekTimer.start(0, 0.5f); |
| } |
| |
| void MediaPlayerPrivate::doSeek() |
| { |
| float oldRate = m_qtMovie->rate(); |
| if (oldRate) |
| m_qtMovie->setRate(0); |
| m_qtMovie->setCurrentTime(m_seekTo); |
| float timeAfterSeek = currentTime(); |
| // restore playback only if not at end, othewise QTMovie will loop |
| if (oldRate && timeAfterSeek < duration()) |
| m_qtMovie->setRate(oldRate); |
| cancelSeek(); |
| } |
| |
| void MediaPlayerPrivate::cancelSeek() |
| { |
| m_seekTo = -1; |
| m_seekTimer.stop(); |
| } |
| |
| void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*) |
| { |
| if (!m_qtMovie || !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 MediaPlayerPrivate::paused() const |
| { |
| if (!m_qtMovie) |
| return true; |
| return m_qtMovie->rate() == 0.0f; |
| } |
| |
| bool MediaPlayerPrivate::seeking() const |
| { |
| if (!m_qtMovie) |
| return false; |
| return m_seekTo >= 0; |
| } |
| |
| IntSize MediaPlayerPrivate::naturalSize() const |
| { |
| if (!m_qtMovie) |
| return IntSize(); |
| int width; |
| int height; |
| m_qtMovie->getNaturalSize(width, height); |
| return IntSize(width, height); |
| } |
| |
| bool MediaPlayerPrivate::hasVideo() const |
| { |
| if (!m_qtMovie) |
| return false; |
| return m_qtMovie->hasVideo(); |
| } |
| |
| bool MediaPlayerPrivate::hasAudio() const |
| { |
| if (!m_qtMovie) |
| return false; |
| return m_qtMovie->hasAudio(); |
| } |
| |
| void MediaPlayerPrivate::setVolume(float volume) |
| { |
| if (!m_qtMovie) |
| return; |
| m_qtMovie->setVolume(volume); |
| } |
| |
| void MediaPlayerPrivate::setRate(float rate) |
| { |
| if (!m_qtMovie) |
| return; |
| m_qtMovie->setRate(rate); |
| } |
| |
| void MediaPlayerPrivate::setPreservesPitch(bool preservesPitch) |
| { |
| if (!m_qtMovie) |
| return; |
| m_qtMovie->setPreservesPitch(preservesPitch); |
| } |
| |
| bool MediaPlayerPrivate::hasClosedCaptions() const |
| { |
| if (!m_qtMovie) |
| return false; |
| return m_qtMovie->hasClosedCaptions(); |
| } |
| |
| void MediaPlayerPrivate::setClosedCaptionsVisible(bool visible) |
| { |
| if (!m_qtMovie) |
| return; |
| m_qtMovie->setClosedCaptionsVisible(visible); |
| } |
| |
| PassRefPtr<TimeRanges> MediaPlayerPrivate::buffered() const |
| { |
| RefPtr<TimeRanges> timeRanges = TimeRanges::create(); |
| float loaded = maxTimeLoaded(); |
| // rtsp streams are not buffered |
| if (!m_isStreaming && loaded > 0) |
| timeRanges->add(0, loaded); |
| return timeRanges.release(); |
| } |
| |
| float MediaPlayerPrivate::maxTimeSeekable() const |
| { |
| // infinite duration means live stream |
| return !isfinite(duration()) ? 0 : maxTimeLoaded(); |
| } |
| |
| float MediaPlayerPrivate::maxTimeLoaded() const |
| { |
| if (!m_qtMovie) |
| return 0; |
| return m_qtMovie->maxTimeLoaded(); |
| } |
| |
| unsigned MediaPlayerPrivate::bytesLoaded() const |
| { |
| if (!m_qtMovie) |
| return 0; |
| float dur = duration(); |
| float maxTime = maxTimeLoaded(); |
| if (!dur) |
| return 0; |
| return totalBytes() * maxTime / dur; |
| } |
| |
| unsigned MediaPlayerPrivate::totalBytes() const |
| { |
| if (!m_qtMovie) |
| return 0; |
| return m_qtMovie->dataSize(); |
| } |
| |
| void MediaPlayerPrivate::cancelLoad() |
| { |
| if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded) |
| return; |
| |
| tearDownVideoRendering(); |
| |
| // Cancel the load by destroying the movie. |
| m_qtMovie.clear(); |
| |
| updateStates(); |
| } |
| |
| void MediaPlayerPrivate::updateStates() |
| { |
| MediaPlayer::NetworkState oldNetworkState = m_networkState; |
| MediaPlayer::ReadyState oldReadyState = m_readyState; |
| |
| long loadState = m_qtMovie ? m_qtMovie->loadState() : QTMovieLoadStateError; |
| |
| if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) { |
| m_qtMovie->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 <video> |
| // element, eg. QuickTime VR, a movie with a sprite track, etc. Tell the |
| // MediaPlayerClient that we won't support it. |
| sawUnsupportedTracks(); |
| return; |
| } |
| } else if (!m_enabledTrackCount) |
| loadState = QTMovieLoadStateError; |
| } |
| |
| // "Loaded" is reserved for fully buffered movies, never the case when streaming |
| if (loadState >= QTMovieLoadStateComplete && !m_isStreaming) { |
| m_networkState = MediaPlayer::Loaded; |
| m_readyState = MediaPlayer::HaveEnoughData; |
| } else if (loadState >= QTMovieLoadStatePlaythroughOK) { |
| m_readyState = MediaPlayer::HaveEnoughData; |
| } else if (loadState >= QTMovieLoadStatePlayable) { |
| // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967> |
| m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData; |
| } else if (loadState >= QTMovieLoadStateLoaded) { |
| m_readyState = MediaPlayer::HaveMetadata; |
| } else if (loadState > QTMovieLoadStateError) { |
| m_networkState = MediaPlayer::Loading; |
| m_readyState = MediaPlayer::HaveNothing; |
| } else { |
| if (m_player->inMediaDocument()) { |
| // Something went wrong in the loading of media within a standalone file. |
| // This can occur with chained ref movies that eventually resolve to a |
| // file we don't support. |
| sawUnsupportedTracks(); |
| return; |
| } |
| |
| float loaded = maxTimeLoaded(); |
| if (!loaded) |
| m_readyState = MediaPlayer::HaveNothing; |
| |
| if (!m_enabledTrackCount) |
| m_networkState = MediaPlayer::FormatError; |
| else { |
| // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692> |
| if (loaded > 0) |
| m_networkState = MediaPlayer::DecodeError; |
| else |
| m_readyState = MediaPlayer::HaveNothing; |
| } |
| } |
| |
| if (isReadyForRendering() && !hasSetUpVideoRendering()) |
| setUpVideoRendering(); |
| |
| if (seeking()) |
| m_readyState = MediaPlayer::HaveNothing; |
| |
| if (m_networkState != oldNetworkState) |
| m_player->networkStateChanged(); |
| if (m_readyState != oldReadyState) |
| m_player->readyStateChanged(); |
| } |
| |
| bool MediaPlayerPrivate::isReadyForRendering() const |
| { |
| return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible(); |
| } |
| |
| void MediaPlayerPrivate::sawUnsupportedTracks() |
| { |
| m_qtMovie->setDisabled(true); |
| m_hasUnsupportedTracks = true; |
| m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player); |
| } |
| |
| void MediaPlayerPrivate::didEnd() |
| { |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| m_startedPlaying = false; |
| #if DRAW_FRAME_RATE |
| m_timeStoppedPlaying = WTF::currentTime(); |
| #endif |
| updateStates(); |
| m_player->timeChanged(); |
| } |
| |
| void MediaPlayerPrivate::setSize(const IntSize& size) |
| { |
| if (m_hasUnsupportedTracks || !m_qtMovie || m_size == size) |
| return; |
| m_size = size; |
| m_qtMovie->setSize(size.width(), size.height()); |
| } |
| |
| void MediaPlayerPrivate::setVisible(bool visible) |
| { |
| if (m_hasUnsupportedTracks || !m_qtMovie || m_visible == visible) |
| return; |
| |
| m_qtMovie->setVisible(visible); |
| m_visible = visible; |
| if (m_visible) { |
| if (isReadyForRendering()) |
| setUpVideoRendering(); |
| } else |
| tearDownVideoRendering(); |
| } |
| |
| void MediaPlayerPrivate::paint(GraphicsContext* p, const IntRect& r) |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| if (m_qtVideoLayer) |
| return; |
| #endif |
| if (p->paintingDisabled() || !m_qtMovie || m_hasUnsupportedTracks) |
| return; |
| |
| bool usingTempBitmap = false; |
| OwnPtr<GraphicsContext::WindowsBitmap> bitmap; |
| HDC hdc = p->getWindowsContext(r); |
| if (!hdc) { |
| // The graphics context doesn't have an associated HDC so create a temporary |
| // bitmap where QTMovieWin can draw the frame and we can copy it. |
| usingTempBitmap = true; |
| bitmap.set(p->createWindowsBitmap(r.size())); |
| hdc = bitmap->hdc(); |
| |
| // FIXME: is this necessary?? |
| XFORM xform; |
| xform.eM11 = 1.0f; |
| xform.eM12 = 0.0f; |
| xform.eM21 = 0.0f; |
| xform.eM22 = 1.0f; |
| xform.eDx = -r.x(); |
| xform.eDy = -r.y(); |
| SetWorldTransform(hdc, &xform); |
| } |
| |
| m_qtMovie->paint(hdc, r.x(), r.y()); |
| if (usingTempBitmap) |
| p->drawWindowsBitmap(bitmap.get(), r.topLeft()); |
| else |
| p->releaseWindowsContext(hdc, r); |
| |
| paintCompleted(*p, r); |
| } |
| |
| void MediaPlayerPrivate::paintCompleted(GraphicsContext& context, const IntRect& rect) |
| { |
| m_newFrameAvailable = false; |
| |
| #if DRAW_FRAME_RATE |
| if (m_frameCountWhilePlaying > 10) { |
| double interval = m_startedPlaying ? WTF::currentTime() - m_timeStartedPlaying : m_timeStoppedPlaying - m_timeStartedPlaying; |
| double frameRate = (m_frameCountWhilePlaying - 1) / interval; |
| CGContextRef cgContext = context.platformContext(); |
| CGRect drawRect = rect; |
| |
| char text[8]; |
| _snprintf(text, sizeof(text), "%1.2f", frameRate); |
| |
| static const int fontSize = 25; |
| static const int fontCharWidth = 12; |
| static const int boxHeight = 25; |
| static const int boxBorderWidth = 4; |
| drawRect.size.width = boxBorderWidth * 2 + fontCharWidth * strlen(text); |
| drawRect.size.height = boxHeight; |
| |
| CGContextSaveGState(cgContext); |
| #if USE(ACCELERATED_COMPOSITING) |
| if (m_qtVideoLayer) |
| CGContextScaleCTM(cgContext, 1, -1); |
| CGContextTranslateCTM(cgContext, rect.width() - drawRect.size.width, m_qtVideoLayer ? -rect.height() : 0); |
| #else |
| CGContextTranslateCTM(cgContext, rect.width() - drawRect.size.width, 0); |
| #endif |
| static const CGFloat backgroundColor[4] = { 0.98, 0.98, 0.82, 0.8 }; |
| CGContextSetFillColor(cgContext, backgroundColor); |
| CGContextFillRect(cgContext, drawRect); |
| |
| static const CGFloat textColor[4] = { 0, 0, 0, 1 }; |
| CGContextSetFillColor(cgContext, textColor); |
| CGContextSetTextMatrix(cgContext, CGAffineTransformMakeScale(1, -1)); |
| CGContextSelectFont(cgContext, "Helvetica", fontSize, kCGEncodingMacRoman); |
| |
| CGContextShowTextAtPoint(cgContext, drawRect.origin.x + boxBorderWidth, drawRect.origin.y + boxHeight - boxBorderWidth, text, strlen(text)); |
| |
| CGContextRestoreGState(cgContext); |
| } |
| #endif |
| } |
| |
| static HashSet<String> mimeTypeCache() |
| { |
| DEFINE_STATIC_LOCAL(HashSet<String>, typeCache, ()); |
| static bool typeListInitialized = false; |
| |
| if (!typeListInitialized) { |
| unsigned count = QTMovieWin::countSupportedTypes(); |
| for (unsigned n = 0; n < count; n++) { |
| const UChar* character; |
| unsigned len; |
| QTMovieWin::getSupportedType(n, character, len); |
| if (len) |
| typeCache.add(String(character, len)); |
| } |
| |
| typeListInitialized = true; |
| } |
| |
| return typeCache; |
| } |
| |
| void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types) |
| { |
| types = mimeTypeCache(); |
| } |
| |
| bool MediaPlayerPrivate::isAvailable() |
| { |
| return QTMovieWin::initializeQuickTime(); |
| } |
| |
| MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs) |
| { |
| // only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an |
| // extended MIME type |
| return mimeTypeCache().contains(type) ? (codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported) : MediaPlayer::IsNotSupported; |
| } |
| |
| void MediaPlayerPrivate::movieEnded(QTMovieWin* movie) |
| { |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| ASSERT(m_qtMovie.get() == movie); |
| didEnd(); |
| } |
| |
| void MediaPlayerPrivate::movieLoadStateChanged(QTMovieWin* movie) |
| { |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| ASSERT(m_qtMovie.get() == movie); |
| updateStates(); |
| } |
| |
| void MediaPlayerPrivate::movieTimeChanged(QTMovieWin* movie) |
| { |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| ASSERT(m_qtMovie.get() == movie); |
| updateStates(); |
| m_player->timeChanged(); |
| } |
| |
| void MediaPlayerPrivate::movieNewImageAvailable(QTMovieWin* movie) |
| { |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| ASSERT(m_qtMovie.get() == movie); |
| #if DRAW_FRAME_RATE |
| if (m_startedPlaying) { |
| m_frameCountWhilePlaying++; |
| // To eliminate preroll costs from our calculation, our frame rate calculation excludes |
| // the first frame drawn after playback starts. |
| if (m_frameCountWhilePlaying == 1) |
| m_timeStartedPlaying = WTF::currentTime(); |
| } |
| #endif |
| |
| m_newFrameAvailable = true; |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| if (m_qtVideoLayer) |
| m_qtVideoLayer->platformLayer()->setNeedsDisplay(); |
| else |
| #endif |
| m_player->repaint(); |
| } |
| |
| bool MediaPlayerPrivate::hasSingleSecurityOrigin() const |
| { |
| // We tell quicktime to disallow resources that come from different origins |
| // so we all media is single origin. |
| return true; |
| } |
| |
| MediaPlayerPrivate::MediaRenderingMode MediaPlayerPrivate::currentRenderingMode() const |
| { |
| if (!m_qtMovie) |
| return MediaRenderingNone; |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| if (m_qtVideoLayer) |
| return MediaRenderingMovieLayer; |
| #endif |
| |
| return MediaRenderingSoftwareRenderer; |
| } |
| |
| MediaPlayerPrivate::MediaRenderingMode MediaPlayerPrivate::preferredRenderingMode() const |
| { |
| if (!m_player->frameView() || !m_qtMovie) |
| return MediaRenderingNone; |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player)) |
| return MediaRenderingMovieLayer; |
| #endif |
| |
| return MediaRenderingSoftwareRenderer; |
| } |
| |
| void MediaPlayerPrivate::setUpVideoRendering() |
| { |
| MediaRenderingMode currentMode = currentRenderingMode(); |
| MediaRenderingMode preferredMode = preferredRenderingMode(); |
| |
| #if !USE(ACCELERATED_COMPOSITING) |
| ASSERT(preferredMode != MediaRenderingMovieLayer); |
| #endif |
| |
| if (currentMode == preferredMode && currentMode != MediaRenderingNone) |
| return; |
| |
| if (currentMode != MediaRenderingNone) |
| tearDownVideoRendering(); |
| |
| if (preferredMode == MediaRenderingMovieLayer) |
| createLayerForMovie(); |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| if (currentMode == MediaRenderingMovieLayer || preferredMode == MediaRenderingMovieLayer) |
| m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player); |
| #endif |
| } |
| |
| void MediaPlayerPrivate::tearDownVideoRendering() |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| if (m_qtVideoLayer) |
| destroyLayerForMovie(); |
| #endif |
| } |
| |
| bool MediaPlayerPrivate::hasSetUpVideoRendering() const |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| return m_qtVideoLayer || currentRenderingMode() != MediaRenderingMovieLayer; |
| #else |
| return true; |
| #endif |
| } |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| |
| // Up-call from compositing layer drawing callback. |
| void MediaPlayerPrivate::paintContents(const GraphicsLayer*, GraphicsContext& context, GraphicsLayerPaintingPhase, const IntRect&) |
| { |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| ASSERT(supportsAcceleratedRendering()); |
| |
| // No reason to replace the current layer image unless we have something new to show. |
| if (!m_newFrameAvailable) |
| return; |
| |
| static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); |
| void* buffer; |
| unsigned bitsPerPixel; |
| unsigned rowBytes; |
| unsigned width; |
| unsigned height; |
| |
| m_qtMovie->getCurrentFrameInfo(buffer, bitsPerPixel, rowBytes, width, height); |
| if (!buffer) |
| return ; |
| |
| RetainPtr<CFDataRef> data(AdoptCF, CFDataCreateWithBytesNoCopy(0, static_cast<UInt8*>(buffer), rowBytes * height, kCFAllocatorNull)); |
| RetainPtr<CGDataProviderRef> provider(AdoptCF, CGDataProviderCreateWithCFData(data.get())); |
| RetainPtr<CGImageRef> frameImage(AdoptCF, CGImageCreate(width, height, 8, bitsPerPixel, rowBytes, colorSpace, |
| kCGBitmapByteOrder32Little | kCGImageAlphaFirst, provider.get(), 0, false, kCGRenderingIntentDefault)); |
| if (!frameImage) |
| return; |
| |
| IntRect rect(0, 0, m_size.width(), m_size.height()); |
| CGContextDrawImage(context.platformContext(), rect, frameImage.get()); |
| paintCompleted(context, rect); |
| } |
| #endif |
| |
| void MediaPlayerPrivate::createLayerForMovie() |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| ASSERT(supportsAcceleratedRendering()); |
| |
| if (!m_qtMovie || m_qtVideoLayer) |
| return; |
| |
| // Create a GraphicsLayer that won't be inserted directly into the render tree, but will used |
| // as a wrapper for a WKCACFLayer which gets inserted as the content layer of the video |
| // renderer's GraphicsLayer. |
| m_qtVideoLayer.set(new GraphicsLayerCACF(this)); |
| if (!m_qtVideoLayer) |
| return; |
| |
| // Mark the layer as drawing itself, anchored in the top left, and bottom-up. |
| m_qtVideoLayer->setDrawsContent(true); |
| m_qtVideoLayer->setAnchorPoint(FloatPoint3D()); |
| m_qtVideoLayer->setContentsOrientation(GraphicsLayer::CompositingCoordinatesBottomUp); |
| #ifndef NDEBUG |
| m_qtVideoLayer->setName("Video layer"); |
| #endif |
| // The layer will get hooked up via RenderLayerBacking::updateGraphicsLayerConfiguration(). |
| #endif |
| } |
| |
| void MediaPlayerPrivate::destroyLayerForMovie() |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| if (!m_qtVideoLayer) |
| return; |
| m_qtVideoLayer = 0; |
| #endif |
| } |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| bool MediaPlayerPrivate::supportsAcceleratedRendering() const |
| { |
| return isReadyForRendering(); |
| } |
| |
| void MediaPlayerPrivate::acceleratedRenderingStateChanged() |
| { |
| // Set up or change the rendering path if necessary. |
| setUpVideoRendering(); |
| } |
| |
| #endif |
| |
| |
| } |
| |
| #endif |