1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Add video playback support for Android and iOS. Update VideoComponent API to support building custom UIs.

This commit is contained in:
Lukasz Kozakiewicz 2018-05-11 17:57:26 +02:00
parent dc7217fbbb
commit 315326477d
45 changed files with 4293 additions and 308 deletions

View file

@ -1467,6 +1467,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_video/capture/juce_CameraDevice.cpp" "../../../../../modules/juce_video/capture/juce_CameraDevice.cpp"
"../../../../../modules/juce_video/capture/juce_CameraDevice.h" "../../../../../modules/juce_video/capture/juce_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_android_CameraDevice.h" "../../../../../modules/juce_video/native/juce_android_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_android_Video.h"
"../../../../../modules/juce_video/native/juce_ios_CameraDevice.h" "../../../../../modules/juce_video/native/juce_ios_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_mac_CameraDevice.h" "../../../../../modules/juce_video/native/juce_mac_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_mac_Video.h" "../../../../../modules/juce_video/native/juce_mac_Video.h"
@ -2932,6 +2933,7 @@ set_source_files_properties("../../../../../modules/juce_product_unlocking/juce_
set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_android_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/native/juce_android_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_android_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_ios_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/native/juce_ios_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_mac_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/native/juce_mac_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_mac_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/native/juce_mac_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE)

View file

@ -31,6 +31,9 @@ import android.content.res.Configuration;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.hardware.camera2.*; import android.hardware.camera2.*;
import android.database.ContentObserver;
import android.media.session.*;
import android.media.MediaMetadata;
import android.net.http.SslError; import android.net.http.SslError;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -94,8 +97,11 @@ public class DemoRunner extends Activity
//============================================================================== //==============================================================================
public boolean isPermissionDeclaredInManifest (int permissionID) public boolean isPermissionDeclaredInManifest (int permissionID)
{ {
String permissionToCheck = getAndroidPermissionName(permissionID); return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
}
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
{
try try
{ {
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS); PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
@ -1997,11 +2003,13 @@ public class DemoRunner extends Activity
implements SurfaceHolder.Callback implements SurfaceHolder.Callback
{ {
private long nativeContext = 0; private long nativeContext = 0;
private boolean forVideo;
NativeSurfaceView (Context context, long nativeContextPtr) NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
{ {
super (context); super (context);
nativeContext = nativeContextPtr; nativeContext = nativeContextPtr;
forVideo = createdForVideo;
} }
public Surface getNativeSurface() public Surface getNativeSurface()
@ -2019,38 +2027,51 @@ public class DemoRunner extends Activity
@Override @Override
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
{ {
surfaceChangedNative (nativeContext, holder, format, width, height); if (forVideo)
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
else
surfaceChangedNative (nativeContext, holder, format, width, height);
} }
@Override @Override
public void surfaceCreated (SurfaceHolder holder) public void surfaceCreated (SurfaceHolder holder)
{ {
surfaceCreatedNative (nativeContext, holder); if (forVideo)
surfaceCreatedNativeVideo (nativeContext, holder);
else
surfaceCreatedNative (nativeContext, holder);
} }
@Override @Override
public void surfaceDestroyed (SurfaceHolder holder) public void surfaceDestroyed (SurfaceHolder holder)
{ {
surfaceDestroyedNative (nativeContext, holder); if (forVideo)
surfaceDestroyedNativeVideo (nativeContext, holder);
else
surfaceDestroyedNative (nativeContext, holder);
} }
@Override @Override
protected void dispatchDraw (Canvas canvas) protected void dispatchDraw (Canvas canvas)
{ {
super.dispatchDraw (canvas); super.dispatchDraw (canvas);
dispatchDrawNative (nativeContext, canvas);
if (forVideo)
dispatchDrawNativeVideo (nativeContext, canvas);
else
dispatchDrawNative (nativeContext, canvas);
} }
//============================================================================== //==============================================================================
@Override @Override
protected void onAttachedToWindow () protected void onAttachedToWindow()
{ {
super.onAttachedToWindow(); super.onAttachedToWindow();
getHolder().addCallback (this); getHolder().addCallback (this);
} }
@Override @Override
protected void onDetachedFromWindow () protected void onDetachedFromWindow()
{ {
super.onDetachedFromWindow(); super.onDetachedFromWindow();
getHolder().removeCallback (this); getHolder().removeCallback (this);
@ -2062,11 +2083,17 @@ public class DemoRunner extends Activity
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder); private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder, private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height); int format, int width, int height);
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height);
} }
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr) public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
{ {
return new NativeSurfaceView (this, nativeSurfacePtr); return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
} }
//============================================================================== //==============================================================================
@ -2826,6 +2853,151 @@ public class DemoRunner extends Activity
} }
//==============================================================================
public class MediaControllerCallback extends MediaController.Callback
{
private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info);
private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata);
private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);
private native void mediaControllerSessionDestroyed (long host);
MediaControllerCallback (long hostToUse)
{
host = hostToUse;
}
@Override
public void onAudioInfoChanged (MediaController.PlaybackInfo info)
{
mediaControllerAudioInfoChanged (host, info);
}
@Override
public void onMetadataChanged (MediaMetadata metadata)
{
mediaControllerMetadataChanged (host, metadata);
}
@Override
public void onPlaybackStateChanged (PlaybackState state)
{
mediaControllerPlaybackStateChanged (host, state);
}
@Override
public void onQueueChanged (List<MediaSession.QueueItem> queue) {}
@Override
public void onSessionDestroyed()
{
mediaControllerSessionDestroyed (host);
}
private long host;
}
//==============================================================================
public class MediaSessionCallback extends MediaSession.Callback
{
private native void mediaSessionPause (long host);
private native void mediaSessionPlay (long host);
private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);
private native void mediaSessionSeekTo (long host, long pos);
private native void mediaSessionStop (long host);
MediaSessionCallback (long hostToUse)
{
host = hostToUse;
}
@Override
public void onPause()
{
mediaSessionPause (host);
}
@Override
public void onPlay()
{
mediaSessionPlay (host);
}
@Override
public void onPlayFromMediaId (String mediaId, Bundle extras)
{
mediaSessionPlayFromMediaId (host, mediaId, extras);
}
@Override
public void onSeekTo (long pos)
{
mediaSessionSeekTo (host, pos);
}
@Override
public void onStop()
{
mediaSessionStop (host);
}
@Override
public void onFastForward() {}
@Override
public boolean onMediaButtonEvent (Intent mediaButtonIntent)
{
return true;
}
@Override
public void onRewind() {}
@Override
public void onSkipToNext() {}
@Override
public void onSkipToPrevious() {}
@Override
public void onSkipToQueueItem (long id) {}
private long host;
}
//==============================================================================
public class SystemVolumeObserver extends ContentObserver
{
private native void mediaSessionSystemVolumeChanged (long host);
SystemVolumeObserver (Activity activityToUse, long hostToUse)
{
super (null);
activity = activityToUse;
host = hostToUse;
}
void setEnabled (boolean shouldBeEnabled)
{
if (shouldBeEnabled)
activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);
else
activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);
}
@Override
public void onChange (boolean selfChange, Uri uri)
{
if (uri.toString().startsWith ("content://settings/system/volume_music"))
mediaSessionSystemVolumeChanged (host);
}
private Activity activity;
private long host;
}
//============================================================================== //==============================================================================
public static final String getLocaleValue (boolean isRegion) public static final String getLocaleValue (boolean isRegion)
{ {

View file

@ -2840,6 +2840,7 @@
<ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/> <ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>

View file

@ -4827,6 +4827,9 @@
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
<Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>

View file

@ -2840,6 +2840,7 @@
<ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/> <ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>

View file

@ -4827,6 +4827,9 @@
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
<Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>

View file

@ -2840,6 +2840,7 @@
<ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/> <ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>

View file

@ -4827,6 +4827,9 @@
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
<Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>

View file

@ -291,6 +291,10 @@
#ifndef JUCE_USE_CAMERA #ifndef JUCE_USE_CAMERA
#define JUCE_USE_CAMERA 1 #define JUCE_USE_CAMERA 1
#endif #endif
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
//#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
#endif
//============================================================================== //==============================================================================
#ifndef JUCE_STANDALONE_APPLICATION #ifndef JUCE_STANDALONE_APPLICATION
#if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone) #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)

View file

@ -61,7 +61,7 @@
#include "../../../GUI/OpenGLDemo2D.h" #include "../../../GUI/OpenGLDemo2D.h"
#endif #endif
#include "../../../GUI/PropertiesDemo.h" #include "../../../GUI/PropertiesDemo.h"
#if JUCE_MAC || JUCE_WINDOWS #if ! JUCE_LINUX
#include "../../../GUI/VideoDemo.h" #include "../../../GUI/VideoDemo.h"
#endif #endif
#include "../../../GUI/WebBrowserDemo.h" #include "../../../GUI/WebBrowserDemo.h"
@ -100,7 +100,7 @@ void registerDemos_Two() noexcept
REGISTER_DEMO_WITH_FILENAME (OpenGLDemoClasses::OpenGLDemo, GUI, OpenGLDemo, true) REGISTER_DEMO_WITH_FILENAME (OpenGLDemoClasses::OpenGLDemo, GUI, OpenGLDemo, true)
#endif #endif
REGISTER_DEMO (PropertiesDemo, GUI, false) REGISTER_DEMO (PropertiesDemo, GUI, false)
#if JUCE_MAC || JUCE_WINDOWS #if ! JUCE_LINUX
REGISTER_DEMO (VideoDemo, GUI, true) REGISTER_DEMO (VideoDemo, GUI, true)
#endif #endif
REGISTER_DEMO (WebBrowserDemo, GUI, true) REGISTER_DEMO (WebBrowserDemo, GUI, true)

View file

@ -159,6 +159,18 @@ public:
} }
setSize (500, 500); setSize (500, 500);
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
[] (bool granted)
{
if (! granted)
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Permissions warning",
"External storage access permission not granted, some files"
" may be inaccessible.");
}
});
} }
//============================================================================== //==============================================================================

View file

