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.

396 lines
14KB

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