text: /*
    ==============================================================================
  
     This file is part of the JUCE library - "Jules' Utility Class Extensions"
     Copyright 2004-11 by Raw Material Software Ltd.
  
    ------------------------------------------------------------------------------
  
     JUCE can be redistributed and/or modified under the terms of the GNU General
     Public License (Version 2), as published by the Free Software Foundation.
     A copy of the license is included in the JUCE distribution, or can be found
     online at www.gnu.org/licenses.
  
     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.
  
    ------------------------------------------------------------------------------
  
     To release a closed-source product which uses JUCE, commercial licenses are
     available: visit www.rawmaterialsoftware.com/juce for more information.
  
    ==============================================================================
  */
  
  using namespace QTOLibrary;
  using namespace QTOControlLib;
  
  bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle);
  
  static bool isQTAvailable = false;
  
  
  //==============================================================================
  class QuickTimeMovieComponent::Pimpl
  {
  public:
      Pimpl() : dataHandle (0)
      {
      }
  
      ~Pimpl()
      {
          clearHandle();
      }
  
      void clearHandle()
      {
          if (dataHandle != 0)
          {
              DisposeHandle (dataHandle);
              dataHandle = 0;
          }
      }
  
      IQTControlPtr qtControl;
      IQTMoviePtr qtMovie;
      Handle dataHandle;
  };
  
  //==============================================================================
  QuickTimeMovieComponent::QuickTimeMovieComponent()
      : movieLoaded (false),
        controllerVisible (true)
  {
      pimpl = new Pimpl();
      setMouseEventsAllowed (false);
  }
  
  QuickTimeMovieComponent::~QuickTimeMovieComponent()
  {
      closeMovie();
      pimpl->qtControl = 0;
      deleteControl();
      pimpl = nullptr;
  }
  
  bool QuickTimeMovieComponent::isQuickTimeAvailable() noexcept
  {
      if (! isQTAvailable)
          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);
              pimpl->qtControl = (IQTControl*) queryInterface (&qtInterfaceIID);
  
              if (pimpl->qtControl != nullptr)
              {
                  pimpl->qtControl->Release(); // it has one ref too many at this point
  
                  pimpl->qtControl->QuickTimeInitialize();
                  pimpl->qtControl->PutSizing (qtMovieFitsControl);
  
                  if (movieFile != File::nonexistent)
                      loadMovie (movieFile, controllerVisible);
              }
          }
      }
  }
  
  bool QuickTimeMovieComponent::isControlCreated() const
  {
      return isControlOpen();
  }
  
  bool QuickTimeMovieComponent::loadMovie (InputStream* movieStream,
                                           const bool isControllerVisible)
  {
      const ScopedPointer<InputStream> movieStreamDeleter (movieStream);
  
      movieFile = File::nonexistent;
      movieLoaded = false;
      pimpl->qtMovie = 0;
      controllerVisible = isControllerVisible;
      createControlIfNeeded();
  
      if (isControlCreated())
      {
          if (pimpl->qtControl != 0)
          {
              pimpl->qtControl->Put_MovieHandle (0);
              pimpl->clearHandle();
  
              Movie movie;
              if (juce_OpenQuickTimeMovieFromStream (movieStream, movie, pimpl->dataHandle))
              {
                  pimpl->qtControl->Put_MovieHandle ((long) (pointer_sized_int) movie);
  
                  pimpl->qtMovie = pimpl->qtControl->GetMovie();
  
                  if (pimpl->qtMovie != 0)
                      pimpl->qtMovie->PutMovieControllerType (isControllerVisible ? qtMovieControllerTypeStandard
                                                                                  : qtMovieControllerTypeNone);
              }
  
              if (movie == 0)
                  pimpl->clearHandle();
          }
  
          movieLoaded = (pimpl->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;
      }
  
      return movieLoaded;
  }
  
  void QuickTimeMovieComponent::closeMovie()
  {
      stop();
      movieFile = File::nonexistent;
      movieLoaded = false;
      pimpl->qtMovie = 0;
  
      if (pimpl->qtControl != 0)
          pimpl->qtControl->Put_MovieHandle (0);
  
      pimpl->clearHandle();
  }
  
  File QuickTimeMovieComponent::getCurrentMovieFile() const
  {
      return movieFile;
  }
  
  bool QuickTimeMovieComponent::isMovieOpen() const
  {
      return movieLoaded;
  }
  
  double QuickTimeMovieComponent::getMovieDuration() const
  {
      if (pimpl->qtMovie != 0)
          return pimpl->qtMovie->GetDuration() / (double) pimpl->qtMovie->GetTimeScale();
  
      return 0.0;
  }
  
  void QuickTimeMovieComponent::getMovieNormalSize (int& width, int& height) const
  {
      if (pimpl->qtMovie != 0)
      {
          struct QTRECT r = pimpl->qtMovie->GetNaturalRect();
  
          width = r.right - r.left;
          height = r.bottom - r.top;
      }
      else
      {
          width = height = 0;
      }
  }
  
  void QuickTimeMovieComponent::play()
  {
      if (pimpl->qtMovie != 0)
          pimpl->qtMovie->Play();
  }
  
  void QuickTimeMovieComponent::stop()
  {
      if (pimpl->qtMovie != 0)
          pimpl->qtMovie->Stop();
  }
  
  bool QuickTimeMovieComponent::isPlaying() const
  {
      return pimpl->qtMovie != 0 && pimpl->qtMovie->GetRate() != 0.0f;
  }
  
  void QuickTimeMovieComponent::setPosition (const double seconds)
  {
      if (pimpl->qtMovie != 0)
          pimpl->qtMovie->PutTime ((long) (seconds * pimpl->qtMovie->GetTimeScale()));
  }
  
  double QuickTimeMovieComponent::getPosition() const
  {
      if (pimpl->qtMovie != 0)
          return pimpl->qtMovie->GetTime() / (double) pimpl->qtMovie->GetTimeScale();
  
      return 0.0;
  }
  
  void QuickTimeMovieComponent::setSpeed (const float newSpeed)
  {
      if (pimpl->qtMovie != 0)
          pimpl->qtMovie->PutRate (newSpeed);
  }
  
  void QuickTimeMovieComponent::setMovieVolume (const float newVolume)
  {
      if (pimpl->qtMovie != 0)
      {
          pimpl->qtMovie->PutAudioVolume (newVolume);
          pimpl->qtMovie->PutAudioMute (newVolume <= 0);
      }
  }
  
  float QuickTimeMovieComponent::getMovieVolume() const
  {
      if (pimpl->qtMovie != 0)
          return pimpl->qtMovie->GetAudioVolume();
  
      return 0.0f;
  }
  
  void QuickTimeMovieComponent::setLooping (const bool shouldLoop)
  {
      if (pimpl->qtMovie != 0)
          pimpl->qtMovie->PutLoop (shouldLoop);
  }
  
  bool QuickTimeMovieComponent::isLooping() const
  {
      return pimpl->qtMovie != 0 && pimpl->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;
  
         #if JUCE_MSVC
          #pragma warning (push)
          #pragma warning (disable: 4244 4996)
         #endif
  
          suffix[0] = strlen (fileName);
          strncpy ((char*) suffix + 1, fileName, 128);
  
         #if JUCE_MSVC
          #pragma warning (pop)
         #endif
  
          err = PtrAndHand (suffix, dataRef, suffix[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)
  {
      return CFStringCreateWithCString (kCFAllocatorDefault, s.toUTF8(), kCFStringEncodingUTF8);
  }
  
  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 == nullptr)
          return false;
  
      dataHandle = 0;
      bool ok = false;
  
      QTNewMoviePropertyElement props[5] = { 0 };
      int prop = 0;
  
      DataReferenceRecord dr;
      props[prop].propClass = kQTPropertyClass_DataLocation;
      props[prop].propID = kQTDataLocationPropertyID_DataReference;
      props[prop].propValueSize = sizeof (dr);
      props[prop].propValueAddress = &dr;
      ++prop;
  
      FileInputStream* const fin = dynamic_cast <FileInputStream*> (input);
  
      if (fin != nullptr)
      {
          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 (static_cast <InputStream*> (movieFile_.createInputStream()), isControllerVisible);
      movieFile = movieFile_;
      return ok;
  }
  
  bool QuickTimeMovieComponent::loadMovie (const URL& movieURL,
                                           const bool isControllerVisible)
  {
      return loadMovie (static_cast <InputStream*> (movieURL.createInputStream (false)), isControllerVisible);
  }
  
  void QuickTimeMovieComponent::goToStart()
  {
      setPosition (0.0);
  }
  
  void QuickTimeMovieComponent::setBoundsWithCorrectAspectRatio (const Rectangle<int>& spaceToFitWithin,
                                                                 const RectanglePlacement& placement)
  {
      int normalWidth, normalHeight;
      getMovieNormalSize (normalWidth, normalHeight);
  
      const Rectangle<int> normalSize (0, 0, normalWidth, normalHeight);
  
      if (! (spaceToFitWithin.isEmpty() || normalSize.isEmpty()))
          setBounds (placement.appliedTo (normalSize, spaceToFitWithin));
      else
          setBounds (spaceToFitWithin);
  }
  