@ -46,6 +46,7 @@
#include "../Assets/DemoUtilities.h" #include "../Assets/DemoUtilities.h"
#if JUCE_MAC || JUCE_WINDOWS
//============================================================================== //==============================================================================
// so that we can easily have two video windows each with a file browser, wrap this up as a class.. // so that we can easily have two video windows each with a file browser, wrap this up as a class..
class MovieComponentWithFileBrowser : public Component, class MovieComponentWithFileBrowser : public Component,
@ -54,6 +55,7 @@ class MovieComponentWithFileBrowser : public Component,
{ {
public: public:
MovieComponentWithFileBrowser() MovieComponentWithFileBrowser()
: videoComp (true)
{ {
addAndMakeVisible (videoComp); addAndMakeVisible (videoComp);
@ -110,8 +112,16 @@ private:
void filenameComponentChanged (FilenameComponent*) override void filenameComponentChanged (FilenameComponent*) override
{ {
auto url = URL (fileChooser.getCurrentFile());
// this is called when the user changes the filename in the file chooser box // this is called when the user changes the filename in the file chooser box
auto result = videoComp.load (fileChooser.getCurrentFile()); auto result = videoComp.load (url);
videoLoadingFinished (url, result);
}
void videoLoadingFinished (const URL& url, Result result)
{
ignoreUnused (url);
if (result.wasOk()) if (result.wasOk())
{ {
@ -209,6 +219,7 @@ public:
} }
private: private:
std::unique_ptr<FileChooser> fileChooser;
WildcardFileFilter moviesWildcardFilter { "*", "*", "Movies File Filter" }; WildcardFileFilter moviesWildcardFilter { "*", "*", "Movies File Filter" };
TimeSliceThread directoryThread { "Movie File Scanner Thread" }; TimeSliceThread directoryThread { "Movie File Scanner Thread" };
DirectoryContentsList movieList { &moviesWildcardFilter, directoryThread }; DirectoryContentsList movieList { &moviesWildcardFilter, directoryThread };
@ -231,5 +242,462 @@ private:
void fileDoubleClicked (const File&) override {} void fileDoubleClicked (const File&) override {}
void browserRootChanged (const File&) override {} void browserRootChanged (const File&) override {}
void selectVideoFile()
{
fileChooser.reset (new FileChooser ("Choose a file to open...", File::getCurrentWorkingDirectory(),
"*", false));
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
[this] (const FileChooser& chooser)
{
String chosen;
auto results = chooser.getURLResults();
// TODO: support non local files too
if (results.size() > 0)
movieCompLeft.setFile (results[0].getLocalFile());
});
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoDemo) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoDemo)
}; };
#elif JUCE_IOS || JUCE_ANDROID
//==============================================================================
class VideoDemo : public Component,
private Timer
{
public:
VideoDemo()
: videoCompWithNativeControls (true),
videoCompNoNativeControls (false)
{
loadLocalButton .onClick = [this] { selectVideoFile(); };
loadUrlButton .onClick = [this] { showVideoUrlPrompt(); };
seekToStartButton.onClick = [this] { seekVideoToStart(); };
playButton .onClick = [this] { playVideo(); };
pauseButton .onClick = [this] { pauseVideo(); };
unloadButton .onClick = [this] { unloadVideoFile(); };
volumeLabel .setColour (Label::textColourId, Colours::white);
currentPositionLabel.setColour (Label::textColourId, Colours::white);
volumeLabel .setJustificationType (Justification::right);
currentPositionLabel.setJustificationType (Justification::right);
volumeSlider .setRange (0.0, 1.0);
positionSlider.setRange (0.0, 1.0);
volumeSlider .setSliderSnapsToMousePosition (false);
positionSlider.setSliderSnapsToMousePosition (false);
volumeSlider.setSkewFactor (1.5);
volumeSlider.setValue (1.0, dontSendNotification);
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
curVideoComp->onGlobalMediaVolumeChanged = [this]() { volumeSlider.setValue (curVideoComp->getAudioVolume(), dontSendNotification); };
#endif
volumeSlider .onValueChange = [this]() { curVideoComp->setAudioVolume ((float) volumeSlider.getValue()); };
positionSlider.onValueChange = [this]() { seekVideoToNormalisedPosition (positionSlider.getValue()); };
positionSlider.onDragStart = [this]()
{
positionSliderDragging = true;
wasPlayingBeforeDragStart = curVideoComp->isPlaying();
if (wasPlayingBeforeDragStart)
curVideoComp->stop();
};
positionSlider.onDragEnd = [this]()
{
if (wasPlayingBeforeDragStart)
curVideoComp->play();
wasPlayingBeforeDragStart = false;
// Ensure the slider does not temporarily jump back on consecutive timer callback.
Timer::callAfterDelay (500, [this]() { positionSliderDragging = false; });
};
playSpeedComboBox.addItem ("25%", 25);
playSpeedComboBox.addItem ("50%", 50);
playSpeedComboBox.addItem ("100%", 100);
playSpeedComboBox.addItem ("200%", 200);
playSpeedComboBox.addItem ("400%", 400);
playSpeedComboBox.setSelectedId (100, dontSendNotification);
playSpeedComboBox.onChange = [this]() { curVideoComp->setPlaySpeed (playSpeedComboBox.getSelectedId() / 100.0); };
setTransportControlsEnabled (false);
addAndMakeVisible (loadLocalButton);
addAndMakeVisible (loadUrlButton);
addAndMakeVisible (volumeLabel);
addAndMakeVisible (volumeSlider);
addChildComponent (videoCompWithNativeControls);
addChildComponent (videoCompNoNativeControls);
addAndMakeVisible (positionSlider);
addAndMakeVisible (currentPositionLabel);
addAndMakeVisible (playSpeedComboBox);
addAndMakeVisible (seekToStartButton);
addAndMakeVisible (playButton);
addAndMakeVisible (unloadButton);
addChildComponent (pauseButton);
setSize (500, 500);
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
[] (bool granted)
{
if (! granted)
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Permissions warning",
"External storage access permission not granted, some files"
" may be inaccessible.");
}
});
setPortraitOrientationEnabled (true);
}
~VideoDemo()
{
curVideoComp->onPlaybackStarted = nullptr;
curVideoComp->onPlaybackStopped = nullptr;
curVideoComp->onErrorOccurred = nullptr;
curVideoComp->onGlobalMediaVolumeChanged = nullptr;
setPortraitOrientationEnabled (false);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
auto area = getLocalBounds();
int marginSize = 5;
int buttonHeight = 20;
area.reduce (0, marginSize);
auto topArea = area.removeFromTop (buttonHeight);
loadLocalButton.setBounds (topArea.removeFromLeft (topArea.getWidth() / 6));
loadUrlButton.setBounds (topArea.removeFromLeft (loadLocalButton.getWidth()));
volumeLabel.setBounds (topArea.removeFromLeft (loadLocalButton.getWidth()));
volumeSlider.setBounds (topArea.reduced (10, 0));
auto transportArea = area.removeFromBottom (buttonHeight);
auto positionArea = area.removeFromBottom (buttonHeight).reduced (marginSize, 0);
playSpeedComboBox.setBounds (transportArea.removeFromLeft (jmax (50, transportArea.getWidth() / 5)));
auto controlWidth = transportArea.getWidth() / 3;
currentPositionLabel.setBounds (positionArea.removeFromRight (jmax (150, controlWidth)));
positionSlider.setBounds (positionArea);
seekToStartButton.setBounds (transportArea.removeFromLeft (controlWidth));
playButton .setBounds (transportArea.removeFromLeft (controlWidth));
unloadButton .setBounds (transportArea.removeFromLeft (controlWidth));
pauseButton.setBounds (playButton.getBounds());
area.removeFromTop (marginSize);
area.removeFromBottom (marginSize);
videoCompWithNativeControls.setBounds (area);
videoCompNoNativeControls.setBounds (area);
if (positionSlider.getWidth() > 0)
positionSlider.setMouseDragSensitivity (positionSlider.getWidth());
}
private:
TextButton loadLocalButton { "Load Local" };
TextButton loadUrlButton { "Load URL" };
Label volumeLabel { "volumeLabel", "Vol:" };
Slider volumeSlider { Slider::LinearHorizontal, Slider::NoTextBox };
VideoComponent videoCompWithNativeControls;
VideoComponent videoCompNoNativeControls;
#if JUCE_IOS || JUCE_MAC
VideoComponent* curVideoComp = &videoCompWithNativeControls;
#else
VideoComponent* curVideoComp = &videoCompNoNativeControls;
#endif
bool isFirstSetup = true;
Slider positionSlider { Slider::LinearHorizontal, Slider::NoTextBox };
bool positionSliderDragging = false;
bool wasPlayingBeforeDragStart = false;
Label currentPositionLabel { "currentPositionLabel", "-:- / -:-" };
ComboBox playSpeedComboBox { "playSpeedComboBox" };
TextButton seekToStartButton { "|<" };
TextButton playButton { "Play" };
TextButton pauseButton { "Pause" };
TextButton unloadButton { "Unload" };
std::unique_ptr<FileChooser> fileChooser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoDemo)
JUCE_DECLARE_WEAK_REFERENCEABLE (VideoDemo)
//==============================================================================
void setPortraitOrientationEnabled (bool shouldBeEnabled)
{
auto allowedOrientations = Desktop::getInstance().getOrientationsEnabled();
if (shouldBeEnabled)
allowedOrientations |= Desktop::upright;
else
allowedOrientations &= ~Desktop::upright;
Desktop::getInstance().setOrientationsEnabled (allowedOrientations);
}
void setTransportControlsEnabled (bool shouldBeEnabled)
{
positionSlider .setEnabled (shouldBeEnabled);
playSpeedComboBox.setEnabled (shouldBeEnabled);
seekToStartButton.setEnabled (shouldBeEnabled);
playButton .setEnabled (shouldBeEnabled);
unloadButton .setEnabled (shouldBeEnabled);
pauseButton .setEnabled (shouldBeEnabled);
}
void selectVideoFile()
{
fileChooser.reset (new FileChooser ("Choose a video file to open...", File::getCurrentWorkingDirectory(),
"*", true));
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
[this] (const FileChooser& chooser)
{
auto results = chooser.getURLResults();
if (results.size() > 0)
loadVideo (results[0]);
});
}
void loadVideo (const URL& url)
{
unloadVideoFile();
#if JUCE_IOS || JUCE_MAC
askIfUseNativeControls (url);
#else
loadUrl (url);
setupVideoComp (false);
#endif
}
void askIfUseNativeControls (const URL& url)
{
auto* aw = new AlertWindow ("Choose viewer type", {}, AlertWindow::NoIcon);
aw->addButton ("Yes", 1, KeyPress (KeyPress::returnKey));
aw->addButton ("No", 0, KeyPress (KeyPress::escapeKey));
aw->addTextBlock ("Do you want to use the viewer with native controls?");
auto callback = ModalCallbackFunction::forComponent (videoViewerTypeChosen, this, url);
aw->enterModalState (true, callback, true);
}
static void videoViewerTypeChosen (int result, VideoDemo* owner, URL url)
{
if (owner != nullptr)
{
owner->setupVideoComp (result != 0);
owner->loadUrl (url);
}
}
void setupVideoComp (bool useNativeViewerWithNativeControls)
{
auto* oldVideoComp = curVideoComp;
if (useNativeViewerWithNativeControls)
curVideoComp = &videoCompWithNativeControls;
else
curVideoComp = &videoCompNoNativeControls;
if (isFirstSetup || oldVideoComp != curVideoComp)
{
oldVideoComp->onPlaybackStarted = nullptr;
oldVideoComp->onPlaybackStopped = nullptr;
oldVideoComp->onErrorOccurred = nullptr;
oldVideoComp->setVisible (false);
curVideoComp->onPlaybackStarted = [this]() { processPlaybackStarted(); };
curVideoComp->onPlaybackStopped = [this]() { processPlaybackPaused(); };
curVideoComp->onErrorOccurred = [this](const String& errorMessage) { errorOccurred (errorMessage); };
curVideoComp->setVisible (true);
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
oldVideoComp->onGlobalMediaVolumeChanged = nullptr;
curVideoComp->onGlobalMediaVolumeChanged = [this]() { volumeSlider.setValue (curVideoComp->getAudioVolume(), dontSendNotification); };
#endif
}
isFirstSetup = false;
}
void loadUrl (const URL& url)
{
curVideoComp->loadAsync (url, [this] (const URL& u, Result r) { videoLoadingFinished (u, r); });
}
void showVideoUrlPrompt()
{
auto* aw = new AlertWindow ("Enter URL for video to load", {}, AlertWindow::NoIcon);
aw->addButton ("OK", 1, KeyPress (KeyPress::returnKey));
aw->addButton ("Cancel", 0, KeyPress (KeyPress::escapeKey));
aw->addTextEditor ("videoUrlTextEditor", "https://www.rmp-streaming.com/media/bbb-360p.mp4");
auto callback = ModalCallbackFunction::forComponent (videoUrlPromptClosed, this, Component::SafePointer<AlertWindow> (aw));
aw->enterModalState (true, callback, true);
}
static void videoUrlPromptClosed (int result, VideoDemo* owner, Component::SafePointer<AlertWindow> aw)
{
if (result != 0 && owner != nullptr && aw != nullptr)
{
auto url = aw->getTextEditorContents ("videoUrlTextEditor");
if (url.isNotEmpty())
owner->loadVideo (url);
}
}
void videoLoadingFinished (const URL& url, Result result)
{
ignoreUnused (url);
if (result.wasOk())
{
resized(); // update to reflect the video's aspect ratio
setTransportControlsEnabled (true);
currentPositionLabel.setText (getPositionString (0.0, curVideoComp->getVideoDuration()), sendNotification);
positionSlider.setValue (0.0, dontSendNotification);
playSpeedComboBox.setSelectedId (100, dontSendNotification);
}
else
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Couldn't load the file!",
result.getErrorMessage());
}
}
static String getPositionString (double playPositionSeconds, double durationSeconds)
{
auto positionMs = static_cast<int> (1000 * playPositionSeconds);
int posMinutes = positionMs / 60000;
int posSeconds = (positionMs % 60000) / 1000;
int posMillis = positionMs % 1000;
auto totalMs = static_cast<int> (1000 * durationSeconds);
int totMinutes = totalMs / 60000;
int totSeconds = (totalMs % 60000) / 1000;
int totMillis = totalMs % 1000;
return String::formatted ("%02d:%02d:%03d / %02d:%02d:%03d",
posMinutes, posSeconds, posMillis,
totMinutes, totSeconds, totMillis);
}
void updatePositionSliderAndLabel()
{
auto position = curVideoComp->getPlayPosition();
auto duration = curVideoComp->getVideoDuration();
currentPositionLabel.setText (getPositionString (position, duration), sendNotification);
if (! positionSliderDragging)
positionSlider.setValue (duration != 0 ? (position / duration) : 0.0, dontSendNotification);
}
void seekVideoToStart()
{
seekVideoToNormalisedPosition (0.0);
}
void seekVideoToNormalisedPosition (double normalisedPos)
{
normalisedPos = jlimit (0.0, 1.0, normalisedPos);
auto duration = curVideoComp->getVideoDuration();
auto newPos = jlimit (0.0, duration, duration * normalisedPos);
curVideoComp->setPlayPosition (newPos);
currentPositionLabel.setText (getPositionString (newPos, curVideoComp->getVideoDuration()), sendNotification);
positionSlider.setValue (normalisedPos, dontSendNotification);
}
void playVideo()
{
curVideoComp->play();
}
void processPlaybackStarted()
{
playButton.setVisible (false);
pauseButton.setVisible (true);
startTimer (20);
}
void pauseVideo()
{
curVideoComp->stop();
}
void processPlaybackPaused()
{
// On seeking to a new pos, the playback may be temporarily paused.
if (positionSliderDragging)
return;
pauseButton.setVisible (false);
playButton.setVisible (true);
}
void errorOccurred (const String& errorMessage)
{
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
"An error has occurred",
errorMessage + ", video will be unloaded.");
unloadVideoFile();
}
void unloadVideoFile()
{
curVideoComp->closeVideo();
setTransportControlsEnabled (false);
stopTimer();
pauseButton.setVisible (false);
playButton.setVisible (true);
currentPositionLabel.setText ("-:- / -:-", sendNotification);
positionSlider.setValue (0.0, dontSendNotification);
}
void timerCallback() override
{
updatePositionSliderAndLabel();
}
};
#endif

View file

