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.

1919 lines
80KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2018 - 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. #if __ANDROID_API__ >= 21
  18. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  19. METHOD (getPlaybackInfo, "getPlaybackInfo", "()Landroid/media/session/MediaController$PlaybackInfo;") \
  20. METHOD (getPlaybackState, "getPlaybackState", "()Landroid/media/session/PlaybackState;") \
  21. METHOD (getTransportControls, "getTransportControls", "()Landroid/media/session/MediaController$TransportControls;") \
  22. METHOD (registerCallback, "registerCallback", "(Landroid/media/session/MediaController$Callback;)V") \
  23. METHOD (setVolumeTo, "setVolumeTo", "(II)V") \
  24. METHOD (unregisterCallback, "unregisterCallback", "(Landroid/media/session/MediaController$Callback;)V")
  25. DECLARE_JNI_CLASS (AndroidMediaController, "android/media/session/MediaController");
  26. #undef JNI_CLASS_MEMBERS
  27. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  28. METHOD (constructor, "<init>", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") \
  29. DECLARE_JNI_CLASS (AndroidMediaControllerCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MediaControllerCallback");
  30. #undef JNI_CLASS_MEMBERS
  31. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  32. METHOD (getAudioAttributes, "getAudioAttributes", "()Landroid/media/AudioAttributes;") \
  33. METHOD (getCurrentVolume, "getCurrentVolume", "()I") \
  34. METHOD (getMaxVolume, "getMaxVolume", "()I")
  35. DECLARE_JNI_CLASS (AndroidMediaControllerPlaybackInfo, "android/media/session/MediaController$PlaybackInfo");
  36. #undef JNI_CLASS_MEMBERS
  37. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  38. METHOD (pause, "pause", "()V") \
  39. METHOD (play, "play", "()V") \
  40. METHOD (playFromMediaId, "playFromMediaId", "(Ljava/lang/String;Landroid/os/Bundle;)V") \
  41. METHOD (seekTo, "seekTo", "(J)V") \
  42. METHOD (stop, "stop", "()V")
  43. DECLARE_JNI_CLASS (AndroidMediaControllerTransportControls, "android/media/session/MediaController$TransportControls");
  44. #undef JNI_CLASS_MEMBERS
  45. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  46. METHOD (constructor, "<init>", "()V") \
  47. METHOD (getCurrentPosition, "getCurrentPosition", "()I") \
  48. METHOD (getDuration, "getDuration", "()I") \
  49. METHOD (getPlaybackParams, "getPlaybackParams", "()Landroid/media/PlaybackParams;") \
  50. METHOD (getVideoHeight, "getVideoHeight", "()I") \
  51. METHOD (getVideoWidth, "getVideoWidth", "()I") \
  52. METHOD (isPlaying, "isPlaying", "()Z") \
  53. METHOD (pause, "pause", "()V") \
  54. METHOD (prepareAsync, "prepareAsync", "()V") \
  55. METHOD (release, "release", "()V") \
  56. METHOD (seekTo, "seekTo", "(I)V") \
  57. METHOD (setAudioAttributes, "setAudioAttributes", "(Landroid/media/AudioAttributes;)V") \
  58. METHOD (setDataSource, "setDataSource", "(Landroid/content/Context;Landroid/net/Uri;)V") \
  59. METHOD (setDisplay, "setDisplay", "(Landroid/view/SurfaceHolder;)V") \
  60. METHOD (setOnBufferingUpdateListener, "setOnBufferingUpdateListener", "(Landroid/media/MediaPlayer$OnBufferingUpdateListener;)V") \
  61. METHOD (setOnCompletionListener, "setOnCompletionListener", "(Landroid/media/MediaPlayer$OnCompletionListener;)V") \
  62. METHOD (setOnErrorListener, "setOnErrorListener", "(Landroid/media/MediaPlayer$OnErrorListener;)V") \
  63. METHOD (setOnInfoListener, "setOnInfoListener", "(Landroid/media/MediaPlayer$OnInfoListener;)V") \
  64. METHOD (setOnPreparedListener, "setOnPreparedListener", "(Landroid/media/MediaPlayer$OnPreparedListener;)V") \
  65. METHOD (setOnSeekCompleteListener, "setOnSeekCompleteListener", "(Landroid/media/MediaPlayer$OnSeekCompleteListener;)V") \
  66. METHOD (setPlaybackParams, "setPlaybackParams", "(Landroid/media/PlaybackParams;)V") \
  67. METHOD (setVolume, "setVolume", "(FF)V") \
  68. METHOD (start, "start", "()V") \
  69. METHOD (stop, "stop", "()V")
  70. DECLARE_JNI_CLASS (AndroidMediaPlayer, "android/media/MediaPlayer");
  71. #undef JNI_CLASS_MEMBERS
  72. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  73. METHOD (constructor, "<init>", "(Landroid/content/Context;Ljava/lang/String;)V") \
  74. METHOD (getController, "getController", "()Landroid/media/session/MediaController;") \
  75. METHOD (release, "release", "()V") \
  76. METHOD (setActive, "setActive", "(Z)V") \
  77. METHOD (setCallback, "setCallback", "(Landroid/media/session/MediaSession$Callback;)V") \
  78. METHOD (setFlags, "setFlags", "(I)V") \
  79. METHOD (setMediaButtonReceiver, "setMediaButtonReceiver", "(Landroid/app/PendingIntent;)V") \
  80. METHOD (setMetadata, "setMetadata", "(Landroid/media/MediaMetadata;)V") \
  81. METHOD (setPlaybackState, "setPlaybackState", "(Landroid/media/session/PlaybackState;)V") \
  82. METHOD (setPlaybackToLocal, "setPlaybackToLocal", "(Landroid/media/AudioAttributes;)V")
  83. DECLARE_JNI_CLASS (AndroidMediaSession, "android/media/session/MediaSession");
  84. #undef JNI_CLASS_MEMBERS
  85. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  86. METHOD (constructor, "<init>", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") \
  87. DECLARE_JNI_CLASS (AndroidMediaSessionCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MediaSessionCallback");
  88. #undef JNI_CLASS_MEMBERS
  89. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  90. METHOD (build, "build", "()Landroid/media/MediaMetadata;") \
  91. METHOD (constructor, "<init>", "()V") \
  92. METHOD (putLong, "putLong", "(Ljava/lang/String;J)Landroid/media/MediaMetadata$Builder;")
  93. DECLARE_JNI_CLASS (AndroidMediaMetadataBuilder, "android/media/MediaMetadata$Builder");
  94. #undef JNI_CLASS_MEMBERS
  95. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  96. METHOD (getSpeed, "getSpeed", "()F") \
  97. METHOD (setSpeed, "setSpeed", "(F)Landroid/media/PlaybackParams;")
  98. DECLARE_JNI_CLASS (AndroidPlaybackParams, "android/media/PlaybackParams");
  99. #undef JNI_CLASS_MEMBERS
  100. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  101. METHOD (getActions, "getActions", "()J") \
  102. METHOD (getErrorMessage, "getErrorMessage", "()Ljava/lang/CharSequence;") \
  103. METHOD (getPlaybackSpeed, "getPlaybackSpeed", "()F") \
  104. METHOD (getPosition, "getPosition", "()J") \
  105. METHOD (getState, "getState", "()I")
  106. DECLARE_JNI_CLASS (AndroidPlaybackState, "android/media/session/PlaybackState");
  107. #undef JNI_CLASS_MEMBERS
  108. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  109. METHOD (build, "build", "()Landroid/media/session/PlaybackState;") \
  110. METHOD (constructor, "<init>", "()V") \
  111. METHOD (setActions, "setActions", "(J)Landroid/media/session/PlaybackState$Builder;") \
  112. METHOD (setErrorMessage, "setErrorMessage", "(Ljava/lang/CharSequence;)Landroid/media/session/PlaybackState$Builder;") \
  113. METHOD (setState, "setState", "(IJF)Landroid/media/session/PlaybackState$Builder;")
  114. DECLARE_JNI_CLASS (AndroidPlaybackStateBuilder, "android/media/session/PlaybackState$Builder");
  115. #undef JNI_CLASS_MEMBERS
  116. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
  117. METHOD (constructor, "<init>", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";Landroid/app/Activity;J)V") \
  118. METHOD (setEnabled, "setEnabled", "(Z)V")
  119. DECLARE_JNI_CLASS (SystemVolumeObserver, JUCE_ANDROID_ACTIVITY_CLASSPATH "$SystemVolumeObserver");
  120. #undef JNI_CLASS_MEMBERS
  121. #endif
  122. //==============================================================================
  123. class MediaPlayerListener : public AndroidInterfaceImplementer
  124. {
  125. public:
  126. struct Owner
  127. {
  128. virtual ~Owner() {}
  129. virtual void onPrepared (LocalRef<jobject>& mediaPlayer) = 0;
  130. virtual void onBufferingUpdate (LocalRef<jobject>& mediaPlayer, int progress) = 0;
  131. virtual void onSeekComplete (LocalRef<jobject>& mediaPlayer) = 0;
  132. virtual void onCompletion (LocalRef<jobject>& mediaPlayer) = 0;
  133. virtual bool onInfo (LocalRef<jobject>& mediaPlayer, int what, int extra) = 0;
  134. virtual bool onError (LocalRef<jobject>& mediaPlayer, int what, int extra) = 0;
  135. };
  136. MediaPlayerListener (Owner& ownerToUse) : owner (ownerToUse) {}
  137. private:
  138. Owner& owner;
  139. jobject invoke (jobject proxy, jobject method, jobjectArray args) override
  140. {
  141. auto* env = getEnv();
  142. auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
  143. int numArgs = args != nullptr ? env->GetArrayLength (args) : 0;
  144. if (methodName == "onPrepared" && numArgs == 1)
  145. {
  146. auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
  147. owner.onPrepared (mediaPlayer);
  148. return nullptr;
  149. }
  150. if (methodName == "onCompletion" && numArgs == 1)
  151. {
  152. auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
  153. owner.onCompletion (mediaPlayer);
  154. return nullptr;
  155. }
  156. if (methodName == "onInfo" && numArgs == 3)
  157. {
  158. auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
  159. auto what = LocalRef<jobject> (env->GetObjectArrayElement (args, 1));
  160. auto extra = LocalRef<jobject> (env->GetObjectArrayElement (args, 2));
  161. auto whatInt = (int) env->CallIntMethod (what, JavaInteger.intValue);
  162. auto extraInt = (int) env->CallIntMethod (extra, JavaInteger.intValue);
  163. auto res = owner.onInfo (mediaPlayer, whatInt, extraInt);
  164. return env->CallStaticObjectMethod (JavaBoolean, JavaBoolean.valueOf, (jboolean) res);
  165. }
  166. if (methodName == "onError" && numArgs == 3)
  167. {
  168. auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
  169. auto what = LocalRef<jobject> (env->GetObjectArrayElement (args, 1));
  170. auto extra = LocalRef<jobject> (env->GetObjectArrayElement (args, 2));
  171. auto whatInt = (int) env->CallIntMethod (what, JavaInteger.intValue);
  172. auto extraInt = (int) env->CallIntMethod (extra, JavaInteger.intValue);
  173. auto res = owner.onError (mediaPlayer, whatInt, extraInt);
  174. return env->CallStaticObjectMethod (JavaBoolean, JavaBoolean.valueOf, (jboolean) res);
  175. }
  176. if (methodName == "onSeekComplete" && numArgs == 1)
  177. {
  178. auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
  179. owner.onSeekComplete (mediaPlayer);
  180. return nullptr;
  181. }
  182. if (methodName == "onBufferingUpdate" && numArgs == 2)
  183. {
  184. auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
  185. auto progress = LocalRef<jobject> (env->GetObjectArrayElement (args, 1));
  186. auto progressInt = (int) env->CallIntMethod (progress, JavaInteger.intValue);
  187. owner.onBufferingUpdate (mediaPlayer, progressInt);
  188. return nullptr;
  189. }
  190. return AndroidInterfaceImplementer::invoke (proxy, method, args);
  191. }
  192. };
  193. //==============================================================================
  194. class AudioManagerOnAudioFocusChangeListener : public AndroidInterfaceImplementer
  195. {
  196. public:
  197. struct Owner
  198. {
  199. virtual ~Owner() {}
  200. virtual void onAudioFocusChange (int changeType) = 0;
  201. };
  202. AudioManagerOnAudioFocusChangeListener (Owner& ownerToUse) : owner (ownerToUse) {}
  203. private:
  204. Owner& owner;
  205. jobject invoke (jobject proxy, jobject method, jobjectArray args) override
  206. {
  207. auto* env = getEnv();
  208. auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
  209. int numArgs = args != nullptr ? env->GetArrayLength (args) : 0;
  210. if (methodName == "onAudioFocusChange" && numArgs == 1)
  211. {
  212. auto changeType = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
  213. auto changeTypeInt = (int) env->CallIntMethod (changeType, JavaInteger.intValue);
  214. owner.onAudioFocusChange (changeTypeInt);
  215. return nullptr;
  216. }
  217. return AndroidInterfaceImplementer::invoke (proxy, method, args);
  218. }
  219. };
  220. //==============================================================================
  221. struct VideoComponent::Pimpl
  222. : public AndroidViewComponent
  223. #if __ANDROID_API__ >= 21
  224. , private AppPausedResumedListener::Owner
  225. #endif
  226. {
  227. Pimpl (VideoComponent& ownerToUse, bool)
  228. #if __ANDROID_API__ >= 21
  229. : owner (ownerToUse),
  230. mediaSession (*this),
  231. appPausedResumedListener (*this),
  232. appPausedResumedListenerNative (CreateJavaInterface (&appPausedResumedListener,
  233. JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener").get())
  234. #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
  235. , systemVolumeListener (*this)
  236. #endif
  237. #endif
  238. {
  239. #if __ANDROID_API__ >= 21
  240. setVisible (true);
  241. auto* env = getEnv();
  242. setView (LocalRef<jobject> (env->CallObjectMethod (android.activity.get(),
  243. JuceAppActivity.createNativeSurfaceView,
  244. reinterpret_cast<jlong> (this),
  245. true)));
  246. env->CallVoidMethod (android.activity, JuceAppActivity.addAppPausedResumedListener,
  247. appPausedResumedListenerNative.get(), reinterpret_cast<jlong> (this));
  248. #endif
  249. }
  250. ~Pimpl()
  251. {
  252. #if __ANDROID_API__ >= 21
  253. getEnv()->CallVoidMethod (android.activity, JuceAppActivity.removeAppPausedResumedListener,
  254. appPausedResumedListenerNative.get(), reinterpret_cast<jlong>(this));
  255. #endif
  256. }
  257. #if __ANDROID_API__ < 21
  258. // Dummy implementations for unsupported API levels.
  259. void loadAsync (const URL&, std::function<void (const URL&, Result)>) {}
  260. void close() {}
  261. bool isOpen() const noexcept { return false; }
  262. bool isPlaying() const noexcept { return false; }
  263. void play() {}
  264. void stop() {}
  265. void setPosition (double) {}
  266. void setSpeed (double) {}
  267. void setVolume (float) {}
  268. float getVolume() const { return 0.0f; }
  269. double getPosition() const { return 0.0; }
  270. double getSpeed() const { return 0.0; }
  271. Rectangle<int> getNativeSize() const { return {}; }
  272. double getDuration() const { return 0.0; }
  273. File currentFile;
  274. URL currentURL;
  275. #else
  276. void loadAsync (const URL& url, std::function<void (const URL&, Result)> callback)
  277. {
  278. close();
  279. wasOpen = false;
  280. if (url.isEmpty())
  281. {
  282. jassertfalse;
  283. return;
  284. }
  285. if (! url.isLocalFile())
  286. {
  287. auto granted = android.activity.callBooleanMethod (JuceAppActivity.isPermissionDeclaredInManifestString,
  288. javaString ("android.permission.INTERNET").get()) != 0;
  289. if (! granted)
  290. {
  291. // In order to access videos from the Internet, the Internet permission has to be specified in
  292. // Android Manifest.
  293. jassertfalse;
  294. return;
  295. }
  296. }
  297. currentURL = url;
  298. jassert (callback != nullptr);
  299. loadFinishedCallback = std::move (callback);
  300. static constexpr jint visible = 0;
  301. getEnv()->CallVoidMethod ((jobject) getView(), AndroidView.setVisibility, visible);
  302. mediaSession.load (url);
  303. }
  304. void close()
  305. {
  306. if (! isOpen())
  307. return;
  308. mediaSession.closeVideo();
  309. static constexpr jint invisible = 4;
  310. getEnv()->CallVoidMethod ((jobject) getView(), AndroidView.setVisibility, invisible);
  311. }
  312. bool isOpen() const noexcept { return mediaSession.isVideoOpen(); }
  313. bool isPlaying() const noexcept { return mediaSession.isPlaying(); }
  314. void play() { mediaSession.play(); }
  315. void stop() { mediaSession.stop(); }
  316. void setPosition (double newPosition) { mediaSession.setPosition (newPosition); }
  317. double getPosition() const { return mediaSession.getPosition(); }
  318. void setSpeed (double newSpeed) { mediaSession.setSpeed (newSpeed); }
  319. double getSpeed() const { return mediaSession.getSpeed(); }
  320. Rectangle<int> getNativeSize() const { return mediaSession.getNativeSize(); }
  321. double getDuration() const { return mediaSession.getDuration(); }
  322. void setVolume (float newVolume) { mediaSession.setVolume (newVolume); }
  323. float getVolume() const { return mediaSession.getVolume(); }
  324. File currentFile;
  325. URL currentURL;
  326. private:
  327. //==============================================================================
  328. class MediaSession : private AudioManagerOnAudioFocusChangeListener::Owner
  329. {
  330. public:
  331. MediaSession (Pimpl& ownerToUse)
  332. : owner (ownerToUse),
  333. sdkVersion (getEnv()->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion)),
  334. audioAttributes (getAudioAttributes()),
  335. nativeMediaSession (LocalRef<jobject> (getEnv()->NewObject (AndroidMediaSession,
  336. AndroidMediaSession.constructor,
  337. android.activity.get(),
  338. javaString ("JuceVideoMediaSession").get()))),
  339. mediaSessionCallback (LocalRef<jobject> (getEnv()->NewObject (AndroidMediaSessionCallback,
  340. AndroidMediaSessionCallback.constructor,
  341. android.activity.get(),
  342. reinterpret_cast<jlong> (this)))),
  343. playbackStateBuilder (LocalRef<jobject> (getEnv()->NewObject (AndroidPlaybackStateBuilder,
  344. AndroidPlaybackStateBuilder.constructor))),
  345. controller (*this, getEnv()->CallObjectMethod (nativeMediaSession,
  346. AndroidMediaSession.getController)),
  347. player (*this),
  348. audioManager (android.activity.callObjectMethod (JuceAppActivity.getSystemService, javaString ("audio").get())),
  349. audioFocusChangeListener (*this),
  350. nativeAudioFocusChangeListener (GlobalRef (CreateJavaInterface (&audioFocusChangeListener,
  351. "android/media/AudioManager$OnAudioFocusChangeListener").get())),
  352. audioFocusRequest (createAudioFocusRequestIfNecessary (sdkVersion, audioAttributes,
  353. nativeAudioFocusChangeListener))
  354. {
  355. auto* env = getEnv();
  356. env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setPlaybackToLocal, audioAttributes.get());
  357. env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setMediaButtonReceiver, nullptr);
  358. env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setCallback, mediaSessionCallback.get());
  359. }
  360. ~MediaSession()
  361. {
  362. auto* env = getEnv();
  363. env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setCallback, nullptr);
  364. controller.stop();
  365. env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.release);
  366. }
  367. bool isVideoOpen() const { return player.isVideoOpen(); }
  368. bool isPlaying() const { return player.isPlaying(); }
  369. void load (const URL& url) { controller.load (url); }
  370. void closeVideo()
  371. {
  372. resetState();
  373. controller.closeVideo();
  374. }
  375. void setDisplay (jobject surfaceHolder) { player.setDisplay (surfaceHolder); }
  376. void play() { controller.play(); }
  377. void stop() { controller.stop(); }
  378. void setPosition (double newPosition) { controller.setPosition (newPosition); }
  379. double getPosition() const { return controller.getPosition(); }
  380. void setSpeed (double newSpeed)
  381. {
  382. playSpeedMult = newSpeed;
  383. // Calling non 0.0 speed on a paused player would start it...
  384. if (player.isPlaying())
  385. {
  386. player.setPlaySpeed (playSpeedMult);
  387. updatePlaybackState();
  388. }
  389. }
  390. double getSpeed() const { return controller.getPlaySpeed(); }
  391. Rectangle<int> getNativeSize() const { return player.getVideoNativeSize(); }
  392. double getDuration() const { return player.getVideoDuration() / 1000.0; }
  393. void setVolume (float newVolume)
  394. {
  395. #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
  396. controller.setVolume (newVolume);
  397. #else
  398. player.setAudioVolume (newVolume);
  399. #endif
  400. }
  401. float getVolume() const
  402. {
  403. #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
  404. return controller.getVolume();
  405. #else
  406. return player.getAudioVolume();
  407. #endif
  408. }
  409. void storeState()
  410. {
  411. storedPlaybackState.clear();
  412. storedPlaybackState = GlobalRef (getCurrentPlaybackState());
  413. }
  414. void restoreState()
  415. {
  416. if (storedPlaybackState.get() == nullptr)
  417. return;
  418. auto* env = getEnv();
  419. auto pos = env->CallLongMethod (storedPlaybackState, AndroidPlaybackState.getPosition);
  420. setPosition (pos / 1000.0);
  421. setSpeed (playSpeedMult);
  422. auto state = env->CallIntMethod (storedPlaybackState, AndroidPlaybackState.getState);
  423. if (state != PlaybackState::STATE_NONE && state != PlaybackState::STATE_STOPPED
  424. && state != PlaybackState::STATE_PAUSED && state != PlaybackState::STATE_ERROR)
  425. {
  426. play();
  427. }
  428. }
  429. private:
  430. struct PlaybackState
  431. {
  432. enum
  433. {
  434. STATE_NONE = 0,
  435. STATE_STOPPED = 1,
  436. STATE_PAUSED = 2,
  437. STATE_PLAYING = 3,
  438. STATE_FAST_FORWARDING = 4,
  439. STATE_REWINDING = 5,
  440. STATE_BUFFERING = 6,
  441. STATE_ERROR = 7,
  442. STATE_CONNECTING = 8,
  443. STATE_SKIPPING_TO_PREVIOUS = 9,
  444. STATE_SKIPPING_TO_NEXT = 10,
  445. STATE_SKIPPING_TO_QUEUE_ITEM = 11,
  446. };
  447. enum
  448. {
  449. ACTION_PAUSE = 0x2,
  450. ACTION_PLAY = 0x4,
  451. ACTION_PLAY_FROM_MEDIA_ID = 0x8000,
  452. ACTION_PLAY_PAUSE = 0x200,
  453. ACTION_SEEK_TO = 0x100,
  454. ACTION_STOP = 0x1,
  455. };
  456. };
  457. //==============================================================================
  458. class Controller
  459. {
  460. public:
  461. Controller (MediaSession& ownerToUse, jobject nativeController)
  462. : owner (ownerToUse),
  463. nativeController (GlobalRef (nativeController)),
  464. controllerTransportControls (LocalRef<jobject> (getEnv()->CallObjectMethod (nativeController,
  465. AndroidMediaController.getTransportControls))),
  466. controllerCallback (LocalRef<jobject> (getEnv()->NewObject (AndroidMediaControllerCallback,
  467. AndroidMediaControllerCallback.constructor,
  468. android.activity.get(),
  469. reinterpret_cast<jlong> (this))))
  470. {
  471. auto* env = getEnv();
  472. env->CallVoidMethod (nativeController, AndroidMediaController.registerCallback, controllerCallback.get());
  473. }
  474. ~Controller()
  475. {
  476. auto* env = getEnv();
  477. env->CallVoidMethod (nativeController, AndroidMediaController.unregisterCallback, controllerCallback.get());
  478. }
  479. void load (const URL& url)
  480. {
  481. // NB: would use playFromUri, but it was only introduced in API 23...
  482. getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.playFromMediaId,
  483. javaString (url.toString (true)).get(), nullptr);
  484. }
  485. void closeVideo()
  486. {
  487. getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.stop);
  488. }
  489. void play()
  490. {
  491. getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.play);
  492. }
  493. void stop()
  494. {
  495. // NB: calling pause, rather than stop, because after calling stop, we would have to call load() again.
  496. getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.pause);
  497. }
  498. void setPosition (double newPosition)
  499. {
  500. auto seekPos = static_cast<jlong> (newPosition * 1000);
  501. getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.seekTo, seekPos);
  502. }
  503. double getPosition() const
  504. {
  505. auto* env = getEnv();
  506. auto playbackState = LocalRef<jobject> (env->CallObjectMethod (nativeController, AndroidMediaController.getPlaybackState));
  507. if (playbackState != nullptr)
  508. return env->CallLongMethod (playbackState, AndroidPlaybackState.getPosition) / 1000.0;
  509. return 0.0;
  510. }
  511. double getPlaySpeed() const
  512. {
  513. auto* env = getEnv();
  514. auto playbackState = LocalRef<jobject> (env->CallObjectMethod (nativeController, AndroidMediaController.getPlaybackState));
  515. if (playbackState != nullptr)
  516. return (double) env->CallFloatMethod (playbackState, AndroidPlaybackState.getPlaybackSpeed);
  517. return 1.0;
  518. }
  519. void setVolume (float newVolume)
  520. {
  521. auto* env = getEnv();
  522. auto playbackInfo = LocalRef<jobject> (env->CallObjectMethod (nativeController, AndroidMediaController.getPlaybackInfo));
  523. auto maxVolume = env->CallIntMethod (playbackInfo, AndroidMediaControllerPlaybackInfo.getMaxVolume);
  524. auto targetVolume = jmin (jint (maxVolume * newVolume), maxVolume);
  525. static constexpr jint flagShowUI = 1;
  526. env->CallVoidMethod (nativeController, AndroidMediaController.setVolumeTo, targetVolume, flagShowUI);
  527. }
  528. float getVolume() const
  529. {
  530. auto* env = getEnv();
  531. auto playbackInfo = LocalRef<jobject> (env->CallObjectMethod (nativeController, AndroidMediaController.getPlaybackInfo));
  532. auto maxVolume = (int) (env->CallIntMethod (playbackInfo, AndroidMediaControllerPlaybackInfo.getMaxVolume));
  533. auto curVolume = (int) (env->CallIntMethod (playbackInfo, AndroidMediaControllerPlaybackInfo.getCurrentVolume));
  534. return static_cast<float> (curVolume) / maxVolume;
  535. }
  536. private:
  537. MediaSession& owner;
  538. GlobalRef nativeController;
  539. GlobalRef controllerTransportControls;
  540. GlobalRef controllerCallback;
  541. bool wasPlaying = false;
  542. bool wasPaused = true;
  543. //==============================================================================
  544. // MediaSessionController callbacks
  545. void audioInfoChanged (jobject info)
  546. {
  547. JUCE_VIDEO_LOG ("MediaSessionController::audioInfoChanged()");
  548. ignoreUnused (info);
  549. }
  550. void metadataChanged (jobject metadata)
  551. {
  552. JUCE_VIDEO_LOG ("MediaSessionController::metadataChanged()");
  553. ignoreUnused (metadata);
  554. }
  555. void playbackStateChanged (jobject playbackState)
  556. {
  557. JUCE_VIDEO_LOG ("MediaSessionController::playbackStateChanged()");
  558. if (playbackState == nullptr)
  559. return;
  560. auto state = getEnv()->CallIntMethod (playbackState, AndroidPlaybackState.getState);
  561. static constexpr jint statePaused = 2;
  562. static constexpr jint statePlaying = 3;
  563. if (wasPlaying == false && state == statePlaying)
  564. owner.playbackStarted();
  565. else if (wasPaused == false && state == statePaused)
  566. owner.playbackStopped();
  567. wasPlaying = state == statePlaying;
  568. wasPaused = state == statePaused;
  569. }
  570. void sessionDestroyed()
  571. {
  572. JUCE_VIDEO_LOG ("MediaSessionController::sessionDestroyed()");
  573. }
  574. friend void juce_mediaControllerAudioInfoChanged (int64, void*);
  575. friend void juce_mediaControllerMetadataChanged (int64, void*);
  576. friend void juce_mediaControllerPlaybackStateChanged (int64, void*);
  577. friend void juce_mediaControllerSessionDestroyed (int64);
  578. };
  579. //==============================================================================
  580. class Player : private MediaPlayerListener::Owner
  581. {
  582. public:
  583. Player (MediaSession& ownerToUse)
  584. : owner (ownerToUse),
  585. mediaPlayerListener (*this),
  586. nativeMediaPlayerListener (GlobalRef (CreateJavaInterface (&mediaPlayerListener,
  587. getNativeMediaPlayerListenerInterfaces())))
  588. {}
  589. void setDisplay (jobject surfaceHolder)
  590. {
  591. if (surfaceHolder == nullptr)
  592. {
  593. videoSurfaceHolder.clear();
  594. if (nativeMediaPlayer.get() != nullptr)
  595. getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDisplay, nullptr);
  596. return;
  597. }
  598. videoSurfaceHolder = GlobalRef (surfaceHolder);
  599. if (nativeMediaPlayer.get() != nullptr)
  600. getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDisplay, videoSurfaceHolder.get());
  601. }
  602. void load (jstring mediaId, jobject extras)
  603. {
  604. ignoreUnused (extras);
  605. closeVideo();
  606. auto* env = getEnv();
  607. nativeMediaPlayer = GlobalRef (LocalRef<jobject> (env->NewObject (AndroidMediaPlayer, AndroidMediaPlayer.constructor)));
  608. currentState = State::idle;
  609. auto uri = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, mediaId));
  610. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDataSource, android.activity.get(), uri.get());
  611. if (jniCheckHasExceptionOccurredAndClear())
  612. {
  613. owner.errorOccurred ("Could not find video under path provided (" + juceString (mediaId) + ")");
  614. return;
  615. }
  616. currentState = State::initialised;
  617. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnBufferingUpdateListener, nativeMediaPlayerListener.get());
  618. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnCompletionListener, nativeMediaPlayerListener.get());
  619. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnErrorListener, nativeMediaPlayerListener.get());
  620. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnInfoListener, nativeMediaPlayerListener.get());
  621. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnPreparedListener, nativeMediaPlayerListener.get());
  622. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnSeekCompleteListener, nativeMediaPlayerListener.get());
  623. if (videoSurfaceHolder != nullptr)
  624. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDisplay, videoSurfaceHolder.get());
  625. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.prepareAsync);
  626. currentState = State::preparing;
  627. }
  628. void closeVideo()
  629. {
  630. if (nativeMediaPlayer.get() == nullptr)
  631. return;
  632. auto* env = getEnv();
  633. if (getCurrentStateInfo().canCallStop)
  634. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.stop);
  635. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.release);
  636. nativeMediaPlayer.clear();
  637. currentState = State::end;
  638. }
  639. bool isVideoOpen() const noexcept
  640. {
  641. return currentState == State::prepared || currentState == State::started
  642. || currentState == State::paused || currentState == State::complete;
  643. }
  644. int getPlaybackStateFlag() const noexcept { return getCurrentStateInfo().playbackStateFlag; }
  645. int getAllowedActions() const noexcept { return getCurrentStateInfo().allowedActions; }
  646. jlong getVideoDuration() const
  647. {
  648. if (! getCurrentStateInfo().canCallGetVideoDuration)
  649. return 0;
  650. return getEnv()->CallIntMethod (nativeMediaPlayer, AndroidMediaPlayer.getDuration);
  651. }
  652. Rectangle<int> getVideoNativeSize() const
  653. {
  654. if (! getCurrentStateInfo().canCallGetVideoHeight)
  655. {
  656. jassertfalse;
  657. return {};
  658. }
  659. auto* env = getEnv();
  660. auto width = (int) env->CallIntMethod (nativeMediaPlayer, AndroidMediaPlayer.getVideoWidth);
  661. auto height = (int) env->CallIntMethod (nativeMediaPlayer, AndroidMediaPlayer.getVideoHeight);
  662. return Rectangle<int> (0, 0, width, height);
  663. }
  664. void play()
  665. {
  666. if (! getCurrentStateInfo().canCallStart)
  667. {
  668. jassertfalse;
  669. return;
  670. }
  671. auto* env = getEnv();
  672. // Perform a potentially pending volume setting
  673. if (lastAudioVolume != std::numeric_limits<float>::min())
  674. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setVolume, (jfloat) lastAudioVolume, (jfloat) lastAudioVolume);
  675. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.start);
  676. currentState = State::started;
  677. }
  678. void pause()
  679. {
  680. if (! getCurrentStateInfo().canCallPause)
  681. {
  682. jassertfalse;
  683. return;
  684. }
  685. getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.pause);
  686. currentState = State::paused;
  687. }
  688. bool isPlaying() const
  689. {
  690. return getCurrentStateInfo().isPlaying;
  691. }
  692. void setPlayPosition (jint newPositionMs)
  693. {
  694. if (! getCurrentStateInfo().canCallSeekTo)
  695. {
  696. jassertfalse;
  697. return;
  698. }
  699. getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.seekTo, (jint) newPositionMs);
  700. }
  701. jint getPlayPosition() const
  702. {
  703. if (! getCurrentStateInfo().canCallGetCurrentPosition)
  704. return 0.0;
  705. return getEnv()->CallIntMethod (nativeMediaPlayer, AndroidMediaPlayer.getCurrentPosition);
  706. }
  707. void setPlaySpeed (double newSpeed)
  708. {
  709. if (! getCurrentStateInfo().canCallSetPlaybackParams)
  710. {
  711. jassertfalse;
  712. return;
  713. }
  714. auto* env = getEnv();
  715. auto playbackParams = LocalRef<jobject> (env->CallObjectMethod (nativeMediaPlayer, AndroidMediaPlayer.getPlaybackParams));
  716. LocalRef<jobject> (env->CallObjectMethod (playbackParams, AndroidPlaybackParams.setSpeed, (jfloat) newSpeed));
  717. env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setPlaybackParams, playbackParams.get());
  718. if (jniCheckHasExceptionOccurredAndClear())
  719. {
  720. // MediaPlayer can't handle speed provided!
  721. jassertfalse;
  722. }
  723. }
  724. double getPlaySpeed() const
  725. {
  726. if (! getCurrentStateInfo().canCallGetPlaybackParams)
  727. return 0.0;
  728. auto* env = getEnv();
  729. auto playbackParams = LocalRef<jobject> (env->CallObjectMethod (nativeMediaPlayer, AndroidMediaPlayer.getPlaybackParams));
  730. return (double) env->CallFloatMethod (playbackParams, AndroidPlaybackParams.getSpeed);
  731. }
  732. void setAudioVolume (float newVolume)
  733. {
  734. if (! getCurrentStateInfo().canCallSetVolume)
  735. {
  736. jassertfalse;
  737. return;
  738. }
  739. lastAudioVolume = jlimit (0.0f, 1.0f, newVolume);
  740. if (nativeMediaPlayer.get() != nullptr)
  741. getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setVolume, (jfloat) lastAudioVolume, (jfloat) lastAudioVolume);
  742. }
  743. float getAudioVolume() const
  744. {
  745. // There is NO getVolume() in MediaPlayer, so the value returned here can be incorrect!
  746. return lastAudioVolume;
  747. }
  748. private:
  749. //=============================================================================
  750. struct StateInfo
  751. {
  752. int playbackStateFlag = 0, allowedActions = 0;
  753. bool isPlaying, canCallGetCurrentPosition, canCallGetVideoDuration,
  754. canCallGetVideoHeight, canCallGetVideoWidth, canCallGetPlaybackParams,
  755. canCallPause, canCallPrepare, canCallSeekTo, canCallSetAudioAttributes,
  756. canCallSetDataSource, canCallSetPlaybackParams, canCallSetVolume,
  757. canCallStart, canCallStop;
  758. };
  759. enum class State
  760. {
  761. idle, initialised, preparing, prepared, started, paused, stopped, complete, error, end
  762. };
  763. static constexpr StateInfo stateInfos[] = {
  764. /* idle */
  765. {PlaybackState::STATE_NONE, PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
  766. false, true, false, true, true, false, false, false, false, true,
  767. true, false, true, false, false},
  768. /* initialised */
  769. {PlaybackState::STATE_NONE, 0, // NB: could use action prepare, but that's API 24 onwards only
  770. false, true, false, true, true, true, false, true, false, true,
  771. false, true, true, false, false},
  772. /* preparing */
  773. {PlaybackState::STATE_BUFFERING, 0,
  774. false, false, false, false, false, true, false, false, false, false,
  775. false, false, false, false, false},
  776. /* prepared */
  777. {PlaybackState::STATE_PAUSED,
  778. PlaybackState::ACTION_PLAY | PlaybackState::ACTION_PLAY_PAUSE | PlaybackState::ACTION_PLAY_FROM_MEDIA_ID | PlaybackState::ACTION_STOP | PlaybackState::ACTION_SEEK_TO,
  779. false, true, true, true, true, true, false, false, true, true,
  780. false, true, true, true, true},
  781. /* started */
  782. {PlaybackState::STATE_PLAYING,
  783. PlaybackState::ACTION_PAUSE | PlaybackState::ACTION_PLAY_PAUSE | PlaybackState::ACTION_SEEK_TO | PlaybackState::ACTION_STOP | PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
  784. true, true, true, true, true, true, true, false, true, true,
  785. false, true, true, true, true},
  786. /* paused */
  787. {PlaybackState::STATE_PAUSED,
  788. PlaybackState::ACTION_PLAY | PlaybackState::ACTION_PLAY_PAUSE | PlaybackState::ACTION_SEEK_TO | PlaybackState::ACTION_STOP | PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
  789. false, true, true, true, true, true, true, false, true, true,
  790. false, true, true, true, true},
  791. /* stopped */
  792. {PlaybackState::STATE_STOPPED,
  793. PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
  794. false, true, true, true, true, true, false, true, false, true,
  795. false, false, true, false, true},
  796. /* complete */
  797. {PlaybackState::STATE_PAUSED,
  798. PlaybackState::ACTION_SEEK_TO | PlaybackState::ACTION_STOP | PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
  799. false, true, true, true, true, true, true, false, true, true,
  800. false, true, true, true, true},
  801. /* error */
  802. {PlaybackState::STATE_ERROR,
  803. PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
  804. false, false, false, false, false, false, false, false, false, false,
  805. false, false, false, false, false},
  806. /* end */
  807. {PlaybackState::STATE_NONE,
  808. PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
  809. false, false, false, false, false, false, false, false, false, false,
  810. false, false, false, false, false}
  811. };
  812. StateInfo getCurrentStateInfo() const noexcept { return stateInfos[static_cast<int> (currentState)]; }
  813. //==============================================================================
  814. MediaSession& owner;
  815. GlobalRef nativeMediaPlayer;
  816. MediaPlayerListener mediaPlayerListener;
  817. GlobalRef nativeMediaPlayerListener;
  818. float lastAudioVolume = std::numeric_limits<float>::min();
  819. GlobalRef videoSurfaceHolder;
  820. State currentState = State::idle;
  821. //==============================================================================
  822. void onPrepared (LocalRef<jobject>& mediaPlayer) override
  823. {
  824. JUCE_VIDEO_LOG ("MediaPlayer::onPrepared()");
  825. ignoreUnused (mediaPlayer);
  826. currentState = State::prepared;
  827. owner.playerPrepared();
  828. }
  829. void onBufferingUpdate (LocalRef<jobject>& mediaPlayer, int progress) override
  830. {
  831. ignoreUnused (mediaPlayer);
  832. owner.playerBufferingUpdated (progress);
  833. }
  834. void onSeekComplete (LocalRef<jobject>& mediaPlayer) override
  835. {
  836. JUCE_VIDEO_LOG ("MediaPlayer::onSeekComplete()");
  837. ignoreUnused (mediaPlayer);
  838. owner.playerSeekCompleted();
  839. }
  840. void onCompletion (LocalRef<jobject>& mediaPlayer) override
  841. {
  842. JUCE_VIDEO_LOG ("MediaPlayer::onCompletion()");
  843. ignoreUnused (mediaPlayer);
  844. currentState = State::complete;
  845. owner.playerPlaybackCompleted();
  846. }
  847. enum
  848. {
  849. MEDIA_INFO_UNKNOWN = 1,
  850. MEDIA_INFO_VIDEO_RENDERING_START = 3,
  851. MEDIA_INFO_VIDEO_TRACK_LAGGING = 700,
  852. MEDIA_INFO_BUFFERING_START = 701,
  853. MEDIA_INFO_BUFFERING_END = 702,
  854. MEDIA_INFO_NETWORK_BANDWIDTH = 703,
  855. MEDIA_INFO_BAD_INTERLEAVING = 800,
  856. MEDIA_INFO_NOT_SEEKABLE = 801,
  857. MEDIA_INFO_METADATA_UPDATE = 802,
  858. MEDIA_INFO_AUDIO_NOT_PLAYING = 804,
  859. MEDIA_INFO_VIDEO_NOT_PLAYING = 805,
  860. MEDIA_INFO_UNSUPPORTED_SUBTITE = 901,
  861. MEDIA_INFO_SUBTITLE_TIMED_OUT = 902
  862. };
  863. bool onInfo (LocalRef<jobject>& mediaPlayer, int what, int extra) override
  864. {
  865. JUCE_VIDEO_LOG ("MediaPlayer::onInfo(), infoCode: " + String (what) + " (" + infoCodeToString (what) + ")"
  866. + ", extraCode: " + String (extra));
  867. ignoreUnused (mediaPlayer, extra);
  868. if (what == MEDIA_INFO_BUFFERING_START)
  869. owner.playerBufferingStarted();
  870. else if (what == MEDIA_INFO_BUFFERING_END)
  871. owner.playerBufferingEnded();
  872. return true;
  873. }
  874. static String infoCodeToString (int code)
  875. {
  876. switch (code)
  877. {
  878. case MEDIA_INFO_UNKNOWN: return "Unknown";
  879. case MEDIA_INFO_VIDEO_RENDERING_START: return "Rendering start";
  880. case MEDIA_INFO_VIDEO_TRACK_LAGGING: return "Video track lagging";
  881. case MEDIA_INFO_BUFFERING_START: return "Buffering start";
  882. case MEDIA_INFO_BUFFERING_END: return "Buffering end";
  883. case MEDIA_INFO_NETWORK_BANDWIDTH: return "Network bandwidth info available";
  884. case MEDIA_INFO_BAD_INTERLEAVING: return "Bad interleaving";
  885. case MEDIA_INFO_NOT_SEEKABLE: return "Video not seekable";
  886. case MEDIA_INFO_METADATA_UPDATE: return "Metadata updated";
  887. case MEDIA_INFO_AUDIO_NOT_PLAYING: return "Audio not playing";
  888. case MEDIA_INFO_VIDEO_NOT_PLAYING: return "Video not playing";
  889. case MEDIA_INFO_UNSUPPORTED_SUBTITE: return "Unsupported subtitle";
  890. case MEDIA_INFO_SUBTITLE_TIMED_OUT: return "Subtitle timed out";
  891. default: return "";
  892. }
  893. }
  894. bool onError (LocalRef<jobject>& mediaPlayer, int what, int extra) override
  895. {
  896. auto errorMessage = errorCodeToString (what);
  897. auto extraMessage = errorCodeToString (extra);
  898. if (extraMessage.isNotEmpty())
  899. errorMessage << ", " << extraMessage;
  900. JUCE_VIDEO_LOG ("MediaPlayer::onError(), errorCode: " + String (what) + " (" + errorMessage + ")"
  901. + ", extraCode: " + String (extra) + " (" + extraMessage + ")");
  902. ignoreUnused (mediaPlayer);
  903. currentState = State::error;
  904. owner.errorOccurred (errorMessage);
  905. return true;
  906. }
  907. static String errorCodeToString (int code)
  908. {
  909. enum
  910. {
  911. MEDIA_ERROR_UNSUPPORTED = -1010,
  912. MEDIA_ERROR_MALFORMED = -1007,
  913. MEDIA_ERROR_IO = -1004,
  914. MEDIA_ERROR_TIMED_OUT = -110,
  915. MEDIA_ERROR_UNKNOWN = 1,
  916. MEDIA_ERROR_SERVER_DIED = 100,
  917. MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200
  918. };
  919. switch (code)
  920. {
  921. case MEDIA_ERROR_UNSUPPORTED: return "Unsupported bitstream";
  922. case MEDIA_ERROR_MALFORMED: return "Malformed bitstream";
  923. case MEDIA_ERROR_IO: return "File/Network I/O error";
  924. case MEDIA_ERROR_TIMED_OUT: return "Timed out";
  925. case MEDIA_ERROR_UNKNOWN: return "Unknown error";
  926. case MEDIA_ERROR_SERVER_DIED: return "Media server died (playback restart required)";
  927. case MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: return "Video container not valid for progressive playback";
  928. default: return "";
  929. }
  930. }
  931. //==============================================================================
  932. static StringArray getNativeMediaPlayerListenerInterfaces()
  933. {
  934. #define IFPREFIX "android/media/MediaPlayer$"
  935. return { IFPREFIX "OnCompletionListener", IFPREFIX "OnErrorListener",
  936. IFPREFIX "OnInfoListener", IFPREFIX "OnPreparedListener",
  937. IFPREFIX "OnBufferingUpdateListener", IFPREFIX "OnSeekCompleteListener"
  938. };
  939. #undef IFPREFIX
  940. }
  941. };
  942. //==============================================================================
  943. Pimpl& owner;
  944. int sdkVersion;
  945. GlobalRef audioAttributes;
  946. GlobalRef nativeMediaSession;
  947. GlobalRef mediaSessionCallback;
  948. GlobalRef playbackStateBuilder;
  949. Controller controller;
  950. Player player;
  951. GlobalRef audioManager;
  952. AudioManagerOnAudioFocusChangeListener audioFocusChangeListener;
  953. GlobalRef nativeAudioFocusChangeListener;
  954. GlobalRef audioFocusRequest;
  955. GlobalRef storedPlaybackState;
  956. bool pendingSeekRequest = false;
  957. bool playerBufferingInProgress = false;
  958. bool usesBuffering = false;
  959. SparseSet<int> bufferedRegions;
  960. double playSpeedMult = 1.0;
  961. bool hasAudioFocus = false;
  962. //==============================================================================
  963. // MediaSession callbacks
  964. void pauseCallback()
  965. {
  966. JUCE_VIDEO_LOG ("MediaSession::pauseCallback()");
  967. player.pause();
  968. updatePlaybackState();
  969. abandonAudioFocus();
  970. }
  971. void playCallback()
  972. {
  973. JUCE_VIDEO_LOG ("MediaSession::playCallback()");
  974. requestAudioFocus();
  975. if (! hasAudioFocus)
  976. {
  977. errorOccurred ("Application has been denied audio focus. Try again later.");
  978. return;
  979. }
  980. getEnv()->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setActive, true);
  981. player.play();
  982. setSpeed (playSpeedMult);
  983. updatePlaybackState();
  984. }
  985. void playFromMediaIdCallback (jstring mediaId, jobject extras)
  986. {
  987. JUCE_VIDEO_LOG ("MediaSession::playFromMediaIdCallback()");
  988. player.load (mediaId, extras);
  989. updatePlaybackState();
  990. }
  991. void seekToCallback (jlong pos)
  992. {
  993. JUCE_VIDEO_LOG ("MediaSession::seekToCallback()");
  994. pendingSeekRequest = true;
  995. player.setPlayPosition ((jint) pos);
  996. updatePlaybackState();
  997. }
  998. void stopCallback()
  999. {
  1000. JUCE_VIDEO_LOG ("MediaSession::stopCallback()");
  1001. auto* env = getEnv();
  1002. env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setActive, false);
  1003. player.closeVideo();
  1004. updatePlaybackState();
  1005. abandonAudioFocus();
  1006. owner.closeVideoFinished();
  1007. }
  1008. //==============================================================================
  1009. bool isSeekInProgress() const noexcept
  1010. {
  1011. if (pendingSeekRequest)
  1012. return true;
  1013. if (! usesBuffering)
  1014. return false;
  1015. // NB: player sometimes notifies us about buffering, but only for regions that
  1016. // were previously buffered already. For buffering happening for the first time,
  1017. // we don't get such notification...
  1018. if (playerBufferingInProgress)
  1019. return true;
  1020. auto playPos = player.getPlayPosition();
  1021. auto durationMs = player.getVideoDuration();
  1022. int playPosPercent = 100 * playPos / static_cast<double> (durationMs);
  1023. // NB: assuming the playback will start roughly when there is 5% of content loaded...
  1024. return ! bufferedRegions.containsRange (Range<int> (playPosPercent, jmin (101, playPosPercent + 5)));
  1025. }
  1026. void updatePlaybackState()
  1027. {
  1028. getEnv()->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setPlaybackState, getCurrentPlaybackState());
  1029. }
  1030. jobject getCurrentPlaybackState()
  1031. {
  1032. static constexpr int bufferingState = 6;
  1033. auto playbackStateFlag = isSeekInProgress() ? bufferingState : player.getPlaybackStateFlag();
  1034. auto playPos = player.getPlayPosition();
  1035. auto playSpeed = player.getPlaySpeed();
  1036. auto allowedActions = player.getAllowedActions();
  1037. auto* env = getEnv();
  1038. LocalRef<jobject> (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.setState,
  1039. (jint) playbackStateFlag, (jlong) playPos, (jfloat) playSpeed));
  1040. LocalRef<jobject> (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.setActions, (jint) allowedActions));
  1041. return env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.build);
  1042. }
  1043. //==============================================================================
  1044. void playerPrepared()
  1045. {
  1046. resetState();
  1047. updateMetadata();
  1048. owner.loadFinished();
  1049. }
  1050. void playerBufferingStarted() { playerBufferingInProgress = true; }
  1051. void playerBufferingEnded() { playerBufferingInProgress = false; }
  1052. void playerBufferingUpdated (int progress)
  1053. {
  1054. usesBuffering = true;
  1055. updatePlaybackState();
  1056. auto playPos = player.getPlayPosition();
  1057. auto durationMs = player.getVideoDuration();
  1058. int playPosPercent = 100 * playPos / static_cast<double> (durationMs);
  1059. bufferedRegions.addRange (Range<int> (playPosPercent, progress + 1));
  1060. String ranges;
  1061. for (auto& r : bufferedRegions.getRanges())
  1062. ranges << "[" << r.getStart() << "%, " << r.getEnd() - 1 << "%] ";
  1063. JUCE_VIDEO_LOG ("Buffering status update, seek pos: " + String (playPosPercent) + "%, buffered regions: " + ranges);
  1064. }
  1065. void playerSeekCompleted()
  1066. {
  1067. pendingSeekRequest = false;
  1068. updatePlaybackState();
  1069. }
  1070. void playerPlaybackCompleted()
  1071. {
  1072. pauseCallback();
  1073. seekToCallback ((jlong) 0);
  1074. }
  1075. void updateMetadata()
  1076. {
  1077. auto* env = getEnv();
  1078. auto metadataBuilder = LocalRef<jobject> (env->NewObject (AndroidMediaMetadataBuilder,
  1079. AndroidMediaMetadataBuilder.constructor));
  1080. auto durationMs = player.getVideoDuration();
  1081. auto jDurationKey = javaString ("android.media.metadata.DURATION");
  1082. LocalRef<jobject> (env->CallObjectMethod (metadataBuilder,
  1083. AndroidMediaMetadataBuilder.putLong,
  1084. jDurationKey.get(),
  1085. (jlong) durationMs));
  1086. auto jNumTracksKey = javaString ("android.media.metadata.NUM_TRACKS");
  1087. LocalRef<jobject> (env->CallObjectMethod (metadataBuilder,
  1088. AndroidMediaMetadataBuilder.putLong,
  1089. jNumTracksKey.get(),
  1090. (jlong) 1));
  1091. env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setMetadata,
  1092. env->CallObjectMethod (metadataBuilder, AndroidMediaMetadataBuilder.build));
  1093. }
  1094. void errorOccurred (const String& errorMessage)
  1095. {
  1096. auto* env = getEnv();
  1097. // Propagate error to session controller(s) and ...
  1098. LocalRef<jobject> (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.setErrorMessage,
  1099. javaString (errorMessage).get()));
  1100. auto state = LocalRef<jobject> (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.build));
  1101. env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setPlaybackState, state.get());
  1102. // ...also notify JUCE side client
  1103. owner.errorOccurred (errorMessage);
  1104. }
  1105. //==============================================================================
  1106. static jobject createAudioFocusRequestIfNecessary (int sdkVersion, const GlobalRef& audioAttributes,
  1107. const GlobalRef& nativeAudioFocusChangeListener)
  1108. {
  1109. if (sdkVersion < 26)
  1110. return nullptr;
  1111. auto* env = getEnv();
  1112. auto requestBuilderClass = LocalRef<jclass> (env->FindClass ("android/media/AudioFocusRequest$Builder"));
  1113. static jmethodID constructor = env->GetMethodID (requestBuilderClass, "<init>", "(I)V");
  1114. static jmethodID buildMethod = env->GetMethodID (requestBuilderClass, "build", "()Landroid/media/AudioFocusRequest;");
  1115. static jmethodID setAudioAttributesMethod = env->GetMethodID (requestBuilderClass, "setAudioAttributes",
  1116. "(Landroid/media/AudioAttributes;)Landroid/media/AudioFocusRequest$Builder;");
  1117. static jmethodID setOnAudioFocusChangeListenerMethod = env->GetMethodID (requestBuilderClass, "setOnAudioFocusChangeListener",
  1118. "(Landroid/media/AudioManager$OnAudioFocusChangeListener;)Landroid/media/AudioFocusRequest$Builder;");
  1119. static constexpr jint audioFocusGain = 1;
  1120. auto requestBuilder = LocalRef<jobject> (env->NewObject (requestBuilderClass, constructor, audioFocusGain));
  1121. LocalRef<jobject> (env->CallObjectMethod (requestBuilder, setAudioAttributesMethod, audioAttributes.get()));
  1122. LocalRef<jobject> (env->CallObjectMethod (requestBuilder, setOnAudioFocusChangeListenerMethod, nativeAudioFocusChangeListener.get()));
  1123. return env->CallObjectMethod (requestBuilder, buildMethod);
  1124. }
  1125. void requestAudioFocus()
  1126. {
  1127. static constexpr jint audioFocusGain = 1;
  1128. static constexpr jint streamMusic = 3;
  1129. static constexpr jint audioFocusRequestGranted = 1;
  1130. jint result = audioFocusRequestGranted;
  1131. if (sdkVersion >= 26)
  1132. {
  1133. static jmethodID requestAudioFocusMethod = getEnv()->GetMethodID (AndroidAudioManager, "requestAudioFocus",
  1134. "(Landroid/media/AudioFocusRequest;)I");
  1135. result = getEnv()->CallIntMethod (audioManager, requestAudioFocusMethod, audioFocusRequest.get());
  1136. }
  1137. else
  1138. {
  1139. result = getEnv()->CallIntMethod (audioManager, AndroidAudioManager.requestAudioFocus,
  1140. nativeAudioFocusChangeListener.get(), streamMusic, audioFocusGain);
  1141. }
  1142. hasAudioFocus = result == audioFocusRequestGranted;
  1143. }
  1144. void abandonAudioFocus()
  1145. {
  1146. if (! hasAudioFocus)
  1147. return;
  1148. static constexpr jint audioFocusRequestGranted = 1;
  1149. jint result = audioFocusRequestGranted;
  1150. if (sdkVersion >= 26)
  1151. {
  1152. static jmethodID abandonAudioFocusMethod = getEnv()->GetMethodID (AndroidAudioManager, "abandonAudioFocusRequest",
  1153. "(Landroid/media/AudioFocusRequest;)I");
  1154. result = getEnv()->CallIntMethod (audioManager, abandonAudioFocusMethod, audioFocusRequest.get());
  1155. }
  1156. else
  1157. {
  1158. result = getEnv()->CallIntMethod (audioManager, AndroidAudioManager.abandonAudioFocus,
  1159. nativeAudioFocusChangeListener.get());
  1160. }
  1161. // NB: granted in this case means "granted to change the focus to abandoned"...
  1162. hasAudioFocus = result != audioFocusRequestGranted;
  1163. }
  1164. void onAudioFocusChange (int changeType) override
  1165. {
  1166. static constexpr jint audioFocusGain = 1;
  1167. if (changeType == audioFocusGain)
  1168. JUCE_VIDEO_LOG ("Audio focus gained");
  1169. else
  1170. JUCE_VIDEO_LOG ("Audio focus lost");
  1171. if (changeType != audioFocusGain)
  1172. {
  1173. if (isPlaying())
  1174. {
  1175. JUCE_VIDEO_LOG ("Received a request to abandon audio focus. Stopping playback...");
  1176. stop();
  1177. }
  1178. abandonAudioFocus();
  1179. }
  1180. }
  1181. //==============================================================================
  1182. void playbackStarted()
  1183. {
  1184. owner.playbackStarted();
  1185. }
  1186. void playbackStopped()
  1187. {
  1188. owner.playbackStopped();
  1189. }
  1190. //==============================================================================
  1191. void resetState()
  1192. {
  1193. usesBuffering = false;
  1194. bufferedRegions.clear();
  1195. playerBufferingInProgress = false;
  1196. pendingSeekRequest = false;
  1197. playSpeedMult = 1.0;
  1198. hasAudioFocus = false;
  1199. }
  1200. //==============================================================================
  1201. static jobject getAudioAttributes()
  1202. {
  1203. auto* env = getEnv();
  1204. auto audioAttribsBuilder = LocalRef<jobject> (env->NewObject (AndroidAudioAttributesBuilder,
  1205. AndroidAudioAttributesBuilder.constructor));
  1206. static constexpr jint contentTypeMovie = 3;
  1207. static constexpr jint usageMedia = 1;
  1208. LocalRef<jobject> (env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.setContentType, contentTypeMovie));
  1209. LocalRef<jobject> (env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.setUsage, usageMedia));
  1210. return env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.build);
  1211. }
  1212. friend void juce_mediaSessionPause (int64);
  1213. friend void juce_mediaSessionPlay (int64);
  1214. friend void juce_mediaSessionPlayFromMediaId (int64, void*, void*);
  1215. friend void juce_mediaSessionSeekTo (int64, int64);
  1216. friend void juce_mediaSessionStop (int64);
  1217. friend void juce_mediaControllerAudioInfoChanged (int64, void*);
  1218. friend void juce_mediaControllerMetadataChanged (int64, void*);
  1219. friend void juce_mediaControllerPlaybackStateChanged (int64, void*);
  1220. friend void juce_mediaControllerSessionDestroyed (int64);
  1221. };
  1222. #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
  1223. //==============================================================================
  1224. class SystemVolumeListener
  1225. {
  1226. public:
  1227. SystemVolumeListener (Pimpl& ownerToUse)
  1228. : owner (ownerToUse),
  1229. nativeObserver (LocalRef<jobject> (getEnv()->NewObject (SystemVolumeObserver,
  1230. SystemVolumeObserver.constructor,
  1231. android.activity.get(),
  1232. android.activity.get(),
  1233. reinterpret_cast<jlong> (this))))
  1234. {
  1235. setEnabled (true);
  1236. }
  1237. ~SystemVolumeListener()
  1238. {
  1239. setEnabled (false);
  1240. }
  1241. void setEnabled (bool shouldBeEnabled)
  1242. {
  1243. getEnv()->CallVoidMethod (nativeObserver, SystemVolumeObserver.setEnabled, shouldBeEnabled);
  1244. // Send first notification instantly to ensure sync.
  1245. if (shouldBeEnabled)
  1246. systemVolumeChanged();
  1247. }
  1248. private:
  1249. Pimpl& owner;
  1250. GlobalRef nativeObserver;
  1251. void systemVolumeChanged()
  1252. {
  1253. WeakReference<SystemVolumeListener> weakThis (this);
  1254. MessageManager::callAsync ([weakThis]() mutable
  1255. {
  1256. if (weakThis == nullptr)
  1257. return;
  1258. if (weakThis->owner.owner.onGlobalMediaVolumeChanged != nullptr)
  1259. weakThis->owner.owner.onGlobalMediaVolumeChanged();
  1260. });
  1261. }
  1262. friend void juce_mediaSessionSystemVolumeChanged (int64);
  1263. JUCE_DECLARE_WEAK_REFERENCEABLE (SystemVolumeListener)
  1264. };
  1265. #endif
  1266. //==============================================================================
  1267. VideoComponent& owner;
  1268. MediaSession mediaSession;
  1269. AppPausedResumedListener appPausedResumedListener;
  1270. GlobalRef appPausedResumedListenerNative;
  1271. #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
  1272. SystemVolumeListener systemVolumeListener;
  1273. #endif
  1274. std::function<void (const URL&, Result)> loadFinishedCallback;
  1275. bool wasOpen = false;
  1276. //==============================================================================
  1277. void loadFinished()
  1278. {
  1279. owner.resized();
  1280. if (loadFinishedCallback != nullptr)
  1281. {
  1282. loadFinishedCallback (currentURL, Result::ok());
  1283. loadFinishedCallback = nullptr;
  1284. }
  1285. }
  1286. void closeVideoFinished()
  1287. {
  1288. owner.resized();
  1289. }
  1290. void errorOccurred (const String& errorMessage)
  1291. {
  1292. if (owner.onErrorOccurred != nullptr)
  1293. owner.onErrorOccurred (errorMessage);
  1294. }
  1295. void playbackStarted()
  1296. {
  1297. if (owner.onPlaybackStarted != nullptr)
  1298. owner.onPlaybackStarted();
  1299. }
  1300. void playbackStopped()
  1301. {
  1302. if (owner.onPlaybackStopped != nullptr)
  1303. owner.onPlaybackStopped();
  1304. }
  1305. void videoSurfaceChanged (jobject surfaceHolder)
  1306. {
  1307. mediaSession.setDisplay (surfaceHolder);
  1308. }
  1309. void videoSurfaceDestroyed (jobject surfaceHolder)
  1310. {
  1311. mediaSession.setDisplay (nullptr);
  1312. }
  1313. //==============================================================================
  1314. void appPaused() override
  1315. {
  1316. wasOpen = isOpen();
  1317. if (! wasOpen)
  1318. return;
  1319. JUCE_VIDEO_LOG ("App paused, releasing media player...");
  1320. mediaSession.storeState();
  1321. mediaSession.closeVideo();
  1322. #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
  1323. systemVolumeListener.setEnabled (false);
  1324. #endif
  1325. }
  1326. void appResumed() override
  1327. {
  1328. if (! wasOpen)
  1329. return;
  1330. JUCE_VIDEO_LOG ("App resumed, restoring media player...");
  1331. loadAsync (currentURL, [this](const URL&, Result r)
  1332. {
  1333. if (r.wasOk())
  1334. mediaSession.restoreState();
  1335. });
  1336. #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
  1337. systemVolumeListener.setEnabled (true);
  1338. #endif
  1339. }
  1340. //==============================================================================
  1341. friend void juce_surfaceChangedNativeVideo (int64, void*);
  1342. friend void juce_surfaceDestroyedNativeVideo (int64, void*);
  1343. friend void juce_mediaSessionPause (int64);
  1344. friend void juce_mediaSessionPlay (int64);
  1345. friend void juce_mediaSessionPlayFromMediaId (int64, void*, void*);
  1346. friend void juce_mediaSessionSeekTo (int64, int64);
  1347. friend void juce_mediaSessionStop (int64);
  1348. friend void juce_mediaControllerAudioInfoChanged (int64, void*);
  1349. friend void juce_mediaControllerMetadataChanged (int64, void*);
  1350. friend void juce_mediaControllerPlaybackStateChanged (int64, void*);
  1351. friend void juce_mediaControllerSessionDestroyed (int64);
  1352. friend void juce_mediaSessionSystemVolumeChanged (int64);
  1353. #endif
  1354. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  1355. };
  1356. #if __ANDROID_API__ >= 21
  1357. //==============================================================================
  1358. void juce_surfaceChangedNativeVideo (int64 host, void* surfaceHolder)
  1359. {
  1360. reinterpret_cast<VideoComponent::Pimpl*> (host)->videoSurfaceChanged (static_cast<jobject> (surfaceHolder));
  1361. }
  1362. void juce_surfaceDestroyedNativeVideo (int64 host, void* surfaceHolder)
  1363. {
  1364. reinterpret_cast<VideoComponent::Pimpl*> (host)->videoSurfaceDestroyed (static_cast<jobject> (surfaceHolder));
  1365. }
  1366. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), dispatchDrawNativeVideo,
  1367. void, (JNIEnv* env, jobject nativeView, jlong host, jobject canvas))
  1368. {
  1369. ignoreUnused (nativeView, host, canvas);
  1370. setEnv (env);
  1371. }
  1372. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), surfaceChangedNativeVideo,
  1373. void, (JNIEnv* env, jobject nativeView, jlong host, jobject holder, jint format, jint width, jint height))
  1374. {
  1375. ignoreUnused (nativeView, format, width, height);
  1376. setEnv (env);
  1377. JUCE_VIDEO_LOG ("video surface changed");
  1378. juce_surfaceChangedNativeVideo (host, holder);
  1379. }
  1380. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), surfaceCreatedNativeVideo,
  1381. void, (JNIEnv* env, jobject nativeView, jlong host, jobject holder))
  1382. {
  1383. ignoreUnused (nativeView, host, holder);
  1384. setEnv (env);
  1385. JUCE_VIDEO_LOG ("video surface created");
  1386. }
  1387. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), surfaceDestroyedNativeVideo,
  1388. void, (JNIEnv* env, jobject nativeView, jlong host, jobject holder))
  1389. {
  1390. ignoreUnused (nativeView, host, holder);
  1391. setEnv (env);
  1392. JUCE_VIDEO_LOG ("video surface destroyed");
  1393. juce_surfaceDestroyedNativeVideo (host, holder);
  1394. }
  1395. //==============================================================================
  1396. void juce_mediaSessionPause (int64 host)
  1397. {
  1398. reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host)->pauseCallback();
  1399. }
  1400. void juce_mediaSessionPlay (int64 host)
  1401. {
  1402. reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host)->playCallback();
  1403. }
  1404. void juce_mediaSessionPlayFromMediaId (int64 host, void* mediaId, void* extras)
  1405. {
  1406. reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host)->playFromMediaIdCallback ((jstring) mediaId, (jobject) extras);
  1407. }
  1408. void juce_mediaSessionSeekTo (int64 host, int64 pos)
  1409. {
  1410. reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host)->seekToCallback (pos);
  1411. }
  1412. void juce_mediaSessionStop (int64 host)
  1413. {
  1414. reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host)->stopCallback();
  1415. }
  1416. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionPause,
  1417. void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host))
  1418. {
  1419. setEnv (env);
  1420. juce_mediaSessionPause (host);
  1421. }
  1422. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionPlay,
  1423. void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host))
  1424. {
  1425. setEnv (env);
  1426. juce_mediaSessionPlay (host);
  1427. }
  1428. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionPlayFromMediaId,
  1429. void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host, jobject mediaId, jobject extras))
  1430. {
  1431. setEnv (env);
  1432. juce_mediaSessionPlayFromMediaId (host, mediaId, extras);
  1433. }
  1434. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionSeekTo,
  1435. void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host, jlong pos))
  1436. {
  1437. setEnv (env);
  1438. juce_mediaSessionSeekTo (host, pos);
  1439. }
  1440. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionStop,
  1441. void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host))
  1442. {
  1443. setEnv (env);
  1444. juce_mediaSessionStop (host);
  1445. }
  1446. //==============================================================================
  1447. void juce_mediaControllerAudioInfoChanged (int64 host, void* info)
  1448. {
  1449. reinterpret_cast<VideoComponent::Pimpl::MediaSession::Controller*> (host)->audioInfoChanged ((jobject) info);
  1450. }
  1451. void juce_mediaControllerMetadataChanged (int64 host, void* metadata)
  1452. {
  1453. reinterpret_cast<VideoComponent::Pimpl::MediaSession::Controller*> (host)->metadataChanged ((jobject) metadata);
  1454. }
  1455. void juce_mediaControllerPlaybackStateChanged (int64 host, void* state)
  1456. {
  1457. reinterpret_cast<VideoComponent::Pimpl::MediaSession::Controller*> (host)->playbackStateChanged ((jobject) state);
  1458. }
  1459. void juce_mediaControllerSessionDestroyed (int64 host)
  1460. {
  1461. reinterpret_cast<VideoComponent::Pimpl::MediaSession::Controller*> (host)->sessionDestroyed();
  1462. }
  1463. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaControllerCallback), mediaControllerAudioInfoChanged,
  1464. void, (JNIEnv* env, jobject /*mediaControllerCallback*/, jlong host, jobject playbackInfo))
  1465. {
  1466. setEnv (env);
  1467. juce_mediaControllerAudioInfoChanged (host, playbackInfo);
  1468. }
  1469. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaControllerCallback), mediaControllerMetadataChanged,
  1470. void, (JNIEnv* env, jobject /*mediaControllerCallback*/, jlong host, jobject metadata))
  1471. {
  1472. setEnv (env);
  1473. juce_mediaControllerMetadataChanged (host, metadata);
  1474. }
  1475. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaControllerCallback), mediaControllerPlaybackStateChanged,
  1476. void, (JNIEnv* env, jobject /*mediaControllerCallback*/, jlong host, jobject playbackState))
  1477. {
  1478. setEnv (env);
  1479. juce_mediaControllerPlaybackStateChanged (host, playbackState);
  1480. }
  1481. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaControllerCallback), mediaControllerSessionDestroyed,
  1482. void, (JNIEnv* env, jobject /*mediaControllerCallback*/, jlong host))
  1483. {
  1484. setEnv (env);
  1485. juce_mediaControllerSessionDestroyed (host);
  1486. }
  1487. //==============================================================================
  1488. void juce_mediaSessionSystemVolumeChanged (int64 host)
  1489. {
  1490. #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
  1491. reinterpret_cast<VideoComponent::Pimpl::SystemVolumeListener*> (host)->systemVolumeChanged();
  1492. #else
  1493. ignoreUnused (host);
  1494. #endif
  1495. }
  1496. JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024SystemVolumeObserver), mediaSessionSystemVolumeChanged,
  1497. void, (JNIEnv* env, jobject /*systemSettingsObserver*/, jlong host))
  1498. {
  1499. setEnv (env);
  1500. juce_mediaSessionSystemVolumeChanged (host);
  1501. }
  1502. //==============================================================================
  1503. constexpr VideoComponent::Pimpl::MediaSession::Player::StateInfo VideoComponent::Pimpl::MediaSession::Player::stateInfos[];
  1504. #endif