1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-13 00:04:19 +00:00
JUCE/modules/juce_video/native/juce_android_Video.h
2019-06-06 16:50:12 +01:00

1855 lines
84 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2018 - ROLI Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
//==============================================================================
// This byte-code is generated from:
//
// native/java/com/roli/juce/MediaControllerCallback.java
// native/java/com/roli/juce/MediaSessionCallback.java
// native/java/com/roli/juce/SystemVolumeObserver.java
//
// files with min sdk version 21
// See juce_core/native/java/README.txt on how to generate this byte-code.
static const unsigned char MediaSessionByteCode[] =
{31,139,8,8,235,44,227,91,0,3,77,101,100,105,97,83,101,115,115,105,111,110,46,100,101,120,0,149,152,127,108,28,71,21,199,
223,236,253,244,157,125,183,119,254,21,55,110,226,52,206,15,151,218,119,36,129,58,57,215,248,7,54,113,56,59,110,206,118,
131,41,13,107,223,214,222,228,188,123,236,238,185,177,80,69,85,165,82,145,250,7,34,32,85,72,69,65,2,137,72,1,84,250,15,
63,42,26,9,81,33,129,10,149,42,17,4,130,34,1,165,168,18,63,148,72,21,164,130,239,204,206,158,247,46,14,42,151,124,246,
189,121,239,205,236,155,55,51,231,219,45,235,23,19,249,163,31,162,82,113,228,219,95,188,252,253,171,127,206,39,23,143,
254,97,228,234,196,107,173,223,121,242,213,183,126,120,41,67,84,37,162,139,75,199,178,36,63,215,85,162,85,242,236,221,
224,54,243,228,89,133,40,2,89,9,17,29,134,252,18,36,254,211,27,97,162,249,86,162,99,112,62,22,39,210,64,25,84,128,13,62,
7,158,6,207,128,23,192,117,240,27,112,11,244,180,16,13,131,243,224,10,248,5,248,15,56,152,32,58,14,230,64,25,60,11,94,4,
191,2,239,129,222,36,81,30,140,131,18,248,50,120,9,188,6,222,6,239,128,191,131,91,224,95,128,144,95,24,180,128,20,232,0,
187,192,189,160,15,244,131,33,240,16,208,193,6,184,4,158,7,95,5,87,193,183,192,75,224,85,240,22,136,181,17,61,0,102,193,
18,48,192,103,193,243,224,26,248,17,248,61,120,27,188,7,98,41,162,46,112,63,120,8,76,131,57,160,129,45,240,121,112,25,
188,0,94,4,175,128,215,193,31,193,59,224,31,224,93,144,73,35,111,48,0,78,128,105,48,11,74,224,83,224,34,120,50,237,173,
85,12,160,204,132,146,18,202,70,178,20,132,116,8,67,17,194,8,91,128,248,226,183,131,14,208,9,186,228,218,239,2,61,224,30,
208,11,246,129,7,64,20,40,114,191,112,61,20,208,219,165,190,79,142,197,63,247,73,253,58,18,218,47,245,159,66,239,151,250,
47,3,250,141,128,254,38,244,3,82,255,11,244,67,82,191,9,253,160,212,111,7,244,112,124,91,111,13,232,29,208,7,164,222,27,
176,31,142,123,123,153,235,249,128,125,24,250,253,82,31,131,254,1,169,159,132,62,40,235,51,31,208,207,198,121,61,147,20,
150,53,61,6,84,41,51,196,104,72,214,153,183,25,254,29,17,245,75,211,89,33,147,244,105,89,195,163,196,215,77,21,181,141,
193,162,136,181,243,100,18,215,115,98,252,176,104,167,17,113,92,200,40,157,16,50,78,5,233,31,17,178,141,166,132,108,165,
25,33,83,52,43,100,150,230,132,12,209,195,34,79,111,60,46,71,165,252,136,144,45,52,38,100,132,198,165,125,66,200,16,125,
76,200,78,58,41,219,167,164,252,184,144,25,42,202,246,105,217,111,94,182,207,72,89,146,246,5,217,94,20,245,73,136,60,50,
176,79,10,217,65,211,66,182,211,178,216,87,93,244,168,172,47,35,111,111,243,207,30,240,77,52,6,84,175,173,74,127,139,244,
239,149,242,19,210,159,145,254,136,180,247,73,249,13,233,231,190,30,232,97,228,197,245,75,105,111,95,87,213,8,218,203,89,
134,58,242,138,241,181,126,78,250,74,125,140,170,99,45,164,124,112,219,119,185,193,151,104,240,125,165,193,151,20,62,69,
158,168,175,165,189,28,82,98,79,48,177,47,174,5,227,243,173,164,176,84,61,191,239,214,243,139,138,252,20,244,100,194,67,
244,131,134,177,188,241,95,73,123,103,55,171,166,235,227,255,164,62,190,130,241,219,197,248,190,239,103,13,190,14,225,
139,192,195,235,247,122,208,183,208,41,230,225,223,251,215,129,123,71,101,252,239,252,248,60,226,23,187,40,52,190,29,255,
167,64,188,111,251,107,67,254,222,183,204,223,118,168,207,173,134,28,187,69,142,81,185,175,255,157,246,246,66,150,170,
125,220,242,201,253,33,90,238,15,139,254,17,57,106,68,245,190,3,77,53,134,94,173,116,15,203,147,217,119,8,123,41,65,195,
216,105,37,244,172,230,251,229,220,189,53,76,169,222,119,220,112,184,157,22,242,33,244,229,217,181,66,134,132,92,65,146,
217,172,57,198,72,61,146,218,49,194,68,182,121,58,252,79,170,127,24,109,239,97,254,247,212,111,43,13,50,42,101,92,202,
132,172,76,187,248,110,87,228,56,254,30,103,228,213,134,145,247,183,128,145,247,183,128,247,73,226,76,249,49,109,178,47,
31,133,183,59,164,189,75,218,187,16,205,219,97,105,111,39,118,152,216,8,69,71,12,211,112,71,41,60,90,24,88,162,228,228,
233,185,133,169,185,133,115,139,103,102,136,157,34,86,164,174,162,102,150,109,203,40,231,180,106,53,55,190,234,26,155,
134,187,85,160,125,117,251,170,101,186,186,233,230,38,61,121,70,119,172,202,166,110,23,168,103,231,144,139,110,129,118,
221,225,154,17,162,64,247,213,61,101,205,213,86,52,71,247,7,62,189,226,232,182,24,248,222,122,204,134,94,54,180,220,44,
191,206,234,174,198,187,20,40,223,228,118,116,199,49,44,211,11,227,131,217,86,165,162,219,253,147,90,165,178,162,173,94,
40,208,177,247,217,99,190,162,109,241,30,51,230,227,86,129,6,255,87,175,146,215,8,220,100,232,253,132,63,92,211,107,250,
140,171,111,20,232,192,93,226,253,36,74,174,230,234,5,202,212,195,76,221,205,45,218,70,129,218,235,38,203,201,77,212,204,
114,5,113,29,65,227,73,141,27,237,96,185,171,182,181,105,148,117,59,87,210,93,215,48,215,156,254,210,150,35,18,57,84,92,
181,54,114,40,129,145,59,95,91,213,155,171,178,61,195,254,157,2,229,204,238,30,229,221,102,201,170,212,54,244,224,26,151,
181,202,166,113,33,167,153,166,133,153,242,153,151,140,53,83,115,107,54,102,147,45,158,215,54,181,92,69,51,215,114,37,
215,70,190,5,74,123,182,154,107,84,114,69,195,113,73,109,50,20,104,172,201,50,242,255,173,201,104,129,122,239,50,251,33,
62,48,245,236,52,101,233,218,105,158,158,139,45,145,178,116,138,66,75,167,196,165,72,97,92,138,176,21,209,44,114,91,145,
219,138,203,220,182,140,38,20,182,76,10,68,92,147,39,146,218,124,109,193,90,116,116,58,40,15,214,137,28,38,229,45,104,
206,17,25,228,54,69,10,231,54,106,142,177,74,81,156,71,91,115,168,115,77,119,199,171,213,138,177,42,106,45,79,42,101,97,
110,58,216,20,94,183,80,220,22,126,245,238,165,24,101,10,27,56,19,148,17,101,156,168,185,174,101,122,39,154,246,110,52,
22,108,188,86,54,44,126,128,38,215,177,120,122,153,246,52,5,248,7,217,247,239,111,242,55,236,127,63,168,249,46,114,9,62,
170,59,48,108,33,32,38,2,102,202,50,69,233,159,215,106,200,95,109,48,97,120,218,221,108,153,182,173,141,89,57,66,54,232,
44,233,250,133,5,171,113,136,146,107,85,101,70,190,37,176,246,126,202,241,13,57,81,202,90,230,29,85,137,99,13,132,74,109,
150,57,173,57,238,180,101,63,161,217,101,30,60,187,93,228,169,77,94,227,12,183,53,86,45,230,79,46,42,167,148,185,115,34,
93,158,233,142,98,166,44,83,236,249,64,46,103,244,39,12,83,104,114,194,89,174,53,213,184,21,182,11,70,117,193,154,227,91,
71,245,91,243,182,190,105,88,53,135,167,224,89,234,39,138,103,39,170,21,170,90,14,69,62,195,237,212,109,235,107,56,155,
56,91,141,95,252,148,112,244,202,227,178,42,208,221,41,83,91,169,224,190,105,103,221,170,85,202,19,186,111,72,56,174,102,
187,206,35,134,187,78,17,135,79,141,194,238,186,225,80,220,181,188,111,11,234,169,153,119,187,77,168,102,27,20,217,212,
42,200,37,203,62,28,75,141,12,82,15,27,142,165,150,105,47,155,226,226,0,123,140,139,1,70,97,118,182,115,15,30,14,135,136,
27,58,100,244,131,252,121,113,132,29,199,117,183,112,236,19,215,67,202,232,17,46,199,184,123,144,61,194,245,73,174,79,
243,203,12,123,16,215,163,34,50,165,228,49,82,113,112,144,246,42,203,43,177,212,143,151,169,139,61,26,75,13,50,53,211,75,
74,150,173,244,70,186,169,123,119,55,235,86,187,241,59,42,18,97,74,252,169,167,194,47,39,216,211,10,5,105,99,55,18,140,
221,4,95,72,50,118,13,92,199,47,129,104,82,81,178,136,255,109,178,57,158,179,139,189,139,184,231,90,25,187,2,94,6,55,192,
77,240,108,27,99,95,7,223,3,63,7,111,182,145,162,40,33,133,41,251,49,218,237,54,222,251,0,187,146,98,244,70,138,212,166,
223,69,92,250,239,38,248,111,18,255,253,68,136,182,223,81,132,105,251,61,5,151,254,187,10,255,57,149,191,175,8,169,222,
88,226,55,86,159,247,108,55,2,61,218,231,217,249,243,4,83,189,103,89,254,204,170,244,121,247,229,239,55,66,50,158,63,23,
132,251,182,159,23,248,131,4,31,95,60,135,200,241,249,187,148,255,2,145,38,223,176,132,17,0,0};
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (getPlaybackInfo, "getPlaybackInfo", "()Landroid/media/session/MediaController$PlaybackInfo;") \
METHOD (getPlaybackState, "getPlaybackState", "()Landroid/media/session/PlaybackState;") \
METHOD (getTransportControls, "getTransportControls", "()Landroid/media/session/MediaController$TransportControls;") \
METHOD (registerCallback, "registerCallback", "(Landroid/media/session/MediaController$Callback;)V") \
METHOD (setVolumeTo, "setVolumeTo", "(II)V") \
METHOD (unregisterCallback, "unregisterCallback", "(Landroid/media/session/MediaController$Callback;)V")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaController, "android/media/session/MediaController", 21)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (getAudioAttributes, "getAudioAttributes", "()Landroid/media/AudioAttributes;") \
METHOD (getCurrentVolume, "getCurrentVolume", "()I") \
METHOD (getMaxVolume, "getMaxVolume", "()I")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaControllerPlaybackInfo, "android/media/session/MediaController$PlaybackInfo", 21)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (pause, "pause", "()V") \
METHOD (play, "play", "()V") \
METHOD (playFromMediaId, "playFromMediaId", "(Ljava/lang/String;Landroid/os/Bundle;)V") \
METHOD (seekTo, "seekTo", "(J)V") \
METHOD (stop, "stop", "()V")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaControllerTransportControls, "android/media/session/MediaController$TransportControls", 21)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "()V") \
METHOD (getCurrentPosition, "getCurrentPosition", "()I") \
METHOD (getDuration, "getDuration", "()I") \
METHOD (getPlaybackParams, "getPlaybackParams", "()Landroid/media/PlaybackParams;") \
METHOD (getVideoHeight, "getVideoHeight", "()I") \
METHOD (getVideoWidth, "getVideoWidth", "()I") \
METHOD (isPlaying, "isPlaying", "()Z") \
METHOD (pause, "pause", "()V") \
METHOD (prepareAsync, "prepareAsync", "()V") \
METHOD (release, "release", "()V") \
METHOD (seekTo, "seekTo", "(I)V") \
METHOD (setAudioAttributes, "setAudioAttributes", "(Landroid/media/AudioAttributes;)V") \
METHOD (setDataSource, "setDataSource", "(Landroid/content/Context;Landroid/net/Uri;)V") \
METHOD (setDisplay, "setDisplay", "(Landroid/view/SurfaceHolder;)V") \
METHOD (setOnBufferingUpdateListener, "setOnBufferingUpdateListener", "(Landroid/media/MediaPlayer$OnBufferingUpdateListener;)V") \
METHOD (setOnCompletionListener, "setOnCompletionListener", "(Landroid/media/MediaPlayer$OnCompletionListener;)V") \
METHOD (setOnErrorListener, "setOnErrorListener", "(Landroid/media/MediaPlayer$OnErrorListener;)V") \
METHOD (setOnInfoListener, "setOnInfoListener", "(Landroid/media/MediaPlayer$OnInfoListener;)V") \
METHOD (setOnPreparedListener, "setOnPreparedListener", "(Landroid/media/MediaPlayer$OnPreparedListener;)V") \
METHOD (setOnSeekCompleteListener, "setOnSeekCompleteListener", "(Landroid/media/MediaPlayer$OnSeekCompleteListener;)V") \
METHOD (setPlaybackParams, "setPlaybackParams", "(Landroid/media/PlaybackParams;)V") \
METHOD (setVolume, "setVolume", "(FF)V") \
METHOD (start, "start", "()V") \
METHOD (stop, "stop", "()V")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaPlayer, "android/media/MediaPlayer", 21)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "(Landroid/content/Context;Ljava/lang/String;)V") \
METHOD (getController, "getController", "()Landroid/media/session/MediaController;") \
METHOD (release, "release", "()V") \
METHOD (setActive, "setActive", "(Z)V") \
METHOD (setCallback, "setCallback", "(Landroid/media/session/MediaSession$Callback;)V") \
METHOD (setFlags, "setFlags", "(I)V") \
METHOD (setMediaButtonReceiver, "setMediaButtonReceiver", "(Landroid/app/PendingIntent;)V") \
METHOD (setMetadata, "setMetadata", "(Landroid/media/MediaMetadata;)V") \
METHOD (setPlaybackState, "setPlaybackState", "(Landroid/media/session/PlaybackState;)V") \
METHOD (setPlaybackToLocal, "setPlaybackToLocal", "(Landroid/media/AudioAttributes;)V")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaSession, "android/media/session/MediaSession", 21)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (build, "build", "()Landroid/media/MediaMetadata;") \
METHOD (constructor, "<init>", "()V") \
METHOD (putLong, "putLong", "(Ljava/lang/String;J)Landroid/media/MediaMetadata$Builder;")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaMetadataBuilder, "android/media/MediaMetadata$Builder", 21)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (getSpeed, "getSpeed", "()F") \
METHOD (setSpeed, "setSpeed", "(F)Landroid/media/PlaybackParams;")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidPlaybackParams, "android/media/PlaybackParams", 21)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (getActions, "getActions", "()J") \
METHOD (getErrorMessage, "getErrorMessage", "()Ljava/lang/CharSequence;") \
METHOD (getPlaybackSpeed, "getPlaybackSpeed", "()F") \
METHOD (getPosition, "getPosition", "()J") \
METHOD (getState, "getState", "()I")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidPlaybackState, "android/media/session/PlaybackState", 21)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (build, "build", "()Landroid/media/session/PlaybackState;") \
METHOD (constructor, "<init>", "()V") \
METHOD (setActions, "setActions", "(J)Landroid/media/session/PlaybackState$Builder;") \
METHOD (setErrorMessage, "setErrorMessage", "(Ljava/lang/CharSequence;)Landroid/media/session/PlaybackState$Builder;") \
METHOD (setState, "setState", "(IJF)Landroid/media/session/PlaybackState$Builder;")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidPlaybackStateBuilder, "android/media/session/PlaybackState$Builder", 21)
#undef JNI_CLASS_MEMBERS
//==============================================================================
class MediaPlayerListener : public AndroidInterfaceImplementer
{
public:
struct Owner
{
virtual ~Owner() {}
virtual void onPrepared (LocalRef<jobject>& mediaPlayer) = 0;
virtual void onBufferingUpdate (LocalRef<jobject>& mediaPlayer, int progress) = 0;
virtual void onSeekComplete (LocalRef<jobject>& mediaPlayer) = 0;
virtual void onCompletion (LocalRef<jobject>& mediaPlayer) = 0;
virtual bool onInfo (LocalRef<jobject>& mediaPlayer, int what, int extra) = 0;
virtual bool onError (LocalRef<jobject>& mediaPlayer, int what, int extra) = 0;
};
MediaPlayerListener (Owner& ownerToUse) : owner (ownerToUse) {}
private:
Owner& owner;
jobject invoke (jobject proxy, jobject method, jobjectArray args) override
{
auto* env = getEnv();
auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
int numArgs = args != nullptr ? env->GetArrayLength (args) : 0;
if (methodName == "onPrepared" && numArgs == 1)
{
auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
owner.onPrepared (mediaPlayer);
return nullptr;
}
if (methodName == "onCompletion" && numArgs == 1)
{
auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
owner.onCompletion (mediaPlayer);
return nullptr;
}
if (methodName == "onInfo" && numArgs == 3)
{
auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
auto what = LocalRef<jobject> (env->GetObjectArrayElement (args, 1));
auto extra = LocalRef<jobject> (env->GetObjectArrayElement (args, 2));
auto whatInt = (int) env->CallIntMethod (what, JavaInteger.intValue);
auto extraInt = (int) env->CallIntMethod (extra, JavaInteger.intValue);
auto res = owner.onInfo (mediaPlayer, whatInt, extraInt);
return env->CallStaticObjectMethod (JavaBoolean, JavaBoolean.valueOf, (jboolean) res);
}
if (methodName == "onError" && numArgs == 3)
{
auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
auto what = LocalRef<jobject> (env->GetObjectArrayElement (args, 1));
auto extra = LocalRef<jobject> (env->GetObjectArrayElement (args, 2));
auto whatInt = (int) env->CallIntMethod (what, JavaInteger.intValue);
auto extraInt = (int) env->CallIntMethod (extra, JavaInteger.intValue);
auto res = owner.onError (mediaPlayer, whatInt, extraInt);
return env->CallStaticObjectMethod (JavaBoolean, JavaBoolean.valueOf, (jboolean) res);
}
if (methodName == "onSeekComplete" && numArgs == 1)
{
auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
owner.onSeekComplete (mediaPlayer);
return nullptr;
}
if (methodName == "onBufferingUpdate" && numArgs == 2)
{
auto mediaPlayer = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
auto progress = LocalRef<jobject> (env->GetObjectArrayElement (args, 1));
auto progressInt = (int) env->CallIntMethod (progress, JavaInteger.intValue);
owner.onBufferingUpdate (mediaPlayer, progressInt);
return nullptr;
}
return AndroidInterfaceImplementer::invoke (proxy, method, args);
}
};
//==============================================================================
class AudioManagerOnAudioFocusChangeListener : public AndroidInterfaceImplementer
{
public:
struct Owner
{
virtual ~Owner() {}
virtual void onAudioFocusChange (int changeType) = 0;
};
AudioManagerOnAudioFocusChangeListener (Owner& ownerToUse) : owner (ownerToUse) {}
private:
Owner& owner;
jobject invoke (jobject proxy, jobject method, jobjectArray args) override
{
auto* env = getEnv();
auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
int numArgs = args != nullptr ? env->GetArrayLength (args) : 0;
if (methodName == "onAudioFocusChange" && numArgs == 1)
{
auto changeType = LocalRef<jobject> (env->GetObjectArrayElement (args, 0));
auto changeTypeInt = (int) env->CallIntMethod (changeType, JavaInteger.intValue);
owner.onAudioFocusChange (changeTypeInt);
return nullptr;
}
return AndroidInterfaceImplementer::invoke (proxy, method, args);
}
};
//==============================================================================
struct VideoComponent::Pimpl
: public AndroidViewComponent, private ActivityLifecycleCallbacks, private SurfaceHolderCallback
{
Pimpl (VideoComponent& ownerToUse, bool)
: owner (ownerToUse),
mediaSession (*this)
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
, systemVolumeListener (*this)
#endif
{
// Video requires SDK version 21 or higher
jassert (getAndroidSDKVersion() >= 21);
setVisible (true);
auto* env = getEnv();
LocalRef<jobject> appContext (getAppContext());
if (appContext != nullptr)
{
ActivityLifecycleCallbacks* callbacks = dynamic_cast<ActivityLifecycleCallbacks*> (this);
activityLifeListener = GlobalRef (CreateJavaInterface (callbacks, "android/app/Application$ActivityLifecycleCallbacks"));
env->CallVoidMethod (appContext.get(), AndroidApplication.registerActivityLifecycleCallbacks, activityLifeListener.get());
}
{
LocalRef<jobject> surfaceView (env->NewObject (AndroidSurfaceView, AndroidSurfaceView.constructor, getAppContext().get()));
LocalRef<jobject> holder (env->CallObjectMethod (surfaceView.get(), AndroidSurfaceView.getHolder));
SurfaceHolderCallback* callbacks = dynamic_cast<SurfaceHolderCallback*> (this);
surfaceHolderCallback = GlobalRef (CreateJavaInterface (callbacks, "android/view/SurfaceHolder$Callback"));
env->CallVoidMethod (holder, AndroidSurfaceHolder.addCallback, surfaceHolderCallback.get());
setView (surfaceView.get());
}
}
~Pimpl() override
{
auto* env = getEnv();
if (surfaceHolderCallback != nullptr)
{
jobject view = reinterpret_cast<jobject> (getView());
if (view != nullptr)
{
LocalRef <jobject> holder (env->CallObjectMethod (view, AndroidSurfaceView.getHolder));
env->CallVoidMethod (holder, AndroidSurfaceHolder.removeCallback, surfaceHolderCallback.get());
SurfaceHolderCallback::clear();
surfaceHolderCallback.clear();
}
}
if (activityLifeListener != nullptr)
{
env->CallVoidMethod (getAppContext().get(), AndroidApplication.unregisterActivityLifecycleCallbacks, activityLifeListener.get());
ActivityLifecycleCallbacks::clear();
activityLifeListener.clear();
}
}
void loadAsync (const URL& url, std::function<void(const URL&, Result)> callback)
{
close();
wasOpen = false;
if (url.isEmpty())
{
jassertfalse;
return;
}
if (! url.isLocalFile())
{
if (! isPermissionDeclaredInManifest ("android.permission.INTERNET"))
{
// In order to access videos from the Internet, the Internet permission has to be specified in
// Android Manifest.
jassertfalse;
return;
}
}
currentURL = url;
jassert (callback != nullptr);
loadFinishedCallback = std::move (callback);
static constexpr jint visible = 0;
getEnv()->CallVoidMethod ((jobject) getView(), AndroidView.setVisibility, visible);
mediaSession.load (url);
}
void close()
{
if (! isOpen())
return;
mediaSession.closeVideo();
static constexpr jint invisible = 4;
getEnv()->CallVoidMethod ((jobject) getView(), AndroidView.setVisibility, invisible);
}
bool isOpen() const noexcept { return mediaSession.isVideoOpen(); }
bool isPlaying() const noexcept { return mediaSession.isPlaying(); }
void play() { mediaSession.play(); }
void stop() { mediaSession.stop(); }
void setPosition (double newPosition) { mediaSession.setPosition (newPosition); }
double getPosition() const { return mediaSession.getPosition(); }
void setSpeed (double newSpeed) { mediaSession.setSpeed (newSpeed); }
double getSpeed() const { return mediaSession.getSpeed(); }
Rectangle<int> getNativeSize() const { return mediaSession.getNativeSize(); }
double getDuration() const { return mediaSession.getDuration(); }
void setVolume (float newVolume) { mediaSession.setVolume (newVolume); }
float getVolume() const { return mediaSession.getVolume(); }
File currentFile;
URL currentURL;
private:
//==============================================================================
class MediaSession : private AudioManagerOnAudioFocusChangeListener::Owner
{
public:
MediaSession (Pimpl& ownerToUse)
: owner (ownerToUse),
sdkVersion (getAndroidSDKVersion()),
audioAttributes (getAudioAttributes()),
nativeMediaSession (LocalRef<jobject> (getEnv()->NewObject (AndroidMediaSession,
AndroidMediaSession.constructor,
getAppContext().get(),
javaString ("JuceVideoMediaSession").get()))),
mediaSessionCallback (createCallbackObject()),
playbackStateBuilder (LocalRef<jobject> (getEnv()->NewObject (AndroidPlaybackStateBuilder,
AndroidPlaybackStateBuilder.constructor))),
controller (*this, LocalRef<jobject> (getEnv()->CallObjectMethod (nativeMediaSession,
AndroidMediaSession.getController))),
player (*this),
audioManager (LocalRef<jobject> (getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService,
javaString ("audio").get()))),
audioFocusChangeListener (*this),
nativeAudioFocusChangeListener (GlobalRef (CreateJavaInterface (&audioFocusChangeListener,
"android/media/AudioManager$OnAudioFocusChangeListener"))),
audioFocusRequest (createAudioFocusRequestIfNecessary (sdkVersion, audioAttributes,
nativeAudioFocusChangeListener))
{
auto* env = getEnv();
env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setPlaybackToLocal, audioAttributes.get());
env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setMediaButtonReceiver, nullptr);
env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setCallback, mediaSessionCallback.get());
}
~MediaSession() override
{
auto* env = getEnv();
env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setCallback, nullptr);
controller.stop();
env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.release);
}
bool isVideoOpen() const { return player.isVideoOpen(); }
bool isPlaying() const { return player.isPlaying(); }
void load (const URL& url) { controller.load (url); }
void closeVideo()
{
resetState();
controller.closeVideo();
}
void setDisplay (const LocalRef<jobject>& surfaceHolder) { player.setDisplay (surfaceHolder); }
void play() { controller.play(); }
void stop() { controller.stop(); }
void setPosition (double newPosition) { controller.setPosition (newPosition); }
double getPosition() const { return controller.getPosition(); }
void setSpeed (double newSpeed)
{
playSpeedMult = newSpeed;
// Calling non 0.0 speed on a paused player would start it...
if (player.isPlaying())
{
player.setPlaySpeed (playSpeedMult);
updatePlaybackState();
}
}
double getSpeed() const { return controller.getPlaySpeed(); }
Rectangle<int> getNativeSize() const { return player.getVideoNativeSize(); }
double getDuration() const { return player.getVideoDuration() / 1000.0; }
void setVolume (float newVolume)
{
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
controller.setVolume (newVolume);
#else
player.setAudioVolume (newVolume);
#endif
}
float getVolume() const
{
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
return controller.getVolume();
#else
return player.getAudioVolume();
#endif
}
void storeState()
{
storedPlaybackState.clear();
storedPlaybackState = GlobalRef (getCurrentPlaybackState());
}
void restoreState()
{
if (storedPlaybackState.get() == nullptr)
return;
auto* env = getEnv();
auto pos = env->CallLongMethod (storedPlaybackState, AndroidPlaybackState.getPosition);
setPosition (pos / 1000.0);
setSpeed (playSpeedMult);
auto state = env->CallIntMethod (storedPlaybackState, AndroidPlaybackState.getState);
if (state != PlaybackState::STATE_NONE && state != PlaybackState::STATE_STOPPED
&& state != PlaybackState::STATE_PAUSED && state != PlaybackState::STATE_ERROR)
{
play();
}
}
private:
struct PlaybackState
{
enum
{
STATE_NONE = 0,
STATE_STOPPED = 1,
STATE_PAUSED = 2,
STATE_PLAYING = 3,
STATE_FAST_FORWARDING = 4,
STATE_REWINDING = 5,
STATE_BUFFERING = 6,
STATE_ERROR = 7,
STATE_CONNECTING = 8,
STATE_SKIPPING_TO_PREVIOUS = 9,
STATE_SKIPPING_TO_NEXT = 10,
STATE_SKIPPING_TO_QUEUE_ITEM = 11,
};
enum
{
ACTION_PAUSE = 0x2,
ACTION_PLAY = 0x4,
ACTION_PLAY_FROM_MEDIA_ID = 0x8000,
ACTION_PLAY_PAUSE = 0x200,
ACTION_SEEK_TO = 0x100,
ACTION_STOP = 0x1,
};
};
//==============================================================================
class Controller
{
public:
Controller (MediaSession& ownerToUse, const LocalRef<jobject>& nativeControllerToUse)
: owner (ownerToUse),
nativeController (GlobalRef (nativeControllerToUse)),
controllerTransportControls (LocalRef<jobject> (getEnv()->CallObjectMethod (nativeControllerToUse,
AndroidMediaController.getTransportControls))),
controllerCallback (createControllerCallbacks())
{
auto* env = getEnv();
env->CallVoidMethod (nativeController, AndroidMediaController.registerCallback, controllerCallback.get());
}
~Controller()
{
auto* env = getEnv();
env->CallVoidMethod (nativeController, AndroidMediaController.unregisterCallback, controllerCallback.get());
}
void load (const URL& url)
{
// NB: would use playFromUri, but it was only introduced in API 23...
getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.playFromMediaId,
javaString (url.toString (true)).get(), nullptr);
}
void closeVideo()
{
getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.stop);
}
void play()
{
getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.play);
}
void stop()
{
// NB: calling pause, rather than stop, because after calling stop, we would have to call load() again.
getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.pause);
}
void setPosition (double newPosition)
{
auto seekPos = static_cast<jlong> (newPosition * 1000);
getEnv()->CallVoidMethod (controllerTransportControls, AndroidMediaControllerTransportControls.seekTo, seekPos);
}
double getPosition() const
{
auto* env = getEnv();
auto playbackState = LocalRef<jobject> (env->CallObjectMethod (nativeController, AndroidMediaController.getPlaybackState));
if (playbackState != nullptr)
return env->CallLongMethod (playbackState, AndroidPlaybackState.getPosition) / 1000.0;
return 0.0;
}
double getPlaySpeed() const
{
auto* env = getEnv();
auto playbackState = LocalRef<jobject> (env->CallObjectMethod (nativeController, AndroidMediaController.getPlaybackState));
if (playbackState != nullptr)
return (double) env->CallFloatMethod (playbackState, AndroidPlaybackState.getPlaybackSpeed);
return 1.0;
}
void setVolume (float newVolume)
{
auto* env = getEnv();
auto playbackInfo = LocalRef<jobject> (env->CallObjectMethod (nativeController, AndroidMediaController.getPlaybackInfo));
auto maxVolume = env->CallIntMethod (playbackInfo, AndroidMediaControllerPlaybackInfo.getMaxVolume);
auto targetVolume = jmin (jint (maxVolume * newVolume), maxVolume);
static constexpr jint flagShowUI = 1;
env->CallVoidMethod (nativeController, AndroidMediaController.setVolumeTo, targetVolume, flagShowUI);
}
float getVolume() const
{
auto* env = getEnv();
auto playbackInfo = LocalRef<jobject> (env->CallObjectMethod (nativeController, AndroidMediaController.getPlaybackInfo));
auto maxVolume = (int) (env->CallIntMethod (playbackInfo, AndroidMediaControllerPlaybackInfo.getMaxVolume));
auto curVolume = (int) (env->CallIntMethod (playbackInfo, AndroidMediaControllerPlaybackInfo.getCurrentVolume));
return static_cast<float> (curVolume) / maxVolume;
}
private:
MediaSession& owner;
GlobalRef nativeController;
GlobalRef controllerTransportControls;
GlobalRef controllerCallback;
bool wasPlaying = false;
bool wasPaused = true;
//==============================================================================
void stateChanged (jobject playbackState)
{
JUCE_VIDEO_LOG ("MediaSessionController::playbackStateChanged()");
if (playbackState == nullptr)
return;
auto state = getEnv()->CallIntMethod (playbackState, AndroidPlaybackState.getState);
static constexpr jint statePaused = 2;
static constexpr jint statePlaying = 3;
if (wasPlaying == false && state == statePlaying)
owner.playbackStarted();
else if (wasPaused == false && state == statePaused)
owner.playbackStopped();
wasPlaying = state == statePlaying;
wasPaused = state == statePaused;
}
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "(J)V") \
CALLBACK (audioInfoChanged, "mediaControllerAudioInfoChanged", "(JLandroid/media/session/MediaController$PlaybackInfo;)V") \
CALLBACK (metadataChanged, "mediaControllerMetadataChanged", "(JLandroid/media/MediaMetadata;)V") \
CALLBACK (playbackStateChanged, "mediaControllerPlaybackStateChanged", "(JLandroid/media/session/PlaybackState;)V") \
CALLBACK (sessionDestroyed, "mediaControllerSessionDestroyed", "(J)V")
DECLARE_JNI_CLASS_WITH_BYTECODE (AndroidMediaControllerCallback, "com/roli/juce/MediaControllerCallback", 21, MediaSessionByteCode, sizeof(MediaSessionByteCode))
#undef JNI_CLASS_MEMBERS
LocalRef<jobject> createControllerCallbacks()
{
return LocalRef<jobject> (getEnv()->NewObject (AndroidMediaControllerCallback,
AndroidMediaControllerCallback.constructor,
reinterpret_cast<jlong> (this)));
}
//==============================================================================
// MediaSessionController callbacks
static void audioInfoChanged (JNIEnv*, jobject, jlong host, jobject playbackInfo)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::MediaSession::Controller*> (host))
{
ignoreUnused (playbackInfo);
JUCE_VIDEO_LOG ("MediaSessionController::audioInfoChanged()");
}
}
static void metadataChanged (JNIEnv*, jobject, jlong host, jobject metadata)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::MediaSession::Controller*> (host))
{
ignoreUnused (metadata);
JUCE_VIDEO_LOG ("MediaSessionController::metadataChanged()");
}
}
static void playbackStateChanged (JNIEnv*, jobject, jlong host, jobject state)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::MediaSession::Controller*> (host))
myself->stateChanged (state);
}
static void sessionDestroyed (JNIEnv*, jobject, jlong host)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::MediaSession::Controller*> (host))
JUCE_VIDEO_LOG ("MediaSessionController::sessionDestroyed()");
}
};
//==============================================================================
class Player : private MediaPlayerListener::Owner
{
public:
Player (MediaSession& ownerToUse)
: owner (ownerToUse),
mediaPlayerListener (*this),
nativeMediaPlayerListener (GlobalRef (CreateJavaInterface (&mediaPlayerListener,
getNativeMediaPlayerListenerInterfaces())))
{}
void setDisplay (const LocalRef<jobject>& surfaceHolder)
{
if (surfaceHolder == nullptr)
{
videoSurfaceHolder.clear();
if (nativeMediaPlayer.get() != nullptr)
getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDisplay, nullptr);
return;
}
videoSurfaceHolder = GlobalRef (surfaceHolder);
if (nativeMediaPlayer.get() != nullptr)
getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDisplay, videoSurfaceHolder.get());
}
void load (const LocalRef<jstring>& mediaId, const LocalRef<jobject>& extras)
{
ignoreUnused (extras);
closeVideo();
auto* env = getEnv();
nativeMediaPlayer = GlobalRef (LocalRef<jobject> (env->NewObject (AndroidMediaPlayer, AndroidMediaPlayer.constructor)));
currentState = State::idle;
auto uri = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, mediaId.get()));
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDataSource, getAppContext().get(), uri.get());
if (jniCheckHasExceptionOccurredAndClear())
{
owner.errorOccurred ("Could not find video under path provided (" + juceString (mediaId) + ")");
return;
}
currentState = State::initialised;
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnBufferingUpdateListener, nativeMediaPlayerListener.get());
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnCompletionListener, nativeMediaPlayerListener.get());
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnErrorListener, nativeMediaPlayerListener.get());
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnInfoListener, nativeMediaPlayerListener.get());
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnPreparedListener, nativeMediaPlayerListener.get());
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setOnSeekCompleteListener, nativeMediaPlayerListener.get());
if (videoSurfaceHolder != nullptr)
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDisplay, videoSurfaceHolder.get());
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.prepareAsync);
currentState = State::preparing;
}
void closeVideo()
{
if (nativeMediaPlayer.get() == nullptr)
return;
auto* env = getEnv();
if (getCurrentStateInfo().canCallStop)
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.stop);
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.release);
nativeMediaPlayer.clear();
currentState = State::end;
}
bool isVideoOpen() const noexcept
{
return currentState == State::prepared || currentState == State::started
|| currentState == State::paused || currentState == State::complete;
}
int getPlaybackStateFlag() const noexcept { return getCurrentStateInfo().playbackStateFlag; }
int getAllowedActions() const noexcept { return getCurrentStateInfo().allowedActions; }
jlong getVideoDuration() const
{
if (! getCurrentStateInfo().canCallGetVideoDuration)
return 0;
return getEnv()->CallIntMethod (nativeMediaPlayer, AndroidMediaPlayer.getDuration);
}
Rectangle<int> getVideoNativeSize() const
{
if (! getCurrentStateInfo().canCallGetVideoHeight)
{
jassertfalse;
return {};
}
auto* env = getEnv();
auto width = (int) env->CallIntMethod (nativeMediaPlayer, AndroidMediaPlayer.getVideoWidth);
auto height = (int) env->CallIntMethod (nativeMediaPlayer, AndroidMediaPlayer.getVideoHeight);
return Rectangle<int> (0, 0, width, height);
}
void play()
{
if (! getCurrentStateInfo().canCallStart)
{
jassertfalse;
return;
}
auto* env = getEnv();
// Perform a potentially pending volume setting
if (lastAudioVolume != std::numeric_limits<float>::min())
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setVolume, (jfloat) lastAudioVolume, (jfloat) lastAudioVolume);
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.start);
currentState = State::started;
}
void pause()
{
if (! getCurrentStateInfo().canCallPause)
{
jassertfalse;
return;
}
getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.pause);
currentState = State::paused;
}
bool isPlaying() const
{
return getCurrentStateInfo().isPlaying;
}
void setPlayPosition (jint newPositionMs)
{
if (! getCurrentStateInfo().canCallSeekTo)
{
jassertfalse;
return;
}
getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.seekTo, (jint) newPositionMs);
}
jint getPlayPosition() const
{
if (! getCurrentStateInfo().canCallGetCurrentPosition)
return 0.0;
return getEnv()->CallIntMethod (nativeMediaPlayer, AndroidMediaPlayer.getCurrentPosition);
}
void setPlaySpeed (double newSpeed)
{
if (! getCurrentStateInfo().canCallSetPlaybackParams)
{
jassertfalse;
return;
}
auto* env = getEnv();
auto playbackParams = LocalRef<jobject> (env->CallObjectMethod (nativeMediaPlayer, AndroidMediaPlayer.getPlaybackParams));
LocalRef<jobject> (env->CallObjectMethod (playbackParams, AndroidPlaybackParams.setSpeed, (jfloat) newSpeed));
env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setPlaybackParams, playbackParams.get());
if (jniCheckHasExceptionOccurredAndClear())
{
// MediaPlayer can't handle speed provided!
jassertfalse;
}
}
double getPlaySpeed() const
{
if (! getCurrentStateInfo().canCallGetPlaybackParams)
return 0.0;
auto* env = getEnv();
auto playbackParams = LocalRef<jobject> (env->CallObjectMethod (nativeMediaPlayer, AndroidMediaPlayer.getPlaybackParams));
return (double) env->CallFloatMethod (playbackParams, AndroidPlaybackParams.getSpeed);
}
void setAudioVolume (float newVolume)
{
if (! getCurrentStateInfo().canCallSetVolume)
{
jassertfalse;
return;
}
lastAudioVolume = jlimit (0.0f, 1.0f, newVolume);
if (nativeMediaPlayer.get() != nullptr)
getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setVolume, (jfloat) lastAudioVolume, (jfloat) lastAudioVolume);
}
float getAudioVolume() const
{
// There is NO getVolume() in MediaPlayer, so the value returned here can be incorrect!
return lastAudioVolume;
}
private:
//=============================================================================
struct StateInfo
{
int playbackStateFlag = 0, allowedActions = 0;
bool isPlaying, canCallGetCurrentPosition, canCallGetVideoDuration,
canCallGetVideoHeight, canCallGetVideoWidth, canCallGetPlaybackParams,
canCallPause, canCallPrepare, canCallSeekTo, canCallSetAudioAttributes,
canCallSetDataSource, canCallSetPlaybackParams, canCallSetVolume,
canCallStart, canCallStop;
};
enum class State
{
idle, initialised, preparing, prepared, started, paused, stopped, complete, error, end
};
static constexpr StateInfo stateInfos[] = {
/* idle */
{PlaybackState::STATE_NONE, PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
false, true, false, true, true, false, false, false, false, true,
true, false, true, false, false},
/* initialised */
{PlaybackState::STATE_NONE, 0, // NB: could use action prepare, but that's API 24 onwards only
false, true, false, true, true, true, false, true, false, true,
false, true, true, false, false},
/* preparing */
{PlaybackState::STATE_BUFFERING, 0,
false, false, false, false, false, true, false, false, false, false,
false, false, false, false, false},
/* prepared */
{PlaybackState::STATE_PAUSED,
PlaybackState::ACTION_PLAY | PlaybackState::ACTION_PLAY_PAUSE | PlaybackState::ACTION_PLAY_FROM_MEDIA_ID | PlaybackState::ACTION_STOP | PlaybackState::ACTION_SEEK_TO,
false, true, true, true, true, true, false, false, true, true,
false, true, true, true, true},
/* started */
{PlaybackState::STATE_PLAYING,
PlaybackState::ACTION_PAUSE | PlaybackState::ACTION_PLAY_PAUSE | PlaybackState::ACTION_SEEK_TO | PlaybackState::ACTION_STOP | PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
true, true, true, true, true, true, true, false, true, true,
false, true, true, true, true},
/* paused */
{PlaybackState::STATE_PAUSED,
PlaybackState::ACTION_PLAY | PlaybackState::ACTION_PLAY_PAUSE | PlaybackState::ACTION_SEEK_TO | PlaybackState::ACTION_STOP | PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
false, true, true, true, true, true, true, false, true, true,
false, true, true, true, true},
/* stopped */
{PlaybackState::STATE_STOPPED,
PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
false, true, true, true, true, true, false, true, false, true,
false, false, true, false, true},
/* complete */
{PlaybackState::STATE_PAUSED,
PlaybackState::ACTION_SEEK_TO | PlaybackState::ACTION_STOP | PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
false, true, true, true, true, true, true, false, true, true,
false, true, true, true, true},
/* error */
{PlaybackState::STATE_ERROR,
PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false},
/* end */
{PlaybackState::STATE_NONE,
PlaybackState::ACTION_PLAY_FROM_MEDIA_ID,
false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false}
};
StateInfo getCurrentStateInfo() const noexcept { return stateInfos[static_cast<int> (currentState)]; }
//==============================================================================
MediaSession& owner;
GlobalRef nativeMediaPlayer;
MediaPlayerListener mediaPlayerListener;
GlobalRef nativeMediaPlayerListener;
float lastAudioVolume = std::numeric_limits<float>::min();
GlobalRef videoSurfaceHolder;
State currentState = State::idle;
//==============================================================================
void onPrepared (LocalRef<jobject>& mediaPlayer) override
{
JUCE_VIDEO_LOG ("MediaPlayer::onPrepared()");
ignoreUnused (mediaPlayer);
currentState = State::prepared;
owner.playerPrepared();
}
void onBufferingUpdate (LocalRef<jobject>& mediaPlayer, int progress) override
{
ignoreUnused (mediaPlayer);
owner.playerBufferingUpdated (progress);
}
void onSeekComplete (LocalRef<jobject>& mediaPlayer) override
{
JUCE_VIDEO_LOG ("MediaPlayer::onSeekComplete()");
ignoreUnused (mediaPlayer);
owner.playerSeekCompleted();
}
void onCompletion (LocalRef<jobject>& mediaPlayer) override
{
JUCE_VIDEO_LOG ("MediaPlayer::onCompletion()");
ignoreUnused (mediaPlayer);
currentState = State::complete;
owner.playerPlaybackCompleted();
}
enum
{
MEDIA_INFO_UNKNOWN = 1,
MEDIA_INFO_VIDEO_RENDERING_START = 3,
MEDIA_INFO_VIDEO_TRACK_LAGGING = 700,
MEDIA_INFO_BUFFERING_START = 701,
MEDIA_INFO_BUFFERING_END = 702,
MEDIA_INFO_NETWORK_BANDWIDTH = 703,
MEDIA_INFO_BAD_INTERLEAVING = 800,
MEDIA_INFO_NOT_SEEKABLE = 801,
MEDIA_INFO_METADATA_UPDATE = 802,
MEDIA_INFO_AUDIO_NOT_PLAYING = 804,
MEDIA_INFO_VIDEO_NOT_PLAYING = 805,
MEDIA_INFO_UNSUPPORTED_SUBTITE = 901,
MEDIA_INFO_SUBTITLE_TIMED_OUT = 902
};
bool onInfo (LocalRef<jobject>& mediaPlayer, int what, int extra) override
{
JUCE_VIDEO_LOG ("MediaPlayer::onInfo(), infoCode: " + String (what) + " (" + infoCodeToString (what) + ")"
+ ", extraCode: " + String (extra));
ignoreUnused (mediaPlayer, extra);
if (what == MEDIA_INFO_BUFFERING_START)
owner.playerBufferingStarted();
else if (what == MEDIA_INFO_BUFFERING_END)
owner.playerBufferingEnded();
return true;
}
static String infoCodeToString (int code)
{
switch (code)
{
case MEDIA_INFO_UNKNOWN: return "Unknown";
case MEDIA_INFO_VIDEO_RENDERING_START: return "Rendering start";
case MEDIA_INFO_VIDEO_TRACK_LAGGING: return "Video track lagging";
case MEDIA_INFO_BUFFERING_START: return "Buffering start";
case MEDIA_INFO_BUFFERING_END: return "Buffering end";
case MEDIA_INFO_NETWORK_BANDWIDTH: return "Network bandwidth info available";
case MEDIA_INFO_BAD_INTERLEAVING: return "Bad interleaving";
case MEDIA_INFO_NOT_SEEKABLE: return "Video not seekable";
case MEDIA_INFO_METADATA_UPDATE: return "Metadata updated";
case MEDIA_INFO_AUDIO_NOT_PLAYING: return "Audio not playing";
case MEDIA_INFO_VIDEO_NOT_PLAYING: return "Video not playing";
case MEDIA_INFO_UNSUPPORTED_SUBTITE: return "Unsupported subtitle";
case MEDIA_INFO_SUBTITLE_TIMED_OUT: return "Subtitle timed out";
default: return "";
}
}
bool onError (LocalRef<jobject>& mediaPlayer, int what, int extra) override
{
auto errorMessage = errorCodeToString (what);
auto extraMessage = errorCodeToString (extra);
if (extraMessage.isNotEmpty())
errorMessage << ", " << extraMessage;
JUCE_VIDEO_LOG ("MediaPlayer::onError(), errorCode: " + String (what) + " (" + errorMessage + ")"
+ ", extraCode: " + String (extra) + " (" + extraMessage + ")");
ignoreUnused (mediaPlayer);
currentState = State::error;
owner.errorOccurred (errorMessage);
return true;
}
static String errorCodeToString (int code)
{
enum
{
MEDIA_ERROR_UNSUPPORTED = -1010,
MEDIA_ERROR_MALFORMED = -1007,
MEDIA_ERROR_IO = -1004,
MEDIA_ERROR_TIMED_OUT = -110,
MEDIA_ERROR_UNKNOWN = 1,
MEDIA_ERROR_SERVER_DIED = 100,
MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200
};
switch (code)
{
case MEDIA_ERROR_UNSUPPORTED: return "Unsupported bitstream";
case MEDIA_ERROR_MALFORMED: return "Malformed bitstream";
case MEDIA_ERROR_IO: return "File/Network I/O error";
case MEDIA_ERROR_TIMED_OUT: return "Timed out";
case MEDIA_ERROR_UNKNOWN: return "Unknown error";
case MEDIA_ERROR_SERVER_DIED: return "Media server died (playback restart required)";
case MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: return "Video container not valid for progressive playback";
default: return "";
}
}
//==============================================================================
static StringArray getNativeMediaPlayerListenerInterfaces()
{
#define IFPREFIX "android/media/MediaPlayer$"
return { IFPREFIX "OnCompletionListener", IFPREFIX "OnErrorListener",
IFPREFIX "OnInfoListener", IFPREFIX "OnPreparedListener",
IFPREFIX "OnBufferingUpdateListener", IFPREFIX "OnSeekCompleteListener"
};
#undef IFPREFIX
}
};
//==============================================================================
Pimpl& owner;
int sdkVersion;
GlobalRef audioAttributes;
GlobalRef nativeMediaSession;
GlobalRef mediaSessionCallback;
GlobalRef playbackStateBuilder;
Controller controller;
Player player;
GlobalRef audioManager;
AudioManagerOnAudioFocusChangeListener audioFocusChangeListener;
GlobalRef nativeAudioFocusChangeListener;
GlobalRef audioFocusRequest;
GlobalRef storedPlaybackState;
bool pendingSeekRequest = false;
bool playerBufferingInProgress = false;
bool usesBuffering = false;
SparseSet<int> bufferedRegions;
double playSpeedMult = 1.0;
bool hasAudioFocus = false;
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "(J)V") \
CALLBACK (pauseCallback, "mediaSessionPause", "(J)V") \
CALLBACK (playCallback, "mediaSessionPlay", "(J)V") \
CALLBACK (playFromMediaIdCallback, "mediaSessionPlayFromMediaId", "(JLjava/lang/String;Landroid/os/Bundle;)V") \
CALLBACK (seekToCallback, "mediaSessionSeekTo", "(JJ)V") \
CALLBACK (stopCallback, "mediaSessionStop", "(J)V")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaSessionCallback, "com/roli/juce/MediaSessionCallback", 21)
#undef JNI_CLASS_MEMBERS
LocalRef<jobject> createCallbackObject()
{
return LocalRef<jobject> (getEnv()->NewObject (AndroidMediaSessionCallback,
AndroidMediaSessionCallback.constructor,
reinterpret_cast<jlong> (this)));
}
//==============================================================================
// MediaSession callbacks
static void pauseCallback (JNIEnv*, jobject, jlong host)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host))
{
JUCE_VIDEO_LOG ("MediaSession::pauseCallback()");
myself->player.pause();
myself->updatePlaybackState();
myself->abandonAudioFocus();
}
}
static void playCallback (JNIEnv*, jobject, jlong host)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host))
{
JUCE_VIDEO_LOG ("MediaSession::playCallback()");
myself->requestAudioFocus();
if (! myself->hasAudioFocus)
{
myself->errorOccurred ("Application has been denied audio focus. Try again later.");
return;
}
getEnv()->CallVoidMethod (myself->nativeMediaSession, AndroidMediaSession.setActive, true);
myself->player.play();
myself->setSpeed (myself->playSpeedMult);
myself->updatePlaybackState();
}
}
static void playFromMediaIdCallback (JNIEnv* env, jobject, jlong host, jstring mediaId, jobject extras)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host))
{
JUCE_VIDEO_LOG ("MediaSession::playFromMediaIdCallback()");
myself->player.load (LocalRef<jstring> ((jstring) env->NewLocalRef(mediaId)), LocalRef<jobject> (env->NewLocalRef(extras)));
myself->updatePlaybackState();
}
}
static void seekToCallback (JNIEnv* /*env*/, jobject, jlong host, jlong pos)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host))
{
JUCE_VIDEO_LOG ("MediaSession::seekToCallback()");
myself->pendingSeekRequest = true;
myself->player.setPlayPosition ((jint) pos);
myself->updatePlaybackState();
}
}
static void stopCallback(JNIEnv* env, jobject, jlong host)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::MediaSession*> (host))
{
JUCE_VIDEO_LOG ("MediaSession::stopCallback()");
env->CallVoidMethod (myself->nativeMediaSession, AndroidMediaSession.setActive, false);
myself->player.closeVideo();
myself->updatePlaybackState();
myself->abandonAudioFocus();
myself->owner.closeVideoFinished();
}
}
//==============================================================================
bool isSeekInProgress() const noexcept
{
if (pendingSeekRequest)
return true;
if (! usesBuffering)
return false;
// NB: player sometimes notifies us about buffering, but only for regions that
// were previously buffered already. For buffering happening for the first time,
// we don't get such notification...
if (playerBufferingInProgress)
return true;
auto playPos = player.getPlayPosition();
auto durationMs = player.getVideoDuration();
auto playPosPercent = (int) (100.0 * playPos / static_cast<double> (durationMs));
// NB: assuming the playback will start roughly when there is 5% of content loaded...
return ! bufferedRegions.containsRange (Range<int> (playPosPercent, jmin (101, playPosPercent + 5)));
}
void updatePlaybackState()
{
getEnv()->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setPlaybackState, getCurrentPlaybackState().get());
}
LocalRef<jobject> getCurrentPlaybackState()
{
static constexpr int bufferingState = 6;
auto playbackStateFlag = isSeekInProgress() ? bufferingState : player.getPlaybackStateFlag();
auto playPos = player.getPlayPosition();
auto playSpeed = player.getPlaySpeed();
auto allowedActions = player.getAllowedActions();
auto* env = getEnv();
LocalRef<jobject> (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.setState,
(jint) playbackStateFlag, (jlong) playPos, (jfloat) playSpeed));
LocalRef<jobject> (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.setActions, (jint) allowedActions));
return LocalRef<jobject> (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.build));
}
//==============================================================================
void playerPrepared()
{
resetState();
updateMetadata();
owner.loadFinished();
}
void playerBufferingStarted() { playerBufferingInProgress = true; }
void playerBufferingEnded() { playerBufferingInProgress = false; }
void playerBufferingUpdated (int progress)
{
usesBuffering = true;
updatePlaybackState();
auto playPos = player.getPlayPosition();
auto durationMs = player.getVideoDuration();
auto playPosPercent = (int) (100.0 * playPos / static_cast<double> (durationMs));
bufferedRegions.addRange (Range<int> (playPosPercent, progress + 1));
String ranges;
for (auto& r : bufferedRegions.getRanges())
ranges << "[" << r.getStart() << "%, " << r.getEnd() - 1 << "%] ";
JUCE_VIDEO_LOG ("Buffering status update, seek pos: " + String (playPosPercent) + "%, buffered regions: " + ranges);
}
void playerSeekCompleted()
{
pendingSeekRequest = false;
updatePlaybackState();
}
void playerPlaybackCompleted()
{
player.pause();
abandonAudioFocus();
pendingSeekRequest = true;
player.setPlayPosition (0);
updatePlaybackState();
}
void updateMetadata()
{
auto* env = getEnv();
auto metadataBuilder = LocalRef<jobject> (env->NewObject (AndroidMediaMetadataBuilder,
AndroidMediaMetadataBuilder.constructor));
auto durationMs = player.getVideoDuration();
auto jDurationKey = javaString ("android.media.metadata.DURATION");
LocalRef<jobject> (env->CallObjectMethod (metadataBuilder,
AndroidMediaMetadataBuilder.putLong,
jDurationKey.get(),
(jlong) durationMs));
auto jNumTracksKey = javaString ("android.media.metadata.NUM_TRACKS");
LocalRef<jobject> (env->CallObjectMethod (metadataBuilder,
AndroidMediaMetadataBuilder.putLong,
jNumTracksKey.get(),
(jlong) 1));
env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setMetadata,
env->CallObjectMethod (metadataBuilder, AndroidMediaMetadataBuilder.build));
}
void errorOccurred (const String& errorMessage)
{
auto* env = getEnv();
// Propagate error to session controller(s) and ...
LocalRef<jobject> (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.setErrorMessage,
javaString (errorMessage).get()));
auto state = LocalRef<jobject> (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.build));
env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setPlaybackState, state.get());
// ...also notify JUCE side client
owner.errorOccurred (errorMessage);
}
//==============================================================================
static LocalRef<jobject> createAudioFocusRequestIfNecessary (int sdkVersion, const GlobalRef& audioAttributes,
const GlobalRef& nativeAudioFocusChangeListener)
{
if (sdkVersion < 26)
return LocalRef<jobject>();
auto* env = getEnv();
auto requestBuilderClass = LocalRef<jclass> (env->FindClass ("android/media/AudioFocusRequest$Builder"));
static jmethodID constructor = env->GetMethodID (requestBuilderClass, "<init>", "(I)V");
static jmethodID buildMethod = env->GetMethodID (requestBuilderClass, "build", "()Landroid/media/AudioFocusRequest;");
static jmethodID setAudioAttributesMethod = env->GetMethodID (requestBuilderClass, "setAudioAttributes",
"(Landroid/media/AudioAttributes;)Landroid/media/AudioFocusRequest$Builder;");
static jmethodID setOnAudioFocusChangeListenerMethod = env->GetMethodID (requestBuilderClass, "setOnAudioFocusChangeListener",
"(Landroid/media/AudioManager$OnAudioFocusChangeListener;)Landroid/media/AudioFocusRequest$Builder;");
static constexpr jint audioFocusGain = 1;
auto requestBuilder = LocalRef<jobject> (env->NewObject (requestBuilderClass, constructor, audioFocusGain));
LocalRef<jobject> (env->CallObjectMethod (requestBuilder, setAudioAttributesMethod, audioAttributes.get()));
LocalRef<jobject> (env->CallObjectMethod (requestBuilder, setOnAudioFocusChangeListenerMethod, nativeAudioFocusChangeListener.get()));
return LocalRef<jobject> (env->CallObjectMethod (requestBuilder, buildMethod));
}
void requestAudioFocus()
{
static constexpr jint audioFocusGain = 1;
static constexpr jint streamMusic = 3;
static constexpr jint audioFocusRequestGranted = 1;
jint result = audioFocusRequestGranted;
if (sdkVersion >= 26)
{
static jmethodID requestAudioFocusMethod = getEnv()->GetMethodID (AndroidAudioManager, "requestAudioFocus",
"(Landroid/media/AudioFocusRequest;)I");
result = getEnv()->CallIntMethod (audioManager, requestAudioFocusMethod, audioFocusRequest.get());
}
else
{
result = getEnv()->CallIntMethod (audioManager, AndroidAudioManager.requestAudioFocus,
nativeAudioFocusChangeListener.get(), streamMusic, audioFocusGain);
}
hasAudioFocus = result == audioFocusRequestGranted;
}
void abandonAudioFocus()
{
if (! hasAudioFocus)
return;
static constexpr jint audioFocusRequestGranted = 1;
jint result = audioFocusRequestGranted;
if (sdkVersion >= 26)
{
static jmethodID abandonAudioFocusMethod = getEnv()->GetMethodID (AndroidAudioManager, "abandonAudioFocusRequest",
"(Landroid/media/AudioFocusRequest;)I");
result = getEnv()->CallIntMethod (audioManager, abandonAudioFocusMethod, audioFocusRequest.get());
}
else
{
result = getEnv()->CallIntMethod (audioManager, AndroidAudioManager.abandonAudioFocus,
nativeAudioFocusChangeListener.get());
}
// NB: granted in this case means "granted to change the focus to abandoned"...
hasAudioFocus = result != audioFocusRequestGranted;
}
void onAudioFocusChange (int changeType) override
{
static constexpr jint audioFocusGain = 1;
if (changeType == audioFocusGain)
JUCE_VIDEO_LOG ("Audio focus gained");
else
JUCE_VIDEO_LOG ("Audio focus lost");
if (changeType != audioFocusGain)
{
if (isPlaying())
{
JUCE_VIDEO_LOG ("Received a request to abandon audio focus. Stopping playback...");
stop();
}
abandonAudioFocus();
}
}
//==============================================================================
void playbackStarted()
{
owner.playbackStarted();
}
void playbackStopped()
{
owner.playbackStopped();
}
//==============================================================================
void resetState()
{
usesBuffering = false;
bufferedRegions.clear();
playerBufferingInProgress = false;
pendingSeekRequest = false;
playSpeedMult = 1.0;
hasAudioFocus = false;
}
//==============================================================================
static LocalRef<jobject> getAudioAttributes()
{
// Video requires SDK version 21 or higher
jassert (getAndroidSDKVersion() >= 21);
auto* env = getEnv();
auto audioAttribsBuilder = LocalRef<jobject> (env->NewObject (AndroidAudioAttributesBuilder,
AndroidAudioAttributesBuilder.constructor));
static constexpr jint contentTypeMovie = 3;
static constexpr jint usageMedia = 1;
LocalRef<jobject> (env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.setContentType, contentTypeMovie));
LocalRef<jobject> (env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.setUsage, usageMedia));
return LocalRef<jobject> (env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.build));
}
};
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
//==============================================================================
class SystemVolumeListener
{
public:
SystemVolumeListener (Pimpl& ownerToUse)
: owner (ownerToUse),
nativeObserver (createCallbackObject())
{
setEnabled (true);
}
~SystemVolumeListener()
{
setEnabled (false);
}
private:
Pimpl& owner;
GlobalRef nativeObserver;
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "(Landroid/app/Activity;J)V") \
METHOD (setEnabled, "setEnabled", "(Z)V") \
CALLBACK (systemVolumeChangedCallback, "mediaSessionSystemVolumeChanged", "(J)V")
DECLARE_JNI_CLASS_WITH_MIN_SDK (SystemVolumeObserver, "com/roli/juce/SystemVolumeObserver", 21)
#undef JNI_CLASS_MEMBERS
LocalRef<jobject> createCallbackObject()
{
return LocalRef<jobject> (getEnv()->NewObject (SystemVolumeObserver,
SystemVolumeObserver.constructor,
getCurrentActivity().get(),
reinterpret_cast<jlong> (this)));
}
public:
void setEnabled (bool shouldBeEnabled)
{
getEnv()->CallVoidMethod (nativeObserver, SystemVolumeObserver.setEnabled, shouldBeEnabled);
// Send first notification instantly to ensure sync.
if (shouldBeEnabled)
systemVolumeChanged();
}
private:
//==============================================================================
void systemVolumeChanged()
{
WeakReference<SystemVolumeListener> weakThis (this);
MessageManager::callAsync ([weakThis]() mutable
{
if (weakThis == nullptr)
return;
if (weakThis->owner.owner.onGlobalMediaVolumeChanged != nullptr)
weakThis->owner.owner.onGlobalMediaVolumeChanged();
});
}
//==============================================================================
static void systemVolumeChangedCallback (JNIEnv*, jobject, jlong host)
{
if (auto* myself = reinterpret_cast<VideoComponent::Pimpl::SystemVolumeListener*> (host))
myself->systemVolumeChanged();
}
JUCE_DECLARE_WEAK_REFERENCEABLE (SystemVolumeListener)
};
//==============================================================================
VideoComponent& owner;
MediaSession mediaSession;
GlobalRef activityLifeListener;
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
SystemVolumeListener systemVolumeListener;
#endif
GlobalRef surfaceHolderCallback;
std::function<void(const URL&, Result)> loadFinishedCallback;
bool wasOpen = false;
//==============================================================================
void loadFinished()
{
owner.resized();
if (loadFinishedCallback != nullptr)
{
loadFinishedCallback (currentURL, Result::ok());
loadFinishedCallback = nullptr;
}
}
void closeVideoFinished()
{
owner.resized();
}
void errorOccurred (const String& errorMessage)
{
if (owner.onErrorOccurred != nullptr)
owner.onErrorOccurred (errorMessage);
}
void playbackStarted()
{
if (owner.onPlaybackStarted != nullptr)
owner.onPlaybackStarted();
}
void playbackStopped()
{
if (owner.onPlaybackStopped != nullptr)
owner.onPlaybackStopped();
}
//==============================================================================
void surfaceChanged (LocalRef<jobject> holder, int /*format*/, int /*width*/, int /*height*/) override
{
mediaSession.setDisplay (holder);
}
void surfaceDestroyed (LocalRef<jobject> /*holder*/) override
{
mediaSession.setDisplay (LocalRef<jobject>());
}
void surfaceCreated (LocalRef<jobject> /*holder*/) override
{
}
//==============================================================================
void onActivityPaused (jobject) override
{
wasOpen = isOpen();
if (! wasOpen)
return;
JUCE_VIDEO_LOG ("App paused, releasing media player...");
mediaSession.storeState();
mediaSession.closeVideo();
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
systemVolumeListener.setEnabled (false);
#endif
}
void onActivityResumed (jobject) override
{
if (! wasOpen)
return;
JUCE_VIDEO_LOG ("App resumed, restoring media player...");
loadAsync (currentURL, [this](const URL&, Result r)
{
if (r.wasOk())
mediaSession.restoreState();
});
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
systemVolumeListener.setEnabled (true);
#endif
}
//==============================================================================
#endif
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
constexpr VideoComponent::Pimpl::MediaSession::Player::StateInfo VideoComponent::Pimpl::MediaSession::Player::stateInfos[];
//==============================================================================
VideoComponent::Pimpl::MediaSession::AndroidMediaSessionCallback_Class VideoComponent::Pimpl::MediaSession::AndroidMediaSessionCallback;
VideoComponent::Pimpl::MediaSession::Controller::AndroidMediaControllerCallback_Class VideoComponent::Pimpl::MediaSession::Controller::AndroidMediaControllerCallback;
VideoComponent::Pimpl::SystemVolumeListener::SystemVolumeObserver_Class VideoComponent::Pimpl::SystemVolumeListener::SystemVolumeObserver;