@ -31,6 +31,9 @@ import android.content.res.Configuration;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.hardware.camera2.*; import android.hardware.camera2.*;
import android.database.ContentObserver;
import android.media.session.*;
import android.media.MediaMetadata;
import android.net.http.SslError; import android.net.http.SslError;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -94,8 +97,11 @@ public class AudioPerformanceTest extends Activity
//============================================================================== //==============================================================================
public boolean isPermissionDeclaredInManifest (int permissionID) public boolean isPermissionDeclaredInManifest (int permissionID)
{ {
String permissionToCheck = getAndroidPermissionName(permissionID); return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
}
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
{
try try
{ {
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS); PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
@ -1997,11 +2003,13 @@ public class AudioPerformanceTest extends Activity
implements SurfaceHolder.Callback implements SurfaceHolder.Callback
{ {
private long nativeContext = 0; private long nativeContext = 0;
private boolean forVideo;
NativeSurfaceView (Context context, long nativeContextPtr) NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
{ {
super (context); super (context);
nativeContext = nativeContextPtr; nativeContext = nativeContextPtr;
forVideo = createdForVideo;
} }
public Surface getNativeSurface() public Surface getNativeSurface()
@ -2019,38 +2027,51 @@ public class AudioPerformanceTest extends Activity
@Override @Override
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
{ {
surfaceChangedNative (nativeContext, holder, format, width, height); if (forVideo)
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
else
surfaceChangedNative (nativeContext, holder, format, width, height);
} }
@Override @Override
public void surfaceCreated (SurfaceHolder holder) public void surfaceCreated (SurfaceHolder holder)
{ {
surfaceCreatedNative (nativeContext, holder); if (forVideo)
surfaceCreatedNativeVideo (nativeContext, holder);
else
surfaceCreatedNative (nativeContext, holder);
} }
@Override @Override
public void surfaceDestroyed (SurfaceHolder holder) public void surfaceDestroyed (SurfaceHolder holder)
{ {
surfaceDestroyedNative (nativeContext, holder); if (forVideo)
surfaceDestroyedNativeVideo (nativeContext, holder);
else
surfaceDestroyedNative (nativeContext, holder);
} }
@Override @Override
protected void dispatchDraw (Canvas canvas) protected void dispatchDraw (Canvas canvas)
{ {
super.dispatchDraw (canvas); super.dispatchDraw (canvas);
dispatchDrawNative (nativeContext, canvas);
if (forVideo)
dispatchDrawNativeVideo (nativeContext, canvas);
else
dispatchDrawNative (nativeContext, canvas);
} }
//============================================================================== //==============================================================================
@Override @Override
protected void onAttachedToWindow () protected void onAttachedToWindow()
{ {
super.onAttachedToWindow(); super.onAttachedToWindow();
getHolder().addCallback (this); getHolder().addCallback (this);
} }
@Override @Override
protected void onDetachedFromWindow () protected void onDetachedFromWindow()
{ {
super.onDetachedFromWindow(); super.onDetachedFromWindow();
getHolder().removeCallback (this); getHolder().removeCallback (this);
@ -2062,11 +2083,17 @@ public class AudioPerformanceTest extends Activity
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder); private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder, private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height); int format, int width, int height);
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height);
} }
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr) public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
{ {
return new NativeSurfaceView (this, nativeSurfacePtr); return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
} }
//============================================================================== //==============================================================================
@ -2826,6 +2853,151 @@ public class AudioPerformanceTest extends Activity
} }
//==============================================================================
public class MediaControllerCallback extends MediaController.Callback
{
private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info);
private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata);
private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);
private native void mediaControllerSessionDestroyed (long host);
MediaControllerCallback (long hostToUse)
{
host = hostToUse;
}
@Override
public void onAudioInfoChanged (MediaController.PlaybackInfo info)
{
mediaControllerAudioInfoChanged (host, info);
}
@Override
public void onMetadataChanged (MediaMetadata metadata)
{
mediaControllerMetadataChanged (host, metadata);
}
@Override
public void onPlaybackStateChanged (PlaybackState state)
{
mediaControllerPlaybackStateChanged (host, state);
}
@Override
public void onQueueChanged (List<MediaSession.QueueItem> queue) {}
@Override
public void onSessionDestroyed()
{
mediaControllerSessionDestroyed (host);
}
private long host;
}
//==============================================================================
public class MediaSessionCallback extends MediaSession.Callback
{
private native void mediaSessionPause (long host);
private native void mediaSessionPlay (long host);
private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);
private native void mediaSessionSeekTo (long host, long pos);
private native void mediaSessionStop (long host);
MediaSessionCallback (long hostToUse)
{
host = hostToUse;
}
@Override
public void onPause()
{
mediaSessionPause (host);
}
@Override
public void onPlay()
{
mediaSessionPlay (host);
}
@Override
public void onPlayFromMediaId (String mediaId, Bundle extras)
{
mediaSessionPlayFromMediaId (host, mediaId, extras);
}
@Override
public void onSeekTo (long pos)
{
mediaSessionSeekTo (host, pos);
}
@Override
public void onStop()
{
mediaSessionStop (host);
}
@Override
public void onFastForward() {}
@Override
public boolean onMediaButtonEvent (Intent mediaButtonIntent)
{
return true;
}
@Override
public void onRewind() {}
@Override
public void onSkipToNext() {}
@Override
public void onSkipToPrevious() {}
@Override
public void onSkipToQueueItem (long id) {}
private long host;
}
//==============================================================================
public class SystemVolumeObserver extends ContentObserver
{
private native void mediaSessionSystemVolumeChanged (long host);
SystemVolumeObserver (Activity activityToUse, long hostToUse)
{
super (null);
activity = activityToUse;
host = hostToUse;
}
void setEnabled (boolean shouldBeEnabled)
{
if (shouldBeEnabled)
activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);
else
activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);
}
@Override
public void onChange (boolean selfChange, Uri uri)
{
if (uri.toString().startsWith ("content://settings/system/volume_music"))
mediaSessionSystemVolumeChanged (host);
}
private Activity activity;
private long host;
}
//============================================================================== //==============================================================================
public static final String getLocaleValue (boolean isRegion) public static final String getLocaleValue (boolean isRegion)
{ {

View file

@ -1251,6 +1251,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_video/capture/juce_CameraDevice.cpp" "../../../../../modules/juce_video/capture/juce_CameraDevice.cpp"
"../../../../../modules/juce_video/capture/juce_CameraDevice.h" "../../../../../modules/juce_video/capture/juce_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_android_CameraDevice.h" "../../../../../modules/juce_video/native/juce_android_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_android_Video.h"
"../../../../../modules/juce_video/native/juce_ios_CameraDevice.h" "../../../../../modules/juce_video/native/juce_ios_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_mac_CameraDevice.h" "../../../../../modules/juce_video/native/juce_mac_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_mac_Video.h" "../../../../../modules/juce_video/native/juce_mac_Video.h"
@ -2491,6 +2492,7 @@ set_source_files_properties("../../../../../modules/juce_opengl/juce_opengl.h" P
set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_android_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/native/juce_android_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_android_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_ios_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/native/juce_ios_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_mac_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/native/juce_mac_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_mac_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_video/native/juce_mac_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE)

View file

@ -31,6 +31,9 @@ import android.content.res.Configuration;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.hardware.camera2.*; import android.hardware.camera2.*;
import android.database.ContentObserver;
import android.media.session.*;
import android.media.MediaMetadata;
import android.net.http.SslError; import android.net.http.SslError;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -94,8 +97,11 @@ public class AudioPluginHost extends Activity
//============================================================================== //==============================================================================
public boolean isPermissionDeclaredInManifest (int permissionID) public boolean isPermissionDeclaredInManifest (int permissionID)
{ {
String permissionToCheck = getAndroidPermissionName(permissionID); return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
}
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
{
try try
{ {
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS); PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
@ -1997,11 +2003,13 @@ public class AudioPluginHost extends Activity
implements SurfaceHolder.Callback implements SurfaceHolder.Callback
{ {
private long nativeContext = 0; private long nativeContext = 0;
private boolean forVideo;
NativeSurfaceView (Context context, long nativeContextPtr) NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
{ {
super (context); super (context);
nativeContext = nativeContextPtr; nativeContext = nativeContextPtr;
forVideo = createdForVideo;
} }
public Surface getNativeSurface() public Surface getNativeSurface()
@ -2019,38 +2027,51 @@ public class AudioPluginHost extends Activity
@Override @Override
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
{ {
surfaceChangedNative (nativeContext, holder, format, width, height); if (forVideo)
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
else
surfaceChangedNative (nativeContext, holder, format, width, height);
} }
@Override @Override
public void surfaceCreated (SurfaceHolder holder) public void surfaceCreated (SurfaceHolder holder)
{ {
surfaceCreatedNative (nativeContext, holder); if (forVideo)
surfaceCreatedNativeVideo (nativeContext, holder);
else
surfaceCreatedNative (nativeContext, holder);
} }
@Override @Override
public void surfaceDestroyed (SurfaceHolder holder) public void surfaceDestroyed (SurfaceHolder holder)
{ {
surfaceDestroyedNative (nativeContext, holder); if (forVideo)
surfaceDestroyedNativeVideo (nativeContext, holder);
else
surfaceDestroyedNative (nativeContext, holder);
} }
@Override @Override
protected void dispatchDraw (Canvas canvas) protected void dispatchDraw (Canvas canvas)
{ {
super.dispatchDraw (canvas); super.dispatchDraw (canvas);
dispatchDrawNative (nativeContext, canvas);
if (forVideo)
dispatchDrawNativeVideo (nativeContext, canvas);
else
dispatchDrawNative (nativeContext, canvas);
} }
//============================================================================== //==============================================================================
@Override @Override
protected void onAttachedToWindow () protected void onAttachedToWindow()
{ {
super.onAttachedToWindow(); super.onAttachedToWindow();
getHolder().addCallback (this); getHolder().addCallback (this);
} }
@Override @Override
protected void onDetachedFromWindow () protected void onDetachedFromWindow()
{ {
super.onDetachedFromWindow(); super.onDetachedFromWindow();
getHolder().removeCallback (this); getHolder().removeCallback (this);
@ -2062,11 +2083,17 @@ public class AudioPluginHost extends Activity
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder); private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder, private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height); int format, int width, int height);
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height);
} }
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr) public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
{ {
return new NativeSurfaceView (this, nativeSurfacePtr); return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
} }
//============================================================================== //==============================================================================
@ -2826,6 +2853,151 @@ public class AudioPluginHost extends Activity
} }
//==============================================================================
public class MediaControllerCallback extends MediaController.Callback
{
private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info);
private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata);
private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);
private native void mediaControllerSessionDestroyed (long host);
MediaControllerCallback (long hostToUse)
{
host = hostToUse;
}
@Override
public void onAudioInfoChanged (MediaController.PlaybackInfo info)
{
mediaControllerAudioInfoChanged (host, info);
}
@Override
public void onMetadataChanged (MediaMetadata metadata)
{
mediaControllerMetadataChanged (host, metadata);
}
@Override
public void onPlaybackStateChanged (PlaybackState state)
{
mediaControllerPlaybackStateChanged (host, state);
}
@Override
public void onQueueChanged (List<MediaSession.QueueItem> queue) {}
@Override
public void onSessionDestroyed()
{
mediaControllerSessionDestroyed (host);
}
private long host;
}
//==============================================================================
public class MediaSessionCallback extends MediaSession.Callback
{
private native void mediaSessionPause (long host);
private native void mediaSessionPlay (long host);
private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);
private native void mediaSessionSeekTo (long host, long pos);
private native void mediaSessionStop (long host);
MediaSessionCallback (long hostToUse)
{
host = hostToUse;
}
@Override
public void onPause()
{
mediaSessionPause (host);
}
@Override
public void onPlay()
{
mediaSessionPlay (host);
}
@Override
public void onPlayFromMediaId (String mediaId, Bundle extras)
{
mediaSessionPlayFromMediaId (host, mediaId, extras);
}
@Override
public void onSeekTo (long pos)
{
mediaSessionSeekTo (host, pos);
}
@Override
public void onStop()
{
mediaSessionStop (host);
}
@Override
public void onFastForward() {}
@Override
public boolean onMediaButtonEvent (Intent mediaButtonIntent)
{
return true;
}
@Override
public void onRewind() {}
@Override
public void onSkipToNext() {}
@Override
public void onSkipToPrevious() {}
@Override
public void onSkipToQueueItem (long id) {}
private long host;
}
//==============================================================================
public class SystemVolumeObserver extends ContentObserver
{
private native void mediaSessionSystemVolumeChanged (long host);
SystemVolumeObserver (Activity activityToUse, long hostToUse)
{
super (null);
activity = activityToUse;
host = hostToUse;
}
void setEnabled (boolean shouldBeEnabled)
{
if (shouldBeEnabled)
activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);
else
activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);
}
@Override
public void onChange (boolean selfChange, Uri uri)
{
if (uri.toString().startsWith ("content://settings/system/volume_music"))
mediaSessionSystemVolumeChanged (host);
}
private Activity activity;
private long host;
}
//============================================================================== //==============================================================================
public static final String getLocaleValue (boolean isRegion) public static final String getLocaleValue (boolean isRegion)
{ {

View file

@ -2436,6 +2436,7 @@
<ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/> <ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>

View file

@ -4065,6 +4065,9 @@
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
<Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>

View file

@ -2436,6 +2436,7 @@
<ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/> <ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>

View file

@ -4065,6 +4065,9 @@
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
<Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>

View file

@ -2436,6 +2436,7 @@
<ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/> <ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>

View file

@ -4065,6 +4065,9 @@
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
<Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>

View file

@ -264,6 +264,10 @@
#ifndef JUCE_USE_CAMERA #ifndef JUCE_USE_CAMERA
#define JUCE_USE_CAMERA 0 #define JUCE_USE_CAMERA 0
#endif #endif
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
//#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
#endif
//============================================================================== //==============================================================================
#ifndef JUCE_STANDALONE_APPLICATION #ifndef JUCE_STANDALONE_APPLICATION
#if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone) #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)

View file

@ -87,8 +87,11 @@ public class JUCENetworkGraphicsDemo extends Activity
//============================================================================== //==============================================================================
public boolean isPermissionDeclaredInManifest (int permissionID) public boolean isPermissionDeclaredInManifest (int permissionID)
{ {
String permissionToCheck = getAndroidPermissionName(permissionID); return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
}
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
{
try try
{ {
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS); PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
@ -1064,11 +1067,13 @@ public class JUCENetworkGraphicsDemo extends Activity
implements SurfaceHolder.Callback implements SurfaceHolder.Callback
{ {
private long nativeContext = 0; private long nativeContext = 0;
private boolean forVideo;
NativeSurfaceView (Context context, long nativeContextPtr) NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
{ {
super (context); super (context);
nativeContext = nativeContextPtr; nativeContext = nativeContextPtr;
forVideo = createdForVideo;
} }
public Surface getNativeSurface() public Surface getNativeSurface()
@ -1086,38 +1091,51 @@ public class JUCENetworkGraphicsDemo extends Activity
@Override @Override
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
{ {
surfaceChangedNative (nativeContext, holder, format, width, height); if (forVideo)
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
else
surfaceChangedNative (nativeContext, holder, format, width, height);
} }
@Override @Override
public void surfaceCreated (SurfaceHolder holder) public void surfaceCreated (SurfaceHolder holder)
{ {
surfaceCreatedNative (nativeContext, holder); if (forVideo)
surfaceCreatedNativeVideo (nativeContext, holder);
else
surfaceCreatedNative (nativeContext, holder);
} }
@Override @Override
public void surfaceDestroyed (SurfaceHolder holder) public void surfaceDestroyed (SurfaceHolder holder)
{ {
surfaceDestroyedNative (nativeContext, holder); if (forVideo)
surfaceDestroyedNativeVideo (nativeContext, holder);
else
surfaceDestroyedNative (nativeContext, holder);
} }
@Override @Override
protected void dispatchDraw (Canvas canvas) protected void dispatchDraw (Canvas canvas)
{ {
super.dispatchDraw (canvas); super.dispatchDraw (canvas);
dispatchDrawNative (nativeContext, canvas);
if (forVideo)
dispatchDrawNativeVideo (nativeContext, canvas);
else
dispatchDrawNative (nativeContext, canvas);
} }
//============================================================================== //==============================================================================
@Override @Override
protected void onAttachedToWindow () protected void onAttachedToWindow()
{ {
super.onAttachedToWindow(); super.onAttachedToWindow();
getHolder().addCallback (this); getHolder().addCallback (this);
} }
@Override @Override
protected void onDetachedFromWindow () protected void onDetachedFromWindow()
{ {
super.onDetachedFromWindow(); super.onDetachedFromWindow();
getHolder().removeCallback (this); getHolder().removeCallback (this);
@ -1129,11 +1147,17 @@ public class JUCENetworkGraphicsDemo extends Activity
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder); private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder, private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height); int format, int width, int height);
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height);
} }
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr) public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
{ {
return new NativeSurfaceView (this, nativeSurfacePtr); return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
} }
//============================================================================== //==============================================================================

View file

@ -1050,6 +1050,7 @@ private:
auto midiCode = getMidiCode (javaSourceFolder, className); auto midiCode = getMidiCode (javaSourceFolder, className);
auto webViewCode = getWebViewCode (javaSourceFolder); auto webViewCode = getWebViewCode (javaSourceFolder);
auto cameraCode = getCameraCode (javaSourceFolder); auto cameraCode = getCameraCode (javaSourceFolder);
auto videoCode = getVideoCode (javaSourceFolder);
auto javaSourceFile = javaSourceFolder.getChildFile ("JuceAppActivity.java"); auto javaSourceFile = javaSourceFolder.getChildFile ("JuceAppActivity.java");
auto javaSourceLines = StringArray::fromLines (javaSourceFile.loadFileAsString()); auto javaSourceLines = StringArray::fromLines (javaSourceFile.loadFileAsString());
@ -1075,6 +1076,10 @@ private:
newFile << cameraCode.imports; newFile << cameraCode.imports;
else if (line.contains ("$$JuceAndroidCameraCode$$")) else if (line.contains ("$$JuceAndroidCameraCode$$"))
newFile << cameraCode.main; newFile << cameraCode.main;
else if (line.contains ("$$JuceAndroidVideoImports$$"))
newFile << videoCode.imports;
else if (line.contains ("$$JuceAndroidVideoCode$$"))
newFile << videoCode.main;
else else
newFile << line.replace ("$$JuceAppActivityBaseClass$$", androidActivityBaseClassName.get().toString()) newFile << line.replace ("$$JuceAppActivityBaseClass$$", androidActivityBaseClassName.get().toString())
.replace ("JuceAppActivity", className) .replace ("JuceAppActivity", className)
@ -1203,13 +1208,12 @@ private:
String juceCameraImports, juceCameraCode; String juceCameraImports, juceCameraCode;
if (static_cast<int> (androidMinimumSDK.get()) >= 21) if (static_cast<int> (androidMinimumSDK.get()) >= 21)
{
juceCameraImports << "import android.hardware.camera2.*;" << newLine; juceCameraImports << "import android.hardware.camera2.*;" << newLine;
auto javaCameraFile = javaSourceFolder.getChildFile ("AndroidCamera.java"); auto javaCameraFile = javaSourceFolder.getChildFile ("AndroidCamera.java");
auto juceCameraCodeAll = javaCameraFile.loadFileAsString(); auto juceCameraCodeAll = javaCameraFile.loadFileAsString();
if (static_cast<int> (androidMinimumSDK.get()) >= 21)
{
juceCameraCode << juceCameraCodeAll.fromFirstOccurrenceOf ("$$CameraApi21", false, false) juceCameraCode << juceCameraCodeAll.fromFirstOccurrenceOf ("$$CameraApi21", false, false)
.upToFirstOccurrenceOf ("CameraApi21$$", false, false); .upToFirstOccurrenceOf ("CameraApi21$$", false, false);
} }
@ -1217,6 +1221,32 @@ private:
return { juceCameraImports, juceCameraCode }; return { juceCameraImports, juceCameraCode };
} }
struct VideoCode
{
String imports;
String main;
};
VideoCode getVideoCode (const File& javaSourceFolder) const
{
String juceVideoImports, juceVideoCode;
if (static_cast<int> (androidMinimumSDK.get()) >= 21)
{
juceVideoImports << "import android.database.ContentObserver;" << newLine;
juceVideoImports << "import android.media.session.*;" << newLine;
juceVideoImports << "import android.media.MediaMetadata;" << newLine;
auto javaVideoFile = javaSourceFolder.getChildFile ("AndroidVideo.java");
auto juceVideoCodeAll = javaVideoFile.loadFileAsString();
juceVideoCode << juceVideoCodeAll.fromFirstOccurrenceOf ("$$VideoApi21", false, false)
.upToFirstOccurrenceOf ("VideoApi21$$", false, false);
}
return { juceVideoImports, juceVideoCode };
}
void copyAdditionalJavaFiles (const File& sourceFolder, const File& targetFolder) const void copyAdditionalJavaFiles (const File& sourceFolder, const File& targetFolder) const
{ {
auto inAppBillingJavaFileName = String ("IInAppBillingService.java"); auto inAppBillingJavaFileName = String ("IInAppBillingService.java");

View file

@ -2642,6 +2642,7 @@
<ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/> <ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>

View file

@ -4473,6 +4473,9 @@
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
<Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>

View file

@ -290,6 +290,10 @@
#ifndef JUCE_USE_CAMERA #ifndef JUCE_USE_CAMERA
//#define JUCE_USE_CAMERA 0 //#define JUCE_USE_CAMERA 0
#endif #endif
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
//#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
#endif
//============================================================================== //==============================================================================
#ifndef JUCE_STANDALONE_APPLICATION #ifndef JUCE_STANDALONE_APPLICATION
#if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone) #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)

View file

@ -2423,6 +2423,7 @@
<ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/> <ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>

View file

@ -4014,6 +4014,9 @@
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
<Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"> <ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
<Filter>JUCE Modules\juce_video\native</Filter> <Filter>JUCE Modules\juce_video\native</Filter>
</ClInclude> </ClInclude>

View file

@ -262,6 +262,10 @@
#ifndef JUCE_USE_CAMERA #ifndef JUCE_USE_CAMERA
//#define JUCE_USE_CAMERA 0 //#define JUCE_USE_CAMERA 0
#endif #endif
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
//#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
#endif
//============================================================================== //==============================================================================
#ifndef JUCE_STANDALONE_APPLICATION #ifndef JUCE_STANDALONE_APPLICATION
#if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone) #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)

View file

@ -0,0 +1,146 @@
$$VideoApi21
//==============================================================================
public class MediaControllerCallback extends MediaController.Callback
{
private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info);
private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata);
private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);
private native void mediaControllerSessionDestroyed (long host);
MediaControllerCallback (long hostToUse)
{
host = hostToUse;
}
@Override
public void onAudioInfoChanged (MediaController.PlaybackInfo info)
{
mediaControllerAudioInfoChanged (host, info);
}
@Override
public void onMetadataChanged (MediaMetadata metadata)
{
mediaControllerMetadataChanged (host, metadata);
}
@Override
public void onPlaybackStateChanged (PlaybackState state)
{
mediaControllerPlaybackStateChanged (host, state);
}
@Override
public void onQueueChanged (List<MediaSession.QueueItem> queue) {}
@Override
public void onSessionDestroyed()
{
mediaControllerSessionDestroyed (host);
}
private long host;
}
//==============================================================================
public class MediaSessionCallback extends MediaSession.Callback
{
private native void mediaSessionPause (long host);
private native void mediaSessionPlay (long host);
private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);
private native void mediaSessionSeekTo (long host, long pos);
private native void mediaSessionStop (long host);
MediaSessionCallback (long hostToUse)
{
host = hostToUse;
}
@Override
public void onPause()
{
mediaSessionPause (host);
}
@Override
public void onPlay()
{
mediaSessionPlay (host);
}
@Override
public void onPlayFromMediaId (String mediaId, Bundle extras)
{
mediaSessionPlayFromMediaId (host, mediaId, extras);
}
@Override
public void onSeekTo (long pos)
{
mediaSessionSeekTo (host, pos);
}
@Override
public void onStop()
{
mediaSessionStop (host);
}
@Override
public void onFastForward() {}
@Override
public boolean onMediaButtonEvent (Intent mediaButtonIntent)
{
return true;
}
@Override
public void onRewind() {}
@Override
public void onSkipToNext() {}
@Override
public void onSkipToPrevious() {}
@Override
public void onSkipToQueueItem (long id) {}
private long host;
}
//==============================================================================
public class SystemVolumeObserver extends ContentObserver
{
private native void mediaSessionSystemVolumeChanged (long host);
SystemVolumeObserver (Activity activityToUse, long hostToUse)
{
super (null);
activity = activityToUse;
host = hostToUse;
}
void setEnabled (boolean shouldBeEnabled)
{
if (shouldBeEnabled)
activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);
else
activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);
}
@Override
public void onChange (boolean selfChange, Uri uri)
{
if (uri.toString().startsWith ("content://settings/system/volume_music"))
mediaSessionSystemVolumeChanged (host);
}
private Activity activity;
private long host;
}
VideoApi21$$

