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.

391 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: CameraDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Showcases camera features.
  24. dependencies: juce_core, juce_cryptography, juce_data_structures, juce_events,
  25. juce_graphics, juce_gui_basics, juce_gui_extra, juce_video
  26. exporters: xcode_mac, vs2017, androidstudio, xcode_iphone
  27. moduleFlags: JUCE_USE_CAMERA=1
  28. type: Component
  29. mainClass: CameraDemo
  30. useLocalCopy: 1
  31. END_JUCE_PIP_METADATA
  32. *******************************************************************************/
  33. #pragma once
  34. #include "../Assets/DemoUtilities.h"
  35. //==============================================================================
  36. class CameraDemo : public Component
  37. {
  38. public:
  39. CameraDemo()
  40. {
  41. setOpaque (true);
  42. #if JUCE_ANDROID
  43. // Android requires exclusive access to the audio device when recording videos.
  44. audioDeviceManager.closeAudioDevice();
  45. #endif
  46. addAndMakeVisible (cameraSelectorComboBox);
  47. updateCameraList();
  48. cameraSelectorComboBox.setSelectedId (1);
  49. cameraSelectorComboBox.onChange = [this] { cameraChanged(); };
  50. addAndMakeVisible (snapshotButton);
  51. snapshotButton.onClick = [this] { takeSnapshot(); };
  52. snapshotButton.setEnabled (false);
  53. addAndMakeVisible (recordMovieButton);
  54. recordMovieButton.onClick = [this] { startRecording(); };
  55. recordMovieButton.setEnabled (false);
  56. addAndMakeVisible (lastSnapshot);
  57. cameraSelectorComboBox.setSelectedId (2);
  58. setSize (500, 500);
  59. #if JUCE_IOS || JUCE_ANDROID
  60. setPortraitOrientationEnabled (true);
  61. #endif
  62. }
  63. ~CameraDemo()
  64. {
  65. #if JUCE_IOS || JUCE_ANDROID
  66. setPortraitOrientationEnabled (false);
  67. #endif
  68. #if JUCE_ANDROID
  69. audioDeviceManager.restartLastAudioDevice();
  70. #endif
  71. }
  72. //==============================================================================
  73. void paint (Graphics& g) override
  74. {
  75. g.fillAll (Colours::black);
  76. }
  77. void resized() override
  78. {
  79. auto r = getLocalBounds().reduced (5);
  80. auto top = r.removeFromTop (25);
  81. cameraSelectorComboBox.setBounds (top.removeFromLeft (250));
  82. r.removeFromTop (4);
  83. top = r.removeFromTop (25);
  84. snapshotButton.changeWidthToFitText (24);
  85. snapshotButton.setBounds (top.removeFromLeft (snapshotButton.getWidth()));
  86. top.removeFromLeft (4);
  87. recordMovieButton.changeWidthToFitText (24);
  88. recordMovieButton.setBounds (top.removeFromLeft (recordMovieButton.getWidth()));
  89. r.removeFromTop (4);
  90. auto previewArea = shouldUseLandscapeLayout() ? r.removeFromLeft (r.getWidth() / 2)
  91. : r.removeFromTop (r.getHeight() / 2);
  92. if (cameraPreviewComp.get() != nullptr)
  93. cameraPreviewComp->setBounds (previewArea);
  94. if (shouldUseLandscapeLayout())
  95. r.removeFromLeft (4);
  96. else
  97. r.removeFromTop (4);
  98. lastSnapshot.setBounds (r);
  99. }
  100. private:
  101. //==============================================================================
  102. // if this PIP is running inside the demo runner, we'll use the shared device manager instead
  103. #ifndef JUCE_DEMO_RUNNER
  104. AudioDeviceManager audioDeviceManager;
  105. #else
  106. AudioDeviceManager& audioDeviceManager { getSharedAudioDeviceManager (0, 2) };
  107. #endif
  108. std::unique_ptr<CameraDevice> cameraDevice;
  109. std::unique_ptr<Component> cameraPreviewComp;
  110. ImageComponent lastSnapshot;
  111. ComboBox cameraSelectorComboBox { "Camera" };
  112. TextButton snapshotButton { "Take a snapshot" };
  113. #if ! JUCE_ANDROID && ! JUCE_IOS
  114. TextButton recordMovieButton { "Record a movie (to your desktop)..." };
  115. #else
  116. TextButton recordMovieButton { "Record a movie" };
  117. #endif
  118. bool recordingMovie = false;
  119. File recordingFile;
  120. bool contentSharingPending = false;
  121. void setPortraitOrientationEnabled (bool shouldBeEnabled)
  122. {
  123. auto allowedOrientations = Desktop::getInstance().getOrientationsEnabled();
  124. if (shouldBeEnabled)
  125. allowedOrientations |= Desktop::upright;
  126. else
  127. allowedOrientations &= ~Desktop::upright;
  128. Desktop::getInstance().setOrientationsEnabled (allowedOrientations);
  129. }
  130. bool shouldUseLandscapeLayout() const noexcept
  131. {
  132. #if JUCE_ANDROID || JUCE_IOS
  133. auto orientation = Desktop::getInstance().getCurrentOrientation();
  134. return orientation == Desktop::rotatedClockwise || orientation == Desktop::rotatedAntiClockwise;
  135. #else
  136. return false;
  137. #endif
  138. }
  139. void updateCameraList()
  140. {
  141. cameraSelectorComboBox.clear();
  142. cameraSelectorComboBox.addItem ("No camera", 1);
  143. cameraSelectorComboBox.addSeparator();
  144. auto cameras = CameraDevice::getAvailableDevices();
  145. for (int i = 0; i < cameras.size(); ++i)
  146. cameraSelectorComboBox.addItem (cameras[i], i + 2);
  147. }
  148. void cameraChanged()
  149. {
  150. // This is called when the user chooses a camera from the drop-down list.
  151. #if JUCE_IOS
  152. // On iOS, when switching camera, open the new camera first, so that it can
  153. // share the underlying camera session with the old camera. Otherwise, the
  154. // session would have to be closed first, which can take several seconds.
  155. if (cameraSelectorComboBox.getSelectedId() == 1)
  156. cameraDevice.reset();
  157. #else
  158. cameraDevice.reset();
  159. #endif
  160. cameraPreviewComp.reset();
  161. recordingMovie = false;
  162. if (cameraSelectorComboBox.getSelectedId() > 1)
  163. {
  164. #if JUCE_ANDROID || JUCE_IOS
  165. openCameraAsync();
  166. #else
  167. cameraDeviceOpenResult (CameraDevice::openDevice (cameraSelectorComboBox.getSelectedId() - 2), {});
  168. #endif
  169. }
  170. else
  171. {
  172. snapshotButton .setEnabled (cameraDevice != nullptr && ! contentSharingPending);
  173. recordMovieButton.setEnabled (cameraDevice != nullptr && ! contentSharingPending);
  174. resized();
  175. }
  176. }
  177. void openCameraAsync()
  178. {
  179. SafePointer<CameraDemo> safeThis (this);
  180. CameraDevice::openDeviceAsync (cameraSelectorComboBox.getSelectedId() - 2,
  181. [safeThis] (CameraDevice* device, const String& error) mutable
  182. {
  183. if (safeThis)
  184. safeThis->cameraDeviceOpenResult (device, error);
  185. });
  186. }
  187. void cameraDeviceOpenResult (CameraDevice* device, const String& error)
  188. {
  189. // If camera opening worked, create a preview component for it..
  190. cameraDevice.reset (device);
  191. if (cameraDevice.get() != nullptr)
  192. {
  193. #if JUCE_ANDROID
  194. SafePointer<CameraDemo> safeThis (this);
  195. cameraDevice->onErrorOccurred = [safeThis] (const String& error) mutable { if (safeThis) safeThis->errorOccurred (error); };
  196. #endif
  197. cameraPreviewComp.reset (cameraDevice->createViewerComponent());
  198. addAndMakeVisible (cameraPreviewComp.get());
  199. }
  200. else
  201. {
  202. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Camera open failed",
  203. "Camera open failed, reason: " + error);
  204. }
  205. snapshotButton .setEnabled (cameraDevice.get() != nullptr && ! contentSharingPending);
  206. recordMovieButton.setEnabled (cameraDevice.get() != nullptr && ! contentSharingPending);
  207. resized();
  208. }
  209. void startRecording()
  210. {
  211. if (cameraDevice.get() != nullptr)
  212. {
  213. // The user has clicked the record movie button..
  214. if (! recordingMovie)
  215. {
  216. // Start recording to a file on the user's desktop..
  217. recordingMovie = true;
  218. #if JUCE_ANDROID || JUCE_IOS
  219. recordingFile = File::getSpecialLocation (File::tempDirectory)
  220. #else
  221. recordingFile = File::getSpecialLocation (File::userDesktopDirectory)
  222. #endif
  223. .getNonexistentChildFile ("JuceCameraVideoDemo", CameraDevice::getFileExtension());
  224. #if JUCE_ANDROID
  225. // Android does not support taking pictures while recording video.
  226. snapshotButton.setEnabled (false);
  227. #endif
  228. cameraSelectorComboBox.setEnabled (false);
  229. cameraDevice->startRecordingToFile (recordingFile);
  230. recordMovieButton.setButtonText ("Stop Recording");
  231. }
  232. else
  233. {
  234. // Already recording, so stop...
  235. recordingMovie = false;
  236. cameraDevice->stopRecording();
  237. #if ! JUCE_ANDROID && ! JUCE_IOS
  238. recordMovieButton.setButtonText ("Start recording (to a file on your desktop)");
  239. #else
  240. recordMovieButton.setButtonText ("Record a movie");
  241. #endif
  242. cameraSelectorComboBox.setEnabled (true);
  243. #if JUCE_ANDROID
  244. snapshotButton.setEnabled (true);
  245. #endif
  246. #if JUCE_ANDROID || JUCE_IOS
  247. URL url (recordingFile);
  248. snapshotButton .setEnabled (false);
  249. recordMovieButton.setEnabled (false);
  250. contentSharingPending = true;
  251. SafePointer<CameraDemo> safeThis (this);
  252. juce::ContentSharer::getInstance()->shareFiles ({url},
  253. [safeThis] (bool success, const String&) mutable
  254. {
  255. if (safeThis)
  256. safeThis->sharingFinished (success, false);
  257. });
  258. #endif
  259. }
  260. }
  261. }
  262. void takeSnapshot()
  263. {
  264. SafePointer<CameraDemo> safeThis (this);
  265. cameraDevice->takeStillPicture ([safeThis] (const Image& image) mutable { safeThis->imageReceived (image); });
  266. }
  267. // This is called by the camera device when a new image arrives
  268. void imageReceived (const Image& image)
  269. {
  270. if (! image.isValid())
  271. return;
  272. lastSnapshot.setImage (image);
  273. #if JUCE_ANDROID || JUCE_IOS
  274. auto imageFile = File::getSpecialLocation (File::tempDirectory).getNonexistentChildFile ("JuceCameraPhotoDemo", ".jpg");
  275. if (auto stream = std::unique_ptr<OutputStream> (imageFile.createOutputStream()))
  276. {
  277. if (JPEGImageFormat().writeImageToStream (image, *stream))
  278. {
  279. URL url (imageFile);
  280. snapshotButton .setEnabled (false);
  281. recordMovieButton.setEnabled (false);
  282. contentSharingPending = true;
  283. SafePointer<CameraDemo> safeThis (this);
  284. juce::ContentSharer::getInstance()->shareFiles ({url},
  285. [safeThis] (bool success, const String&) mutable
  286. {
  287. if (safeThis)
  288. safeThis->sharingFinished (success, true);
  289. });
  290. }
  291. }
  292. #endif
  293. }
  294. void errorOccurred (const String& error)
  295. {
  296. AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
  297. "Camera Device Error",
  298. "An error has occurred: " + error + " Camera will be closed.");
  299. cameraDevice.reset();
  300. cameraSelectorComboBox.setSelectedId (1);
  301. snapshotButton .setEnabled (false);
  302. recordMovieButton.setEnabled (false);
  303. }
  304. void sharingFinished (bool success, bool isCapture)
  305. {
  306. AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
  307. isCapture ? "Image sharing result" : "Video sharing result",
  308. success ? "Success!" : "Failed!");
  309. contentSharingPending = false;
  310. snapshotButton .setEnabled (true);
  311. recordMovieButton.setEnabled (true);
  312. }
  313. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CameraDemo)
  314. };