/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-7 by Raw Material Software ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License, as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with JUCE; if not, visit www.gnu.org/licenses or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------------------ If you'd like to release a closed-source product which uses JUCE, commercial licenses are also available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ // (This file gets included by juce_win32_NativeCode.cpp, rather than being // compiled on its own). #if JUCE_INCLUDED_FILE && JUCE_QUICKTIME using namespace QTOLibrary; using namespace QTOControlLib; bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle); static bool hasLoadedQT = false; static bool isQTAvailable = false; //============================================================================== struct QTMovieCompInternal { QTMovieCompInternal() : dataHandle (0) { } ~QTMovieCompInternal() { clearHandle(); } IQTControlPtr qtControlInternal; IQTMoviePtr qtMovieInternal; Handle dataHandle; void clearHandle() { if (dataHandle != 0) { DisposeHandle (dataHandle); dataHandle = 0; } } }; #define qtControl (((QTMovieCompInternal*) internal)->qtControlInternal) #define qtMovie (((QTMovieCompInternal*) internal)->qtMovieInternal) //============================================================================== QuickTimeMovieComponent::QuickTimeMovieComponent() : movieLoaded (false), controllerVisible (true) { internal = new QTMovieCompInternal(); setMouseEventsAllowed (false); } QuickTimeMovieComponent::~QuickTimeMovieComponent() { closeMovie(); qtControl = 0; deleteControl(); delete internal; internal = 0; } bool QuickTimeMovieComponent::isQuickTimeAvailable() throw() { if (! hasLoadedQT) { hasLoadedQT = true; isQTAvailable = (InitializeQTML (0) == noErr) && (EnterMovies() == noErr); } return isQTAvailable; } //============================================================================== void QuickTimeMovieComponent::createControlIfNeeded() { if (isShowing() && ! isControlCreated()) { const IID qtIID = __uuidof (QTControl); if (createControl (&qtIID)) { const IID qtInterfaceIID = __uuidof (IQTControl); qtControl = (IQTControl*) queryInterface (&qtInterfaceIID); if (qtControl != 0) { qtControl->Release(); // it has one ref too many at this point qtControl->QuickTimeInitialize(); qtControl->PutSizing (qtMovieFitsControl); if (movieFile != File::nonexistent) loadMovie (movieFile, controllerVisible); } } } } bool QuickTimeMovieComponent::isControlCreated() const { return isControlOpen(); } bool QuickTimeMovieComponent::loadMovie (InputStream* movieStream, const bool isControllerVisible) { movieFile = File::nonexistent; movieLoaded = false; qtMovie = 0; controllerVisible = isControllerVisible; createControlIfNeeded(); if (isControlCreated()) { if (qtControl != 0) { qtControl->Put_MovieHandle (0); ((QTMovieCompInternal*) internal)->clearHandle(); Movie movie; if (juce_OpenQuickTimeMovieFromStream (movieStream, movie, ((QTMovieCompInternal*) internal)->dataHandle)) { qtControl->Put_MovieHandle ((long) (pointer_sized_int) movie); qtMovie = qtControl->GetMovie(); if (qtMovie != 0) qtMovie->PutMovieControllerType (isControllerVisible ? qtMovieControllerTypeStandard : qtMovieControllerTypeNone); } if (movie == 0) ((QTMovieCompInternal*) internal)->clearHandle(); } movieLoaded = (qtMovie != 0); } else { // You're trying to open a movie when the control hasn't yet been created, probably because // you've not yet added this component to a Window and made the whole component hierarchy visible. jassertfalse } delete movieStream; return movieLoaded; } void QuickTimeMovieComponent::closeMovie() { stop(); movieFile = File::nonexistent; movieLoaded = false; qtMovie = 0; if (qtControl != 0) qtControl->Put_MovieHandle (0); ((QTMovieCompInternal*) internal)->clearHandle(); } const File QuickTimeMovieComponent::getCurrentMovieFile() const { return movieFile; } bool QuickTimeMovieComponent::isMovieOpen() const { return movieLoaded; } double QuickTimeMovieComponent::getMovieDuration() const { if (qtMovie != 0) return qtMovie->GetDuration() / (double) qtMovie->GetTimeScale(); return 0.0; } void QuickTimeMovieComponent::getMovieNormalSize (int& width, int& height) const { if (qtMovie != 0) { struct QTRECT r = qtMovie->GetNaturalRect(); width = r.right - r.left; height = r.bottom - r.top; } else { width = height = 0; } } void QuickTimeMovieComponent::play() { if (qtMovie != 0) qtMovie->Play(); } void QuickTimeMovieComponent::stop() { if (qtMovie != 0) qtMovie->Stop(); } bool QuickTimeMovieComponent::isPlaying() const { return qtMovie != 0 && qtMovie->GetRate() != 0.0f; } void QuickTimeMovieComponent::setPosition (const double seconds) { if (qtMovie != 0) qtMovie->PutTime ((long) (seconds * qtMovie->GetTimeScale())); } double QuickTimeMovieComponent::getPosition() const { if (qtMovie != 0) return qtMovie->GetTime() / (double) qtMovie->GetTimeScale(); return 0.0; } void QuickTimeMovieComponent::setSpeed (const float newSpeed) { if (qtMovie != 0) qtMovie->PutRate (newSpeed); } void QuickTimeMovieComponent::setMovieVolume (const float newVolume) { if (qtMovie != 0) { qtMovie->PutAudioVolume (newVolume); qtMovie->PutAudioMute (newVolume <= 0); } } float QuickTimeMovieComponent::getMovieVolume() const { if (qtMovie != 0) return qtMovie->GetAudioVolume(); return 0.0f; } void QuickTimeMovieComponent::setLooping (const bool shouldLoop) { if (qtMovie != 0) qtMovie->PutLoop (shouldLoop); } bool QuickTimeMovieComponent::isLooping() const { return qtMovie != 0 && qtMovie->GetLoop(); } bool QuickTimeMovieComponent::isControllerVisible() const { return controllerVisible; } void QuickTimeMovieComponent::parentHierarchyChanged() { createControlIfNeeded(); QTCompBaseClass::parentHierarchyChanged(); } void QuickTimeMovieComponent::visibilityChanged() { createControlIfNeeded(); QTCompBaseClass::visibilityChanged(); } void QuickTimeMovieComponent::paint (Graphics& g) { if (! isControlCreated()) g.fillAll (Colours::black); } //============================================================================== static Handle createHandleDataRef (Handle dataHandle, const char* fileName) { Handle dataRef = 0; OSStatus err = PtrToHand (&dataHandle, &dataRef, sizeof (Handle)); if (err == noErr) { Str255 suffix; CharacterFunctions::copy ((char*) suffix, fileName, 128); StringPtr name = suffix; err = PtrAndHand (name, dataRef, name[0] + 1); if (err == noErr) { long atoms[3]; atoms[0] = EndianU32_NtoB (3 * sizeof (long)); atoms[1] = EndianU32_NtoB (kDataRefExtensionMacOSFileType); atoms[2] = EndianU32_NtoB (MovieFileType); err = PtrAndHand (atoms, dataRef, 3 * sizeof (long)); if (err == noErr) return dataRef; } DisposeHandle (dataRef); } return 0; } static CFStringRef juceStringToCFString (const String& s) { const int len = s.length(); const juce_wchar* const t = (const juce_wchar*) s; UniChar* temp = (UniChar*) juce_malloc (sizeof (UniChar) * len + 4); for (int i = 0; i <= len; ++i) temp[i] = t[i]; CFStringRef result = CFStringCreateWithCharacters (kCFAllocatorDefault, temp, len); juce_free (temp); return result; } static bool openMovie (QTNewMoviePropertyElement* props, int prop, Movie& movie) { Boolean trueBool = true; props[prop].propClass = kQTPropertyClass_MovieInstantiation; props[prop].propID = kQTMovieInstantiationPropertyID_DontResolveDataRefs; props[prop].propValueSize = sizeof (trueBool); props[prop].propValueAddress = &trueBool; ++prop; props[prop].propClass = kQTPropertyClass_MovieInstantiation; props[prop].propID = kQTMovieInstantiationPropertyID_AsyncOK; props[prop].propValueSize = sizeof (trueBool); props[prop].propValueAddress = &trueBool; ++prop; Boolean isActive = true; props[prop].propClass = kQTPropertyClass_NewMovieProperty; props[prop].propID = kQTNewMoviePropertyID_Active; props[prop].propValueSize = sizeof (isActive); props[prop].propValueAddress = &isActive; ++prop; MacSetPort (0); jassert (prop <= 5); OSStatus err = NewMovieFromProperties (prop, props, 0, 0, &movie); return err == noErr; } bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle) { if (input == 0) return false; dataHandle = 0; bool ok = false; QTNewMoviePropertyElement props[5]; zeromem (props, sizeof (props)); int prop = 0; DataReferenceRecord dr; props[prop].propClass = kQTPropertyClass_DataLocation; props[prop].propID = kQTDataLocationPropertyID_DataReference; props[prop].propValueSize = sizeof (dr); props[prop].propValueAddress = (void*) &dr; ++prop; FileInputStream* const fin = dynamic_cast (input); if (fin != 0) { CFStringRef filePath = juceStringToCFString (fin->getFile().getFullPathName()); QTNewDataReferenceFromFullPathCFString (filePath, (QTPathStyle) kQTNativeDefaultPathStyle, 0, &dr.dataRef, &dr.dataRefType); ok = openMovie (props, prop, movie); DisposeHandle (dr.dataRef); CFRelease (filePath); } else { // sanity-check because this currently needs to load the whole stream into memory.. jassert (input->getTotalLength() < 50 * 1024 * 1024); dataHandle = NewHandle ((Size) input->getTotalLength()); HLock (dataHandle); // read the entire stream into memory - this is a pain, but can't get it to work // properly using a custom callback to supply the data. input->read (*dataHandle, (int) input->getTotalLength()); HUnlock (dataHandle); // different types to get QT to try. (We should really be a bit smarter here by // working out in advance which one the stream contains, rather than just trying // each one) const char* const suffixesToTry[] = { "\04.mov", "\04.mp3", "\04.avi", "\04.m4a" }; for (int i = 0; i < numElementsInArray (suffixesToTry) && ! ok; ++i) { /* // this fails for some bizarre reason - it can be bodged to work with // movies, but can't seem to do it for other file types.. QTNewMovieUserProcRecord procInfo; procInfo.getMovieUserProc = NewGetMovieUPP (readMovieStreamProc); procInfo.getMovieUserProcRefcon = this; procInfo.defaultDataRef.dataRef = dataRef; procInfo.defaultDataRef.dataRefType = HandleDataHandlerSubType; props[prop].propClass = kQTPropertyClass_DataLocation; props[prop].propID = kQTDataLocationPropertyID_MovieUserProc; props[prop].propValueSize = sizeof (procInfo); props[prop].propValueAddress = (void*) &procInfo; ++prop; */ dr.dataRef = createHandleDataRef (dataHandle, suffixesToTry [i]); dr.dataRefType = HandleDataHandlerSubType; ok = openMovie (props, prop, movie); DisposeHandle (dr.dataRef); } } return ok; } bool QuickTimeMovieComponent::loadMovie (const File& movieFile_, const bool isControllerVisible) { const bool ok = loadMovie ((InputStream*) movieFile_.createInputStream(), isControllerVisible); movieFile = movieFile_; return ok; } void QuickTimeMovieComponent::goToStart() { setPosition (0.0); } void QuickTimeMovieComponent::setBoundsWithCorrectAspectRatio (const Rectangle& spaceToFitWithin, const RectanglePlacement& placement) { int normalWidth, normalHeight; getMovieNormalSize (normalWidth, normalHeight); if (normalWidth > 0 && normalHeight > 0 && ! spaceToFitWithin.isEmpty()) { double x = 0.0, y = 0.0, w = normalWidth, h = normalHeight; placement.applyTo (x, y, w, h, spaceToFitWithin.getX(), spaceToFitWithin.getY(), spaceToFitWithin.getWidth(), spaceToFitWithin.getHeight()); if (w > 0 && h > 0) { setBounds (roundDoubleToInt (x), roundDoubleToInt (y), roundDoubleToInt (w), roundDoubleToInt (h)); } } else { setBounds (spaceToFitWithin); } } #endif