View file

@ -30,7 +30,8 @@ import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
$$JuceAndroidCameraImports$$ // If you get an error here, you need to re-save your project with the Projucer! $$JuceAndroidCameraImports$$ // If you get an error here, you need to re-save your project with the Projucer!
$$JuceAndroidVideoImports$$ // If you get an error here, you need to re-save your project with the Projucer!
import android.net.http.SslError; import android.net.http.SslError;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -87,10 +88,13 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
} }
//============================================================================== //==============================================================================
public boolean isPermissionDeclaredInManifest (int permissionID) public boolean isPermissionDeclaredInManifest (int permissionID)
{
return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
}
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
{ {
String permissionToCheck = getAndroidPermissionName(permissionID);
try try
{ {
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS); PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
@ -982,12 +986,14 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
public static class NativeSurfaceView extends SurfaceView public static class NativeSurfaceView extends SurfaceView
implements SurfaceHolder.Callback implements SurfaceHolder.Callback
{ {
private long nativeContext = 0; private long nativeContext = 0;
private boolean forVideo;
NativeSurfaceView (Context context, long nativeContextPtr) NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
{ {
super (context); super (context);
nativeContext = nativeContextPtr; nativeContext = nativeContextPtr;
forVideo = createdForVideo;
} }
public Surface getNativeSurface() public Surface getNativeSurface()
@ -1004,39 +1010,52 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
//============================================================================== //==============================================================================
@Override @Override
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
{ {
surfaceChangedNative (nativeContext, holder, format, width, height); if (forVideo)
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
else
surfaceChangedNative (nativeContext, holder, format, width, height);
} }
@Override @Override
public void surfaceCreated (SurfaceHolder holder) public void surfaceCreated (SurfaceHolder holder)
{ {
surfaceCreatedNative (nativeContext, holder); if (forVideo)
surfaceCreatedNativeVideo (nativeContext, holder);
else
surfaceCreatedNative (nativeContext, holder);
} }
@Override @Override
public void surfaceDestroyed (SurfaceHolder holder) public void surfaceDestroyed (SurfaceHolder holder)
{ {
surfaceDestroyedNative (nativeContext, holder); if (forVideo)
surfaceDestroyedNativeVideo (nativeContext, holder);
else
surfaceDestroyedNative (nativeContext, holder);
} }
@Override @Override
protected void dispatchDraw (Canvas canvas) protected void dispatchDraw (Canvas canvas)
{ {
super.dispatchDraw (canvas); super.dispatchDraw (canvas);
dispatchDrawNative (nativeContext, canvas);
if (forVideo)
dispatchDrawNativeVideo (nativeContext, canvas);
else
dispatchDrawNative (nativeContext, canvas);
} }
//============================================================================== //==============================================================================
@Override @Override
protected void onAttachedToWindow () protected void onAttachedToWindow()
{ {
super.onAttachedToWindow(); super.onAttachedToWindow();
getHolder().addCallback (this); getHolder().addCallback (this);
} }
@Override @Override
protected void onDetachedFromWindow () protected void onDetachedFromWindow()
{ {
super.onDetachedFromWindow(); super.onDetachedFromWindow();
getHolder().removeCallback (this); getHolder().removeCallback (this);
@ -1047,12 +1066,18 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder); private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder); private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder, private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height); int format, int width, int height);
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
int format, int width, int height);
} }
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr) public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
{ {
return new NativeSurfaceView (this, nativeSurfacePtr); return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
} }
//============================================================================== //==============================================================================
@ -1610,7 +1635,8 @@ $$JuceAndroidWebViewNativeCode$$ // If you get an error here, you need to re-sav
private final Object hostLock = new Object(); private final Object hostLock = new Object();
} }
$$JuceAndroidCameraCode$$ // If you get an error here, you need to re-save your project with the Projucer! $$JuceAndroidCameraCode$$ // If you get an error here, you need to re-save your project with the Projucer!
$$JuceAndroidVideoCode$$ // If you get an error here, you need to re-save your project with the Projucer!
//============================================================================== //==============================================================================
public static final String getLocaleValue (boolean isRegion) public static final String getLocaleValue (boolean isRegion)

View file

@ -188,6 +188,13 @@ private:
uri.get(), projection.get(), jSelection.get(), uri.get(), projection.get(), jSelection.get(),
args.get(), nullptr)); args.get(), nullptr));
if (jniCheckHasExceptionOccurredAndClear())
{
// An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission?
jassertfalse;
return {};
}
if (cursor) if (cursor)
{ {
if (env->CallBooleanMethod (cursor.get(), AndroidCursor.moveToFirst) != 0) if (env->CallBooleanMethod (cursor.get(), AndroidCursor.moveToFirst) != 0)
@ -380,6 +387,13 @@ private:
uri.get(), projection.get(), nullptr, uri.get(), projection.get(), nullptr,
nullptr, nullptr)); nullptr, nullptr));
if (jniCheckHasExceptionOccurredAndClear())
{
// An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission?
jassertfalse;
return {};
}
if (cursor == 0) if (cursor == 0)
return {}; return {};

View file

