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.

1801 lines
82KB

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