The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

484 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. using namespace QTOLibrary;
  18. using namespace QTOControlLib;
  19. bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle);
  20. static bool isQTAvailable = false;
  21. //==============================================================================
  22. class QuickTimeMovieComponent::Pimpl
  23. {
  24. public:
  25. Pimpl() : dataHandle (0)
  26. {
  27. }
  28. ~Pimpl()
  29. {
  30. clearHandle();
  31. }
  32. void clearHandle()
  33. {
  34. if (dataHandle != 0)
  35. {
  36. DisposeHandle (dataHandle);
  37. dataHandle = 0;
  38. }
  39. }
  40. IQTControlPtr qtControl;
  41. IQTMoviePtr qtMovie;
  42. Handle dataHandle;
  43. };
  44. //==============================================================================
  45. QuickTimeMovieComponent::QuickTimeMovieComponent()
  46. : movieLoaded (false),
  47. controllerVisible (true)
  48. {
  49. pimpl = new Pimpl();
  50. setMouseEventsAllowed (false);
  51. }
  52. QuickTimeMovieComponent::~QuickTimeMovieComponent()
  53. {
  54. closeMovie();
  55. pimpl->qtControl = 0;
  56. deleteControl();
  57. pimpl = nullptr;
  58. }
  59. bool QuickTimeMovieComponent::isQuickTimeAvailable() noexcept
  60. {
  61. if (! isQTAvailable)
  62. isQTAvailable = (InitializeQTML (0) == noErr) && (EnterMovies() == noErr);
  63. return isQTAvailable;
  64. }
  65. //==============================================================================
  66. void QuickTimeMovieComponent::createControlIfNeeded()
  67. {
  68. if (isShowing() && ! isControlCreated())
  69. {
  70. const IID qtIID = __uuidof (QTControl);
  71. if (createControl (&qtIID))
  72. {
  73. const IID qtInterfaceIID = __uuidof (IQTControl);
  74. pimpl->qtControl = (IQTControl*) queryInterface (&qtInterfaceIID);
  75. if (pimpl->qtControl != nullptr)
  76. {
  77. pimpl->qtControl->Release(); // it has one ref too many at this point
  78. pimpl->qtControl->QuickTimeInitialize();
  79. pimpl->qtControl->PutSizing (qtMovieFitsControl);
  80. if (movieFile != File::nonexistent)
  81. loadMovie (movieFile, controllerVisible);
  82. }
  83. }
  84. }
  85. }
  86. bool QuickTimeMovieComponent::isControlCreated() const
  87. {
  88. return isControlOpen();
  89. }
  90. bool QuickTimeMovieComponent::loadMovie (InputStream* movieStream,
  91. const bool isControllerVisible)
  92. {
  93. const ScopedPointer<InputStream> movieStreamDeleter (movieStream);
  94. movieFile = File::nonexistent;
  95. movieLoaded = false;
  96. pimpl->qtMovie = 0;
  97. controllerVisible = isControllerVisible;
  98. createControlIfNeeded();
  99. if (isControlCreated())
  100. {
  101. if (pimpl->qtControl != 0)
  102. {
  103. pimpl->qtControl->Put_MovieHandle (0);
  104. pimpl->clearHandle();
  105. Movie movie;
  106. if (juce_OpenQuickTimeMovieFromStream (movieStream, movie, pimpl->dataHandle))
  107. {
  108. pimpl->qtControl->Put_MovieHandle ((long) (pointer_sized_int) movie);
  109. pimpl->qtMovie = pimpl->qtControl->GetMovie();
  110. if (pimpl->qtMovie != 0)
  111. pimpl->qtMovie->PutMovieControllerType (isControllerVisible ? qtMovieControllerTypeStandard
  112. : qtMovieControllerTypeNone);
  113. }
  114. if (movie == 0)
  115. pimpl->clearHandle();
  116. }
  117. movieLoaded = (pimpl->qtMovie != 0);
  118. }
  119. else
  120. {
  121. // You're trying to open a movie when the control hasn't yet been created, probably because
  122. // you've not yet added this component to a Window and made the whole component hierarchy visible.
  123. jassertfalse;
  124. }
  125. return movieLoaded;
  126. }
  127. void QuickTimeMovieComponent::closeMovie()
  128. {
  129. stop();
  130. movieFile = File::nonexistent;
  131. movieLoaded = false;
  132. pimpl->qtMovie = 0;
  133. if (pimpl->qtControl != 0)
  134. pimpl->qtControl->Put_MovieHandle (0);
  135. pimpl->clearHandle();
  136. }
  137. File QuickTimeMovieComponent::getCurrentMovieFile() const
  138. {
  139. return movieFile;
  140. }
  141. bool QuickTimeMovieComponent::isMovieOpen() const
  142. {
  143. return movieLoaded;
  144. }
  145. double QuickTimeMovieComponent::getMovieDuration() const
  146. {
  147. if (pimpl->qtMovie != 0)
  148. return pimpl->qtMovie->GetDuration() / (double) pimpl->qtMovie->GetTimeScale();
  149. return 0.0;
  150. }
  151. void QuickTimeMovieComponent::getMovieNormalSize (int& width, int& height) const
  152. {
  153. if (pimpl->qtMovie != 0)
  154. {
  155. struct QTRECT r = pimpl->qtMovie->GetNaturalRect();
  156. width = r.right - r.left;
  157. height = r.bottom - r.top;
  158. }
  159. else
  160. {
  161. width = height = 0;
  162. }
  163. }
  164. void QuickTimeMovieComponent::play()
  165. {
  166. if (pimpl->qtMovie != 0)
  167. pimpl->qtMovie->Play();
  168. }
  169. void QuickTimeMovieComponent::stop()
  170. {
  171. if (pimpl->qtMovie != 0)
  172. pimpl->qtMovie->Stop();
  173. }
  174. bool QuickTimeMovieComponent::isPlaying() const
  175. {
  176. return pimpl->qtMovie != 0 && pimpl->qtMovie->GetRate() != 0.0f;
  177. }
  178. void QuickTimeMovieComponent::setPosition (const double seconds)
  179. {
  180. if (pimpl->qtMovie != 0)
  181. pimpl->qtMovie->PutTime ((long) (seconds * pimpl->qtMovie->GetTimeScale()));
  182. }
  183. double QuickTimeMovieComponent::getPosition() const
  184. {
  185. if (pimpl->qtMovie != 0)
  186. return pimpl->qtMovie->GetTime() / (double) pimpl->qtMovie->GetTimeScale();
  187. return 0.0;
  188. }
  189. void QuickTimeMovieComponent::setSpeed (const float newSpeed)
  190. {
  191. if (pimpl->qtMovie != 0)
  192. pimpl->qtMovie->PutRate (newSpeed);
  193. }
  194. void QuickTimeMovieComponent::setMovieVolume (const float newVolume)
  195. {
  196. if (pimpl->qtMovie != 0)
  197. {
  198. pimpl->qtMovie->PutAudioVolume (newVolume);
  199. pimpl->qtMovie->PutAudioMute (newVolume <= 0);
  200. }
  201. }
  202. float QuickTimeMovieComponent::getMovieVolume() const
  203. {
  204. if (pimpl->qtMovie != 0)
  205. return pimpl->qtMovie->GetAudioVolume();
  206. return 0.0f;
  207. }
  208. void QuickTimeMovieComponent::setLooping (const bool shouldLoop)
  209. {
  210. if (pimpl->qtMovie != 0)
  211. pimpl->qtMovie->PutLoop (shouldLoop);
  212. }
  213. bool QuickTimeMovieComponent::isLooping() const
  214. {
  215. return pimpl->qtMovie != 0 && pimpl->qtMovie->GetLoop();
  216. }
  217. bool QuickTimeMovieComponent::isControllerVisible() const
  218. {
  219. return controllerVisible;
  220. }
  221. void QuickTimeMovieComponent::parentHierarchyChanged()
  222. {
  223. createControlIfNeeded();
  224. QTCompBaseClass::parentHierarchyChanged();
  225. }
  226. void QuickTimeMovieComponent::visibilityChanged()
  227. {
  228. createControlIfNeeded();
  229. QTCompBaseClass::visibilityChanged();
  230. }
  231. void QuickTimeMovieComponent::paint (Graphics& g)
  232. {
  233. if (! isControlCreated())
  234. g.fillAll (Colours::black);
  235. }
  236. //==============================================================================
  237. static Handle createHandleDataRef (Handle dataHandle, const char* fileName)
  238. {
  239. Handle dataRef = 0;
  240. OSStatus err = PtrToHand (&dataHandle, &dataRef, sizeof (Handle));
  241. if (err == noErr)
  242. {
  243. Str255 suffix;
  244. #if JUCE_MSVC
  245. #pragma warning (push)
  246. #pragma warning (disable: 4244 4996)
  247. #endif
  248. suffix[0] = strlen (fileName);
  249. strncpy ((char*) suffix + 1, fileName, 128);
  250. #if JUCE_MSVC
  251. #pragma warning (pop)
  252. #endif
  253. err = PtrAndHand (suffix, dataRef, suffix[0] + 1);
  254. if (err == noErr)
  255. {
  256. long atoms[3];
  257. atoms[0] = EndianU32_NtoB (3 * sizeof (long));
  258. atoms[1] = EndianU32_NtoB (kDataRefExtensionMacOSFileType);
  259. atoms[2] = EndianU32_NtoB (MovieFileType);
  260. err = PtrAndHand (atoms, dataRef, 3 * sizeof (long));
  261. if (err == noErr)
  262. return dataRef;
  263. }
  264. DisposeHandle (dataRef);
  265. }
  266. return 0;
  267. }
  268. static CFStringRef juceStringToCFString (const String& s)
  269. {
  270. return CFStringCreateWithCString (kCFAllocatorDefault, s.toUTF8(), kCFStringEncodingUTF8);
  271. }
  272. static bool openMovie (QTNewMoviePropertyElement* props, int prop, Movie& movie)
  273. {
  274. Boolean trueBool = true;
  275. props[prop].propClass = kQTPropertyClass_MovieInstantiation;
  276. props[prop].propID = kQTMovieInstantiationPropertyID_DontResolveDataRefs;
  277. props[prop].propValueSize = sizeof (trueBool);
  278. props[prop].propValueAddress = &trueBool;
  279. ++prop;
  280. props[prop].propClass = kQTPropertyClass_MovieInstantiation;
  281. props[prop].propID = kQTMovieInstantiationPropertyID_AsyncOK;
  282. props[prop].propValueSize = sizeof (trueBool);
  283. props[prop].propValueAddress = &trueBool;
  284. ++prop;
  285. Boolean isActive = true;
  286. props[prop].propClass = kQTPropertyClass_NewMovieProperty;
  287. props[prop].propID = kQTNewMoviePropertyID_Active;
  288. props[prop].propValueSize = sizeof (isActive);
  289. props[prop].propValueAddress = &isActive;
  290. ++prop;
  291. MacSetPort (0);
  292. jassert (prop <= 5);
  293. OSStatus err = NewMovieFromProperties (prop, props, 0, 0, &movie);
  294. return err == noErr;
  295. }
  296. bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle)
  297. {
  298. if (input == nullptr)
  299. return false;
  300. dataHandle = 0;
  301. bool ok = false;
  302. QTNewMoviePropertyElement props[5] = { 0 };
  303. int prop = 0;
  304. DataReferenceRecord dr;
  305. props[prop].propClass = kQTPropertyClass_DataLocation;
  306. props[prop].propID = kQTDataLocationPropertyID_DataReference;
  307. props[prop].propValueSize = sizeof (dr);
  308. props[prop].propValueAddress = &dr;
  309. ++prop;
  310. FileInputStream* const fin = dynamic_cast <FileInputStream*> (input);
  311. if (fin != nullptr)
  312. {
  313. CFStringRef filePath = juceStringToCFString (fin->getFile().getFullPathName());
  314. QTNewDataReferenceFromFullPathCFString (filePath, (QTPathStyle) kQTNativeDefaultPathStyle, 0,
  315. &dr.dataRef, &dr.dataRefType);
  316. ok = openMovie (props, prop, movie);
  317. DisposeHandle (dr.dataRef);
  318. CFRelease (filePath);
  319. }
  320. else
  321. {
  322. // sanity-check because this currently needs to load the whole stream into memory..
  323. jassert (input->getTotalLength() < 50 * 1024 * 1024);
  324. dataHandle = NewHandle ((Size) input->getTotalLength());
  325. HLock (dataHandle);
  326. // read the entire stream into memory - this is a pain, but can't get it to work
  327. // properly using a custom callback to supply the data.
  328. input->read (*dataHandle, (int) input->getTotalLength());
  329. HUnlock (dataHandle);
  330. // different types to get QT to try. (We should really be a bit smarter here by
  331. // working out in advance which one the stream contains, rather than just trying
  332. // each one)
  333. static const char* const suffixesToTry[] = { "\04.mov", "\04.mp3",
  334. "\04.avi", "\04.m4a" };
  335. for (int i = 0; i < numElementsInArray (suffixesToTry) && ! ok; ++i)
  336. {
  337. /* // this fails for some bizarre reason - it can be bodged to work with
  338. // movies, but can't seem to do it for other file types..
  339. QTNewMovieUserProcRecord procInfo;
  340. procInfo.getMovieUserProc = NewGetMovieUPP (readMovieStreamProc);
  341. procInfo.getMovieUserProcRefcon = this;
  342. procInfo.defaultDataRef.dataRef = dataRef;
  343. procInfo.defaultDataRef.dataRefType = HandleDataHandlerSubType;
  344. props[prop].propClass = kQTPropertyClass_DataLocation;
  345. props[prop].propID = kQTDataLocationPropertyID_MovieUserProc;
  346. props[prop].propValueSize = sizeof (procInfo);
  347. props[prop].propValueAddress = (void*) &procInfo;
  348. ++prop; */
  349. dr.dataRef = createHandleDataRef (dataHandle, suffixesToTry [i]);
  350. dr.dataRefType = HandleDataHandlerSubType;
  351. ok = openMovie (props, prop, movie);
  352. DisposeHandle (dr.dataRef);
  353. }
  354. }
  355. return ok;
  356. }
  357. bool QuickTimeMovieComponent::loadMovie (const File& movieFile_,
  358. const bool isControllerVisible)
  359. {
  360. const bool ok = loadMovie (static_cast <InputStream*> (movieFile_.createInputStream()), isControllerVisible);
  361. movieFile = movieFile_;
  362. return ok;
  363. }
  364. bool QuickTimeMovieComponent::loadMovie (const URL& movieURL,
  365. const bool isControllerVisible)
  366. {
  367. return loadMovie (static_cast <InputStream*> (movieURL.createInputStream (false)), isControllerVisible);
  368. }
  369. void QuickTimeMovieComponent::goToStart()
  370. {
  371. setPosition (0.0);
  372. }
  373. void QuickTimeMovieComponent::setBoundsWithCorrectAspectRatio (const Rectangle<int>& spaceToFitWithin,
  374. RectanglePlacement placement)
  375. {
  376. int normalWidth, normalHeight;
  377. getMovieNormalSize (normalWidth, normalHeight);
  378. const Rectangle<int> normalSize (0, 0, normalWidth, normalHeight);
  379. if (! (spaceToFitWithin.isEmpty() || normalSize.isEmpty()))
  380. setBounds (placement.appliedTo (normalSize, spaceToFitWithin));
  381. else
  382. setBounds (spaceToFitWithin);
  383. }