@ -250,60 +250,79 @@ extern AndroidSystem android;
//============================================================================== //==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \
METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \
METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(J)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \ METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(JZ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \
METHOD (finish, "finish", "()V") \ METHOD (finish, "finish", "()V") \
METHOD (getWindowManager, "getWindowManager", "()Landroid/view/WindowManager;") \ METHOD (getWindowManager, "getWindowManager", "()Landroid/view/WindowManager;") \
METHOD (setRequestedOrientation, "setRequestedOrientation", "(I)V") \ METHOD (setRequestedOrientation, "setRequestedOrientation", "(I)V") \
METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \
METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \
METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \
METHOD (renderGlyph, "renderGlyph", "(CCLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ METHOD (renderGlyph, "renderGlyph", "(CCLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \
STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \
METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \ METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \
METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \
METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;)V") \ METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;)V") \
METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \
STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \ STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \
STATICMETHOD (getDocumentsFolder, "getDocumentsFolder", "()Ljava/lang/String;") \ STATICMETHOD (getDocumentsFolder, "getDocumentsFolder", "()Ljava/lang/String;") \
STATICMETHOD (getPicturesFolder, "getPicturesFolder", "()Ljava/lang/String;") \ STATICMETHOD (getPicturesFolder, "getPicturesFolder", "()Ljava/lang/String;") \
STATICMETHOD (getMusicFolder, "getMusicFolder", "()Ljava/lang/String;") \ STATICMETHOD (getMusicFolder, "getMusicFolder", "()Ljava/lang/String;") \
STATICMETHOD (getDownloadsFolder, "getDownloadsFolder", "()Ljava/lang/String;") \ STATICMETHOD (getDownloadsFolder, "getDownloadsFolder", "()Ljava/lang/String;") \
STATICMETHOD (getMoviesFolder, "getMoviesFolder", "()Ljava/lang/String;") \ STATICMETHOD (getMoviesFolder, "getMoviesFolder", "()Ljava/lang/String;") \
METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
METHOD (getTypeFaceFromByteArray, "getTypeFaceFromByteArray", "([B)Landroid/graphics/Typeface;") \ METHOD (getTypeFaceFromByteArray, "getTypeFaceFromByteArray", "([B)Landroid/graphics/Typeface;") \
METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \ METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \
METHOD (getScreenSaver, "getScreenSaver", "()Z") \ METHOD (getScreenSaver, "getScreenSaver", "()Z") \
METHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager;") \ METHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager;") \
METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \
STATICMETHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ STATICMETHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \
METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \
METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \
METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \
METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \
METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \
METHOD (getAssets, "getAssets", "()Landroid/content/res/AssetManager;") \ METHOD (isPermissionDeclaredInManifestString, "isPermissionDeclaredInManifest", "(Ljava/lang/String;)Z") \
METHOD (getSystemService, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;") \ METHOD (getAssets, "getAssets", "()Landroid/content/res/AssetManager;") \
METHOD (getPackageManager, "getPackageManager", "()Landroid/content/pm/PackageManager;") \ METHOD (getSystemService, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;") \
METHOD (getPackageName, "getPackageName", "()Ljava/lang/String;") \ METHOD (getPackageManager, "getPackageManager", "()Landroid/content/pm/PackageManager;") \
METHOD (getResources, "getResources", "()Landroid/content/res/Resources;") \ METHOD (getPackageName, "getPackageName", "()Ljava/lang/String;") \
METHOD (createInvocationHandler, "createInvocationHandler", "(J)Ljava/lang/reflect/InvocationHandler;") \ METHOD (getResources, "getResources", "()Landroid/content/res/Resources;") \
METHOD (invocationHandlerContextDeleted, "invocationHandlerContextDeleted", "(Ljava/lang/reflect/InvocationHandler;)V") \ METHOD (createInvocationHandler, "createInvocationHandler", "(J)Ljava/lang/reflect/InvocationHandler;") \
METHOD (bindService, "bindService", "(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z") \ METHOD (invocationHandlerContextDeleted, "invocationHandlerContextDeleted", "(Ljava/lang/reflect/InvocationHandler;)V") \
METHOD (unbindService, "unbindService", "(Landroid/content/ServiceConnection;)V") \ METHOD (bindService, "bindService", "(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z") \
METHOD (startIntentSenderForResult, "startIntentSenderForResult", "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V") \ METHOD (unbindService, "unbindService", "(Landroid/content/ServiceConnection;)V") \
METHOD (moveTaskToBack, "moveTaskToBack", "(Z)Z") \ METHOD (startIntentSenderForResult, "startIntentSenderForResult", "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V") \
METHOD (startActivity, "startActivity", "(Landroid/content/Intent;)V") \ METHOD (moveTaskToBack, "moveTaskToBack", "(Z)Z") \
METHOD (startActivityForResult, "startActivityForResult", "(Landroid/content/Intent;I)V") \ METHOD (startActivity, "startActivity", "(Landroid/content/Intent;)V") \
METHOD (getContentResolver, "getContentResolver", "()Landroid/content/ContentResolver;") \ METHOD (startActivityForResult, "startActivityForResult", "(Landroid/content/Intent;I)V") \
METHOD (addAppPausedResumedListener, "addAppPausedResumedListener", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener;J)V") \ METHOD (getContentResolver, "getContentResolver", "()Landroid/content/ContentResolver;") \
METHOD (removeAppPausedResumedListener, "removeAppPausedResumedListener", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener;J)V") METHOD (addAppPausedResumedListener, "addAppPausedResumedListener", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener;J)V") \
METHOD (removeAppPausedResumedListener, "removeAppPausedResumedListener", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener;J)V")
DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH);
#undef JNI_CLASS_MEMBERS #undef JNI_CLASS_MEMBERS
//============================================================================== //==============================================================================
#if __ANDROID_API__ >= 21
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (build, "build", "()Landroid/media/AudioAttributes;") \
METHOD (constructor, "<init>", "()V") \
METHOD (setContentType, "setContentType", "(I)Landroid/media/AudioAttributes$Builder;") \
METHOD (setUsage, "setUsage", "(I)Landroid/media/AudioAttributes$Builder;")
DECLARE_JNI_CLASS (AndroidAudioAttributesBuilder, "android/media/AudioAttributes$Builder")
#undef JNI_CLASS_MEMBERS
#endif
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (abandonAudioFocus, "abandonAudioFocus", "(Landroid/media/AudioManager$OnAudioFocusChangeListener;)I") \
METHOD (requestAudioFocus, "requestAudioFocus", "(Landroid/media/AudioManager$OnAudioFocusChangeListener;II)I")
DECLARE_JNI_CLASS (AndroidAudioManager, "android/media/AudioManager");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (createBitmap, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;") \ STATICMETHOD (createBitmap, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;") \
STATICMETHOD (createBitmapFrom, "createBitmap", "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)Landroid/graphics/Bitmap;") \ STATICMETHOD (createBitmapFrom, "createBitmap", "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)Landroid/graphics/Bitmap;") \
@ -741,6 +760,21 @@ namespace
return result; return result;
} }
inline bool jniCheckHasExceptionOccurredAndClear()
{
auto* env = getEnv();
LocalRef<jobject> exception (env->ExceptionOccurred());
if (exception != nullptr)
{
env->ExceptionClear();
return true;
}
return false;
}
} }
//============================================================================== //==============================================================================
@ -778,4 +812,24 @@ LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
const String& interfaceName); const String& interfaceName);
//==============================================================================
class AppPausedResumedListener : public AndroidInterfaceImplementer
{
public:
struct Owner
{
virtual ~Owner() {}
virtual void appPaused() = 0;
virtual void appResumed() = 0;
};
AppPausedResumedListener (Owner&);
jobject invoke (jobject proxy, jobject method, jobjectArray args) override;
private:
Owner& owner;
};
} // namespace juce } // namespace juce

View file

@ -200,6 +200,35 @@ JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024Nativ
juce_dispatchDelete (env, thisPtr); juce_dispatchDelete (env, thisPtr);
} }
//==============================================================================
AppPausedResumedListener::AppPausedResumedListener (Owner& ownerToUse)
: owner (ownerToUse)
{
}
jobject AppPausedResumedListener::invoke (jobject proxy, jobject method, jobjectArray args)
{
auto* env = getEnv();
auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
int numArgs = args != nullptr ? env->GetArrayLength (args) : 0;
if (methodName == "appPaused" && numArgs == 0)
{
owner.appPaused();
return nullptr;
}
if (methodName == "appResumed" && numArgs == 0)
{
owner.appResumed();
return nullptr;
}
return AndroidInterfaceImplementer::invoke (proxy, method, args);
}
//============================================================================== //==============================================================================
JavaVM* androidJNIJavaVM = nullptr; JavaVM* androidJNIJavaVM = nullptr;

View file

@ -307,14 +307,10 @@ private:
auto inputStream = StreamCloser (LocalRef<jobject> (env->CallObjectMethod (assetFd, auto inputStream = StreamCloser (LocalRef<jobject> (env->CallObjectMethod (assetFd,
AssetFileDescriptor.createInputStream))); AssetFileDescriptor.createInputStream)));
auto exception = LocalRef<jobject> (env->ExceptionOccurred()); if (jniCheckHasExceptionOccurredAndClear())
if (exception != 0)
{ {
// Failed to open file stream for resource // Failed to open file stream for resource
jassertfalse; jassertfalse;
env->ExceptionClear();
return {}; return {};
} }
@ -326,14 +322,10 @@ private:
JavaFileOutputStream.constructor, JavaFileOutputStream.constructor,
javaString (tempFile.getFullPathName()).get()))); javaString (tempFile.getFullPathName()).get())));
exception = LocalRef<jobject> (env->ExceptionOccurred()); if (jniCheckHasExceptionOccurredAndClear())
if (exception != 0)
{ {
// Failed to open file stream for temporary file // Failed to open file stream for temporary file
jassertfalse; jassertfalse;
env->ExceptionClear();
return {}; return {};
} }
@ -347,14 +339,10 @@ private:
bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get()); bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get());
exception = LocalRef<jobject> (env->ExceptionOccurred()); if (jniCheckHasExceptionOccurredAndClear())
if (exception != 0)
{ {
// Failed to read from resource file. // Failed to read from resource file.
jassertfalse; jassertfalse;
env->ExceptionClear();
return {}; return {};
} }
@ -363,12 +351,10 @@ private:
env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead); env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead);
if (exception != 0) if (jniCheckHasExceptionOccurredAndClear())
{ {
// Failed to write to temporary file. // Failed to write to temporary file.
jassertfalse; jassertfalse;
env->ExceptionClear();
return {}; return {};
} }
} }
@ -714,14 +700,10 @@ private:
ParcelFileDescriptor.open, ParcelFileDescriptor.open,
javaFile.get(), modeReadOnly)); javaFile.get(), modeReadOnly));
auto exception = LocalRef<jobject> (env->ExceptionOccurred()); if (jniCheckHasExceptionOccurredAndClear())
if (exception != 0)
{ {
// Failed to create file descriptor. Have you provided a valid file path/resource name? // Failed to create file descriptor. Have you provided a valid file path/resource name?
jassertfalse; jassertfalse;
env->ExceptionClear();
return nullptr; return nullptr;
} }

View file

@ -27,17 +27,6 @@
namespace juce namespace juce
{ {
#if __ANDROID_API__ >= 21
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (build, "build", "()Landroid/media/AudioAttributes;") \
METHOD (constructor, "<init>", "()V") \
METHOD (setContentType, "setContentType", "(I)Landroid/media/AudioAttributes$Builder;") \
METHOD (setUsage, "setUsage", "(I)Landroid/media/AudioAttributes$Builder;")
DECLARE_JNI_CLASS (AudioAttributesBuilder, "android/media/AudioAttributes$Builder")
#undef JNI_CLASS_MEMBERS
#endif
#if __ANDROID_API__ >= 26 #if __ANDROID_API__ >= 26
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \ METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \
@ -1504,12 +1493,12 @@ struct PushNotifications::Pimpl
env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration); env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration);
} }
auto audioAttributesBuilder = LocalRef<jobject> (env->NewObject (AudioAttributesBuilder, AudioAttributesBuilder.constructor)); auto AndroidAudioAttributesBuilder = LocalRef<jobject> (env->NewObject (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.constructor));
const int contentTypeSonification = 4; const int contentTypeSonification = 4;
const int usageNotification = 5; const int usageNotification = 5;
env->CallObjectMethod (audioAttributesBuilder, AudioAttributesBuilder.setContentType, contentTypeSonification); env->CallObjectMethod (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.setContentType, contentTypeSonification);
env->CallObjectMethod (audioAttributesBuilder, AudioAttributesBuilder.setUsage, usageNotification); env->CallObjectMethod (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.setUsage, usageNotification);
auto audioAttributes = LocalRef<jobject> (env->CallObjectMethod (audioAttributesBuilder, AudioAttributesBuilder.build)); auto audioAttributes = LocalRef<jobject> (env->CallObjectMethod (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.build));
env->CallVoidMethod (channel, NotificationChannel.setSound, juceUrlToAndroidUri (c.soundToPlay).get(), audioAttributes.get()); env->CallVoidMethod (channel, NotificationChannel.setSound, juceUrlToAndroidUri (c.soundToPlay).get(), audioAttributes.get());
env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannel, channel.get()); env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannel, channel.get());

View file

@ -61,7 +61,8 @@ public:
// create a native surface view // create a native surface view
surfaceView = GlobalRef (env->CallObjectMethod (android.activity.get(), surfaceView = GlobalRef (env->CallObjectMethod (android.activity.get(),
JuceAppActivity.createNativeSurfaceView, JuceAppActivity.createNativeSurfaceView,
reinterpret_cast<jlong> (this))); reinterpret_cast<jlong> (this),
false));
if (surfaceView.get() == nullptr) if (surfaceView.get() == nullptr)
return; return;

View file

@ -79,6 +79,25 @@
#undef JUCE_USE_CAMERA #undef JUCE_USE_CAMERA
#endif #endif
//=============================================================================
/** Config: JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
Enables synchronisation between video playback volume and OS media volume.
Currently supported on Android only.
*/
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
#endif
#ifndef JUCE_VIDEO_LOG_ENABLED
#define JUCE_VIDEO_LOG_ENABLED 1
#endif
#if JUCE_VIDEO_LOG_ENABLED
#define JUCE_VIDEO_LOG(x) DBG(x)
#else
#define JUCE_VIDEO_LOG(x) {}
#endif
//============================================================================= //=============================================================================
#include "playback/juce_VideoComponent.h" #include "playback/juce_VideoComponent.h"
#include "capture/juce_CameraDevice.h" #include "capture/juce_CameraDevice.h"

