| /* |
| * Copyright (C) 2007 Apple Computer, 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" |
| |
| #include <windows.h> |
| |
| #include "QTMovieWin.h" |
| |
| // Put Movies.h first so build failures here point clearly to QuickTime |
| #include <Movies.h> |
| #include <QuickTimeComponents.h> |
| #include <GXMath.h> |
| #include <QTML.h> |
| |
| #include "QTMovieWinTimer.h" |
| |
| #include <wtf/Assertions.h> |
| #include <wtf/HashSet.h> |
| #include <wtf/Noncopyable.h> |
| #include <wtf/Vector.h> |
| |
| using namespace std; |
| |
| static const long minimumQuickTimeVersion = 0x07300000; // 7.3 |
| |
| // Resizing GWorlds is slow, give them a minimum size so size of small |
| // videos can be animated smoothly |
| static const int cGWorldMinWidth = 640; |
| static const int cGWorldMinHeight = 360; |
| |
| static const float cNonContinuousTimeChange = 0.2f; |
| |
| union UppParam { |
| long longValue; |
| void* ptr; |
| }; |
| |
| static MovieDrawingCompleteUPP gMovieDrawingCompleteUPP = 0; |
| static HashSet<QTMovieWinPrivate*>* gTaskList; |
| static Vector<CFStringRef>* gSupportedTypes = 0; |
| static SInt32 quickTimeVersion = 0; |
| |
| static void updateTaskTimer(int maxInterval = 1000) |
| { |
| if (!gTaskList->size()) { |
| stopSharedTimer(); |
| return; |
| } |
| |
| long intervalInMS; |
| QTGetTimeUntilNextTask(&intervalInMS, 1000); |
| if (intervalInMS > maxInterval) |
| intervalInMS = maxInterval; |
| setSharedTimerFireDelay(static_cast<float>(intervalInMS) / 1000); |
| } |
| |
| class QTMovieWinPrivate : Noncopyable { |
| public: |
| QTMovieWinPrivate(); |
| ~QTMovieWinPrivate(); |
| void task(); |
| void startTask(); |
| void endTask(); |
| |
| void createMovieController(); |
| void registerDrawingCallback(); |
| void drawingComplete(); |
| void updateGWorld(); |
| void createGWorld(); |
| void deleteGWorld(); |
| void clearGWorld(); |
| |
| void setSize(int, int); |
| |
| QTMovieWin* m_movieWin; |
| Movie m_movie; |
| MovieController m_movieController; |
| bool m_tasking; |
| QTMovieWinClient* m_client; |
| long m_loadState; |
| bool m_ended; |
| bool m_seeking; |
| float m_lastMediaTime; |
| double m_lastLoadStateCheckTime; |
| int m_width; |
| int m_height; |
| bool m_visible; |
| GWorldPtr m_gWorld; |
| int m_gWorldWidth; |
| int m_gWorldHeight; |
| GWorldPtr m_savedGWorld; |
| long m_loadError; |
| }; |
| |
| QTMovieWinPrivate::QTMovieWinPrivate() |
| : m_movieWin(0) |
| , m_movie(0) |
| , m_movieController(0) |
| , m_tasking(false) |
| , m_client(0) |
| , m_loadState(0) |
| , m_ended(false) |
| , m_seeking(false) |
| , m_lastMediaTime(0) |
| , m_lastLoadStateCheckTime(0) |
| , m_width(0) |
| , m_height(0) |
| , m_visible(false) |
| , m_gWorld(0) |
| , m_gWorldWidth(0) |
| , m_gWorldHeight(0) |
| , m_savedGWorld(0) |
| , m_loadError(0) |
| { |
| } |
| |
| QTMovieWinPrivate::~QTMovieWinPrivate() |
| { |
| endTask(); |
| if (m_gWorld) |
| deleteGWorld(); |
| if (m_movieController) |
| DisposeMovieController(m_movieController); |
| if (m_movie) |
| DisposeMovie(m_movie); |
| } |
| |
| static void taskTimerFired() |
| { |
| // The hash content might change during task() |
| Vector<QTMovieWinPrivate*> tasks; |
| copyToVector(*gTaskList, tasks); |
| size_t count = tasks.size(); |
| for (unsigned n = 0; n < count; ++n) |
| tasks[n]->task(); |
| |
| updateTaskTimer(); |
| } |
| |
| void QTMovieWinPrivate::startTask() |
| { |
| if (m_tasking) |
| return; |
| if (!gTaskList) |
| gTaskList = new HashSet<QTMovieWinPrivate*>; |
| gTaskList->add(this); |
| m_tasking = true; |
| updateTaskTimer(); |
| } |
| |
| void QTMovieWinPrivate::endTask() |
| { |
| if (!m_tasking) |
| return; |
| gTaskList->remove(this); |
| m_tasking = false; |
| updateTaskTimer(); |
| } |
| |
| void QTMovieWinPrivate::task() |
| { |
| ASSERT(m_tasking); |
| |
| if (!m_loadError) { |
| if (m_movieController) |
| MCIdle(m_movieController); |
| else |
| MoviesTask(m_movie, 0); |
| } |
| |
| // GetMovieLoadState documentation says that you should not call it more often than every quarter of a second. |
| if (systemTime() >= m_lastLoadStateCheckTime + 0.25 || m_loadError) { |
| // If load fails QT's load state is kMovieLoadStateComplete. |
| // This is different from QTKit API and seems strange. |
| long loadState = m_loadError ? kMovieLoadStateError : GetMovieLoadState(m_movie); |
| if (loadState != m_loadState) { |
| |
| // we only need to erase the movie gworld when the load state changes to loaded while it |
| // is visible as the gworld is destroyed/created when visibility changes |
| if (loadState >= QTMovieLoadStateLoaded && m_loadState < QTMovieLoadStateLoaded && m_visible) |
| clearGWorld(); |
| |
| m_loadState = loadState; |
| if (!m_movieController && m_loadState >= kMovieLoadStateLoaded) |
| createMovieController(); |
| m_client->movieLoadStateChanged(m_movieWin); |
| } |
| m_lastLoadStateCheckTime = systemTime(); |
| } |
| |
| bool ended = !!IsMovieDone(m_movie); |
| if (ended != m_ended) { |
| m_ended = ended; |
| if (m_client && ended) |
| m_client->movieEnded(m_movieWin); |
| } |
| |
| float time = m_movieWin->currentTime(); |
| if (time < m_lastMediaTime || time >= m_lastMediaTime + cNonContinuousTimeChange || m_seeking) { |
| m_seeking = false; |
| if (m_client) |
| m_client->movieTimeChanged(m_movieWin); |
| } |
| m_lastMediaTime = time; |
| |
| if (m_loadError) |
| endTask(); |
| } |
| |
| void QTMovieWinPrivate::createMovieController() |
| { |
| Rect bounds; |
| long flags; |
| |
| if (!m_movie) |
| return; |
| |
| if (m_movieController) |
| DisposeMovieController(m_movieController); |
| |
| GetMovieBox(m_movie, &bounds); |
| flags = mcTopLeftMovie | mcNotVisible; |
| m_movieController = NewMovieController(m_movie, &bounds, flags); |
| if (!m_movieController) |
| return; |
| |
| MCSetControllerPort(m_movieController, m_gWorld); |
| MCSetControllerAttached(m_movieController, false); |
| } |
| |
| void QTMovieWinPrivate::registerDrawingCallback() |
| { |
| UppParam param; |
| param.ptr = this; |
| SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, gMovieDrawingCompleteUPP, param.longValue); |
| } |
| |
| void QTMovieWinPrivate::drawingComplete() |
| { |
| if (!m_gWorld || m_loadState < kMovieLoadStateLoaded) |
| return; |
| m_client->movieNewImageAvailable(m_movieWin); |
| } |
| |
| void QTMovieWinPrivate::updateGWorld() |
| { |
| bool shouldBeVisible = m_visible; |
| if (!m_height || !m_width) |
| shouldBeVisible = false; |
| |
| if (shouldBeVisible && !m_gWorld) |
| createGWorld(); |
| else if (!shouldBeVisible && m_gWorld) |
| deleteGWorld(); |
| else if (m_gWorld && (m_width > m_gWorldWidth || m_height > m_gWorldHeight)) { |
| // need a bigger, better gWorld |
| deleteGWorld(); |
| createGWorld(); |
| } |
| } |
| |
| void QTMovieWinPrivate::createGWorld() |
| { |
| ASSERT(!m_gWorld); |
| if (!m_movie) |
| return; |
| |
| m_gWorldWidth = max(cGWorldMinWidth, m_width); |
| m_gWorldHeight = max(cGWorldMinHeight, m_height); |
| Rect bounds; |
| bounds.top = 0; |
| bounds.left = 0; |
| bounds.right = m_gWorldWidth; |
| bounds.bottom = m_gWorldHeight; |
| OSErr err = QTNewGWorld(&m_gWorld, k32BGRAPixelFormat, &bounds, NULL, NULL, NULL); |
| if (err) |
| return; |
| GetMovieGWorld(m_movie, &m_savedGWorld, 0); |
| if (m_movieController) |
| MCSetControllerPort(m_movieController, m_gWorld); |
| SetMovieGWorld(m_movie, m_gWorld, 0); |
| bounds.right = m_width; |
| bounds.bottom = m_height; |
| if (m_movieController) |
| MCSetControllerBoundsRect(m_movieController, &bounds); |
| SetMovieBox(m_movie, &bounds); |
| } |
| |
| void QTMovieWinPrivate::clearGWorld() |
| { |
| if (!m_movie||!m_gWorld) |
| return; |
| |
| GrafPtr savePort; |
| GetPort(&savePort); |
| MacSetPort((GrafPtr)m_gWorld); |
| |
| Rect bounds; |
| bounds.top = 0; |
| bounds.left = 0; |
| bounds.right = m_gWorldWidth; |
| bounds.bottom = m_gWorldHeight; |
| EraseRect(&bounds); |
| |
| MacSetPort(savePort); |
| } |
| |
| |
| void QTMovieWinPrivate::setSize(int width, int height) |
| { |
| if (m_width == width && m_height == height) |
| return; |
| m_width = width; |
| m_height = height; |
| if (!m_movie) |
| return; |
| Rect bounds; |
| bounds.top = 0; |
| bounds.left = 0; |
| bounds.right = width; |
| bounds.bottom = height; |
| if (m_movieController) |
| MCSetControllerBoundsRect(m_movieController, &bounds); |
| SetMovieBox(m_movie, &bounds); |
| updateGWorld(); |
| } |
| |
| void QTMovieWinPrivate::deleteGWorld() |
| { |
| ASSERT(m_gWorld); |
| if (m_movieController) |
| MCSetControllerPort(m_movieController, m_savedGWorld); |
| if (m_movie) |
| SetMovieGWorld(m_movie, m_savedGWorld, 0); |
| m_savedGWorld = 0; |
| DisposeGWorld(m_gWorld); |
| m_gWorld = 0; |
| m_gWorldWidth = 0; |
| m_gWorldHeight = 0; |
| } |
| |
| |
| QTMovieWin::QTMovieWin(QTMovieWinClient* client) |
| : m_private(new QTMovieWinPrivate()) |
| { |
| m_private->m_movieWin = this; |
| m_private->m_client = client; |
| initializeQuickTime(); |
| } |
| |
| QTMovieWin::~QTMovieWin() |
| { |
| delete m_private; |
| } |
| |
| void QTMovieWin::play() |
| { |
| if (m_private->m_movieController) |
| MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)GetMoviePreferredRate(m_private->m_movie)); |
| else |
| StartMovie(m_private->m_movie); |
| m_private->startTask(); |
| } |
| |
| void QTMovieWin::pause() |
| { |
| if (m_private->m_movieController) |
| MCDoAction(m_private->m_movieController, mcActionPlay, 0); |
| else |
| StopMovie(m_private->m_movie); |
| updateTaskTimer(); |
| } |
| |
| float QTMovieWin::rate() const |
| { |
| if (!m_private->m_movie) |
| return 0; |
| return FixedToFloat(GetMovieRate(m_private->m_movie)); |
| } |
| |
| void QTMovieWin::setRate(float rate) |
| { |
| if (!m_private->m_movie) |
| return; |
| if (m_private->m_movieController) |
| MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)FloatToFixed(rate)); |
| else |
| SetMovieRate(m_private->m_movie, FloatToFixed(rate)); |
| updateTaskTimer(); |
| } |
| |
| float QTMovieWin::duration() const |
| { |
| if (!m_private->m_movie) |
| return 0; |
| TimeValue val = GetMovieDuration(m_private->m_movie); |
| TimeScale scale = GetMovieTimeScale(m_private->m_movie); |
| return static_cast<float>(val) / scale; |
| } |
| |
| float QTMovieWin::currentTime() const |
| { |
| if (!m_private->m_movie) |
| return 0; |
| TimeValue val = GetMovieTime(m_private->m_movie, 0); |
| TimeScale scale = GetMovieTimeScale(m_private->m_movie); |
| return static_cast<float>(val) / scale; |
| } |
| |
| void QTMovieWin::setCurrentTime(float time) const |
| { |
| if (!m_private->m_movie) |
| return; |
| m_private->m_seeking = true; |
| TimeScale scale = GetMovieTimeScale(m_private->m_movie); |
| if (m_private->m_movieController){ |
| QTRestartAtTimeRecord restart = { time * scale , 0 }; |
| MCDoAction(m_private->m_movieController, mcActionRestartAtTime, (void *)&restart); |
| } else |
| SetMovieTimeValue(m_private->m_movie, TimeValue(time * scale)); |
| updateTaskTimer(); |
| } |
| |
| void QTMovieWin::setVolume(float volume) |
| { |
| if (!m_private->m_movie) |
| return; |
| SetMovieVolume(m_private->m_movie, static_cast<short>(volume * 256)); |
| } |
| |
| unsigned QTMovieWin::dataSize() const |
| { |
| if (!m_private->m_movie) |
| return 0; |
| return GetMovieDataSize(m_private->m_movie, 0, GetMovieDuration(m_private->m_movie)); |
| } |
| |
| float QTMovieWin::maxTimeLoaded() const |
| { |
| if (!m_private->m_movie) |
| return 0; |
| TimeValue val; |
| GetMaxLoadedTimeInMovie(m_private->m_movie, &val); |
| TimeScale scale = GetMovieTimeScale(m_private->m_movie); |
| return static_cast<float>(val) / scale; |
| } |
| |
| long QTMovieWin::loadState() const |
| { |
| return m_private->m_loadState; |
| } |
| |
| void QTMovieWin::getNaturalSize(int& width, int& height) |
| { |
| Rect rect = { 0, }; |
| |
| if (m_private->m_movie) |
| GetMovieNaturalBoundsRect(m_private->m_movie, &rect); |
| width = rect.right; |
| height = rect.bottom; |
| } |
| |
| void QTMovieWin::setSize(int width, int height) |
| { |
| m_private->setSize(width, height); |
| updateTaskTimer(0); |
| } |
| |
| void QTMovieWin::setVisible(bool b) |
| { |
| m_private->m_visible = b; |
| m_private->updateGWorld(); |
| } |
| |
| void QTMovieWin::paint(HDC hdc, int x, int y) |
| { |
| if (!m_private->m_gWorld) |
| return; |
| |
| HDC hdcSrc = static_cast<HDC>(GetPortHDC(reinterpret_cast<GrafPtr>(m_private->m_gWorld))); |
| if (!hdcSrc) |
| return; |
| |
| // FIXME: If we could determine the movie has no alpha, we could use BitBlt for those cases, which might be faster. |
| BLENDFUNCTION blendFunction; |
| blendFunction.BlendOp = AC_SRC_OVER; |
| blendFunction.BlendFlags = 0; |
| blendFunction.SourceConstantAlpha = 255; |
| blendFunction.AlphaFormat = AC_SRC_ALPHA; |
| AlphaBlend(hdc, x, y, m_private->m_width, m_private->m_height, hdcSrc, |
| 0, 0, m_private->m_width, m_private->m_height, blendFunction); |
| } |
| |
| void QTMovieWin::load(const UChar* url, int len) |
| { |
| if (m_private->m_movie) { |
| m_private->endTask(); |
| if (m_private->m_gWorld) |
| m_private->deleteGWorld(); |
| if (m_private->m_movieController) |
| DisposeMovieController(m_private->m_movieController); |
| m_private->m_movieController = 0; |
| DisposeMovie(m_private->m_movie); |
| m_private->m_movie = 0; |
| } |
| |
| // Define a property array for NewMovieFromProperties. 8 should be enough for our needs. |
| QTNewMoviePropertyElement movieProps[8]; |
| ItemCount moviePropCount = 0; |
| |
| bool boolTrue = true; |
| |
| // Create a URL data reference of type CFURL |
| CFStringRef urlStringRef = CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(url), len); |
| |
| // Disable streaming support for now. |
| if (CFStringHasPrefix(urlStringRef, CFSTR("rtsp:"))) { |
| m_private->m_loadError = noMovieFound; |
| goto end; |
| } |
| |
| CFURLRef urlRef = CFURLCreateWithString(kCFAllocatorDefault, urlStringRef, 0); |
| |
| // Add the movie data location to the property array |
| movieProps[moviePropCount].propClass = kQTPropertyClass_DataLocation; |
| movieProps[moviePropCount].propID = kQTDataLocationPropertyID_CFURL; |
| movieProps[moviePropCount].propValueSize = sizeof(urlRef); |
| movieProps[moviePropCount].propValueAddress = &urlRef; |
| movieProps[moviePropCount].propStatus = 0; |
| moviePropCount++; |
| |
| movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; |
| movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_DontAskUnresolvedDataRefs; |
| movieProps[moviePropCount].propValueSize = sizeof(boolTrue); |
| movieProps[moviePropCount].propValueAddress = &boolTrue; |
| movieProps[moviePropCount].propStatus = 0; |
| moviePropCount++; |
| |
| movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; |
| movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_AsyncOK; |
| movieProps[moviePropCount].propValueSize = sizeof(boolTrue); |
| movieProps[moviePropCount].propValueAddress = &boolTrue; |
| movieProps[moviePropCount].propStatus = 0; |
| moviePropCount++; |
| |
| movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty; |
| movieProps[moviePropCount].propID = kQTNewMoviePropertyID_Active; |
| movieProps[moviePropCount].propValueSize = sizeof(boolTrue); |
| movieProps[moviePropCount].propValueAddress = &boolTrue; |
| movieProps[moviePropCount].propStatus = 0; |
| moviePropCount++; |
| |
| movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty; |
| movieProps[moviePropCount].propID = kQTNewMoviePropertyID_DontInteractWithUser; |
| movieProps[moviePropCount].propValueSize = sizeof(boolTrue); |
| movieProps[moviePropCount].propValueAddress = &boolTrue; |
| movieProps[moviePropCount].propStatus = 0; |
| moviePropCount++; |
| |
| movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; |
| movieProps[moviePropCount].propID = '!url'; |
| movieProps[moviePropCount].propValueSize = sizeof(boolTrue); |
| movieProps[moviePropCount].propValueAddress = &boolTrue; |
| movieProps[moviePropCount].propStatus = 0; |
| moviePropCount++; |
| |
| movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; |
| movieProps[moviePropCount].propID = 'site'; |
| movieProps[moviePropCount].propValueSize = sizeof(boolTrue); |
| movieProps[moviePropCount].propValueAddress = &boolTrue; |
| movieProps[moviePropCount].propStatus = 0; |
| moviePropCount++; |
| |
| m_private->m_loadError = NewMovieFromProperties(moviePropCount, movieProps, 0, NULL, &m_private->m_movie); |
| |
| CFRelease(urlRef); |
| end: |
| m_private->startTask(); |
| // get the load fail callback quickly |
| if (m_private->m_loadError) |
| updateTaskTimer(0); |
| else |
| m_private->registerDrawingCallback(); |
| |
| CFRelease(urlStringRef); |
| } |
| |
| void QTMovieWin::disableUnsupportedTracks(unsigned& enabledTrackCount) |
| { |
| if (!m_private->m_movie) { |
| enabledTrackCount = 0; |
| return; |
| } |
| |
| static HashSet<OSType>* allowedTrackTypes = 0; |
| if (!allowedTrackTypes) { |
| allowedTrackTypes = new HashSet<OSType>; |
| allowedTrackTypes->add(VideoMediaType); |
| allowedTrackTypes->add(SoundMediaType); |
| allowedTrackTypes->add(TextMediaType); |
| allowedTrackTypes->add(BaseMediaType); |
| allowedTrackTypes->add('clcp'); // Closed caption |
| allowedTrackTypes->add('sbtl'); // Subtitle |
| } |
| |
| long trackCount = GetMovieTrackCount(m_private->m_movie); |
| enabledTrackCount = trackCount; |
| |
| // Track indexes are 1-based. yuck. These things must descend from old- |
| // school mac resources or something. |
| for (long trackIndex = 1; trackIndex <= trackCount; trackIndex++) { |
| // Grab the track at the current index. If there isn't one there, then |
| // we can move onto the next one. |
| Track currentTrack = GetMovieIndTrack(m_private->m_movie, trackIndex); |
| if (!currentTrack) |
| continue; |
| |
| // Check to see if the track is disabled already, we should move along. |
| // We don't need to re-disable it. |
| if (!GetTrackEnabled(currentTrack)) |
| continue; |
| |
| // Grab the track's media. We're going to check to see if we need to |
| // disable the tracks. They could be unsupported. |
| Media trackMedia = GetTrackMedia(currentTrack); |
| if (!trackMedia) |
| continue; |
| |
| // Grab the media type for this track. Make sure that we don't |
| // get an error in doing so. If we do, then something really funky is |
| // wrong. |
| OSType mediaType; |
| GetMediaHandlerDescription(trackMedia, &mediaType, nil, nil); |
| OSErr mediaErr = GetMoviesError(); |
| if (mediaErr != noErr) |
| continue; |
| |
| if (!allowedTrackTypes->contains(mediaType)) { |
| SetTrackEnabled(currentTrack, false); |
| --enabledTrackCount; |
| } |
| |
| // Grab the track reference count for chapters. This will tell us if it |
| // has chapter tracks in it. If there aren't any references, then we |
| // can move on the next track. |
| long referenceCount = GetTrackReferenceCount(currentTrack, kTrackReferenceChapterList); |
| if (referenceCount <= 0) |
| continue; |
| |
| long referenceIndex = 0; |
| while (1) { |
| // If we get nothing here, we've overstepped our bounds and can stop |
| // looking. Chapter indices here are 1-based as well - hence, the |
| // pre-increment. |
| referenceIndex++; |
| Track chapterTrack = GetTrackReference(currentTrack, kTrackReferenceChapterList, referenceIndex); |
| if (!chapterTrack) |
| break; |
| |
| // Try to grab the media for the track. |
| Media chapterMedia = GetTrackMedia(chapterTrack); |
| if (!chapterMedia) |
| continue; |
| |
| // Grab the media type for this track. Make sure that we don't |
| // get an error in doing so. If we do, then something really |
| // funky is wrong. |
| OSType mediaType; |
| GetMediaHandlerDescription(chapterMedia, &mediaType, nil, nil); |
| OSErr mediaErr = GetMoviesError(); |
| if (mediaErr != noErr) |
| continue; |
| |
| // Check to see if the track is a video track. We don't care about |
| // other non-video tracks. |
| if (mediaType != VideoMediaType) |
| continue; |
| |
| // Check to see if the track is already disabled. If it is, we |
| // should move along. |
| if (!GetTrackEnabled(chapterTrack)) |
| continue; |
| |
| // Disabled the evil, evil track. |
| SetTrackEnabled(chapterTrack, false); |
| --enabledTrackCount; |
| } |
| } |
| } |
| |
| pascal OSErr movieDrawingCompleteProc(Movie movie, long data) |
| { |
| UppParam param; |
| param.longValue = data; |
| QTMovieWinPrivate* mp = static_cast<QTMovieWinPrivate*>(param.ptr); |
| if (mp) |
| mp->drawingComplete(); |
| return 0; |
| } |
| |
| static void initializeSupportedTypes() |
| { |
| if (gSupportedTypes) |
| return; |
| |
| gSupportedTypes = new Vector<CFStringRef>; |
| if (quickTimeVersion < minimumQuickTimeVersion) { |
| LOG_ERROR("QuickTime version %x detected, at least %x required. Returning empty list of supported media MIME types.", quickTimeVersion, minimumQuickTimeVersion); |
| return; |
| } |
| |
| // QuickTime doesn't have an importer for video/quicktime. Add it manually. |
| gSupportedTypes->append(CFSTR("video/quicktime")); |
| |
| for (int index = 0; index < 2; index++) { |
| ComponentDescription findCD; |
| |
| // look at all movie importers that can import in place and are installed. |
| findCD.componentType = MovieImportType; |
| findCD.componentSubType = 0; |
| findCD.componentManufacturer = 0; |
| findCD.componentFlagsMask = cmpIsMissing | movieImportSubTypeIsFileExtension | canMovieImportInPlace | dontAutoFileMovieImport; |
| |
| // look at those registered by HFS file types the first time through, by file extension the second time |
| findCD.componentFlags = canMovieImportInPlace | (index ? movieImportSubTypeIsFileExtension : 0); |
| |
| long componentCount = CountComponents(&findCD); |
| if (!componentCount) |
| continue; |
| |
| Component comp = 0; |
| while (comp = FindNextComponent(comp, &findCD)) { |
| // Does this component have a MIME type container? |
| ComponentDescription infoCD; |
| OSErr err = GetComponentInfo(comp, &infoCD, nil /*name*/, nil /*info*/, nil /*icon*/); |
| if (err) |
| continue; |
| if (!(infoCD.componentFlags & hasMovieImportMIMEList)) |
| continue; |
| QTAtomContainer mimeList = NULL; |
| err = MovieImportGetMIMETypeList((ComponentInstance)comp, &mimeList); |
| if (err || !mimeList) |
| continue; |
| |
| // Grab every type from the container. |
| QTLockContainer(mimeList); |
| int typeCount = QTCountChildrenOfType(mimeList, kParentAtomIsContainer, kMimeInfoMimeTypeTag); |
| for (int typeIndex = 1; typeIndex <= typeCount; typeIndex++) { |
| QTAtom mimeTag = QTFindChildByIndex(mimeList, 0, kMimeInfoMimeTypeTag, typeIndex, NULL); |
| if (!mimeTag) |
| continue; |
| char* atomData; |
| long typeLength; |
| if (noErr != QTGetAtomDataPtr(mimeList, mimeTag, &typeLength, &atomData)) |
| continue; |
| |
| char typeBuffer[256]; |
| if (typeLength >= sizeof(typeBuffer)) |
| continue; |
| memcpy(typeBuffer, atomData, typeLength); |
| typeBuffer[typeLength] = 0; |
| |
| // Only add "audio/..." and "video/..." types. |
| if (strncmp(typeBuffer, "audio/", 6) && strncmp(typeBuffer, "video/", 6)) |
| continue; |
| |
| CFStringRef cfMimeType = CFStringCreateWithCString(NULL, typeBuffer, kCFStringEncodingUTF8); |
| if (!cfMimeType) |
| continue; |
| |
| // Only add each type once. |
| bool alreadyAdded = false; |
| for (int addedIndex = 0; addedIndex < gSupportedTypes->size(); addedIndex++) { |
| CFStringRef type = gSupportedTypes->at(addedIndex); |
| if (kCFCompareEqualTo == CFStringCompare(cfMimeType, type, kCFCompareCaseInsensitive)) { |
| alreadyAdded = true; |
| break; |
| } |
| } |
| if (!alreadyAdded) |
| gSupportedTypes->append(cfMimeType); |
| else |
| CFRelease(cfMimeType); |
| } |
| DisposeHandle(mimeList); |
| } |
| } |
| } |
| |
| unsigned QTMovieWin::countSupportedTypes() |
| { |
| initializeSupportedTypes(); |
| return static_cast<unsigned>(gSupportedTypes->size()); |
| } |
| |
| void QTMovieWin::getSupportedType(unsigned index, const UChar*& str, unsigned& len) |
| { |
| initializeSupportedTypes(); |
| ASSERT(index < gSupportedTypes->size()); |
| |
| // Allocate sufficient buffer to hold any MIME type |
| static UniChar* staticBuffer = 0; |
| if (!staticBuffer) |
| staticBuffer = new UniChar[32]; |
| |
| CFStringRef cfstr = gSupportedTypes->at(index); |
| len = CFStringGetLength(cfstr); |
| CFRange range = { 0, len }; |
| CFStringGetCharacters(cfstr, range, staticBuffer); |
| str = reinterpret_cast<const UChar*>(staticBuffer); |
| |
| } |
| |
| bool QTMovieWin::initializeQuickTime() |
| { |
| static bool initialized = false; |
| static bool initializationSucceeded = false; |
| if (!initialized) { |
| initialized = true; |
| // Initialize and check QuickTime version |
| OSErr result = InitializeQTML(0); |
| if (result == noErr) |
| result = Gestalt(gestaltQuickTime, &quickTimeVersion); |
| if (result != noErr) { |
| LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support."); |
| return false; |
| } |
| if (quickTimeVersion < minimumQuickTimeVersion) { |
| LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", quickTimeVersion, minimumQuickTimeVersion); |
| return false; |
| } |
| EnterMovies(); |
| setSharedTimerFiredFunction(taskTimerFired); |
| gMovieDrawingCompleteUPP = NewMovieDrawingCompleteUPP(movieDrawingCompleteProc); |
| initializationSucceeded = true; |
| } |
| return initializationSucceeded; |
| } |
| |
| BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) |
| { |
| switch (fdwReason) { |
| case DLL_PROCESS_ATTACH: |
| setSharedTimerInstanceHandle(hinstDLL); |
| return TRUE; |
| case DLL_PROCESS_DETACH: |
| case DLL_THREAD_ATTACH: |
| case DLL_THREAD_DETACH: |
| return FALSE; |
| } |
| ASSERT_NOT_REACHED(); |
| return FALSE; |
| } |