View file

@ -442,49 +442,6 @@ private:
Owner& owner; Owner& owner;
}; };
//==============================================================================
class AppPausedResumedListener : public AndroidInterfaceImplementer
{
public:
struct Owner
{
virtual ~Owner() {}
virtual void appPaused() = 0;
virtual void appResumed() = 0;
};
AppPausedResumedListener (Owner& ownerToUse)
: owner (ownerToUse)
{}
jobject invoke (jobject proxy, jobject method, jobjectArray args) override
{
auto* env = getEnv();
auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
int numArgs = args != nullptr ? env->GetArrayLength (args) : 0;
if (methodName == "appPaused" && numArgs == 0)
{
owner.appPaused();
return nullptr;
}
if (methodName == "appResumed" && numArgs == 0)
{
owner.appResumed();
return nullptr;
}
return AndroidInterfaceImplementer::invoke (proxy, method, args);
}
private:
Owner& owner;
};
//============================================================================== //==============================================================================
struct CameraDevice::Pimpl struct CameraDevice::Pimpl
#if __ANDROID_API__ >= 21 #if __ANDROID_API__ >= 21
@ -506,7 +463,6 @@ struct CameraDevice::Pimpl
appPausedResumedListener (*this), appPausedResumedListener (*this),
appPausedResumedListenerNative (CreateJavaInterface (&appPausedResumedListener, appPausedResumedListenerNative (CreateJavaInterface (&appPausedResumedListener,
JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener").get()), JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener").get()),
cameraManager (initialiseCameraManager()), cameraManager (initialiseCameraManager()),
cameraCharacteristics (initialiseCameraCharacteristics (cameraManager, cameraId)), cameraCharacteristics (initialiseCameraCharacteristics (cameraManager, cameraId)),
streamConfigurationMap (cameraCharacteristics), streamConfigurationMap (cameraCharacteristics),
@ -869,7 +825,7 @@ private:
bool isOutputSupportedForSurface (const LocalRef<jobject>& surface) const bool isOutputSupportedForSurface (const LocalRef<jobject>& surface) const
{ {
return getEnv()->CallBooleanMethod (scalerStreamConfigurationMap, AndroidStreamConfigurationMap.isOutputSupportedForSurface, surface.get()); return getEnv()->CallBooleanMethod (scalerStreamConfigurationMap, AndroidStreamConfigurationMap.isOutputSupportedForSurface, surface.get()) != 0;
} }
static constexpr int jpegImageFormat = 256; static constexpr int jpegImageFormat = 256;
@ -1460,10 +1416,7 @@ private:
// ... ignore RuntimeException that can be thrown if stop() was called after recording // ... ignore RuntimeException that can be thrown if stop() was called after recording
// has started but before any frame was written to a file. This is not an error. // has started but before any frame was written to a file. This is not an error.
auto exception = LocalRef<jobject> (env->ExceptionOccurred()); jniCheckHasExceptionOccurredAndClear();
if (exception != 0)
env->ExceptionClear();
unlockScreenOrientation(); unlockScreenOrientation();
} }
@ -1630,16 +1583,12 @@ private:
} }
} }
auto exception = LocalRef<jobject> (env->ExceptionOccurred());
// When exception occurs, CameraCaptureSession.close will never finish, so // When exception occurs, CameraCaptureSession.close will never finish, so
// we should not wait for it. For fatal error an exception does occur, but // we should not wait for it. For fatal error an exception does occur, but
// it is catched internally in Java... // it is catched internally in Java...
if (exception != 0 || scopedCameraDevice.fatalErrorOccurred.get()) if (jniCheckHasExceptionOccurredAndClear() || scopedCameraDevice.fatalErrorOccurred.get())
{ {
JUCE_CAMERA_LOG ("Exception or fatal error occurred while closing Capture Session, closing by force"); JUCE_CAMERA_LOG ("Exception or fatal error occurred while closing Capture Session, closing by force");
env->ExceptionClear();
} }
else if (calledClose) else if (calledClose)
{ {
@ -1768,7 +1717,7 @@ private:
void lockFocus() void lockFocus()
{ {
if (Pimpl::checkHasExceptionOccurred()) if (jniCheckHasExceptionOccurredAndClear())
return; return;
JUCE_CAMERA_LOG ("Performing auto-focus if possible..."); JUCE_CAMERA_LOG ("Performing auto-focus if possible...");
@ -1796,7 +1745,7 @@ private:
// IllegalStateException can be thrown when accessing CaptureSession, // IllegalStateException can be thrown when accessing CaptureSession,
// claiming that capture session was already closed but we may not // claiming that capture session was already closed but we may not
// get relevant callback yet, so check for this and bailout when needed. // get relevant callback yet, so check for this and bailout when needed.
if (Pimpl::checkHasExceptionOccurred()) if (jniCheckHasExceptionOccurredAndClear())
return; return;
auto* env = getEnv(); auto* env = getEnv();
@ -1902,7 +1851,7 @@ private:
void captureStillPictureDelayed() void captureStillPictureDelayed()
{ {
if (Pimpl::checkHasExceptionOccurred()) if (jniCheckHasExceptionOccurredAndClear())
return; return;
JUCE_CAMERA_LOG ("Still picture capture, device ready, capturing now..."); JUCE_CAMERA_LOG ("Still picture capture, device ready, capturing now...");
@ -1911,12 +1860,12 @@ private:
env->CallVoidMethod (captureSession, CameraCaptureSession.stopRepeating); env->CallVoidMethod (captureSession, CameraCaptureSession.stopRepeating);
if (Pimpl::checkHasExceptionOccurred()) if (jniCheckHasExceptionOccurredAndClear())
return; return;
env->CallVoidMethod (captureSession, CameraCaptureSession.abortCaptures); env->CallVoidMethod (captureSession, CameraCaptureSession.abortCaptures);
if (Pimpl::checkHasExceptionOccurred()) if (jniCheckHasExceptionOccurredAndClear())
return; return;
// Delay still picture capture for devices that can't handle it right after // Delay still picture capture for devices that can't handle it right after
@ -1929,7 +1878,7 @@ private:
void runPrecaptureSequence() void runPrecaptureSequence()
{ {
if (Pimpl::checkHasExceptionOccurred()) if (jniCheckHasExceptionOccurredAndClear())
return; return;
auto* env = getEnv(); auto* env = getEnv();
@ -1950,7 +1899,7 @@ private:
void unlockFocus() void unlockFocus()
{ {
if (Pimpl::checkHasExceptionOccurred()) if (jniCheckHasExceptionOccurredAndClear())
return; return;
JUCE_CAMERA_LOG ("Unlocking focus..."); JUCE_CAMERA_LOG ("Unlocking focus...");
@ -1970,7 +1919,7 @@ private:
env->CallIntMethod (captureSession, CameraCaptureSession.capture, resetAutoFocusRequest.get(), env->CallIntMethod (captureSession, CameraCaptureSession.capture, resetAutoFocusRequest.get(),
nullptr, handler.get()); nullptr, handler.get());
if (Pimpl::checkHasExceptionOccurred()) if (jniCheckHasExceptionOccurredAndClear())
return; return;
// NB: for preview, using preview capture request again // NB: for preview, using preview capture request again
@ -2233,10 +2182,7 @@ private:
// If something went wrong we will be pinged in cameraDeviceStateError() // If something went wrong we will be pinged in cameraDeviceStateError()
// callback, silence the redundant exception. // callback, silence the redundant exception.
auto exception = LocalRef<jobject> (env->ExceptionOccurred()); jniCheckHasExceptionOccurredAndClear();
if (exception != 0)
env->ExceptionClear();
} }
void close() void close()
@ -2500,12 +2446,12 @@ private:
env->CallVoidMethod (session, CameraCaptureSession.stopRepeating); env->CallVoidMethod (session, CameraCaptureSession.stopRepeating);
if (Pimpl::checkHasExceptionOccurred()) if (jniCheckHasExceptionOccurredAndClear())
return; return;
env->CallVoidMethod (session, CameraCaptureSession.abortCaptures); env->CallVoidMethod (session, CameraCaptureSession.abortCaptures);
Pimpl::checkHasExceptionOccurred(); jniCheckHasExceptionOccurredAndClear();
} }
} }
@ -3064,29 +3010,11 @@ private:
env->CallBooleanMethod (handlerThread, AndroidHandlerThread.quitSafely); env->CallBooleanMethod (handlerThread, AndroidHandlerThread.quitSafely);
env->CallVoidMethod (handlerThread, AndroidHandlerThread.join); env->CallVoidMethod (handlerThread, AndroidHandlerThread.join);
auto exception = LocalRef<jobject> (env->ExceptionOccurred()); jniCheckHasExceptionOccurredAndClear();
if (exception != 0)
env->ExceptionClear();
handlerThread.clear(); handlerThread.clear();
handler.clear(); handler.clear();
} }
static bool checkHasExceptionOccurred()
{
auto* env = getEnv();
auto exception = LocalRef<jobject> (env->ExceptionOccurred());
if (exception != 0)
{
env->ExceptionClear();
return true;
}
return false;
}
#endif #endif
friend struct CameraDevice::ViewerComponent; friend struct CameraDevice::ViewerComponent;

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
============================================================================== ==============================================================================
This file is part of the JUCE library. This file is part of the JUCE library.
Copyright (c) 2015 - ROLI Ltd. Copyright (c) 2018 - ROLI Ltd.
Permission is granted to use this software under the terms of either: Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version) a) the GPL v2 (or any later version)
@ -22,34 +22,26 @@
============================================================================== ==============================================================================
*/ */
#if JUCE_IOS #if JUCE_MAC
using BaseClass = UIViewComponent; using Base = NSViewComponent;
#else #else
using BaseClass = NSViewComponent; using Base = UIViewComponent;
#endif #endif
struct VideoComponent::Pimpl : public BaseClass struct VideoComponent::Pimpl : public Base
{ {
Pimpl() Pimpl (VideoComponent& ownerToUse, bool useNativeControlsIfAvailable)
: owner (ownerToUse),
playerController (*this, useNativeControlsIfAvailable)
{ {
setVisible (true); setVisible (true);
#if JUCE_MAC && JUCE_32BIT auto* view = playerController.getView();
auto view = [[NSView alloc] init]; // 32-bit builds don't have AVPlayerView, so need to use a layer
controller = [[AVPlayerLayer alloc] init];
setView (view); setView (view);
#if JUCE_MAC
[view setNextResponder: [view superview]]; [view setNextResponder: [view superview]];
[view setWantsLayer: YES]; [view setWantsLayer: YES];
[view setLayer: controller];
[view release];
#elif JUCE_MAC
controller = [[AVPlayerView alloc] init];
setView (controller);
[controller setNextResponder: [controller superview]];
[controller setWantsLayer: YES];
#else
controller = [[AVPlayerViewController alloc] init];
setView ([controller view]);
#endif #endif
} }
@ -57,7 +49,6 @@ struct VideoComponent::Pimpl : public BaseClass
{ {
close(); close();
setView (nil); setView (nil);
[controller release];
} }
Result load (const File& file) Result load (const File& file)
@ -85,34 +76,46 @@ struct VideoComponent::Pimpl : public BaseClass
if (url != nil) if (url != nil)
{ {
close(); close();
return playerController.load (url);
if (auto* player = [AVPlayer playerWithURL: url])
{
[controller setPlayer: player];
return Result::ok();
}
} }
return Result::fail ("Couldn't open movie"); return Result::fail ("Couldn't open movie");
} }
void loadAsync (const URL& url, std::function<void (const URL&, Result)> callback)
{
if (url.isEmpty())
{
jassertfalse;
return;
}
currentURL = url;
jassert (callback != nullptr);
loadFinishedCallback = std::move (callback);
playerController.loadAsync (url);
}
void close() void close()
{ {
stop(); stop();
[controller setPlayer: nil]; playerController.close();
currentFile = File(); currentFile = File();
currentURL = {}; currentURL = {};
} }
bool isOpen() const noexcept { return getAVPlayer() != nil; } bool isOpen() const noexcept { return playerController.getPlayer() != nil; }
bool isPlaying() const noexcept { return getSpeed() != 0; } bool isPlaying() const noexcept { return getSpeed() != 0; }
void play() noexcept { [getAVPlayer() play]; } void play() noexcept { [playerController.getPlayer() play]; setSpeed (playSpeedMult); }
void stop() noexcept { [getAVPlayer() pause]; } void stop() noexcept { [playerController.getPlayer() pause]; }
void setPosition (double newPosition) void setPosition (double newPosition)
{ {
if (auto* p = getAVPlayer()) if (auto* p = playerController.getPlayer())
{ {
CMTime t = { (CMTimeValue) (100000.0 * newPosition), CMTime t = { (CMTimeValue) (100000.0 * newPosition),
(CMTimeScale) 100000, kCMTimeFlags_Valid }; (CMTimeScale) 100000, kCMTimeFlags_Valid };
@ -125,7 +128,7 @@ struct VideoComponent::Pimpl : public BaseClass
double getPosition() const double getPosition() const
{ {
if (auto* p = getAVPlayer()) if (auto* p = playerController.getPlayer())
return toSeconds ([p currentTime]); return toSeconds ([p currentTime]);
return 0.0; return 0.0;
@ -133,12 +136,16 @@ struct VideoComponent::Pimpl : public BaseClass
void setSpeed (double newSpeed) void setSpeed (double newSpeed)
{ {
[getAVPlayer() setRate: (float) newSpeed]; playSpeedMult = newSpeed;
// Calling non 0.0 speed on a paused player would start it...
if (isPlaying())
[playerController.getPlayer() setRate: (float) playSpeedMult];
} }
double getSpeed() const double getSpeed() const
{ {
if (auto* p = getAVPlayer()) if (auto* p = playerController.getPlayer())
return [p rate]; return [p rate];
return 0.0; return 0.0;
@ -146,9 +153,9 @@ struct VideoComponent::Pimpl : public BaseClass
Rectangle<int> getNativeSize() const Rectangle<int> getNativeSize() const
{ {
if (auto* player = getAVPlayer()) if (auto* p = playerController.getPlayer())
{ {
auto s = [[player currentItem] presentationSize]; auto s = [[p currentItem] presentationSize];
return { (int) s.width, (int) s.height }; return { (int) s.width, (int) s.height };
} }
@ -157,20 +164,20 @@ struct VideoComponent::Pimpl : public BaseClass
double getDuration() const double getDuration() const
{ {
if (auto* player = getAVPlayer()) if (auto* p = playerController.getPlayer())
return toSeconds ([[player currentItem] duration]); return toSeconds ([[p currentItem] duration]);
return 0.0; return 0.0;
} }
void setVolume (float newVolume) void setVolume (float newVolume)
{ {
[getAVPlayer() setVolume: newVolume]; [playerController.getPlayer() setVolume: newVolume];
} }
float getVolume() const float getVolume() const
{ {
if (auto* p = getAVPlayer()) if (auto* p = playerController.getPlayer())
return [p volume]; return [p volume];
return 0.0f; return 0.0f;
@ -180,20 +187,633 @@ struct VideoComponent::Pimpl : public BaseClass
URL currentURL; URL currentURL;
private: private:
#if JUCE_IOS //==============================================================================
AVPlayerViewController* controller = nil; template <typename Derived>
#elif JUCE_32BIT class PlayerControllerBase
AVPlayerLayer* controller = nil; {
public:
~PlayerControllerBase()
{
detachPlayerStatusObserver();
detachPlaybackObserver();
}
protected:
//==============================================================================
struct JucePlayerStatusObserverClass : public ObjCClass<NSObject>
{
JucePlayerStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerStatusObserverClass_")
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged, "v@:@@@?");
#pragma clang diagnostic pop
addIvar<PlayerAsyncInitialiser*> ("owner");
registerClass();
}
//==============================================================================
static PlayerControllerBase& getOwner (id self) { return *getIvar<PlayerControllerBase*> (self, "owner"); }
static void setOwner (id self, PlayerControllerBase* p) { object_setInstanceVariable (self, "owner", p); }
private:
static void valueChanged (id self, SEL, NSString* keyPath, id,
NSDictionary<NSKeyValueChangeKey, id>* change, void*)
{
auto& owner = getOwner (self);
if ([keyPath isEqualToString: nsStringLiteral ("rate")])
{
auto oldRate = [change[NSKeyValueChangeOldKey] floatValue];
auto newRate = [change[NSKeyValueChangeNewKey] floatValue];
if (oldRate == 0 && newRate != 0)
owner.playbackStarted();
else if (oldRate != 0 && newRate == 0)
owner.playbackStopped();
}
else if ([keyPath isEqualToString: nsStringLiteral ("status")])
{
auto status = [change[NSKeyValueChangeNewKey] intValue];
if (status == AVPlayerStatusFailed)
owner.errorOccurred();
}
}
};
//==============================================================================
struct JucePlayerItemPlaybackStatusObserverClass : public ObjCClass<NSObject>
{
JucePlayerItemPlaybackStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerItemPlaybackStatusObserverClass_")
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
addMethod (@selector (processNotification:), notificationReceived, "v@:@");
#pragma clang diagnostic pop
addIvar<PlayerControllerBase*> ("owner");
registerClass();
}
//==============================================================================
static PlayerControllerBase& getOwner (id self) { return *getIvar<PlayerControllerBase*> (self, "owner"); }
static void setOwner (id self, PlayerControllerBase* p) { object_setInstanceVariable (self, "owner", p); }
private:
static void notificationReceived (id self, SEL, NSNotification* notification)
{
if ([notification.name isEqualToString: AVPlayerItemDidPlayToEndTimeNotification])
getOwner (self).playbackReachedEndTime();
}
};
//==============================================================================
class PlayerAsyncInitialiser
{
public:
PlayerAsyncInitialiser (PlayerControllerBase& ownerToUse)
: owner (ownerToUse),
assetKeys ([[NSArray alloc] initWithObjects: nsStringLiteral ("duration"), nsStringLiteral ("tracks"),
nsStringLiteral ("playable"), nil])
{
static JucePlayerItemPreparationStatusObserverClass cls;
playerItemPreparationStatusObserver.reset ([cls.createInstance() init]);
JucePlayerItemPreparationStatusObserverClass::setOwner (playerItemPreparationStatusObserver.get(), this);
}
~PlayerAsyncInitialiser()
{
detachPreparationStatusObserver();
}
void loadAsync (URL url)
{
auto* nsUrl = [NSURL URLWithString: juceStringToNS (url.toString (true))];
asset.reset ([[AVURLAsset alloc] initWithURL: nsUrl options: nil]);
[asset.get() loadValuesAsynchronouslyForKeys: assetKeys.get()
completionHandler: ^() { checkAllKeysReadyFor (asset.get(), url); }];
}
private:
//==============================================================================
struct JucePlayerItemPreparationStatusObserverClass : public ObjCClass<NSObject>
{
JucePlayerItemPreparationStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerItemStatusObserverClass_")
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged, "v@:@@@?");
#pragma clang diagnostic pop
addIvar<PlayerAsyncInitialiser*> ("owner");
registerClass();
}
//==============================================================================
static PlayerAsyncInitialiser& getOwner (id self) { return *getIvar<PlayerAsyncInitialiser*> (self, "owner"); }
static void setOwner (id self, PlayerAsyncInitialiser* p) { object_setInstanceVariable (self, "owner", p); }
private:
static void valueChanged (id self, SEL, NSString*, id object,
NSDictionary<NSKeyValueChangeKey, id>* change, void* context)
{
auto& owner = getOwner (self);
if (context == &owner)
{
auto* playerItem = (AVPlayerItem*) object;
auto* urlAsset = (AVURLAsset*) playerItem.asset;
URL url (nsStringToJuce (urlAsset.URL.absoluteString));
auto oldStatus = [change[NSKeyValueChangeOldKey] intValue];
auto newStatus = [change[NSKeyValueChangeNewKey] intValue];
// Ignore spurious notifications
if (oldStatus == newStatus)
return;
if (newStatus == AVPlayerItemStatusFailed)
{
auto errorMessage = playerItem.error != nil
? nsStringToJuce (playerItem.error.localizedDescription)
: String();
owner.notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
}
else if (newStatus == AVPlayerItemStatusReadyToPlay)
{
owner.notifyOwnerPreparationFinished (url, Result::ok(), owner.player.release());
}
else
{
jassertfalse;
}
}
}
};
//==============================================================================
PlayerControllerBase& owner;
std::unique_ptr<AVURLAsset, NSObjectDeleter> asset;
std::unique_ptr<NSArray<NSString*>, NSObjectDeleter> assetKeys;
std::unique_ptr<AVPlayerItem, NSObjectDeleter> playerItem;
std::unique_ptr<NSObject, NSObjectDeleter> playerItemPreparationStatusObserver;
std::unique_ptr<AVPlayer, NSObjectDeleter> player;
//==============================================================================
void checkAllKeysReadyFor (AVAsset* assetToCheck, const URL& url)
{
NSError* error = nil;
int successCount = 0;
for (NSString* key : assetKeys.get())
{
switch ([assetToCheck statusOfValueForKey: key error: &error])
{
case AVKeyValueStatusLoaded:
{
++successCount;
break;
}
case AVKeyValueStatusCancelled:
{
notifyOwnerPreparationFinished (url, Result::fail ("Loading cancelled"), nullptr);
return;
}
case AVKeyValueStatusFailed:
{
auto errorMessage = error != nil ? nsStringToJuce (error.localizedDescription) : String();
notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
return;
}
default:
{}
}
}
jassert (successCount == (int) [assetKeys.get() count]);
preparePlayerItem();
}
void preparePlayerItem()
{
playerItem.reset ([[AVPlayerItem alloc] initWithAsset: asset.get()]);
attachPreparationStatusObserver();
player.reset ([[AVPlayer alloc] initWithPlayerItem: playerItem.get()]);
}
//==============================================================================
void attachPreparationStatusObserver()
{
[playerItem.get() addObserver: playerItemPreparationStatusObserver.get()
forKeyPath: nsStringLiteral ("status")
options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context: this];
}
void detachPreparationStatusObserver()
{
if (playerItem != nullptr && playerItemPreparationStatusObserver != nullptr)
{
[playerItem.get() removeObserver: playerItemPreparationStatusObserver.get()
forKeyPath: nsStringLiteral ("status")
context: this];
}
}
//==============================================================================
void notifyOwnerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
{
WeakReference<PlayerAsyncInitialiser> safeThis (this);
MessageManager::callAsync ([safeThis, url, r, preparedPlayer]() mutable
{
if (safeThis != nullptr)
safeThis->owner.playerPreparationFinished (url, r, preparedPlayer);
});
}
JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerAsyncInitialiser)
};
//==============================================================================
Pimpl& owner;
bool useNativeControls;
PlayerAsyncInitialiser playerAsyncInitialiser;
std::unique_ptr<NSObject, NSObjectDeleter> playerStatusObserver;
std::unique_ptr<NSObject, NSObjectDeleter> playerItemPlaybackStatusObserver;
//==============================================================================
PlayerControllerBase (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
: owner (ownerToUse),
useNativeControls (useNativeControlsIfAvailable),
playerAsyncInitialiser (*this)
{
static JucePlayerStatusObserverClass playerObserverClass;
playerStatusObserver.reset ([playerObserverClass.createInstance() init]);
JucePlayerStatusObserverClass::setOwner (playerStatusObserver.get(), this);
static JucePlayerItemPlaybackStatusObserverClass itemObserverClass;
playerItemPlaybackStatusObserver.reset ([itemObserverClass.createInstance() init]);
JucePlayerItemPlaybackStatusObserverClass::setOwner (playerItemPlaybackStatusObserver.get(), this);
}
//==============================================================================
void attachPlayerStatusObserver()
{
[crtp().getPlayer() addObserver: playerStatusObserver.get()
forKeyPath: nsStringLiteral ("rate")
options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context: this];
[crtp().getPlayer() addObserver: playerStatusObserver.get()
forKeyPath: nsStringLiteral ("status")
options: NSKeyValueObservingOptionNew
context: this];
}
void detachPlayerStatusObserver()
{
if (crtp().getPlayer() != nullptr && playerStatusObserver != nullptr)
{
[crtp().getPlayer() removeObserver: playerStatusObserver.get()
forKeyPath: nsStringLiteral ("rate")
context: this];
[crtp().getPlayer() removeObserver: playerStatusObserver.get()
forKeyPath: nsStringLiteral ("status")
context: this];
}
}
void attachPlaybackObserver()
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
[[NSNotificationCenter defaultCenter] addObserver: playerItemPlaybackStatusObserver.get()
selector: @selector (processNotification:)
name: AVPlayerItemDidPlayToEndTimeNotification
object: [crtp().getPlayer() currentItem]];
#pragma clang diagnostic pop
}
void detachPlaybackObserver()
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
[[NSNotificationCenter defaultCenter] removeObserver: playerItemPlaybackStatusObserver.get()];
#pragma clang diagnostic pop
}
private:
//==============================================================================
Derived& crtp() { return static_cast<Derived&> (*this); }
//==============================================================================
void playerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
{
if (preparedPlayer != nil)
crtp().setPlayer (preparedPlayer);
owner.playerPreparationFinished (url, r);
}
void playbackReachedEndTime()
{
WeakReference<PlayerControllerBase> safeThis (this);
MessageManager::callAsync ([safeThis]() mutable
{
if (safeThis != nullptr)
safeThis->owner.playbackReachedEndTime();
});
}
//==============================================================================
void errorOccurred()
{
auto errorMessage = (crtp().getPlayer() != nil && crtp().getPlayer().error != nil)
? nsStringToJuce (crtp().getPlayer().error.localizedDescription)
: String();
owner.errorOccurred (errorMessage);
}
void playbackStarted()
{
owner.playbackStarted();
}
void playbackStopped()
{
owner.playbackStopped();
}
JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerControllerBase)
};
#if JUCE_MAC
//==============================================================================
class PlayerController : public PlayerControllerBase<PlayerController>
{
public:
PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
: PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
{
#if JUCE_32BIT
// 32-bit builds don't have AVPlayerView, so need to use a layer
useNativeControls = false;
#endif
if (useNativeControls)
{
#if ! JUCE_32BIT
playerView = [[AVPlayerView alloc] init];
#endif
}
else
{
view = [[NSView alloc] init];
playerLayer = [[AVPlayerLayer alloc] init];
[view setLayer: playerLayer];
}
}
~PlayerController()
{
#if JUCE_32BIT
[view release];
[playerLayer release];
#else
[playerView release];
#endif
}
NSView* getView()
{
#if ! JUCE_32BIT
if (useNativeControls)
return playerView;
#endif
return view;
}
Result load (NSURL* url)
{
if (auto* player = [AVPlayer playerWithURL: url])
{
setPlayer (player);
return Result::ok();
}
return Result::fail ("Couldn't open movie");
}
void loadAsync (URL url)
{
playerAsyncInitialiser.loadAsync (url);
}
void close() { setPlayer (nil); }
void setPlayer (AVPlayer* player)
{
#if ! JUCE_32BIT
if (useNativeControls)
{
[playerView setPlayer: player];
attachPlayerStatusObserver();
attachPlaybackObserver();
return;
}
#endif
[playerLayer setPlayer: player];
attachPlayerStatusObserver();
attachPlaybackObserver();
}
AVPlayer* getPlayer() const
{
#if ! JUCE_32BIT
if (useNativeControls)
return [playerView player];
#endif
return [playerLayer player];
}
private:
NSView* view = nil;
AVPlayerLayer* playerLayer = nil;
#if ! JUCE_32BIT
// 32-bit builds don't have AVPlayerView
AVPlayerView* playerView = nil;
#endif
};
#else #else
AVPlayerView* controller = nil; //==============================================================================
class PlayerController : public PlayerControllerBase<PlayerController>
{
public:
PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
: PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
{
if (useNativeControls)
{
playerViewController.reset ([[AVPlayerViewController alloc] init]);
}
else
{
static JuceVideoViewerClass cls;
playerView.reset ([cls.createInstance() init]);
playerLayer.reset ([[AVPlayerLayer alloc] init]);
[playerView.get().layer addSublayer: playerLayer.get()];
}
}
UIView* getView()
{
if (useNativeControls)
return [playerViewController.get() view];
// Should call getView() only once.
jassert (playerView != nil);
return playerView.release();
}
Result load (NSURL*)
{
jassertfalse;
return Result::fail ("Synchronous loading is not supported on iOS, use loadAsync()");
}
void loadAsync (URL url)
{
playerAsyncInitialiser.loadAsync (url);
}
void close() { setPlayer (nil); }
AVPlayer* getPlayer() const
{
if (useNativeControls)
return [playerViewController.get() player];
return [playerLayer.get() player];
}
void setPlayer (AVPlayer* playerToUse)
{
if (useNativeControls)
[playerViewController.get() setPlayer: playerToUse];
else
[playerLayer.get() setPlayer: playerToUse];
attachPlayerStatusObserver();
attachPlaybackObserver();
}
private:
//==============================================================================
struct JuceVideoViewerClass : public ObjCClass<UIView>
{
JuceVideoViewerClass() : ObjCClass<UIView> ("JuceVideoViewerClass_")
{
addMethod (@selector (layoutSubviews), layoutSubviews, "v@:");
registerClass();
}
private:
static void layoutSubviews (id self, SEL)
{
sendSuperclassMessage (self, @selector (layoutSubviews));
UIView* asUIView = (UIView*) self;
if (auto* previewLayer = getPreviewLayer (self))
previewLayer.frame = asUIView.bounds;
}
static AVPlayerLayer* getPreviewLayer (id self)
{
UIView* asUIView = (UIView*) self;
if (asUIView.layer.sublayers != nil && [asUIView.layer.sublayers count] > 0)
if ([asUIView.layer.sublayers[0] isKindOfClass: [AVPlayerLayer class]])
return (AVPlayerLayer*) asUIView.layer.sublayers[0];
return nil;
}
};
//==============================================================================
std::unique_ptr<AVPlayerViewController, NSObjectDeleter> playerViewController;
std::unique_ptr<UIView, NSObjectDeleter> playerView;
std::unique_ptr<AVPlayerLayer, NSObjectDeleter> playerLayer;
};
#endif #endif
AVPlayer* getAVPlayer() const noexcept { return [controller player]; } //==============================================================================
VideoComponent& owner;
PlayerController playerController;
std::function<void (const URL&, Result)> loadFinishedCallback;
double playSpeedMult = 1.0;
static double toSeconds (const CMTime& t) noexcept static double toSeconds (const CMTime& t) noexcept
{ {
return t.timescale != 0 ? (t.value / (double) t.timescale) : 0.0; return t.timescale != 0 ? (t.value / (double) t.timescale) : 0.0;
} }
void playerPreparationFinished (const URL& url, Result r)
{
owner.resized();
loadFinishedCallback (url, r);
loadFinishedCallback = nullptr;
}
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 playbackReachedEndTime()
{
stop();
setPosition (0.0);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
}; };

View file

@ -160,7 +160,9 @@ namespace VideoRenderers
//============================================================================== //==============================================================================
struct VideoComponent::Pimpl : public Component struct VideoComponent::Pimpl : public Component
{ {
Pimpl() : videoLoaded (false) Pimpl (VideoComponent& ownerToUse, bool)
: owner (ownerToUse),
videoLoaded (false)
{ {
setOpaque (true); setOpaque (true);
context.reset (new DirectShowContext (*this)); context.reset (new DirectShowContext (*this));
@ -257,6 +259,11 @@ struct VideoComponent::Pimpl : public Component
context->setSpeed (newSpeed); context->setSpeed (newSpeed);
} }
double getSpeed() const
{
return videoLoaded ? context->getSpeed() : 0.0;
}
Rectangle<int> getNativeSize() const Rectangle<int> getNativeSize() const
{ {
return videoLoaded ? context->getVideoSize() return videoLoaded ? context->getVideoSize()
@ -307,10 +314,30 @@ struct VideoComponent::Pimpl : public Component
repaint(); repaint();
} }
void playbackStarted()
{
if (owner.onPlaybackStarted != nullptr)
owner.onPlaybackStarted();
}
void playbackStopped()
{
if (owner.onPlaybackStopped != nullptr)
owner.onPlaybackStopped();
}
void errorOccurred (const String& errorMessage)
{
if (owner.onErrorOccurred != nullptr)
owner.onErrorOccurred (errorMessage);
}
File currentFile; File currentFile;
URL currentURL; URL currentURL;
private: private:
VideoComponent& owner;
bool videoLoaded; bool videoLoaded;
//============================================================================== //==============================================================================
@ -395,12 +422,15 @@ private:
deleteNativeWindow(); deleteNativeWindow();
mediaEvent->SetNotifyWindow (0, 0, 0); mediaEvent->SetNotifyWindow (0, 0, 0);
if (videoRenderer != nullptr) if (videoRenderer != nullptr)
videoRenderer->setVideoWindow (nullptr); videoRenderer->setVideoWindow (nullptr);
createNativeWindow(); createNativeWindow();
mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0); mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
if (videoRenderer != nullptr) if (videoRenderer != nullptr)
videoRenderer->setVideoWindow (hwnd); videoRenderer->setVideoWindow (hwnd);
} }
@ -510,7 +540,10 @@ private:
// set window to receive events // set window to receive events
if (SUCCEEDED (hr)) if (SUCCEEDED (hr))
{
mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
hr = mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0); hr = mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
}
if (SUCCEEDED (hr)) if (SUCCEEDED (hr))
{ {
@ -586,22 +619,33 @@ private:
switch (ec) switch (ec)
{ {
case EC_REPAINT: case EC_REPAINT:
component.repaint(); component.repaint();
break; break;
case EC_COMPLETE: case EC_COMPLETE:
component.stop(); component.stop();
break; component.setPosition (0.0);
break;
case EC_USERABORT: case EC_ERRORABORT:
case EC_ERRORABORT: case EC_ERRORABORTEX:
case EC_ERRORABORTEX: component.errorOccurred (getErrorMessageFromResult ((HRESULT) p1).getErrorMessage());
component.close(); // intentional fallthrough
break; case EC_USERABORT:
component.close();
break;
default: case EC_STATE_CHANGE:
break; switch (p1)
{
case State_Paused: component.playbackStopped(); break;
case State_Running: component.playbackStarted(); break;
default: break;
}
default:
break;
} }
} }
} }
@ -644,6 +688,13 @@ private:
return duration; return duration;
} }
double getSpeed() const
{
double speed;
mediaPosition->get_Rate (&speed);
return speed;
}
double getPosition() const double getPosition() const
{ {
REFTIME seconds; REFTIME seconds;

View file

@ -25,16 +25,19 @@
namespace juce namespace juce
{ {
#if JUCE_MAC || JUCE_IOS || JUCE_MSVC #if ! JUCE_LINUX
#if JUCE_MAC || JUCE_IOS #if JUCE_MAC || JUCE_IOS
#include "../native/juce_mac_Video.h" #include "../native/juce_mac_Video.h"
#elif JUCE_WINDOWS #elif JUCE_WINDOWS
#include "../native/juce_win32_Video.h" #include "../native/juce_win32_Video.h"
#elif JUCE_ANDROID
#include "../native/juce_android_Video.h"
#endif #endif
//============================================================================== //==============================================================================
VideoComponent::VideoComponent() : pimpl (new Pimpl()) VideoComponent::VideoComponent (bool useNativeControlsIfAvailable)
: pimpl (new Pimpl (*this, useNativeControlsIfAvailable))
{ {
addAndMakeVisible (pimpl.get()); addAndMakeVisible (pimpl.get());
} }
@ -46,22 +49,55 @@ VideoComponent::~VideoComponent()
Result VideoComponent::load (const File& file) Result VideoComponent::load (const File& file)
{ {
#if JUCE_ANDROID || JUCE_IOS
ignoreUnused (file);
jassertfalse;
return Result::fail ("load() is not supported on this platform. Use loadAsync() instead.");
#else
auto r = pimpl->load (file); auto r = pimpl->load (file);
resized(); resized();
return r; return r;
#endif
} }
Result VideoComponent::load (const URL& url) Result VideoComponent::load (const URL& url)
{ {
#if JUCE_ANDROID || JUCE_IOS
// You need to use loadAsync on Android & iOS.
ignoreUnused (url);
jassertfalse;
return Result::fail ("load() is not supported on this platform. Use loadAsync() instead.");
#else
auto r = pimpl->load (url); auto r = pimpl->load (url);
resized(); resized();
return r; return r;
#endif
}
void VideoComponent::loadAsync (const URL& url, std::function<void (const URL&, Result)> callback)
{
if (callback == nullptr)
{
jassertfalse;
return;
}
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
pimpl->loadAsync (url, callback);
#else
auto result = load (url);
callback (url, result);
#endif
} }
void VideoComponent::closeVideo() void VideoComponent::closeVideo()
{ {
pimpl->close(); pimpl->close();
// Closing on Android is async and resized() will be called internally by pimpl once
// close operation finished.
#if ! JUCE_ANDROID// TODO JUCE_IOS too?
resized(); resized();
#endif
} }
bool VideoComponent::isVideoOpen() const { return pimpl->isOpen(); } bool VideoComponent::isVideoOpen() const { return pimpl->isOpen(); }
@ -81,6 +117,8 @@ void VideoComponent::setPlayPosition (double newPos) { pimpl->setPosition
double VideoComponent::getPlayPosition() const { return pimpl->getPosition(); } double VideoComponent::getPlayPosition() const { return pimpl->getPosition(); }
void VideoComponent::setPlaySpeed (double newSpeed) { pimpl->setSpeed (newSpeed); } void VideoComponent::setPlaySpeed (double newSpeed) { pimpl->setSpeed (newSpeed); }
double VideoComponent::getPlaySpeed() const { return pimpl->getSpeed(); }
void VideoComponent::setAudioVolume (float newVolume) { pimpl->setVolume (newVolume); } void VideoComponent::setAudioVolume (float newVolume) { pimpl->setVolume (newVolume); }
float VideoComponent::getAudioVolume() const { return pimpl->getVolume(); } float VideoComponent::getAudioVolume() const { return pimpl->getVolume(); }

View file

@ -44,25 +44,55 @@ public:
//============================================================================== //==============================================================================
/** Creates an empty VideoComponent. /** Creates an empty VideoComponent.
Use the load() method to open a video once you've added this component to Use the loadAsync() or load() method to open a video once you've added
a parent (or put it on the desktop). this component to a parent (or put it on the desktop).
If useNativeControlsIfAvailable is enabled and a target OS has a video view with
dedicated controls for transport etc, that view will be used. In opposite
case a bare video view without any controls will be presented, allowing you to
tailor your own UI. Currently this flag is used on iOS and 64bit macOS.
Android, Windows and 32bit macOS will always use plain video views without
dedicated controls.
*/ */
VideoComponent(); VideoComponent (bool useNativeControlsIfAvailable);
/** Destructor. */ /** Destructor. */
~VideoComponent(); ~VideoComponent();
//============================================================================== //==============================================================================
/** Tries to load a video from a local file. /** Tries to load a video from a local file.
This function is supported on macOS and Windows. For iOS and Android, use
loadAsync() instead.
@returns an error if the file failed to be loaded correctly @returns an error if the file failed to be loaded correctly
@see loadAsync
*/ */
Result load (const File& file); Result load (const File& file);
/** Tries to load a video from a URL. /** Tries to load a video from a URL.
This function is supported on macOS and Windows. For iOS and Android, use
loadAsync() instead.
@returns an error if the file failed to be loaded correctly @returns an error if the file failed to be loaded correctly
@see loadAsync
*/ */
Result load (const URL& url); Result load (const URL& url);
/** Tries to load a video from a URL asynchronously. When finished, invokes the
callback supplied to the function on the message thread.
This is the preferred way of loading content, since it works not only on
macOS and Windows, but also on iOS and Android. On Windows, it will internally
call load().
@see load
*/
void loadAsync (const URL& url, std::function<void (const URL&, Result)> loadFinishedCallback);
/** Closes the video and resets the component. */ /** Closes the video and resets the component. */
void closeVideo(); void closeVideo();
@ -109,6 +139,9 @@ public:
*/ */
void setPlaySpeed (double newSpeed); void setPlaySpeed (double newSpeed);
/** Returns the current play speed of the video. */
double getPlaySpeed() const;
/** Changes the video's playback volume. /** Changes the video's playback volume.
@param newVolume the volume in the range 0 (silent) to 1.0 (full) @param newVolume the volume in the range 0 (silent) to 1.0 (full)
*/ */
@ -119,6 +152,23 @@ public:
*/ */
float getAudioVolume() const; float getAudioVolume() const;
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
/** Set this callback to be notified whenever OS global media volume changes.
Currently used on Android only.
*/
std::function<void()> onGlobalMediaVolumeChanged;
#endif
/** Set this callback to be notified whenever the playback starts. */
std::function<void()> onPlaybackStarted;
/** Set this callback to be notified whenever the playback stops. */
std::function<void()> onPlaybackStopped;
/** Set this callback to be notified whenever an error occurs. Upon error, you
may need to load the video again. */
std::function<void (const String& /*error*/)> onErrorOccurred;
private: private:
//============================================================================== //==============================================================================
struct Pimpl; struct Pimpl;
@ -129,6 +179,24 @@ private:
void resized() override; void resized() override;
void timerCallback() override; void timerCallback() override;
#if JUCE_ANDROID
friend void juce_surfaceChangedNativeVideo (int64, void*);
friend void juce_surfaceDestroyedNativeVideo (int64, void*);
friend void juce_mediaSessionPause (int64);
friend void juce_mediaSessionPlay (int64);
friend void juce_mediaSessionPlayFromMediaId (int64, void*, void*);
friend void juce_mediaSessionSeekTo (int64, int64);
friend void juce_mediaSessionStop (int64);
friend void juce_mediaControllerAudioInfoChanged (int64, void*);
friend void juce_mediaControllerMetadataChanged (int64, void*);
friend void juce_mediaControllerPlaybackStateChanged (int64, void*);
friend void juce_mediaControllerSessionDestroyed (int64);
friend void juce_mediaSessionSystemVolumeChanged (int64);
#endif
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoComponent) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoComponent)
}; };