diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt
index ee39738aa4..9a9e932c55 100644
--- a/BREAKING-CHANGES.txt
+++ b/BREAKING-CHANGES.txt
@@ -4,6 +4,33 @@ JUCE breaking changes
Develop
=======
+Change
+------
+CameraDevice::Listener::imageReceived() has been replaced by a new function
+CameraDevice::takeStillPicture(). The callback passed in takeStillPicture()
+will always be triggered on the message thread.
+
+Possible Issues
+---------------
+The code handling image capture needs to be adjusted to work well on a message
+thread. This means that you should not perform any lengthy operations in your
+callback, as this will stall your UI.
+
+Workaround
+----------
+Use CameraDevice::takeStillPicture() instead of old listener callback. Pass
+your lambda or other std::function compliant object to takeStillPicture which
+will be called for you when the capture has finished. If you want to perform
+any time consuming operation upon receiving the picture, schedule it on a
+separate worker thread.
+
+Rationale
+---------
+The old Listener interface was not working in a typical listener pattern. It
+feels more natural to request still picture capture with a dedicated function.
+This is also more compliant with async mobile APIs.
+
+
Change
------
JUCE no longer supports OS X deployment targets earlier than 10.7.
diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
index e9de472a27..378e5dfa58 100644
--- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
+++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
@@ -8,7 +8,7 @@ SET(BINARY_NAME "juce_jni")
add_library("cpufeatures" STATIC "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c")
set_source_files_properties("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" PROPERTIES COMPILE_FLAGS "-Wno-sign-conversion -Wno-gnu-statement-expression")
-add_definitions("-DJUCE_ANDROID=1" "-DJUCE_ANDROID_API_VERSION=23" "-DJUCE_ANDROID_ACTIVITY_CLASSNAME=com_roli_juce_demorunner_DemoRunner" "-DJUCE_ANDROID_ACTIVITY_CLASSPATH=\"com/roli/juce/demorunner/DemoRunner\"" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME=com_roli_juce_demorunner_SharingContentProvider" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH=\"com/roli/juce/demorunner/SharingContentProvider\"" "-DJUCE_PUSH_NOTIFICATIONS=1" "-DJUCE_ANDROID_GL_ES_VERSION_3_0=1" "-DJUCE_DEMO_RUNNER=1" "-DJUCE_UNIT_TESTS=1" "-DJUCER_ANDROIDSTUDIO_7F0E4A25=1" "-DJUCE_APP_VERSION=5.3.1" "-DJUCE_APP_VERSION_HEX=0x50301")
+add_definitions("-DJUCE_ANDROID=1" "-DJUCE_ANDROID_API_VERSION=23" "-DJUCE_ANDROID_ACTIVITY_CLASSNAME=com_juce_demorunner_DemoRunner" "-DJUCE_ANDROID_ACTIVITY_CLASSPATH=\"com/juce/demorunner/DemoRunner\"" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME=com_juce_demorunner_SharingContentProvider" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH=\"com/juce/demorunner/SharingContentProvider\"" "-DJUCE_PUSH_NOTIFICATIONS=1" "-DJUCE_ANDROID_GL_ES_VERSION_3_0=1" "-DJUCE_DEMO_RUNNER=1" "-DJUCE_UNIT_TESTS=1" "-DJUCER_ANDROIDSTUDIO_7F0E4A25=1" "-DJUCE_APP_VERSION=5.3.1" "-DJUCE_APP_VERSION_HEX=0x50301")
include_directories( AFTER
"../../../JuceLibraryCode"
@@ -546,6 +546,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/containers/juce_ReferenceCountedArray.h"
"../../../../../modules/juce_core/containers/juce_ScopedValueSetter.h"
"../../../../../modules/juce_core/containers/juce_SortedSet.h"
+ "../../../../../modules/juce_core/containers/juce_SparseSet.cpp"
"../../../../../modules/juce_core/containers/juce_SparseSet.h"
"../../../../../modules/juce_core/containers/juce_Variant.cpp"
"../../../../../modules/juce_core/containers/juce_Variant.h"
@@ -1465,6 +1466,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_video/capture/juce_CameraDevice.cpp"
"../../../../../modules/juce_video/capture/juce_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_android_CameraDevice.h"
+ "../../../../../modules/juce_video/native/juce_ios_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_mac_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_mac_Video.h"
"../../../../../modules/juce_video/native/juce_win32_CameraDevice.h"
@@ -2008,6 +2010,7 @@ set_source_files_properties("../../../../../modules/juce_core/containers/juce_Pr
set_source_files_properties("../../../../../modules/juce_core/containers/juce_ReferenceCountedArray.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_ScopedValueSetter.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_SortedSet.h" PROPERTIES HEADER_FILE_ONLY TRUE)
+set_source_files_properties("../../../../../modules/juce_core/containers/juce_SparseSet.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_SparseSet.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_Variant.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_Variant.h" PROPERTIES HEADER_FILE_ONLY TRUE)
@@ -2927,6 +2930,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.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_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_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_win32_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
diff --git a/examples/DemoRunner/Builds/Android/app/build.gradle b/examples/DemoRunner/Builds/Android/app/build.gradle
index 1bbe7b6c87..b2250b3f20 100644
--- a/examples/DemoRunner/Builds/Android/app/build.gradle
+++ b/examples/DemoRunner/Builds/Android/app/build.gradle
@@ -19,7 +19,7 @@ android {
}
defaultConfig {
- applicationId "com.roli.juce.demorunner"
+ applicationId "com.juce.demorunner"
minSdkVersion 23
targetSdkVersion 23
externalNativeBuild {
diff --git a/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml b/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml
index b1d2d7dba6..cde58d3c0b 100644
--- a/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml
+++ b/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml
@@ -1,7 +1,7 @@
+ package="com.juce.demorunner">
@@ -9,18 +9,19 @@
+
+ android:screenOrientation="unspecified" android:launchMode="singleTask" android:hardwareAccelerated="true">
-
diff --git a/examples/DemoRunner/Builds/Android/app/src/main/java/com/roli/juce/demorunner/DemoRunner.java b/examples/DemoRunner/Builds/Android/app/src/main/java/com/roli/juce/demorunner/DemoRunner.java
index af0505a40d..7e4f1ae939 100644
--- a/examples/DemoRunner/Builds/Android/app/src/main/java/com/roli/juce/demorunner/DemoRunner.java
+++ b/examples/DemoRunner/Builds/Android/app/src/main/java/com/roli/juce/demorunner/DemoRunner.java
@@ -30,6 +30,7 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.hardware.camera2.*;
import android.net.http.SslError;
import android.net.Uri;
import android.os.Bundle;
@@ -119,6 +120,7 @@ public class DemoRunner extends Activity
private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;
private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;
+ private static final int JUCE_PERMISSIONS_CAMERA = 5;
private static String getAndroidPermissionName (int permissionID)
{
@@ -129,6 +131,7 @@ public class DemoRunner extends Activity
// use string value as this is not defined in SDKs < 16
case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE: return "android.permission.READ_EXTERNAL_STORAGE";
case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;
+ case JUCE_PERMISSIONS_CAMERA: return Manifest.permission.CAMERA;
}
// unknown permission ID!
@@ -1205,6 +1208,7 @@ public class DemoRunner extends Activity
setVolumeControlStream (AudioManager.STREAM_MUSIC);
permissionCallbackPtrMap = new HashMap();
+ appPausedResumedListeners = new HashMap();
}
@Override
@@ -1221,6 +1225,11 @@ public class DemoRunner extends Activity
{
suspendApp();
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appPaused();
+
try
{
Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down
@@ -1236,12 +1245,10 @@ public class DemoRunner extends Activity
super.onResume();
resumeApp();
- // Ensure that navigation/status bar visibility is correctly restored.
- for (int i = 0; i < viewHolder.getChildCount(); ++i)
- {
- if (viewHolder.getChildAt (i) instanceof ComponentPeerView)
- ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();
- }
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appResumed();
}
@Override
@@ -1368,11 +1375,14 @@ public class DemoRunner extends Activity
{
ComponentPeerView v = new ComponentPeerView (this, opaque, host);
viewHolder.addView (v);
+ addAppPausedResumedListener (v, host);
return v;
}
public final void deleteView (ComponentPeerView view)
{
+ removeAppPausedResumedListener (view, view.host);
+
view.host = 0;
ViewGroup group = (ViewGroup) (view.getParent());
@@ -1590,9 +1600,28 @@ public class DemoRunner extends Activity
public native void alertDismissed (long callback, int id);
+ //==============================================================================
+ public interface AppPausedResumedListener
+ {
+ void appPaused();
+ void appResumed();
+ }
+
+ private Map appPausedResumedListeners;
+
+ public void addAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.put (new Long (listenerHost), l);
+ }
+
+ public void removeAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.remove (new Long (listenerHost));
+ }
+
//==============================================================================
public final class ComponentPeerView extends ViewGroup
- implements View.OnFocusChangeListener
+ implements View.OnFocusChangeListener, AppPausedResumedListener
{
public ComponentPeerView (Context context, boolean opaque_, long host)
{
@@ -1940,13 +1969,25 @@ public class DemoRunner extends Activity
}
//==============================================================================
+ private native void handleAppPaused (long host);
private native void handleAppResumed (long host);
+ @Override
+ public void appPaused()
+ {
+ if (host == 0)
+ return;
+
+ handleAppPaused (host);
+ }
+
+ @Override
public void appResumed()
{
if (host == 0)
return;
+ // Ensure that navigation/status bar visibility is correctly restored.
handleAppResumed (host);
}
}
@@ -2616,6 +2657,179 @@ public class DemoRunner extends Activity
private final Object hostLock = new Object();
}
+
+ //==============================================================================
+ public class CameraDeviceStateCallback extends CameraDevice.StateCallback
+ {
+ private native void cameraDeviceStateClosed (long host, CameraDevice camera);
+ private native void cameraDeviceStateDisconnected (long host, CameraDevice camera);
+ private native void cameraDeviceStateError (long host, CameraDevice camera, int error);
+ private native void cameraDeviceStateOpened (long host, CameraDevice camera);
+
+ CameraDeviceStateCallback (long hostToUse)
+ {
+ host = hostToUse;
+ }
+
+ @Override
+ public void onClosed (CameraDevice camera)
+ {
+ cameraDeviceStateClosed (host, camera);
+ }
+
+ @Override
+ public void onDisconnected (CameraDevice camera)
+ {
+ cameraDeviceStateDisconnected (host, camera);
+ }
+
+ @Override
+ public void onError (CameraDevice camera, int error)
+ {
+ cameraDeviceStateError (host, camera, error);
+ }
+
+ @Override
+ public void onOpened (CameraDevice camera)
+ {
+ cameraDeviceStateOpened (host, camera);
+ }
+
+ private long host;
+ }
+
+ //==============================================================================
+ public class CameraCaptureSessionStateCallback extends CameraCaptureSession.StateCallback
+ {
+ private native void cameraCaptureSessionActive (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionClosed (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionConfigured (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionReady (long host, CameraCaptureSession session);
+
+ CameraCaptureSessionStateCallback (long hostToUse)
+ {
+ host = hostToUse;
+ }
+
+ @Override
+ public void onActive (CameraCaptureSession session)
+ {
+ cameraCaptureSessionActive (host, session);
+ }
+
+ @Override
+ public void onClosed (CameraCaptureSession session)
+ {
+ cameraCaptureSessionClosed (host, session);
+ }
+
+ @Override
+ public void onConfigureFailed (CameraCaptureSession session)
+ {
+ cameraCaptureSessionConfigureFailed (host, session);
+ }
+
+ @Override
+ public void onConfigured (CameraCaptureSession session)
+ {
+ cameraCaptureSessionConfigured (host, session);
+ }
+
+ @Override
+ public void onReady (CameraCaptureSession session)
+ {
+ cameraCaptureSessionReady (host, session);
+ }
+
+ private long host;
+ }
+
+ //==============================================================================
+ public class CameraCaptureSessionCaptureCallback extends CameraCaptureSession.CaptureCallback
+ {
+ private native void cameraCaptureSessionCaptureCompleted (long host, boolean isPreview, CameraCaptureSession session,
+ CaptureRequest request, TotalCaptureResult result);
+ private native void cameraCaptureSessionCaptureFailed (long host, boolean isPreview, CameraCaptureSession session,
+ CaptureRequest request, CaptureFailure failure);
+ private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session,
+ CaptureRequest request, CaptureResult partialResult);
+ private native void cameraCaptureSessionCaptureSequenceAborted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId);
+ private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber);
+ private native void cameraCaptureSessionCaptureStarted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request,
+ long timestamp, long frameNumber);
+
+ CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview)
+ {
+ host = hostToUse;
+ preview = shouldBePreview;
+ }
+
+ @Override
+ public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result)
+ {
+ cameraCaptureSessionCaptureCompleted (host, preview, session, request, result);
+ }
+
+ @Override
+ public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)
+ {
+ cameraCaptureSessionCaptureFailed (host, preview, session, request, failure);
+ }
+
+ @Override
+ public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request,
+ CaptureResult partialResult)
+ {
+ cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult);
+ }
+
+ @Override
+ public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId)
+ {
+ cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId);
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber)
+ {
+ cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber);
+ }
+
+ @Override
+ public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp,
+ long frameNumber)
+ {
+ cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber);
+ }
+
+ private long host;
+ private boolean preview;
+ }
+
+ //==============================================================================
+ public class JuceOrientationEventListener extends OrientationEventListener
+ {
+ private native void deviceOrientationChanged (long host, int orientation);
+
+ public JuceOrientationEventListener (long hostToUse, Context context, int rate)
+ {
+ super (context, rate);
+
+ host = hostToUse;
+ }
+
+ @Override
+ public void onOrientationChanged (int orientation)
+ {
+ deviceOrientationChanged (host, orientation);
+ }
+
+ private long host;
+ }
+
+
//==============================================================================
public static final String getLocaleValue (boolean isRegion)
{
diff --git a/examples/DemoRunner/Builds/MacOSX/DemoRunner.xcodeproj/project.pbxproj b/examples/DemoRunner/Builds/MacOSX/DemoRunner.xcodeproj/project.pbxproj
index ae0bdc5c17..c71ea15be6 100644
--- a/examples/DemoRunner/Builds/MacOSX/DemoRunner.xcodeproj/project.pbxproj
+++ b/examples/DemoRunner/Builds/MacOSX/DemoRunner.xcodeproj/project.pbxproj
@@ -259,7 +259,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET_ppc = 10.4;
OTHER_CPLUSPLUSFLAGS = "-Wall -Wshadow -Wno-missing-field-initializers -Wshadow -Wshorten-64-to-32 -Wstrict-aliasing -Wuninitialized -Wunused-parameter -Wconversion -Wsign-compare -Wint-conversion -Wconditional-uninitialized -Woverloaded-virtual -Wreorder -Wconstant-conversion -Wsign-conversion -Wunused-private-field -Wbool-conversion -Wextra-semi -Wno-ignored-qualifiers -Wunreachable-code";
- PRODUCT_BUNDLE_IDENTIFIER = com.roli.juce.demorunner;
+ PRODUCT_BUNDLE_IDENTIFIER = com.juce.demorunner;
SDKROOT_ppc = macosx10.5;
USE_HEADERMAP = NO; }; name = Debug; };
69330F27DD2C71609336C7D2 = {isa = XCBuildConfiguration; buildSettings = {
@@ -296,7 +296,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET_ppc = 10.4;
OTHER_CPLUSPLUSFLAGS = "-Wall -Wshadow -Wno-missing-field-initializers -Wshadow -Wshorten-64-to-32 -Wstrict-aliasing -Wuninitialized -Wunused-parameter -Wconversion -Wsign-compare -Wint-conversion -Wconditional-uninitialized -Woverloaded-virtual -Wreorder -Wconstant-conversion -Wsign-conversion -Wunused-private-field -Wbool-conversion -Wextra-semi -Wno-ignored-qualifiers -Wunreachable-code";
- PRODUCT_BUNDLE_IDENTIFIER = com.roli.juce.demorunner;
+ PRODUCT_BUNDLE_IDENTIFIER = com.juce.demorunner;
SDKROOT_ppc = macosx10.5;
USE_HEADERMAP = NO; }; name = Release; };
C01EC82F42B640CA1E54AD53 = {isa = XCBuildConfiguration; buildSettings = {
diff --git a/examples/DemoRunner/Builds/MacOSX/Info-App.plist b/examples/DemoRunner/Builds/MacOSX/Info-App.plist
index 39ca56ccb1..9dda4fe9be 100644
--- a/examples/DemoRunner/Builds/MacOSX/Info-App.plist
+++ b/examples/DemoRunner/Builds/MacOSX/Info-App.plist
@@ -8,7 +8,7 @@
CFBundleIconFile
Icon.icns
CFBundleIdentifier
- com.roli.juce.demorunner
+ com.juce.demorunner
CFBundleName
DemoRunner
CFBundleDisplayName
diff --git a/examples/DemoRunner/Builds/VisualStudio2013/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2013/DemoRunner_App.vcxproj
index e04300c6a1..280bb2631a 100644
--- a/examples/DemoRunner/Builds/VisualStudio2013/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2013/DemoRunner_App.vcxproj
@@ -772,6 +772,9 @@
true
+
+ true
+
true
@@ -2836,6 +2839,7 @@
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2013/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2013/DemoRunner_App.vcxproj.filters
index a17949c392..1eeee3f9ec 100644
--- a/examples/DemoRunner/Builds/VisualStudio2013/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2013/DemoRunner_App.vcxproj.filters
@@ -1141,6 +1141,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
@@ -4821,6 +4824,9 @@
JUCE Modules\juce_video\native
+
+ JUCE Modules\juce_video\native
+
JUCE Modules\juce_video\native
diff --git a/examples/DemoRunner/Builds/VisualStudio2015/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2015/DemoRunner_App.vcxproj
index c064e86a7b..7df49e7433 100644
--- a/examples/DemoRunner/Builds/VisualStudio2015/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2015/DemoRunner_App.vcxproj
@@ -772,6 +772,9 @@
true
+
+ true
+
true
@@ -2836,6 +2839,7 @@
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2015/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2015/DemoRunner_App.vcxproj.filters
index fe88ae0055..ef3cce5ab3 100644
--- a/examples/DemoRunner/Builds/VisualStudio2015/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2015/DemoRunner_App.vcxproj.filters
@@ -1141,6 +1141,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
@@ -4821,6 +4824,9 @@
JUCE Modules\juce_video\native
+
+ JUCE Modules\juce_video\native
+
JUCE Modules\juce_video\native
diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj
index 696a7ae1ac..01e6756273 100644
--- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj
@@ -772,6 +772,9 @@
true
+
+ true
+
true
@@ -2836,6 +2839,7 @@
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters
index e7ed5a8e0d..a738f90fce 100644
--- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters
@@ -1141,6 +1141,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
@@ -4821,6 +4824,9 @@
JUCE Modules\juce_video\native
+
+ JUCE Modules\juce_video\native
+
JUCE Modules\juce_video\native
diff --git a/examples/DemoRunner/Builds/iOS/DemoRunner.xcodeproj/project.pbxproj b/examples/DemoRunner/Builds/iOS/DemoRunner.xcodeproj/project.pbxproj
index 1aee579b01..dd4b0a09a5 100644
--- a/examples/DemoRunner/Builds/iOS/DemoRunner.xcodeproj/project.pbxproj
+++ b/examples/DemoRunner/Builds/iOS/DemoRunner.xcodeproj/project.pbxproj
@@ -19,6 +19,7 @@
6A61CBB4E39BFD392D97528F = {isa = PBXBuildFile; fileRef = 61AE09C749B007B70A265D9B; };
0B0CE6D5062E5C02A41F24BC = {isa = PBXBuildFile; fileRef = 873F9DD54978E601102353B4; };
5E4310B3F6BB639875D3E9B8 = {isa = PBXBuildFile; fileRef = 49ECA8B998B339A083674A22; };
+ AE7FB2AC3885F4BF53A5DDA1 = {isa = PBXBuildFile; fileRef = 7983C452610C1638B7E78F12; };
1FB200F4AE3E4E7CDFF629BB = {isa = PBXBuildFile; fileRef = 24D74AF1C95BEF957DC4FA77; };
AC783ECD84496E0B77911EEE = {isa = PBXBuildFile; fileRef = 34F1320BC5C23702C08DF9F0; };
B1981F62F6A91FD2F579A198 = {isa = PBXBuildFile; fileRef = 23CD1A3F9067C3A0ECE7BB67; };
@@ -92,6 +93,7 @@
6C5E26B4D28F8450435B8AE1 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_cryptography.mm"; path = "../../JuceLibraryCode/include_juce_cryptography.mm"; sourceTree = "SOURCE_ROOT"; };
72129757D2A553B90A7157C6 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppConfig.h; path = ../../JuceLibraryCode/AppConfig.h; sourceTree = "SOURCE_ROOT"; };
76A157A111866670A4678F04 = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+ 7983C452610C1638B7E78F12 = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; };
7A5AAE9EE573FC6105CC4AAC = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SettingsContent.h; path = ../../Source/UI/SettingsContent.h; sourceTree = "SOURCE_ROOT"; };
8135645508EEFDBDCDF2ADC6 = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = DemoRunner/Images.xcassets; sourceTree = "SOURCE_ROOT"; };
831A01C745C905F5715CD822 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "include_juce_blocks_basics.cpp"; path = "../../JuceLibraryCode/include_juce_blocks_basics.cpp"; sourceTree = "SOURCE_ROOT"; };
@@ -209,6 +211,7 @@
61AE09C749B007B70A265D9B,
873F9DD54978E601102353B4,
49ECA8B998B339A083674A22,
+ 7983C452610C1638B7E78F12,
24D74AF1C95BEF957DC4FA77,
34F1320BC5C23702C08DF9F0,
23CD1A3F9067C3A0ECE7BB67,
@@ -256,7 +259,7 @@
INFOPLIST_PREPROCESS = NO;
INSTALL_PATH = "$(HOME)/Applications";
OTHER_CPLUSPLUSFLAGS = "-Wall -Wshadow -Wno-missing-field-initializers -Wshadow -Wshorten-64-to-32 -Wstrict-aliasing -Wuninitialized -Wunused-parameter -Wconversion -Wsign-compare -Wint-conversion -Wconditional-uninitialized -Woverloaded-virtual -Wreorder -Wconstant-conversion -Wsign-conversion -Wunused-private-field -Wbool-conversion -Wextra-semi -Wno-ignored-qualifiers -Wunreachable-code";
- PRODUCT_BUNDLE_IDENTIFIER = com.roli.juce.demorunner;
+ PRODUCT_BUNDLE_IDENTIFIER = com.juce.demorunner;
USE_HEADERMAP = NO; }; name = Debug; };
69330F27DD2C71609336C7D2 = {isa = XCBuildConfiguration; buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
@@ -293,7 +296,7 @@
INSTALL_PATH = "$(HOME)/Applications";
LLVM_LTO = YES;
OTHER_CPLUSPLUSFLAGS = "-Wall -Wshadow -Wno-missing-field-initializers -Wshadow -Wshorten-64-to-32 -Wstrict-aliasing -Wuninitialized -Wunused-parameter -Wconversion -Wsign-compare -Wint-conversion -Wconditional-uninitialized -Woverloaded-virtual -Wreorder -Wconstant-conversion -Wsign-conversion -Wunused-private-field -Wbool-conversion -Wextra-semi -Wno-ignored-qualifiers -Wunreachable-code";
- PRODUCT_BUNDLE_IDENTIFIER = com.roli.juce.demorunner;
+ PRODUCT_BUNDLE_IDENTIFIER = com.juce.demorunner;
USE_HEADERMAP = NO; }; name = Release; };
C01EC82F42B640CA1E54AD53 = {isa = XCBuildConfiguration; buildSettings = {
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@@ -428,6 +431,7 @@
6A61CBB4E39BFD392D97528F,
0B0CE6D5062E5C02A41F24BC,
5E4310B3F6BB639875D3E9B8,
+ AE7FB2AC3885F4BF53A5DDA1,
1FB200F4AE3E4E7CDFF629BB,
AC783ECD84496E0B77911EEE,
B1981F62F6A91FD2F579A198,
diff --git a/examples/DemoRunner/Builds/iOS/Info-App.plist b/examples/DemoRunner/Builds/iOS/Info-App.plist
index a864c56f75..50c30b6f34 100644
--- a/examples/DemoRunner/Builds/iOS/Info-App.plist
+++ b/examples/DemoRunner/Builds/iOS/Info-App.plist
@@ -7,12 +7,14 @@
NSMicrophoneUsageDescription
This is an audio app which requires audio input. If you do not have a USB audio interface connected it will use the microphone.
+ NSCameraUsageDescription
+ This app requires camera usage to function properly.
UIViewControllerBasedStatusBarAppearance
CFBundleExecutable
${EXECUTABLE_NAME}
CFBundleIdentifier
- com.roli.juce.demorunner
+ com.juce.demorunner
CFBundleName
DemoRunner
CFBundleDisplayName
@@ -39,6 +41,7 @@
UISupportedInterfaceOrientations
+ UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
diff --git a/examples/DemoRunner/DemoRunner.jucer b/examples/DemoRunner/DemoRunner.jucer
index 03a22f3935..11176d38fc 100644
--- a/examples/DemoRunner/DemoRunner.jucer
+++ b/examples/DemoRunner/DemoRunner.jucer
@@ -1,9 +1,9 @@
+ companyEmail="info@juce.com" id="yj7xMM" reportAppUsage="1">
@@ -88,10 +88,10 @@
+ smallIcon="YyqWd2" bigIcon="YyqWd2" cameraPermissionNeeded="1">
@@ -119,10 +119,10 @@
-
+
diff --git a/examples/DemoRunner/JuceLibraryCode/AppConfig.h b/examples/DemoRunner/JuceLibraryCode/AppConfig.h
index 9b1c334db5..de08905c2f 100644
--- a/examples/DemoRunner/JuceLibraryCode/AppConfig.h
+++ b/examples/DemoRunner/JuceLibraryCode/AppConfig.h
@@ -156,6 +156,10 @@
//#define JUCE_PLUGINHOST_AU 0
#endif
+#ifndef JUCE_PLUGINHOST_LADSPA
+ //#define JUCE_PLUGINHOST_LADSPA 0
+#endif
+
//==============================================================================
// juce_audio_utils flags:
diff --git a/examples/DemoRunner/Source/Demos/DemoPIPs2.cpp b/examples/DemoRunner/Source/Demos/DemoPIPs2.cpp
index 588d5b997f..98ed93834e 100644
--- a/examples/DemoRunner/Source/Demos/DemoPIPs2.cpp
+++ b/examples/DemoRunner/Source/Demos/DemoPIPs2.cpp
@@ -34,7 +34,7 @@
#include "../../../GUI/AnimationAppDemo.h"
#include "../../../GUI/AnimationDemo.h"
#include "../../../GUI/BouncingBallWavetableDemo.h"
-#if JUCE_MAC || JUCE_WINDOWS
+#if JUCE_USE_CAMERA && ! JUCE_LINUX
#include "../../../GUI/CameraDemo.h"
#endif
#if ! JUCE_ANDROID
@@ -73,7 +73,7 @@ void registerDemos_Two() noexcept
REGISTER_DEMO (AnimationAppDemo, GUI, false)
REGISTER_DEMO (AnimationDemo, GUI, false)
REGISTER_DEMO (BouncingBallWavetableDemo, GUI, false)
- #if JUCE_MAC || JUCE_WINDOWS
+ #if JUCE_USE_CAMERA && ! JUCE_LINUX
REGISTER_DEMO (CameraDemo, GUI, true)
#endif
#if ! JUCE_ANDROID
diff --git a/examples/DemoRunner/Source/Main.cpp b/examples/DemoRunner/Source/Main.cpp
index 71379fba93..ad770d7e00 100644
--- a/examples/DemoRunner/Source/Main.cpp
+++ b/examples/DemoRunner/Source/Main.cpp
@@ -122,6 +122,7 @@ private:
#if JUCE_IOS || JUCE_ANDROID
setFullScreen (true);
+ Desktop::getInstance().setOrientationsEnabled (Desktop::rotatedClockwise | Desktop::rotatedAntiClockwise);
#else
setBounds ((int) (0.1f * getParentWidth()),
(int) (0.1f * getParentHeight()),
diff --git a/examples/GUI/CameraDemo.h b/examples/GUI/CameraDemo.h
index 300c64cc2e..6c1ef60964 100644
--- a/examples/GUI/CameraDemo.h
+++ b/examples/GUI/CameraDemo.h
@@ -31,7 +31,7 @@
dependencies: juce_core, juce_cryptography, juce_data_structures, juce_events,
juce_graphics, juce_gui_basics, juce_gui_extra, juce_video
- exporters: xcode_mac, vs2017, linux_make
+ exporters: xcode_mac, vs2017, androidstudio, xcode_iphone
moduleFlags: JUCE_USE_CAMERA=1
@@ -49,15 +49,18 @@
#include "../Assets/DemoUtilities.h"
//==============================================================================
-class CameraDemo : public Component,
- private CameraDevice::Listener,
- private AsyncUpdater
+class CameraDemo : public Component
{
public:
CameraDemo()
{
setOpaque (true);
+ #if JUCE_ANDROID
+ // Android requires exclusive access to the audio device when recording videos.
+ audioDeviceManager.closeAudioDevice();
+ #endif
+
addAndMakeVisible (cameraSelectorComboBox);
updateCameraList();
cameraSelectorComboBox.setSelectedId (1);
@@ -76,6 +79,21 @@ public:
cameraSelectorComboBox.setSelectedId (2);
setSize (500, 500);
+
+ #if JUCE_IOS || JUCE_ANDROID
+ setPortraitOrientationEnabled (true);
+ #endif
+ }
+
+ ~CameraDemo()
+ {
+ #if JUCE_IOS || JUCE_ANDROID
+ setPortraitOrientationEnabled (false);
+ #endif
+
+ #if JUCE_ANDROID
+ audioDeviceManager.restartLastAudioDevice();
+ #endif
}
//==============================================================================
@@ -101,26 +119,66 @@ public:
recordMovieButton.setBounds (top.removeFromLeft (recordMovieButton.getWidth()));
r.removeFromTop (4);
- auto previewArea = r.removeFromTop (r.getHeight() / 2);
+ auto previewArea = shouldUseLandscapeLayout() ? r.removeFromLeft (r.getWidth() / 2)
+ : r.removeFromTop (r.getHeight() / 2);
if (cameraPreviewComp.get() != nullptr)
cameraPreviewComp->setBounds (previewArea);
- r.removeFromTop (4);
+ if (shouldUseLandscapeLayout())
+ r.removeFromLeft (4);
+ else
+ r.removeFromTop (4);
+
lastSnapshot.setBounds (r);
}
private:
//==============================================================================
+ // if this PIP is running inside the demo runner, we'll use the shared device manager instead
+ #ifndef JUCE_DEMO_RUNNER
+ AudioDeviceManager audioDeviceManager;
+ #else
+ AudioDeviceManager& audioDeviceManager { getSharedAudioDeviceManager (0, 2) };
+ #endif
+
std::unique_ptr cameraDevice;
std::unique_ptr cameraPreviewComp;
ImageComponent lastSnapshot;
ComboBox cameraSelectorComboBox { "Camera" };
TextButton snapshotButton { "Take a snapshot" };
+ #if ! JUCE_ANDROID && ! JUCE_IOS
TextButton recordMovieButton { "Record a movie (to your desktop)..." };
+ #else
+ TextButton recordMovieButton { "Record a movie" };
+ #endif
bool recordingMovie = false;
+ File recordingFile;
+ bool contentSharingPending = false;
+
+ void setPortraitOrientationEnabled (bool shouldBeEnabled)
+ {
+ auto allowedOrientations = Desktop::getInstance().getOrientationsEnabled();
+
+ if (shouldBeEnabled)
+ allowedOrientations |= Desktop::upright;
+ else
+ allowedOrientations &= ~Desktop::upright;
+
+ Desktop::getInstance().setOrientationsEnabled (allowedOrientations);
+ }
+
+ bool shouldUseLandscapeLayout() const noexcept
+ {
+ #if JUCE_ANDROID || JUCE_IOS
+ auto orientation = Desktop::getInstance().getCurrentOrientation();
+ return orientation == Desktop::rotatedClockwise || orientation == Desktop::rotatedAntiClockwise;
+ #else
+ return false;
+ #endif
+ }
void updateCameraList()
{
@@ -137,25 +195,68 @@ private:
void cameraChanged()
{
// This is called when the user chooses a camera from the drop-down list.
- cameraDevice .reset();
+ #if JUCE_IOS
+ // On iOS, when switching camera, open the new camera first, so that it can
+ // share the underlying camera session with the old camera. Otherwise, the
+ // session would have to be closed first, which can take several seconds.
+ if (cameraSelectorComboBox.getSelectedId() == 1)
+ cameraDevice.reset();
+ #else
+ cameraDevice.reset();
+ #endif
cameraPreviewComp.reset();
recordingMovie = false;
if (cameraSelectorComboBox.getSelectedId() > 1)
{
- // Try to open the user's choice of camera..
- cameraDevice.reset (CameraDevice::openDevice (cameraSelectorComboBox.getSelectedId() - 2));
+ #if JUCE_ANDROID || JUCE_IOS
+ openCameraAsync();
+ #else
+ cameraDeviceOpenResult (CameraDevice::openDevice (cameraSelectorComboBox.getSelectedId() - 2), {});
+ #endif
+ }
+ else
+ {
+ snapshotButton .setEnabled (cameraDevice != nullptr && ! contentSharingPending);
+ recordMovieButton.setEnabled (cameraDevice != nullptr && ! contentSharingPending);
+ resized();
+ }
+ }
- // and if it worked, create a preview component for it..
- if (cameraDevice.get() != nullptr)
- {
- cameraPreviewComp.reset (cameraDevice->createViewerComponent());
- addAndMakeVisible (cameraPreviewComp.get());
- }
+ void openCameraAsync()
+ {
+ SafePointer safeThis (this);
+
+ CameraDevice::openDeviceAsync (cameraSelectorComboBox.getSelectedId() - 2,
+ [safeThis] (CameraDevice* device, const String& error) mutable
+ {
+ if (safeThis)
+ safeThis->cameraDeviceOpenResult (device, error);
+ });
+ }
+
+ void cameraDeviceOpenResult (CameraDevice* device, const String& error)
+ {
+ // If camera opening worked, create a preview component for it..
+ cameraDevice.reset (device);
+
+ if (cameraDevice.get() != nullptr)
+ {
+ #if JUCE_ANDROID
+ SafePointer safeThis (this);
+ cameraDevice->onErrorOccurred = [safeThis] (const String& error) mutable { if (safeThis) safeThis->errorOccurred (error); };
+ #endif
+ cameraPreviewComp.reset (cameraDevice->createViewerComponent());
+ addAndMakeVisible (cameraPreviewComp.get());
+ }
+ else
+ {
+ AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Camera open failed",
+ "Camera open failed, reason: " + error);
}
- snapshotButton .setEnabled (cameraDevice.get() != nullptr);
- recordMovieButton.setEnabled (cameraDevice.get() != nullptr);
+ snapshotButton .setEnabled (cameraDevice.get() != nullptr && ! contentSharingPending);
+ recordMovieButton.setEnabled (cameraDevice.get() != nullptr && ! contentSharingPending);
resized();
}
@@ -169,10 +270,20 @@ private:
// Start recording to a file on the user's desktop..
recordingMovie = true;
- auto file = File::getSpecialLocation (File::userDesktopDirectory)
- .getNonexistentChildFile ("JuceCameraDemo", CameraDevice::getFileExtension());
+ #if JUCE_ANDROID || JUCE_IOS
+ recordingFile = File::getSpecialLocation (File::tempDirectory)
+ #else
+ recordingFile = File::getSpecialLocation (File::userDesktopDirectory)
+ #endif
+ .getNonexistentChildFile ("JuceCameraVideoDemo", CameraDevice::getFileExtension());
- cameraDevice->startRecordingToFile (file);
+ #if JUCE_ANDROID
+ // Android does not support taking pictures while recording video.
+ snapshotButton.setEnabled (false);
+ #endif
+
+ cameraSelectorComboBox.setEnabled (false);
+ cameraDevice->startRecordingToFile (recordingFile);
recordMovieButton.setButtonText ("Stop Recording");
}
else
@@ -180,40 +291,99 @@ private:
// Already recording, so stop...
recordingMovie = false;
cameraDevice->stopRecording();
+ #if ! JUCE_ANDROID && ! JUCE_IOS
recordMovieButton.setButtonText ("Start recording (to a file on your desktop)");
+ #else
+ recordMovieButton.setButtonText ("Record a movie");
+ #endif
+ cameraSelectorComboBox.setEnabled (true);
+
+ #if JUCE_ANDROID
+ snapshotButton.setEnabled (true);
+ #endif
+
+ #if JUCE_ANDROID || JUCE_IOS
+ URL url (recordingFile);
+
+ snapshotButton .setEnabled (false);
+ recordMovieButton.setEnabled (false);
+ contentSharingPending = true;
+
+ SafePointer safeThis (this);
+
+ juce::ContentSharer::getInstance()->shareFiles ({url},
+ [safeThis] (bool success, const String&) mutable
+ {
+ if (safeThis)
+ safeThis->sharingFinished (success, false);
+ });
+ #endif
}
}
}
void takeSnapshot()
{
- // When the user clicks the snapshot button, we'll attach ourselves to
- // the camera as a listener, and wait for an image to arrive...
- cameraDevice->addListener (this);
+ SafePointer safeThis (this);
+ cameraDevice->takeStillPicture ([safeThis] (const Image& image) mutable { safeThis->imageReceived (image); });
}
// This is called by the camera device when a new image arrives
- void imageReceived (const Image& image) override
+ void imageReceived (const Image& image)
{
- // In this app we just want to take one image, so as soon as this happens,
- // we'll unregister ourselves as a listener.
- if (cameraDevice.get() != nullptr)
- cameraDevice->removeListener (this);
+ if (! image.isValid())
+ return;
- // This callback won't be on the message thread, so to get the image back to
- // the message thread, we'll stash a pointer to it (which is reference-counted in
- // a thead-safe way), and trigger an async callback which will then display the
- // new image..
- incomingImage = image;
- triggerAsyncUpdate();
+ lastSnapshot.setImage (image);
+
+ #if JUCE_ANDROID || JUCE_IOS
+ auto imageFile = File::getSpecialLocation (File::tempDirectory).getNonexistentChildFile ("JuceCameraPhotoDemo", ".jpg");
+
+ if (auto stream = std::unique_ptr (imageFile.createOutputStream()))
+ {
+ if (JPEGImageFormat().writeImageToStream (image, *stream))
+ {
+ URL url (imageFile);
+
+ snapshotButton .setEnabled (false);
+ recordMovieButton.setEnabled (false);
+ contentSharingPending = true;
+
+ SafePointer safeThis (this);
+
+ juce::ContentSharer::getInstance()->shareFiles ({url},
+ [safeThis] (bool success, const String&) mutable
+ {
+ if (safeThis)
+ safeThis->sharingFinished (success, true);
+ });
+ }
+ }
+ #endif
}
- Image incomingImage;
-
- void handleAsyncUpdate() override
+ void errorOccurred (const String& error)
{
- if (incomingImage.isValid())
- lastSnapshot.setImage (incomingImage);
+ AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
+ "Camera Device Error",
+ "An error has occurred: " + error + " Camera will be closed.");
+
+ cameraDevice.reset();
+
+ cameraSelectorComboBox.setSelectedId (1);
+ snapshotButton .setEnabled (false);
+ recordMovieButton.setEnabled (false);
+ }
+
+ void sharingFinished (bool success, bool isCapture)
+ {
+ AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
+ isCapture ? "Image sharing result" : "Video sharing result",
+ success ? "Success!" : "Failed!");
+
+ contentSharingPending = false;
+ snapshotButton .setEnabled (true);
+ recordMovieButton.setEnabled (true);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CameraDemo)
diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
index 1acb8f22b0..f688f097d1 100644
--- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
+++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
@@ -399,6 +399,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/containers/juce_ReferenceCountedArray.h"
"../../../../../modules/juce_core/containers/juce_ScopedValueSetter.h"
"../../../../../modules/juce_core/containers/juce_SortedSet.h"
+ "../../../../../modules/juce_core/containers/juce_SparseSet.cpp"
"../../../../../modules/juce_core/containers/juce_SparseSet.h"
"../../../../../modules/juce_core/containers/juce_Variant.cpp"
"../../../../../modules/juce_core/containers/juce_Variant.h"
@@ -1566,6 +1567,7 @@ set_source_files_properties("../../../../../modules/juce_core/containers/juce_Pr
set_source_files_properties("../../../../../modules/juce_core/containers/juce_ReferenceCountedArray.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_ScopedValueSetter.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_SortedSet.h" PROPERTIES HEADER_FILE_ONLY TRUE)
+set_source_files_properties("../../../../../modules/juce_core/containers/juce_SparseSet.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_SparseSet.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_Variant.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_Variant.h" PROPERTIES HEADER_FILE_ONLY TRUE)
diff --git a/extras/AudioPerformanceTest/Builds/Android/app/src/main/java/com/juce/audioperformancetest/AudioPerformanceTest.java b/extras/AudioPerformanceTest/Builds/Android/app/src/main/java/com/juce/audioperformancetest/AudioPerformanceTest.java
index 28e29b6460..2ca9c6d929 100644
--- a/extras/AudioPerformanceTest/Builds/Android/app/src/main/java/com/juce/audioperformancetest/AudioPerformanceTest.java
+++ b/extras/AudioPerformanceTest/Builds/Android/app/src/main/java/com/juce/audioperformancetest/AudioPerformanceTest.java
@@ -30,6 +30,7 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.hardware.camera2.*;
import android.net.http.SslError;
import android.net.Uri;
import android.os.Bundle;
@@ -119,6 +120,7 @@ public class AudioPerformanceTest extends Activity
private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;
private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;
+ private static final int JUCE_PERMISSIONS_CAMERA = 5;
private static String getAndroidPermissionName (int permissionID)
{
@@ -129,6 +131,7 @@ public class AudioPerformanceTest extends Activity
// use string value as this is not defined in SDKs < 16
case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE: return "android.permission.READ_EXTERNAL_STORAGE";
case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;
+ case JUCE_PERMISSIONS_CAMERA: return Manifest.permission.CAMERA;
}
// unknown permission ID!
@@ -1205,6 +1208,7 @@ public class AudioPerformanceTest extends Activity
setVolumeControlStream (AudioManager.STREAM_MUSIC);
permissionCallbackPtrMap = new HashMap();
+ appPausedResumedListeners = new HashMap();
}
@Override
@@ -1221,6 +1225,11 @@ public class AudioPerformanceTest extends Activity
{
suspendApp();
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appPaused();
+
try
{
Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down
@@ -1236,12 +1245,10 @@ public class AudioPerformanceTest extends Activity
super.onResume();
resumeApp();
- // Ensure that navigation/status bar visibility is correctly restored.
- for (int i = 0; i < viewHolder.getChildCount(); ++i)
- {
- if (viewHolder.getChildAt (i) instanceof ComponentPeerView)
- ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();
- }
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appResumed();
}
@Override
@@ -1368,11 +1375,14 @@ public class AudioPerformanceTest extends Activity
{
ComponentPeerView v = new ComponentPeerView (this, opaque, host);
viewHolder.addView (v);
+ addAppPausedResumedListener (v, host);
return v;
}
public final void deleteView (ComponentPeerView view)
{
+ removeAppPausedResumedListener (view, view.host);
+
view.host = 0;
ViewGroup group = (ViewGroup) (view.getParent());
@@ -1590,9 +1600,28 @@ public class AudioPerformanceTest extends Activity
public native void alertDismissed (long callback, int id);
+ //==============================================================================
+ public interface AppPausedResumedListener
+ {
+ void appPaused();
+ void appResumed();
+ }
+
+ private Map appPausedResumedListeners;
+
+ public void addAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.put (new Long (listenerHost), l);
+ }
+
+ public void removeAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.remove (new Long (listenerHost));
+ }
+
//==============================================================================
public final class ComponentPeerView extends ViewGroup
- implements View.OnFocusChangeListener
+ implements View.OnFocusChangeListener, AppPausedResumedListener
{
public ComponentPeerView (Context context, boolean opaque_, long host)
{
@@ -1940,13 +1969,25 @@ public class AudioPerformanceTest extends Activity
}
//==============================================================================
+ private native void handleAppPaused (long host);
private native void handleAppResumed (long host);
+ @Override
+ public void appPaused()
+ {
+ if (host == 0)
+ return;
+
+ handleAppPaused (host);
+ }
+
+ @Override
public void appResumed()
{
if (host == 0)
return;
+ // Ensure that navigation/status bar visibility is correctly restored.
handleAppResumed (host);
}
}
@@ -2616,6 +2657,175 @@ public class AudioPerformanceTest extends Activity
private final Object hostLock = new Object();
}
+
+ //==============================================================================
+ public class CameraDeviceStateCallback extends CameraDevice.StateCallback
+ {
+ private native void cameraDeviceStateClosed (long host, CameraDevice camera);
+ private native void cameraDeviceStateDisconnected (long host, CameraDevice camera);
+ private native void cameraDeviceStateError (long host, CameraDevice camera, int error);
+ private native void cameraDeviceStateOpened (long host, CameraDevice camera);
+
+ CameraDeviceStateCallback (long hostToUse)
+ {
+ host = hostToUse;
+ }
+
+ @Override
+ public void onClosed (CameraDevice camera)
+ {
+ cameraDeviceStateClosed (host, camera);
+ }
+
+ @Override
+ public void onDisconnected (CameraDevice camera)
+ {
+ cameraDeviceStateDisconnected (host, camera);
+ }
+
+ @Override
+ public void onError (CameraDevice camera, int error)
+ {
+ cameraDeviceStateError (host, camera, error);
+ }
+
+ @Override
+ public void onOpened (CameraDevice camera)
+ {
+ cameraDeviceStateOpened (host, camera);
+ }
+
+ private long host;
+ }
+
+ //==============================================================================
+ public class CameraCaptureSessionStateCallback extends CameraCaptureSession.StateCallback
+ {
+ private native void cameraCaptureSessionActive (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionClosed (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionConfigured (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionReady (long host, CameraCaptureSession session);
+
+ CameraCaptureSessionStateCallback (long hostToUse)
+ {
+ host = hostToUse;
+ }
+
+ @Override
+ public void onActive (CameraCaptureSession session)
+ {
+ cameraCaptureSessionActive (host, session);
+ }
+
+ @Override
+ public void onClosed (CameraCaptureSession session)
+ {
+ cameraCaptureSessionClosed (host, session);
+ }
+
+ @Override
+ public void onConfigureFailed (CameraCaptureSession session)
+ {
+ cameraCaptureSessionConfigureFailed (host, session);
+ }
+
+ @Override
+ public void onConfigured (CameraCaptureSession session)
+ {
+ cameraCaptureSessionConfigured (host, session);
+ }
+
+ @Override
+ public void onReady (CameraCaptureSession session)
+ {
+ cameraCaptureSessionReady (host, session);
+ }
+
+ private long host;
+ }
+
+ //==============================================================================
+ public class CameraCaptureSessionCaptureCallback extends CameraCaptureSession.CaptureCallback
+ {
+ private native void cameraCaptureSessionCaptureCompleted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result);
+ private native void cameraCaptureSessionCaptureFailed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureFailure failure);
+ private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult);
+ private native void cameraCaptureSessionCaptureStarted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber);
+ private native void cameraCaptureSessionCaptureSequenceAborted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId);
+ private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber);
+
+ CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview)
+ {
+ host = hostToUse;
+ preview = shouldBePreview;
+ }
+
+ @Override
+ public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result)
+ {
+ cameraCaptureSessionCaptureCompleted (host, preview, session, request, result);
+ }
+
+ @Override
+ public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)
+ {
+ cameraCaptureSessionCaptureFailed (host, preview, session, request, failure);
+ }
+
+ @Override
+ public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request,
+ CaptureResult partialResult)
+ {
+ cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult);
+ }
+
+ @Override
+ public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId)
+ {
+ cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId);
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber)
+ {
+ cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber);
+ }
+
+ @Override
+ public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp,
+ long frameNumber)
+ {
+ cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber);
+ }
+
+ private long host;
+ private boolean preview;
+ }
+
+ //==============================================================================
+ public class JuceOrientationEventListener extends OrientationEventListener
+ {
+ private native void deviceOrientationChanged (long host, int orientation);
+
+ public JuceOrientationEventListener (long hostToUse, Context context, int rate)
+ {
+ super (context, rate);
+
+ host = hostToUse;
+ }
+
+ @Override
+ public void onOrientationChanged (int orientation)
+ {
+ deviceOrientationChanged (host, orientation);
+ }
+
+ private long host;
+ }
+
+
//==============================================================================
public static final String getLocaleValue (boolean isRegion)
{
diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2017/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2017/AudioPerformanceTest_App.vcxproj
index e050297cb4..a7c1f197f1 100644
--- a/extras/AudioPerformanceTest/Builds/VisualStudio2017/AudioPerformanceTest_App.vcxproj
+++ b/extras/AudioPerformanceTest/Builds/VisualStudio2017/AudioPerformanceTest_App.vcxproj
@@ -596,6 +596,9 @@
true
+
+ true
+
true
diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2017/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2017/AudioPerformanceTest_App.vcxproj.filters
index 7a52572e9f..f593065254 100644
--- a/extras/AudioPerformanceTest/Builds/VisualStudio2017/AudioPerformanceTest_App.vcxproj.filters
+++ b/extras/AudioPerformanceTest/Builds/VisualStudio2017/AudioPerformanceTest_App.vcxproj.filters
@@ -817,6 +817,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
diff --git a/extras/AudioPerformanceTest/JuceLibraryCode/AppConfig.h b/extras/AudioPerformanceTest/JuceLibraryCode/AppConfig.h
index 109ec4a772..29b0ea00b5 100644
--- a/extras/AudioPerformanceTest/JuceLibraryCode/AppConfig.h
+++ b/extras/AudioPerformanceTest/JuceLibraryCode/AppConfig.h
@@ -147,6 +147,10 @@
//#define JUCE_PLUGINHOST_AU 0
#endif
+#ifndef JUCE_PLUGINHOST_LADSPA
+ //#define JUCE_PLUGINHOST_LADSPA 0
+#endif
+
//==============================================================================
// juce_audio_utils flags:
diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
index 11a1f09e18..3b05e76a11 100644
--- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
+++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
@@ -415,6 +415,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/containers/juce_ReferenceCountedArray.h"
"../../../../../modules/juce_core/containers/juce_ScopedValueSetter.h"
"../../../../../modules/juce_core/containers/juce_SortedSet.h"
+ "../../../../../modules/juce_core/containers/juce_SparseSet.cpp"
"../../../../../modules/juce_core/containers/juce_SparseSet.h"
"../../../../../modules/juce_core/containers/juce_Variant.cpp"
"../../../../../modules/juce_core/containers/juce_Variant.h"
@@ -1250,6 +1251,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_video/capture/juce_CameraDevice.cpp"
"../../../../../modules/juce_video/capture/juce_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_android_CameraDevice.h"
+ "../../../../../modules/juce_video/native/juce_ios_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_mac_CameraDevice.h"
"../../../../../modules/juce_video/native/juce_mac_Video.h"
"../../../../../modules/juce_video/native/juce_win32_CameraDevice.h"
@@ -1653,6 +1655,7 @@ set_source_files_properties("../../../../../modules/juce_core/containers/juce_Pr
set_source_files_properties("../../../../../modules/juce_core/containers/juce_ReferenceCountedArray.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_ScopedValueSetter.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_SortedSet.h" PROPERTIES HEADER_FILE_ONLY TRUE)
+set_source_files_properties("../../../../../modules/juce_core/containers/juce_SparseSet.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_SparseSet.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_Variant.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_Variant.h" PROPERTIES HEADER_FILE_ONLY TRUE)
@@ -2488,6 +2491,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.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_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_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_video/native/juce_win32_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
diff --git a/extras/AudioPluginHost/Builds/Android/app/src/main/java/com/roli/juce/pluginhost/AudioPluginHost.java b/extras/AudioPluginHost/Builds/Android/app/src/main/java/com/roli/juce/pluginhost/AudioPluginHost.java
index 11134d24e2..2b62f5a596 100644
--- a/extras/AudioPluginHost/Builds/Android/app/src/main/java/com/roli/juce/pluginhost/AudioPluginHost.java
+++ b/extras/AudioPluginHost/Builds/Android/app/src/main/java/com/roli/juce/pluginhost/AudioPluginHost.java
@@ -30,6 +30,7 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.hardware.camera2.*;
import android.net.http.SslError;
import android.net.Uri;
import android.os.Bundle;
@@ -119,6 +120,7 @@ public class AudioPluginHost extends Activity
private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;
private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;
+ private static final int JUCE_PERMISSIONS_CAMERA = 5;
private static String getAndroidPermissionName (int permissionID)
{
@@ -129,6 +131,7 @@ public class AudioPluginHost extends Activity
// use string value as this is not defined in SDKs < 16
case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE: return "android.permission.READ_EXTERNAL_STORAGE";
case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;
+ case JUCE_PERMISSIONS_CAMERA: return Manifest.permission.CAMERA;
}
// unknown permission ID!
@@ -1205,6 +1208,7 @@ public class AudioPluginHost extends Activity
setVolumeControlStream (AudioManager.STREAM_MUSIC);
permissionCallbackPtrMap = new HashMap();
+ appPausedResumedListeners = new HashMap();
}
@Override
@@ -1221,6 +1225,11 @@ public class AudioPluginHost extends Activity
{
suspendApp();
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appPaused();
+
try
{
Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down
@@ -1236,12 +1245,10 @@ public class AudioPluginHost extends Activity
super.onResume();
resumeApp();
- // Ensure that navigation/status bar visibility is correctly restored.
- for (int i = 0; i < viewHolder.getChildCount(); ++i)
- {
- if (viewHolder.getChildAt (i) instanceof ComponentPeerView)
- ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();
- }
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appResumed();
}
@Override
@@ -1368,11 +1375,14 @@ public class AudioPluginHost extends Activity
{
ComponentPeerView v = new ComponentPeerView (this, opaque, host);
viewHolder.addView (v);
+ addAppPausedResumedListener (v, host);
return v;
}
public final void deleteView (ComponentPeerView view)
{
+ removeAppPausedResumedListener (view, view.host);
+
view.host = 0;
ViewGroup group = (ViewGroup) (view.getParent());
@@ -1590,9 +1600,28 @@ public class AudioPluginHost extends Activity
public native void alertDismissed (long callback, int id);
+ //==============================================================================
+ public interface AppPausedResumedListener
+ {
+ void appPaused();
+ void appResumed();
+ }
+
+ private Map appPausedResumedListeners;
+
+ public void addAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.put (new Long (listenerHost), l);
+ }
+
+ public void removeAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.remove (new Long (listenerHost));
+ }
+
//==============================================================================
public final class ComponentPeerView extends ViewGroup
- implements View.OnFocusChangeListener
+ implements View.OnFocusChangeListener, AppPausedResumedListener
{
public ComponentPeerView (Context context, boolean opaque_, long host)
{
@@ -1940,13 +1969,25 @@ public class AudioPluginHost extends Activity
}
//==============================================================================
+ private native void handleAppPaused (long host);
private native void handleAppResumed (long host);
+ @Override
+ public void appPaused()
+ {
+ if (host == 0)
+ return;
+
+ handleAppPaused (host);
+ }
+
+ @Override
public void appResumed()
{
if (host == 0)
return;
+ // Ensure that navigation/status bar visibility is correctly restored.
handleAppResumed (host);
}
}
@@ -2616,6 +2657,175 @@ public class AudioPluginHost extends Activity
private final Object hostLock = new Object();
}
+
+ //==============================================================================
+ public class CameraDeviceStateCallback extends CameraDevice.StateCallback
+ {
+ private native void cameraDeviceStateClosed (long host, CameraDevice camera);
+ private native void cameraDeviceStateDisconnected (long host, CameraDevice camera);
+ private native void cameraDeviceStateError (long host, CameraDevice camera, int error);
+ private native void cameraDeviceStateOpened (long host, CameraDevice camera);
+
+ CameraDeviceStateCallback (long hostToUse)
+ {
+ host = hostToUse;
+ }
+
+ @Override
+ public void onClosed (CameraDevice camera)
+ {
+ cameraDeviceStateClosed (host, camera);
+ }
+
+ @Override
+ public void onDisconnected (CameraDevice camera)
+ {
+ cameraDeviceStateDisconnected (host, camera);
+ }
+
+ @Override
+ public void onError (CameraDevice camera, int error)
+ {
+ cameraDeviceStateError (host, camera, error);
+ }
+
+ @Override
+ public void onOpened (CameraDevice camera)
+ {
+ cameraDeviceStateOpened (host, camera);
+ }
+
+ private long host;
+ }
+
+ //==============================================================================
+ public class CameraCaptureSessionStateCallback extends CameraCaptureSession.StateCallback
+ {
+ private native void cameraCaptureSessionActive (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionClosed (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionConfigured (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionReady (long host, CameraCaptureSession session);
+
+ CameraCaptureSessionStateCallback (long hostToUse)
+ {
+ host = hostToUse;
+ }
+
+ @Override
+ public void onActive (CameraCaptureSession session)
+ {
+ cameraCaptureSessionActive (host, session);
+ }
+
+ @Override
+ public void onClosed (CameraCaptureSession session)
+ {
+ cameraCaptureSessionClosed (host, session);
+ }
+
+ @Override
+ public void onConfigureFailed (CameraCaptureSession session)
+ {
+ cameraCaptureSessionConfigureFailed (host, session);
+ }
+
+ @Override
+ public void onConfigured (CameraCaptureSession session)
+ {
+ cameraCaptureSessionConfigured (host, session);
+ }
+
+ @Override
+ public void onReady (CameraCaptureSession session)
+ {
+ cameraCaptureSessionReady (host, session);
+ }
+
+ private long host;
+ }
+
+ //==============================================================================
+ public class CameraCaptureSessionCaptureCallback extends CameraCaptureSession.CaptureCallback
+ {
+ private native void cameraCaptureSessionCaptureCompleted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result);
+ private native void cameraCaptureSessionCaptureFailed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureFailure failure);
+ private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult);
+ private native void cameraCaptureSessionCaptureStarted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber);
+ private native void cameraCaptureSessionCaptureSequenceAborted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId);
+ private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber);
+
+ CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview)
+ {
+ host = hostToUse;
+ preview = shouldBePreview;
+ }
+
+ @Override
+ public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result)
+ {
+ cameraCaptureSessionCaptureCompleted (host, preview, session, request, result);
+ }
+
+ @Override
+ public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)
+ {
+ cameraCaptureSessionCaptureFailed (host, preview, session, request, failure);
+ }
+
+ @Override
+ public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request,
+ CaptureResult partialResult)
+ {
+ cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult);
+ }
+
+ @Override
+ public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId)
+ {
+ cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId);
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber)
+ {
+ cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber);
+ }
+
+ @Override
+ public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp,
+ long frameNumber)
+ {
+ cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber);
+ }
+
+ private long host;
+ private boolean preview;
+ }
+
+ //==============================================================================
+ public class JuceOrientationEventListener extends OrientationEventListener
+ {
+ private native void deviceOrientationChanged (long host, int orientation);
+
+ public JuceOrientationEventListener (long hostToUse, Context context, int rate)
+ {
+ super (context, rate);
+
+ host = hostToUse;
+ }
+
+ @Override
+ public void onOrientationChanged (int orientation)
+ {
+ deviceOrientationChanged (host, orientation);
+ }
+
+ private long host;
+ }
+
+
//==============================================================================
public static final String getLocaleValue (boolean isRegion)
{
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2013/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2013/AudioPluginHost_App.vcxproj
index dcc1d5dd8a..5a14058346 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2013/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2013/AudioPluginHost_App.vcxproj
@@ -601,6 +601,9 @@
true
+
+ true
+
true
@@ -2433,6 +2436,7 @@
+
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2013/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2013/AudioPluginHost_App.vcxproj.filters
index 7415956798..d73bb1e334 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2013/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2013/AudioPluginHost_App.vcxproj.filters
@@ -874,6 +874,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
@@ -4062,6 +4065,9 @@
JUCE Modules\juce_video\native
+
+ JUCE Modules\juce_video\native
+
JUCE Modules\juce_video\native
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2015/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2015/AudioPluginHost_App.vcxproj
index a27197c849..03cea25fd4 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2015/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2015/AudioPluginHost_App.vcxproj
@@ -601,6 +601,9 @@
true
+
+ true
+
true
@@ -2433,6 +2436,7 @@
+
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2015/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2015/AudioPluginHost_App.vcxproj.filters
index 87f7846b1a..3425cca7a5 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2015/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2015/AudioPluginHost_App.vcxproj.filters
@@ -874,6 +874,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
@@ -4062,6 +4065,9 @@
JUCE Modules\juce_video\native
+
+ JUCE Modules\juce_video\native
+
JUCE Modules\juce_video\native
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj
index 989607160f..5d76b0597b 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj
@@ -601,6 +601,9 @@
true
+
+ true
+
true
@@ -2433,6 +2436,7 @@
+
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters
index 87a6356e2b..33384a787e 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters
@@ -874,6 +874,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
@@ -4062,6 +4065,9 @@
JUCE Modules\juce_video\native
+
+ JUCE Modules\juce_video\native
+
JUCE Modules\juce_video\native
diff --git a/extras/BinaryBuilder/Builds/VisualStudio2017/BinaryBuilder_ConsoleApp.vcxproj b/extras/BinaryBuilder/Builds/VisualStudio2017/BinaryBuilder_ConsoleApp.vcxproj
index 3249110293..151ce86a93 100644
--- a/extras/BinaryBuilder/Builds/VisualStudio2017/BinaryBuilder_ConsoleApp.vcxproj
+++ b/extras/BinaryBuilder/Builds/VisualStudio2017/BinaryBuilder_ConsoleApp.vcxproj
@@ -164,6 +164,9 @@
true
+
+ true
+
true
diff --git a/extras/BinaryBuilder/Builds/VisualStudio2017/BinaryBuilder_ConsoleApp.vcxproj.filters b/extras/BinaryBuilder/Builds/VisualStudio2017/BinaryBuilder_ConsoleApp.vcxproj.filters
index 427c4c833e..1150273d70 100644
--- a/extras/BinaryBuilder/Builds/VisualStudio2017/BinaryBuilder_ConsoleApp.vcxproj.filters
+++ b/extras/BinaryBuilder/Builds/VisualStudio2017/BinaryBuilder_ConsoleApp.vcxproj.filters
@@ -91,6 +91,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
index da821bb39c..0b94bf62c4 100644
--- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
+++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
@@ -403,6 +403,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/containers/juce_ReferenceCountedArray.h"
"../../../../../modules/juce_core/containers/juce_ScopedValueSetter.h"
"../../../../../modules/juce_core/containers/juce_SortedSet.h"
+ "../../../../../modules/juce_core/containers/juce_SparseSet.cpp"
"../../../../../modules/juce_core/containers/juce_SparseSet.h"
"../../../../../modules/juce_core/containers/juce_Variant.cpp"
"../../../../../modules/juce_core/containers/juce_Variant.h"
@@ -1645,6 +1646,7 @@ set_source_files_properties("../../../../../modules/juce_core/containers/juce_Pr
set_source_files_properties("../../../../../modules/juce_core/containers/juce_ReferenceCountedArray.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_ScopedValueSetter.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_SortedSet.h" PROPERTIES HEADER_FILE_ONLY TRUE)
+set_source_files_properties("../../../../../modules/juce_core/containers/juce_SparseSet.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_SparseSet.h" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_Variant.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties("../../../../../modules/juce_core/containers/juce_Variant.h" PROPERTIES HEADER_FILE_ONLY TRUE)
diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/java/com/juce/networkgraphicsdemo/JUCENetworkGraphicsDemo.java b/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/java/com/juce/networkgraphicsdemo/JUCENetworkGraphicsDemo.java
index e37f33ec90..fb24cf7354 100644
--- a/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/java/com/juce/networkgraphicsdemo/JUCENetworkGraphicsDemo.java
+++ b/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/java/com/juce/networkgraphicsdemo/JUCENetworkGraphicsDemo.java
@@ -113,6 +113,7 @@ public class JUCENetworkGraphicsDemo extends Activity
private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;
private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;
+ private static final int JUCE_PERMISSIONS_CAMERA = 5;
private static String getAndroidPermissionName (int permissionID)
{
@@ -123,6 +124,7 @@ public class JUCENetworkGraphicsDemo extends Activity
// use string value as this is not defined in SDKs < 16
case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE: return "android.permission.READ_EXTERNAL_STORAGE";
case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;
+ case JUCE_PERMISSIONS_CAMERA: return Manifest.permission.CAMERA;
}
// unknown permission ID!
@@ -273,6 +275,7 @@ public class JUCENetworkGraphicsDemo extends Activity
setVolumeControlStream (AudioManager.STREAM_MUSIC);
permissionCallbackPtrMap = new HashMap();
+ appPausedResumedListeners = new HashMap();
}
@Override
@@ -289,6 +292,11 @@ public class JUCENetworkGraphicsDemo extends Activity
{
suspendApp();
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appPaused();
+
try
{
Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down
@@ -304,12 +312,10 @@ public class JUCENetworkGraphicsDemo extends Activity
super.onResume();
resumeApp();
- // Ensure that navigation/status bar visibility is correctly restored.
- for (int i = 0; i < viewHolder.getChildCount(); ++i)
- {
- if (viewHolder.getChildAt (i) instanceof ComponentPeerView)
- ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();
- }
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appResumed();
}
@Override
@@ -436,11 +442,14 @@ public class JUCENetworkGraphicsDemo extends Activity
{
ComponentPeerView v = new ComponentPeerView (this, opaque, host);
viewHolder.addView (v);
+ addAppPausedResumedListener (v, host);
return v;
}
public final void deleteView (ComponentPeerView view)
{
+ removeAppPausedResumedListener (view, view.host);
+
view.host = 0;
ViewGroup group = (ViewGroup) (view.getParent());
@@ -658,9 +667,28 @@ public class JUCENetworkGraphicsDemo extends Activity
public native void alertDismissed (long callback, int id);
+ //==============================================================================
+ public interface AppPausedResumedListener
+ {
+ void appPaused();
+ void appResumed();
+ }
+
+ private Map appPausedResumedListeners;
+
+ public void addAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.put (new Long (listenerHost), l);
+ }
+
+ public void removeAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.remove (new Long (listenerHost));
+ }
+
//==============================================================================
public final class ComponentPeerView extends ViewGroup
- implements View.OnFocusChangeListener
+ implements View.OnFocusChangeListener, AppPausedResumedListener
{
public ComponentPeerView (Context context, boolean opaque_, long host)
{
@@ -1008,13 +1036,25 @@ public class JUCENetworkGraphicsDemo extends Activity
}
//==============================================================================
+ private native void handleAppPaused (long host);
private native void handleAppResumed (long host);
+ @Override
+ public void appPaused()
+ {
+ if (host == 0)
+ return;
+
+ handleAppPaused (host);
+ }
+
+ @Override
public void appResumed()
{
if (host == 0)
return;
+ // Ensure that navigation/status bar visibility is correctly restored.
handleAppResumed (host);
}
}
@@ -1656,6 +1696,7 @@ public class JUCENetworkGraphicsDemo extends Activity
private final Object hostLock = new Object();
}
+
//==============================================================================
public static final String getLocaleValue (boolean isRegion)
{
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2013/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2013/NetworkGraphicsDemo_App.vcxproj
index 68a37b87c1..deec8db869 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2013/NetworkGraphicsDemo_App.vcxproj
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2013/NetworkGraphicsDemo_App.vcxproj
@@ -596,6 +596,9 @@
true
+
+ true
+
true
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2013/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2013/NetworkGraphicsDemo_App.vcxproj.filters
index 0694a1b515..fdeb11f148 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2013/NetworkGraphicsDemo_App.vcxproj.filters
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2013/NetworkGraphicsDemo_App.vcxproj.filters
@@ -847,6 +847,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2017/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2017/NetworkGraphicsDemo_App.vcxproj
index db60810df0..2eaf232c5b 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2017/NetworkGraphicsDemo_App.vcxproj
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2017/NetworkGraphicsDemo_App.vcxproj
@@ -596,6 +596,9 @@
true
+
+ true
+
true
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2017/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2017/NetworkGraphicsDemo_App.vcxproj.filters
index 0801a22dcc..70136663bb 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2017/NetworkGraphicsDemo_App.vcxproj.filters
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2017/NetworkGraphicsDemo_App.vcxproj.filters
@@ -847,6 +847,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
diff --git a/extras/NetworkGraphicsDemo/JuceLibraryCode/AppConfig.h b/extras/NetworkGraphicsDemo/JuceLibraryCode/AppConfig.h
index a7d5fe14aa..4efb82c60a 100644
--- a/extras/NetworkGraphicsDemo/JuceLibraryCode/AppConfig.h
+++ b/extras/NetworkGraphicsDemo/JuceLibraryCode/AppConfig.h
@@ -150,6 +150,10 @@
//#define JUCE_PLUGINHOST_AU 0
#endif
+#ifndef JUCE_PLUGINHOST_LADSPA
+ //#define JUCE_PLUGINHOST_LADSPA 0
+#endif
+
//==============================================================================
// juce_audio_utils flags:
diff --git a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj
index c442d2a2f8..572c130cf4 100644
--- a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj
@@ -276,6 +276,9 @@
true
+
+ true
+
true
diff --git a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters
index 695f3e93aa..4163efab29 100644
--- a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters
@@ -541,6 +541,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj
index a57dea7877..ef598efd12 100644
--- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj
@@ -276,6 +276,9 @@
true
+
+ true
+
true
diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters
index 22073c7afb..8d2da9e4f2 100644
--- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters
@@ -541,6 +541,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj
index 055a65ae00..29133a4798 100644
--- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj
@@ -276,6 +276,9 @@
true
+
+ true
+
true
diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters
index ecaaaa4f75..42313cdb1e 100644
--- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters
@@ -541,6 +541,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h
index 7844238797..4e75af75d4 100644
--- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h
+++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h
@@ -103,7 +103,7 @@ public:
ValueWithDefault androidJavaLibs, androidRepositories, androidDependencies, androidScreenOrientation, androidActivityClass,
androidActivitySubClassName, androidActivityBaseClassName, androidManifestCustomXmlElements, androidVersionCode,
androidMinimumSDK, androidTheme, androidSharedLibraries, androidStaticLibraries, androidExtraAssetsFolder,
- androidOboeRepositoryPath, androidInternetNeeded, androidMicNeeded, androidBluetoothNeeded, androidExternalReadPermission,
+ androidOboeRepositoryPath, androidInternetNeeded, androidMicNeeded, androidCameraNeeded, androidBluetoothNeeded, androidExternalReadPermission,
androidExternalWritePermission, androidInAppBillingPermission, androidVibratePermission,androidOtherPermissions,
androidEnableRemoteNotifications, androidRemoteNotificationsConfigFile, androidEnableContentSharing, androidKeyStore,
androidKeyStorePass, androidKeyAlias, androidKeyAliasPass, gradleVersion, gradleToolchain, androidPluginVersion, buildToolsVersion;
@@ -128,6 +128,7 @@ public:
androidOboeRepositoryPath (settings, Ids::androidOboeRepositoryPath, getUndoManager()),
androidInternetNeeded (settings, Ids::androidInternetNeeded, getUndoManager(), true),
androidMicNeeded (settings, Ids::microphonePermissionNeeded, getUndoManager(), false),
+ androidCameraNeeded (settings, Ids::cameraPermissionNeeded, getUndoManager(), false),
androidBluetoothNeeded (settings, Ids::androidBluetoothNeeded, getUndoManager(), true),
androidExternalReadPermission (settings, Ids::androidExternalReadNeeded, getUndoManager(), true),
androidExternalWritePermission (settings, Ids::androidExternalWriteNeeded, getUndoManager(), true),
@@ -920,6 +921,9 @@ private:
props.add (new ChoicePropertyComponent (androidMicNeeded, "Audio Input Required"),
"If enabled, this will set the android.permission.RECORD_AUDIO flag in the manifest.");
+ props.add (new ChoicePropertyComponent (androidCameraNeeded, "Camera Required"),
+ "If enabled, this will set the android.permission.CAMERA flag in the manifest.");
+
props.add (new ChoicePropertyComponent (androidBluetoothNeeded, "Bluetooth permissions Required"),
"If enabled, this will set the android.permission.BLUETOOTH and android.permission.BLUETOOTH_ADMIN flag in the manifest. This is required for Bluetooth MIDI on Android.");
@@ -1034,25 +1038,92 @@ private:
createDirectoryOrThrow (targetFolder);
+ auto activityCode = getActivityCode (javaSourceFolder, className, package);
+
auto javaDestFile = targetFolder.getChildFile (className + ".java");
+ overwriteFileIfDifferentOrThrow (javaDestFile, activityCode);
+ }
+ String getActivityCode (const File& javaSourceFolder, const String& className, const String& package) const
+ {
+ auto runtimePermissionsCode = getRuntimePermissionsCode (javaSourceFolder, className);
+ auto midiCode = getMidiCode (javaSourceFolder, className);
+ auto webViewCode = getWebViewCode (javaSourceFolder);
+ auto cameraCode = getCameraCode (javaSourceFolder);
- String juceMidiCode, juceMidiImports, juceRuntimePermissionsCode;
+ auto javaSourceFile = javaSourceFolder.getChildFile ("JuceAppActivity.java");
+ auto javaSourceLines = StringArray::fromLines (javaSourceFile.loadFileAsString());
+
+ {
+ MemoryOutputStream newFile;
+
+ for (auto& line : javaSourceLines)
+ {
+ if (line.contains ("$$JuceAndroidMidiImports$$"))
+ newFile << midiCode.imports;
+ else if (line.contains ("$$JuceAndroidMidiCode$$"))
+ newFile << midiCode.main;
+ else if (line.contains ("$$JuceAndroidRuntimePermissionsCode$$"))
+ newFile << runtimePermissionsCode;
+ else if (line.contains ("$$JuceAndroidWebViewImports$$"))
+ newFile << webViewCode.imports;
+ else if (line.contains ("$$JuceAndroidWebViewNativeCode$$"))
+ newFile << webViewCode.native;
+ else if (line.contains ("$$JuceAndroidWebViewCode$$"))
+ newFile << webViewCode.main;
+ else if (line.contains ("$$JuceAndroidCameraImports$$"))
+ newFile << cameraCode.imports;
+ else if (line.contains ("$$JuceAndroidCameraCode$$"))
+ newFile << cameraCode.main;
+ else
+ newFile << line.replace ("$$JuceAppActivityBaseClass$$", androidActivityBaseClassName.get().toString())
+ .replace ("JuceAppActivity", className)
+ .replace ("package com.juce;", "package " + package + ";") << newLine;
+ }
+
+ javaSourceLines = StringArray::fromLines (newFile.toString());
+ }
+
+ while (javaSourceLines.size() > 2
+ && javaSourceLines[javaSourceLines.size() - 1].trim().isEmpty()
+ && javaSourceLines[javaSourceLines.size() - 2].trim().isEmpty())
+ javaSourceLines.remove (javaSourceLines.size() - 1);
+
+ return javaSourceLines.joinIntoString (newLine);
+ }
+
+ String getRuntimePermissionsCode (const File& javaSourceFolder, const String& className) const
+ {
+ if (static_cast (androidMinimumSDK.get()) >= 23)
+ {
+ auto javaRuntimePermissions = javaSourceFolder.getChildFile ("AndroidRuntimePermissions.java");
+ return javaRuntimePermissions.loadFileAsString().replace ("JuceAppActivity", className);
+ }
+
+ return {};
+ }
+
+ struct MidiCode
+ {
+ String imports;
+ String main;
+ };
+
+ MidiCode getMidiCode (const File& javaSourceFolder, const String& className) const
+ {
+ String juceMidiCode, juceMidiImports;
juceMidiImports << newLine;
if (static_cast (androidMinimumSDK.get()) >= 23)
{
auto javaAndroidMidi = javaSourceFolder.getChildFile ("AndroidMidi.java");
- auto javaRuntimePermissions = javaSourceFolder.getChildFile ("AndroidRuntimePermissions.java");
juceMidiImports << "import android.media.midi.*;" << newLine
<< "import android.bluetooth.*;" << newLine
<< "import android.bluetooth.le.*;" << newLine;
juceMidiCode = javaAndroidMidi.loadFileAsString().replace ("JuceAppActivity", className);
-
- juceRuntimePermissionsCode = javaRuntimePermissions.loadFileAsString().replace ("JuceAppActivity", className);
}
else
{
@@ -1061,6 +1132,18 @@ private:
.replace ("JuceAppActivity", className);
}
+ return { juceMidiImports, juceMidiCode };
+ }
+
+ struct WebViewCode
+ {
+ String imports;
+ String native;
+ String main;
+ };
+
+ WebViewCode getWebViewCode (const File& javaSourceFolder) const
+ {
String juceWebViewImports, juceWebViewCodeNative, juceWebViewCode;
if (static_cast (androidMinimumSDK.get()) >= 23)
@@ -1106,41 +1189,32 @@ private:
}
}
- auto javaSourceFile = javaSourceFolder.getChildFile ("JuceAppActivity.java");
- auto javaSourceLines = StringArray::fromLines (javaSourceFile.loadFileAsString());
+ return { juceWebViewImports, juceWebViewCodeNative, juceWebViewCode };
+ }
+ struct CameraCode
+ {
+ String imports;
+ String main;
+ };
+
+ CameraCode getCameraCode (const File& javaSourceFolder) const
+ {
+ String juceCameraImports, juceCameraCode;
+
+ if (static_cast (androidMinimumSDK.get()) >= 21)
+ juceCameraImports << "import android.hardware.camera2.*;" << newLine;
+
+ auto javaCameraFile = javaSourceFolder.getChildFile ("AndroidCamera.java");
+ auto juceCameraCodeAll = javaCameraFile.loadFileAsString();
+
+ if (static_cast (androidMinimumSDK.get()) >= 21)
{
- MemoryOutputStream newFile;
-
- for (auto& line : javaSourceLines)
- {
- if (line.contains ("$$JuceAndroidMidiImports$$"))
- newFile << juceMidiImports;
- else if (line.contains ("$$JuceAndroidMidiCode$$"))
- newFile << juceMidiCode;
- else if (line.contains ("$$JuceAndroidRuntimePermissionsCode$$"))
- newFile << juceRuntimePermissionsCode;
- else if (line.contains ("$$JuceAndroidWebViewImports$$"))
- newFile << juceWebViewImports;
- else if (line.contains ("$$JuceAndroidWebViewNativeCode$$"))
- newFile << juceWebViewCodeNative;
- else if (line.contains ("$$JuceAndroidWebViewCode$$"))
- newFile << juceWebViewCode;
- else
- newFile << line.replace ("$$JuceAppActivityBaseClass$$", androidActivityBaseClassName.get().toString())
- .replace ("JuceAppActivity", className)
- .replace ("package com.juce;", "package " + package + ";") << newLine;
- }
-
- javaSourceLines = StringArray::fromLines (newFile.toString());
+ juceCameraCode << juceCameraCodeAll.fromFirstOccurrenceOf ("$$CameraApi21", false, false)
+ .upToFirstOccurrenceOf ("CameraApi21$$", false, false);
}
- while (javaSourceLines.size() > 2
- && javaSourceLines[javaSourceLines.size() - 1].trim().isEmpty()
- && javaSourceLines[javaSourceLines.size() - 2].trim().isEmpty())
- javaSourceLines.remove (javaSourceLines.size() - 1);
-
- overwriteFileIfDifferentOrThrow (javaDestFile, javaSourceLines.joinIntoString (newLine));
+ return { juceCameraImports, juceCameraCode };
}
void copyAdditionalJavaFiles (const File& sourceFolder, const File& targetFolder) const
@@ -1882,6 +1956,9 @@ private:
if (androidMicNeeded.get())
s.add ("android.permission.RECORD_AUDIO");
+ if (androidCameraNeeded.get())
+ s.add ("android.permission.CAMERA");
+
if (androidBluetoothNeeded.get())
{
s.add ("android.permission.BLUETOOTH");
diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h
index 667e6ed82d..26f767b161 100644
--- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h
+++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h
@@ -73,6 +73,9 @@ public:
microphonePermissionNeededValue (settings, Ids::microphonePermissionNeeded, getUndoManager()),
microphonePermissionsTextValue (settings, Ids::microphonePermissionsText, getUndoManager(),
"This is an audio app which requires audio input. If you do not have a USB audio interface connected it will use the microphone."),
+ cameraPermissionNeededValue (settings, Ids::cameraPermissionNeeded, getUndoManager()),
+ cameraPermissionTextValue (settings, Ids::cameraPermissionText, getUndoManager(),
+ "This app requires camera usage to function properly."),
uiFileSharingEnabledValue (settings, Ids::UIFileSharingEnabled, getUndoManager()),
uiSupportsDocumentBrowserValue (settings, Ids::UISupportsDocumentBrowser, getUndoManager()),
uiStatusBarHiddenValue (settings, Ids::UIStatusBarHidden, getUndoManager()),
@@ -124,6 +127,9 @@ public:
bool isMicrophonePermissionEnabled() const { return microphonePermissionNeededValue.get(); }
String getMicrophonePermissionsTextString() const { return microphonePermissionsTextValue.get(); }
+ bool isCameraPermissionEnabled() const { return cameraPermissionNeededValue.get(); }
+ String getCameraPermissionTextString() const { return cameraPermissionTextValue.get(); }
+
bool isInAppPurchasesEnabled() const { return iosInAppPurchasesValue.get(); }
bool isBackgroundAudioEnabled() const { return iosBackgroundAudioValue.get(); }
bool isBackgroundBleEnabled() const { return iosBackgroundBleValue.get(); }
@@ -236,6 +242,14 @@ public:
props.add (new TextPropertyComponentWithEnablement (microphonePermissionsTextValue, microphonePermissionNeededValue,
"Microphone Access Text", 1024, false),
"A short description of why your app requires microphone access.");
+
+ props.add (new ChoicePropertyComponent (cameraPermissionNeededValue, "Camera Access"),
+ "Enable this to allow your app to use the camera. "
+ "The user of your app will be prompted to grant camera access permissions.");
+
+ props.add (new TextPropertyComponentWithEnablement (cameraPermissionTextValue, cameraPermissionNeededValue,
+ "Camera Access Text", 1024, false),
+ "A short description of why your app requires camera access.");
}
else if (projectType.isGUIApplication())
{
@@ -1280,9 +1294,13 @@ public:
if (owner.iOS)
{
addPlistDictionaryKeyBool (dict, "LSRequiresIPhoneOS", true);
+
if (owner.isMicrophonePermissionEnabled())
addPlistDictionaryKey (dict, "NSMicrophoneUsageDescription", owner.getMicrophonePermissionsTextString());
+ if (owner.isCameraPermissionEnabled())
+ addPlistDictionaryKey (dict, "NSCameraUsageDescription", owner.getCameraPermissionTextString());
+
if (type != AudioUnitv3PlugIn)
addPlistDictionaryKeyBool (dict, "UIViewControllerBasedStatusBarAppearance", false);
}
@@ -1682,7 +1700,8 @@ private:
ValueWithDefault customPListValue, pListPrefixHeaderValue, pListPreprocessValue, extraFrameworksValue, postbuildCommandValue,
prebuildCommandValue, duplicateAppExResourcesFolderValue, iosDeviceFamilyValue, iPhoneScreenOrientationValue,
- iPadScreenOrientationValue, customXcodeResourceFoldersValue, customXcassetsFolderValue, microphonePermissionNeededValue, microphonePermissionsTextValue,
+ iPadScreenOrientationValue, customXcodeResourceFoldersValue, customXcassetsFolderValue,
+ microphonePermissionNeededValue, microphonePermissionsTextValue, cameraPermissionNeededValue, cameraPermissionTextValue,
uiFileSharingEnabledValue, uiSupportsDocumentBrowserValue, uiStatusBarHiddenValue, documentExtensionsValue, iosInAppPurchasesValue,
iosBackgroundAudioValue, iosBackgroundBleValue, iosPushNotificationsValue, iosAppGroupsValue, iCloudPermissionsValue,
iosDevelopmentTeamIDValue, iosAppGroupsIDValue, keepCustomXcodeSchemesValue, useHeaderMapValue;
@@ -2299,6 +2318,9 @@ private:
if (iOS && isPushNotificationsEnabled())
xcodeFrameworks.addIfNotAlreadyThere ("UserNotifications");
+ if (isiOS() && project.getConfigFlag ("JUCE_USE_CAMERA").get())
+ xcodeFrameworks.addIfNotAlreadyThere ("ImageIO");
+
xcodeFrameworks.addTokens (getExtraFrameworksString(), ",;", "\"'");
xcodeFrameworks.trim();
diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h
index 1a2346967d..7b66a4695e 100644
--- a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h
+++ b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h
@@ -178,6 +178,8 @@ namespace Ids
DECLARE_ID (overwriteOnSave);
DECLARE_ID (microphonePermissionNeeded);
DECLARE_ID (microphonePermissionsText);
+ DECLARE_ID (cameraPermissionNeeded);
+ DECLARE_ID (cameraPermissionText);
DECLARE_ID (androidJavaLibs);
DECLARE_ID (androidRepositories);
DECLARE_ID (androidDependencies);
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj
index b59b827937..dea8878e6d 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj
@@ -628,6 +628,9 @@
true
+
+ true
+
true
@@ -2638,6 +2641,7 @@
+
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters
index 47e017ffdd..84b335e3bf 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -949,6 +949,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
@@ -4467,6 +4470,9 @@
JUCE Modules\juce_video\native
+
+ JUCE Modules\juce_video\native
+
JUCE Modules\juce_video\native
diff --git a/extras/UnitTestRunner/JuceLibraryCode/AppConfig.h b/extras/UnitTestRunner/JuceLibraryCode/AppConfig.h
index d15076fc5f..059f7a28f8 100644
--- a/extras/UnitTestRunner/JuceLibraryCode/AppConfig.h
+++ b/extras/UnitTestRunner/JuceLibraryCode/AppConfig.h
@@ -155,6 +155,10 @@
//#define JUCE_PLUGINHOST_AU 0
#endif
+#ifndef JUCE_PLUGINHOST_LADSPA
+ //#define JUCE_PLUGINHOST_LADSPA 0
+#endif
+
//==============================================================================
// juce_audio_utils flags:
diff --git a/extras/WindowsDLL/Builds/VisualStudio2017/WindowsDLL_StaticLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2017/WindowsDLL_StaticLibrary.vcxproj
index 92109b0740..97696d4e20 100644
--- a/extras/WindowsDLL/Builds/VisualStudio2017/WindowsDLL_StaticLibrary.vcxproj
+++ b/extras/WindowsDLL/Builds/VisualStudio2017/WindowsDLL_StaticLibrary.vcxproj
@@ -595,6 +595,9 @@
true
+
+ true
+
true
@@ -2420,6 +2423,7 @@
+
diff --git a/extras/WindowsDLL/Builds/VisualStudio2017/WindowsDLL_StaticLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2017/WindowsDLL_StaticLibrary.vcxproj.filters
index 898a4b4789..f8c3447503 100644
--- a/extras/WindowsDLL/Builds/VisualStudio2017/WindowsDLL_StaticLibrary.vcxproj.filters
+++ b/extras/WindowsDLL/Builds/VisualStudio2017/WindowsDLL_StaticLibrary.vcxproj.filters
@@ -844,6 +844,9 @@
JUCE Modules\juce_core\containers
+
+ JUCE Modules\juce_core\containers
+
JUCE Modules\juce_core\containers
@@ -4011,6 +4014,9 @@
JUCE Modules\juce_video\native
+
+ JUCE Modules\juce_video\native
+
JUCE Modules\juce_video\native
diff --git a/extras/WindowsDLL/JuceLibraryCode/AppConfig.h b/extras/WindowsDLL/JuceLibraryCode/AppConfig.h
index de3a1d428c..8510bde870 100644
--- a/extras/WindowsDLL/JuceLibraryCode/AppConfig.h
+++ b/extras/WindowsDLL/JuceLibraryCode/AppConfig.h
@@ -150,6 +150,10 @@
//#define JUCE_PLUGINHOST_AU 0
#endif
+#ifndef JUCE_PLUGINHOST_LADSPA
+ //#define JUCE_PLUGINHOST_LADSPA 0
+#endif
+
//==============================================================================
// juce_audio_utils flags:
diff --git a/modules/juce_audio_devices/native/juce_android_Midi.cpp b/modules/juce_audio_devices/native/juce_android_Midi.cpp
index f290cb4291..a178abf01f 100644
--- a/modules/juce_audio_devices/native/juce_android_Midi.cpp
+++ b/modules/juce_audio_devices/native/juce_android_Midi.cpp
@@ -221,22 +221,6 @@ public:
}
private:
- static StringArray javaStringArrayToJuce (jobjectArray jStrings)
- {
- StringArray retval;
-
- JNIEnv* env = getEnv();
- const int count = env->GetArrayLength (jStrings);
-
- for (int i = 0; i < count; ++i)
- {
- LocalRef string ((jstring) env->GetObjectArrayElement (jStrings, i));
- retval.add (juceString (string));
- }
-
- return retval;
- }
-
GlobalRef deviceManager;
};
diff --git a/modules/juce_core/misc/juce_RuntimePermissions.h b/modules/juce_core/misc/juce_RuntimePermissions.h
index 43200ebea8..532a0038ea 100644
--- a/modules/juce_core/misc/juce_RuntimePermissions.h
+++ b/modules/juce_core/misc/juce_RuntimePermissions.h
@@ -83,7 +83,10 @@ public:
readExternalStorage = 3,
/** Permission to write to external storage such as SD cards */
- writeExternalStorage = 4
+ writeExternalStorage = 4,
+
+ /** Permission to use camera */
+ camera = 5
};
//==============================================================================
diff --git a/modules/juce_core/native/java/AndroidCamera.java b/modules/juce_core/native/java/AndroidCamera.java
new file mode 100644
index 0000000000..bbce71f4fd
--- /dev/null
+++ b/modules/juce_core/native/java/AndroidCamera.java
@@ -0,0 +1,169 @@
+$$CameraApi21
+ //==============================================================================
+ public class CameraDeviceStateCallback extends CameraDevice.StateCallback
+ {
+ private native void cameraDeviceStateClosed (long host, CameraDevice camera);
+ private native void cameraDeviceStateDisconnected (long host, CameraDevice camera);
+ private native void cameraDeviceStateError (long host, CameraDevice camera, int error);
+ private native void cameraDeviceStateOpened (long host, CameraDevice camera);
+
+ CameraDeviceStateCallback (long hostToUse)
+ {
+ host = hostToUse;
+ }
+
+ @Override
+ public void onClosed (CameraDevice camera)
+ {
+ cameraDeviceStateClosed (host, camera);
+ }
+
+ @Override
+ public void onDisconnected (CameraDevice camera)
+ {
+ cameraDeviceStateDisconnected (host, camera);
+ }
+
+ @Override
+ public void onError (CameraDevice camera, int error)
+ {
+ cameraDeviceStateError (host, camera, error);
+ }
+
+ @Override
+ public void onOpened (CameraDevice camera)
+ {
+ cameraDeviceStateOpened (host, camera);
+ }
+
+ private long host;
+ }
+
+ //==============================================================================
+ public class CameraCaptureSessionStateCallback extends CameraCaptureSession.StateCallback
+ {
+ private native void cameraCaptureSessionActive (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionClosed (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionConfigured (long host, CameraCaptureSession session);
+ private native void cameraCaptureSessionReady (long host, CameraCaptureSession session);
+
+ CameraCaptureSessionStateCallback (long hostToUse)
+ {
+ host = hostToUse;
+ }
+
+ @Override
+ public void onActive (CameraCaptureSession session)
+ {
+ cameraCaptureSessionActive (host, session);
+ }
+
+ @Override
+ public void onClosed (CameraCaptureSession session)
+ {
+ cameraCaptureSessionClosed (host, session);
+ }
+
+ @Override
+ public void onConfigureFailed (CameraCaptureSession session)
+ {
+ cameraCaptureSessionConfigureFailed (host, session);
+ }
+
+ @Override
+ public void onConfigured (CameraCaptureSession session)
+ {
+ cameraCaptureSessionConfigured (host, session);
+ }
+
+ @Override
+ public void onReady (CameraCaptureSession session)
+ {
+ cameraCaptureSessionReady (host, session);
+ }
+
+ private long host;
+ }
+
+ //==============================================================================
+ public class CameraCaptureSessionCaptureCallback extends CameraCaptureSession.CaptureCallback
+ {
+ private native void cameraCaptureSessionCaptureCompleted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result);
+ private native void cameraCaptureSessionCaptureFailed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureFailure failure);
+ private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult);
+ private native void cameraCaptureSessionCaptureStarted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber);
+ private native void cameraCaptureSessionCaptureSequenceAborted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId);
+ private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber);
+
+ CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview)
+ {
+ host = hostToUse;
+ preview = shouldBePreview;
+ }
+
+ @Override
+ public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result)
+ {
+ cameraCaptureSessionCaptureCompleted (host, preview, session, request, result);
+ }
+
+ @Override
+ public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)
+ {
+ cameraCaptureSessionCaptureFailed (host, preview, session, request, failure);
+ }
+
+ @Override
+ public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request,
+ CaptureResult partialResult)
+ {
+ cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult);
+ }
+
+ @Override
+ public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId)
+ {
+ cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId);
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber)
+ {
+ cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber);
+ }
+
+ @Override
+ public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp,
+ long frameNumber)
+ {
+ cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber);
+ }
+
+ private long host;
+ private boolean preview;
+ }
+
+ //==============================================================================
+ public class JuceOrientationEventListener extends OrientationEventListener
+ {
+ private native void deviceOrientationChanged (long host, int orientation);
+
+ public JuceOrientationEventListener (long hostToUse, Context context, int rate)
+ {
+ super (context, rate);
+
+ host = hostToUse;
+ }
+
+ @Override
+ public void onOrientationChanged (int orientation)
+ {
+ deviceOrientationChanged (host, orientation);
+ }
+
+ private long host;
+ }
+
+CameraApi21$$
diff --git a/modules/juce_core/native/java/JuceAppActivity.java b/modules/juce_core/native/java/JuceAppActivity.java
index eb2a50a305..4fdeac0d9d 100644
--- a/modules/juce_core/native/java/JuceAppActivity.java
+++ b/modules/juce_core/native/java/JuceAppActivity.java
@@ -30,6 +30,7 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+$$JuceAndroidCameraImports$$ // If you get an error here, you need to re-save your project with the Projucer!
import android.net.http.SslError;
import android.net.Uri;
import android.os.Bundle;
@@ -114,6 +115,7 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;
private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;
+ private static final int JUCE_PERMISSIONS_CAMERA = 5;
private static String getAndroidPermissionName (int permissionID)
{
@@ -124,6 +126,7 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
// use string value as this is not defined in SDKs < 16
case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE: return "android.permission.READ_EXTERNAL_STORAGE";
case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;
+ case JUCE_PERMISSIONS_CAMERA: return Manifest.permission.CAMERA;
}
// unknown permission ID!
@@ -191,6 +194,7 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
setVolumeControlStream (AudioManager.STREAM_MUSIC);
permissionCallbackPtrMap = new HashMap();
+ appPausedResumedListeners = new HashMap();
}
@Override
@@ -207,6 +211,11 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
{
suspendApp();
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appPaused();
+
try
{
Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down
@@ -222,12 +231,10 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
super.onResume();
resumeApp();
- // Ensure that navigation/status bar visibility is correctly restored.
- for (int i = 0; i < viewHolder.getChildCount(); ++i)
- {
- if (viewHolder.getChildAt (i) instanceof ComponentPeerView)
- ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();
- }
+ Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]);
+
+ for (Long k : keys)
+ appPausedResumedListeners.get (k).appResumed();
}
@Override
@@ -354,11 +361,14 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
{
ComponentPeerView v = new ComponentPeerView (this, opaque, host);
viewHolder.addView (v);
+ addAppPausedResumedListener (v, host);
return v;
}
public final void deleteView (ComponentPeerView view)
{
+ removeAppPausedResumedListener (view, view.host);
+
view.host = 0;
ViewGroup group = (ViewGroup) (view.getParent());
@@ -576,9 +586,28 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
public native void alertDismissed (long callback, int id);
+ //==============================================================================
+ public interface AppPausedResumedListener
+ {
+ void appPaused();
+ void appResumed();
+ }
+
+ private Map appPausedResumedListeners;
+
+ public void addAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.put (new Long (listenerHost), l);
+ }
+
+ public void removeAppPausedResumedListener (AppPausedResumedListener l, long listenerHost)
+ {
+ appPausedResumedListeners.remove (new Long (listenerHost));
+ }
+
//==============================================================================
public final class ComponentPeerView extends ViewGroup
- implements View.OnFocusChangeListener
+ implements View.OnFocusChangeListener, AppPausedResumedListener
{
public ComponentPeerView (Context context, boolean opaque_, long host)
{
@@ -926,13 +955,25 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
}
//==============================================================================
+ private native void handleAppPaused (long host);
private native void handleAppResumed (long host);
+ @Override
+ public void appPaused()
+ {
+ if (host == 0)
+ return;
+
+ handleAppPaused (host);
+ }
+
+ @Override
public void appResumed()
{
if (host == 0)
return;
+ // Ensure that navigation/status bar visibility is correctly restored.
handleAppResumed (host);
}
}
@@ -1569,6 +1610,8 @@ $$JuceAndroidWebViewNativeCode$$ // If you get an error here, you need to re-sav
private final Object hostLock = new Object();
}
+ $$JuceAndroidCameraCode$$ // If you get an error here, you need to re-save your project with the Projucer!
+
//==============================================================================
public static final String getLocaleValue (boolean isRegion)
{
diff --git a/modules/juce_core/native/juce_android_JNIHelpers.h b/modules/juce_core/native/juce_android_JNIHelpers.h
index 1b050b9440..1324649685 100644
--- a/modules/juce_core/native/juce_android_JNIHelpers.h
+++ b/modules/juce_core/native/juce_android_JNIHelpers.h
@@ -163,39 +163,6 @@ private:
}
};
-//==============================================================================
-namespace
-{
- inline String juceString (JNIEnv* env, jstring s)
- {
- if (s == 0)
- return {};
-
- const char* const utf8 = env->GetStringUTFChars (s, nullptr);
- CharPointer_UTF8 utf8CP (utf8);
- const String result (utf8CP);
- env->ReleaseStringUTFChars (s, utf8);
- return result;
- }
-
- inline String juceString (jstring s)
- {
- return juceString (getEnv(), s);
- }
-
- inline LocalRef javaString (const String& s)
- {
- return LocalRef (getEnv()->NewStringUTF (s.toUTF8()));
- }
-
- inline LocalRef javaStringFromChar (const juce_wchar c)
- {
- char utf8[8] = { 0 };
- CharPointer_UTF8 (utf8).write (c);
- return LocalRef (getEnv()->NewStringUTF (utf8));
- }
-}
-
//==============================================================================
class JNIClassBase
{
@@ -287,6 +254,7 @@ extern AndroidSystem android;
METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \
METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(J)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \
METHOD (finish, "finish", "()V") \
+ METHOD (getWindowManager, "getWindowManager", "()Landroid/view/WindowManager;") \
METHOD (setRequestedOrientation, "setRequestedOrientation", "(I)V") \
METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \
METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \
@@ -329,14 +297,21 @@ extern AndroidSystem android;
METHOD (startActivity, "startActivity", "(Landroid/content/Intent;)V") \
METHOD (startActivityForResult, "startActivityForResult", "(Landroid/content/Intent;I)V") \
METHOD (getContentResolver, "getContentResolver", "()Landroid/content/ContentResolver;") \
+ 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);
#undef JNI_CLASS_MEMBERS
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
- STATICMETHOD (createBitmap, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;") \
- METHOD (setPixel, "setPixel", "(III)V")
+ STATICMETHOD (createBitmap, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;") \
+ STATICMETHOD (createBitmapFrom, "createBitmap", "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)Landroid/graphics/Bitmap;") \
+ METHOD (compress, "compress", "(Landroid/graphics/Bitmap$CompressFormat;ILjava/io/OutputStream;)Z") \
+ METHOD (getHeight, "getHeight", "()I") \
+ METHOD (getWidth, "getWidth", "()I") \
+ METHOD (recycle, "recycle", "()V") \
+ METHOD (setPixel, "setPixel", "(III)V")
DECLARE_JNI_CLASS (AndroidBitmap, "android/graphics/Bitmap");
#undef JNI_CLASS_MEMBERS
@@ -347,6 +322,12 @@ DECLARE_JNI_CLASS (AndroidBitmap, "android/graphics/Bitmap");
DECLARE_JNI_CLASS (AndroidBitmapConfig, "android/graphics/Bitmap$Config");
#undef JNI_CLASS_MEMBERS
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ STATICMETHOD (decodeByteArray, "decodeByteArray", "([BII)Landroid/graphics/Bitmap;")
+
+DECLARE_JNI_CLASS (AndroidBitmapFactory, "android/graphics/BitmapFactory");
+#undef JNI_CLASS_MEMBERS
+
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (dumpReferenceTables, "dumpReferenceTables", "()V")
@@ -355,6 +336,31 @@ DECLARE_JNI_CLASS (AndroidBitmapConfig, "android/graphics/Bitmap$Config");
#define JUCE_LOG_JNI_REFERENCES_TABLE getEnv()->CallStaticVoidMethod (AndroidDebug, AndroidDebug.dumpReferenceTables);
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (getRotation, "getRotation", "()I")
+
+DECLARE_JNI_CLASS (AndroidDisplay, "android/view/Display");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (constructor, "", "()V") \
+ METHOD (constructorWithLooper, "", "(Landroid/os/Looper;)V") \
+ METHOD (post, "post", "(Ljava/lang/Runnable;)Z") \
+ METHOD (postDelayed, "postDelayed", "(Ljava/lang/Runnable;J)Z") \
+
+DECLARE_JNI_CLASS (AndroidHandler, "android/os/Handler");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (constructor, "", "(Ljava/lang/String;)V") \
+ METHOD (getLooper, "getLooper", "()Landroid/os/Looper;") \
+ METHOD (join, "join", "()V") \
+ METHOD (quitSafely, "quitSafely", "()Z") \
+ METHOD (start, "start", "()V")
+
+DECLARE_JNI_CLASS (AndroidHandlerThread, "android/os/HandlerThread");
+#undef JNI_CLASS_MEMBERS
+
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (createChooser, "createChooser", "(Landroid/content/Intent;Ljava/lang/CharSequence;)Landroid/content/Intent;") \
METHOD (addCategory, "addCategory", "(Ljava/lang/String;)Landroid/content/Intent;") \
@@ -382,8 +388,11 @@ DECLARE_JNI_CLASS (AndroidIntent, "android/content/Intent");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
- METHOD (constructor, "", "()V") \
- METHOD (setValues, "setValues", "([F)V") \
+ METHOD (constructor, "", "()V") \
+ METHOD (postRotate, "postRotate", "(FFF)Z") \
+ METHOD (postScale, "postScale", "(FFFF)Z") \
+ METHOD (postTranslate, "postTranslate", "(FF)Z") \
+ METHOD (setValues, "setValues", "([F)V")
DECLARE_JNI_CLASS (AndroidMatrix, "android/graphics/Matrix");
#undef JNI_CLASS_MEMBERS
@@ -417,6 +426,12 @@ DECLARE_JNI_CLASS (AndroidPaint, "android/graphics/Paint");
DECLARE_JNI_CLASS (AndroidPendingIntent, "android/app/PendingIntent");
#undef JNI_CLASS_MEMBERS
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (toString, "toString", "()Ljava/lang/String;")
+
+DECLARE_JNI_CLASS (AndroidRange, "android/util/Range");
+#undef JNI_CLASS_MEMBERS
+
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (constructor, "", "(IIII)V") \
FIELD (left, "left", "I") \
@@ -424,7 +439,7 @@ DECLARE_JNI_CLASS (AndroidPendingIntent, "android/app/PendingIntent");
FIELD (top, "top", "I") \
FIELD (bottom, "bottom", "I") \
-DECLARE_JNI_CLASS (AndroidRectClass, "android/graphics/Rect");
+DECLARE_JNI_CLASS (AndroidRect, "android/graphics/Rect");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
@@ -434,6 +449,13 @@ DECLARE_JNI_CLASS (AndroidRectClass, "android/graphics/Rect");
DECLARE_JNI_CLASS (AndroidResources, "android/content/res/Resources")
#undef JNI_CLASS_MEMBERS
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (getHeight, "getHeight", "()I") \
+ METHOD (getWidth, "getWidth", "()I")
+
+DECLARE_JNI_CLASS (AndroidSize, "android/util/Size");
+#undef JNI_CLASS_MEMBERS
+
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (parse, "parse", "(Ljava/lang/String;)Landroid/net/Uri;") \
METHOD (toString, "toString", "()Ljava/lang/String;")
@@ -465,6 +487,12 @@ DECLARE_JNI_CLASS (AndroidView, "android/view/View");
DECLARE_JNI_CLASS (AndroidViewGroup, "android/view/ViewGroup")
#undef JNI_CLASS_MEMBERS
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (getDefaultDisplay, "getDefaultDisplay", "()Landroid/view/Display;")
+
+DECLARE_JNI_CLASS (AndroidWindowManager, "android/view/WindowManager");
+#undef JNI_CLASS_MEMBERS
+
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (constructor, "", "(I)V") \
@@ -477,6 +505,7 @@ DECLARE_JNI_CLASS (JavaArrayList, "java/util/ArrayList");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ STATICMETHOD (valueOf, "valueOf", "(Z)Ljava/lang/Boolean;") \
METHOD (booleanValue, "booleanValue", "()Z")
DECLARE_JNI_CLASS (JavaBoolean, "java/lang/Boolean");
@@ -507,6 +536,13 @@ DECLARE_JNI_CLASS (JavaBoolean, "java/lang/Boolean");
DECLARE_JNI_CLASS (JavaBundle, "android/os/Bundle");
#undef JNI_CLASS_MEMBERS
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (get, "get", "([B)Ljava/nio/ByteBuffer;") \
+ METHOD (remaining, "remaining", "()I")
+
+DECLARE_JNI_CLASS (JavaByteBuffer, "java/nio/ByteBuffer");
+#undef JNI_CLASS_MEMBERS
+
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (toString, "toString", "()Ljava/lang/String;")
@@ -514,6 +550,7 @@ DECLARE_JNI_CLASS (JavaCharSequence, "java/lang/CharSequence");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ STATICMETHOD (forName, "forName", "(Ljava/lang/String;)Ljava/lang/Class;") \
METHOD (getName, "getName", "()Ljava/lang/String;") \
METHOD (getModifiers, "getModifiers", "()I") \
METHOD (isAnnotation, "isAnnotation", "()Z") \
@@ -571,7 +608,8 @@ DECLARE_JNI_CLASS (JavaHashMap, "java/util/HashMap");
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (parseInt, "parseInt", "(Ljava/lang/String;I)I") \
- STATICMETHOD (valueOf, "valueOf", "(I)Ljava/lang/Integer;")
+ STATICMETHOD (valueOf, "valueOf", "(I)Ljava/lang/Integer;") \
+ METHOD (intValue, "intValue", "()I")
DECLARE_JNI_CLASS (JavaInteger, "java/lang/Integer");
#undef JNI_CLASS_MEMBERS
@@ -583,6 +621,13 @@ DECLARE_JNI_CLASS (JavaInteger, "java/lang/Integer");
DECLARE_JNI_CLASS (JavaIterator, "java/util/Iterator");
#undef JNI_CLASS_MEMBERS
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (get, "get", "(I)Ljava/lang/Object;") \
+ METHOD (size, "size", "()I")
+
+DECLARE_JNI_CLASS (JavaList, "java/util/List");
+#undef JNI_CLASS_MEMBERS
+
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (constructor, "", "(J)V")
@@ -633,6 +678,71 @@ DECLARE_JNI_CLASS (JavaSet, "java/util/Set");
DECLARE_JNI_CLASS (JavaString, "java/lang/String");
#undef JNI_CLASS_MEMBERS
+//==============================================================================
+namespace
+{
+ inline String juceString (JNIEnv* env, jstring s)
+ {
+ if (s == 0)
+ return {};
+
+ const char* const utf8 = env->GetStringUTFChars (s, nullptr);
+ CharPointer_UTF8 utf8CP (utf8);
+ const String result (utf8CP);
+ env->ReleaseStringUTFChars (s, utf8);
+ return result;
+ }
+
+ inline String juceString (jstring s)
+ {
+ return juceString (getEnv(), s);
+ }
+
+ inline LocalRef javaString (const String& s)
+ {
+ return LocalRef (getEnv()->NewStringUTF (s.toUTF8()));
+ }
+
+ inline LocalRef javaStringFromChar (const juce_wchar c)
+ {
+ char utf8[8] = { 0 };
+ CharPointer_UTF8 (utf8).write (c);
+ return LocalRef (getEnv()->NewStringUTF (utf8));
+ }
+
+ inline LocalRef juceStringArrayToJava (const StringArray& juceArray)
+ {
+ auto* env = getEnv();
+
+ LocalRef result (env->NewObjectArray ((jsize) juceArray.size(),
+ JavaString,
+ javaString ("").get()));
+
+ for (int i = 0; i < juceArray.size(); ++i)
+ env->SetObjectArrayElement (result, i, javaString (juceArray [i]).get());
+
+ return result;
+ }
+
+ inline StringArray javaStringArrayToJuce (const LocalRef& javaArray)
+ {
+ if (javaArray.get() == nullptr)
+ return {};
+
+ auto* env = getEnv();
+
+ StringArray result;
+
+ for (int i = 0; i < env->GetArrayLength (javaArray.get()); ++i)
+ {
+ LocalRef javaString ((jstring) env->GetObjectArrayElement (javaArray.get(), i));
+ result.add (juceString (javaString.get()));
+ }
+
+ return result;
+ }
+}
+
//==============================================================================
class AndroidInterfaceImplementer;
diff --git a/modules/juce_events/native/juce_android_Messaging.cpp b/modules/juce_events/native/juce_android_Messaging.cpp
index 3913ed7f81..abc5e6bcee 100644
--- a/modules/juce_events/native/juce_android_Messaging.cpp
+++ b/modules/juce_events/native/juce_android_Messaging.cpp
@@ -23,14 +23,6 @@
namespace juce
{
-#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
- METHOD (constructor, "", "()V") \
- METHOD (post, "post", "(Ljava/lang/Runnable;)Z") \
-
-DECLARE_JNI_CLASS (JNIHandler, "android/os/Handler");
-#undef JNI_CLASS_MEMBERS
-
-
//==============================================================================
namespace Android
{
@@ -58,14 +50,14 @@ namespace Android
struct Handler
{
- Handler() : nativeHandler (getEnv()->NewObject (JNIHandler, JNIHandler.constructor)) {}
+ Handler() : nativeHandler (getEnv()->NewObject (AndroidHandler, AndroidHandler.constructor)) {}
~Handler() { clearSingletonInstance(); }
JUCE_DECLARE_SINGLETON (Handler, false)
bool post (jobject runnable)
{
- return (getEnv()->CallBooleanMethod (nativeHandler.get(), JNIHandler.post, runnable) != 0);
+ return (getEnv()->CallBooleanMethod (nativeHandler.get(), AndroidHandler.post, runnable) != 0);
}
GlobalRef nativeHandler;
diff --git a/modules/juce_graphics/native/juce_android_Fonts.cpp b/modules/juce_graphics/native/juce_android_Fonts.cpp
index eea53ec6d0..e058f87cea 100644
--- a/modules/juce_graphics/native/juce_android_Fonts.cpp
+++ b/modules/juce_graphics/native/juce_android_Fonts.cpp
@@ -176,7 +176,7 @@ public:
void initialise (JNIEnv* const env)
{
- rect = GlobalRef (env->NewObject (AndroidRectClass, AndroidRectClass.constructor, 0, 0, 0, 0));
+ rect = GlobalRef (env->NewObject (AndroidRect, AndroidRect.constructor, 0, 0, 0, 0));
paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality));
const LocalRef ignored (paint.callObjectMethod (AndroidPaint.setTypeface, typeface.get()));
@@ -298,10 +298,10 @@ public:
env->DeleteLocalRef (matrix);
- const int left = env->GetIntField (rect.get(), AndroidRectClass.left);
- const int top = env->GetIntField (rect.get(), AndroidRectClass.top);
- const int right = env->GetIntField (rect.get(), AndroidRectClass.right);
- const int bottom = env->GetIntField (rect.get(), AndroidRectClass.bottom);
+ const int left = env->GetIntField (rect.get(), AndroidRect.left);
+ const int top = env->GetIntField (rect.get(), AndroidRect.top);
+ const int right = env->GetIntField (rect.get(), AndroidRect.right);
+ const int bottom = env->GetIntField (rect.get(), AndroidRect.bottom);
const Rectangle bounds (left, top, right - left, bottom - top);
diff --git a/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp b/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp
index a78e7a027f..15b18d8fc0 100644
--- a/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp
+++ b/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp
@@ -486,7 +486,7 @@ public:
{
ignoreUnused (selection, selectionArgs, sortOrder);
- StringArray requestedColumns = javaStringArrayToJuceStringArray (projection);
+ StringArray requestedColumns = javaStringArrayToJuce (projection);
StringArray supportedColumns = getSupportedColumns();
StringArray resultColumns;
@@ -501,7 +501,7 @@ public:
if (resultColumns.isEmpty())
return nullptr;
- auto resultJavaColumns = juceStringArrayToJavaStringArray (resultColumns);
+ auto resultJavaColumns = juceStringArrayToJava (resultColumns);
auto* env = getEnv();
@@ -550,7 +550,7 @@ public:
if (extension.isEmpty())
return nullptr;
- return juceStringArrayToJavaStringArray (filterMimeTypes (getMimeTypesForFileExtension (extension),
+ return juceStringArrayToJava (filterMimeTypes (getMimeTypesForFileExtension (extension),
juceString (mimeTypeFilter.get())));
}
@@ -683,40 +683,6 @@ private:
return { index, filename, prepareFilesThread->getFilePaths()[index.getIntValue()] };
}
- static LocalRef juceStringArrayToJavaStringArray (const StringArray& juceArray)
- {
- auto* env = getEnv();
-
- auto javaArray = LocalRef (env->NewObjectArray ((jsize) juceArray.size(),
- JavaString,
- javaString ("").get()));
-
- for (int i = 0; i < juceArray.size(); ++i)
- env->SetObjectArrayElement (javaArray, i, javaString (juceArray [i]).get());
-
- return javaArray;
- }
-
- static StringArray javaStringArrayToJuceStringArray (const LocalRef& javaArray)
- {
- if (javaArray.get() == 0)
- return {};
-
- auto* env = getEnv();
-
- const int size = env->GetArrayLength (javaArray.get());
-
- StringArray juceArray;
-
- for (int i = 0; i < size; ++i)
- {
- auto javaString = LocalRef ((jstring) env->GetObjectArrayElement (javaArray.get(), i));
- juceArray.add (juceString (javaString.get()));
- }
-
- return juceArray;
- }
-
static StringArray getSupportedColumns()
{
return StringArray ("_display_name", "_size");
diff --git a/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/modules/juce_gui_basics/native/juce_android_Windowing.cpp
index 0c5614cb94..9f8ca138d5 100644
--- a/modules/juce_gui_basics/native/juce_android_Windowing.cpp
+++ b/modules/juce_gui_basics/native/juce_android_Windowing.cpp
@@ -560,6 +560,8 @@ public:
Component::unfocusAllComponents();
}
+ void handleAppPausedCallback() {}
+
void handleAppResumedCallback()
{
if (Component* kiosk = Desktop::getInstance().getKioskModeComponent())
@@ -630,10 +632,10 @@ public:
void handlePaintCallback (JNIEnv* env, jobject canvas, jobject paint)
{
jobject rect = env->CallObjectMethod (canvas, CanvasMinimal.getClipBounds);
- const int left = env->GetIntField (rect, AndroidRectClass.left);
- const int top = env->GetIntField (rect, AndroidRectClass.top);
- const int right = env->GetIntField (rect, AndroidRectClass.right);
- const int bottom = env->GetIntField (rect, AndroidRectClass.bottom);
+ const int left = env->GetIntField (rect, AndroidRect.left);
+ const int top = env->GetIntField (rect, AndroidRect.top);
+ const int right = env->GetIntField (rect, AndroidRect.right);
+ const int bottom = env->GetIntField (rect, AndroidRect.bottom);
env->DeleteLocalRef (rect);
const Rectangle clip (left, top, right - left, bottom - top);
@@ -810,6 +812,7 @@ JUCE_VIEW_CALLBACK (void, handleKeyDown, (JNIEnv* env, jobject /*view*/,
JUCE_VIEW_CALLBACK (void, handleKeyUp, (JNIEnv* env, jobject /*view*/, jlong host, jint k, jint kc), handleKeyUpCallback ((int) k, (int) kc))
JUCE_VIEW_CALLBACK (void, handleBackButton, (JNIEnv* env, jobject /*view*/, jlong host), handleBackButtonCallback())
JUCE_VIEW_CALLBACK (void, handleKeyboardHidden, (JNIEnv* env, jobject /*view*/, jlong host), handleKeyboardHiddenCallback())
+JUCE_VIEW_CALLBACK (void, handleAppPaused, (JNIEnv* env, jobject /*view*/, jlong host), handleAppPausedCallback())
JUCE_VIEW_CALLBACK (void, handleAppResumed, (JNIEnv* env, jobject /*view*/, jlong host), handleAppResumedCallback())
//==============================================================================
@@ -819,18 +822,6 @@ ComponentPeer* Component::createNewPeer (int styleFlags, void*)
}
//==============================================================================
-#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
- METHOD (getRotation, "getRotation", "()I")
-
-DECLARE_JNI_CLASS (Display, "android/view/Display");
-#undef JNI_CLASS_MEMBERS
-
-#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
- METHOD (getDefaultDisplay, "getDefaultDisplay", "()Landroid/view/Display;")
-
-DECLARE_JNI_CLASS (WindowManager, "android/view/WindowManager");
-#undef JNI_CLASS_MEMBERS
-
bool Desktop::canUseSemiTransparentWindows() noexcept
{
return true;
@@ -857,11 +848,11 @@ Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
if (windowManager.get() != 0)
{
- LocalRef display = LocalRef (env->CallObjectMethod (windowManager, WindowManager.getDefaultDisplay));
+ LocalRef display = LocalRef (env->CallObjectMethod (windowManager, AndroidWindowManager.getDefaultDisplay));
if (display.get() != 0)
{
- int rotation = env->CallIntMethod (display, Display.getRotation);
+ int rotation = env->CallIntMethod (display, AndroidDisplay.getRotation);
switch (rotation)
{
diff --git a/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp b/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp
index 458edfd73f..b6d6217db0 100644
--- a/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp
+++ b/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp
@@ -1427,8 +1427,8 @@ struct PushNotifications::Pimpl
propertiesDynamicObject->setProperty ("clickAction", juceString (clickAction.get()));
propertiesDynamicObject->setProperty ("bodyLocalizationKey", juceString (bodyLocalizationKey.get()));
propertiesDynamicObject->setProperty ("titleLocalizationKey", juceString (titleLocalizationKey.get()));
- propertiesDynamicObject->setProperty ("bodyLocalizationArgs", jobjectArrayToStringArray (bodyLocalizationArgs));
- propertiesDynamicObject->setProperty ("titleLocalizationArgs", jobjectArrayToStringArray (titleLocalizationArgs));
+ propertiesDynamicObject->setProperty ("bodyLocalizationArgs", javaStringArrayToJuce (bodyLocalizationArgs));
+ propertiesDynamicObject->setProperty ("titleLocalizationArgs", javaStringArrayToJuce (titleLocalizationArgs));
propertiesDynamicObject->setProperty ("link", link.get() != 0 ? juceString ((jstring) env->CallObjectMethod (link, AndroidUri.toString)) : String());
}
@@ -1436,23 +1436,6 @@ struct PushNotifications::Pimpl
return n;
}
-
- static StringArray jobjectArrayToStringArray (const LocalRef& array)
- {
- if (array == 0)
- return {};
-
- auto* env = getEnv();
-
- const int size = env->GetArrayLength (array.get());
-
- StringArray stringArray;
-
- for (int i = 0; i < size; ++i)
- stringArray.add (juceString ((jstring) env->GetObjectArrayElement (array.get(), (jsize) i)));
-
- return stringArray;
- }
#endif
void setupChannels (const Array& groups, const Array& channels)
diff --git a/modules/juce_video/capture/juce_CameraDevice.cpp b/modules/juce_video/capture/juce_CameraDevice.cpp
index 62d23e7f20..bfed050cb0 100644
--- a/modules/juce_video/capture/juce_CameraDevice.cpp
+++ b/modules/juce_video/capture/juce_CameraDevice.cpp
@@ -27,22 +27,129 @@
namespace juce
{
-#if JUCE_MAC || JUCE_IOS
+#if JUCE_MAC
#include "../native/juce_mac_CameraDevice.h"
#elif JUCE_WINDOWS
#include "../native/juce_win32_CameraDevice.h"
+#elif JUCE_IOS
+ #if JUCE_CLANG
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wunguarded-availability-new"
+ #endif
+
+ #include "../native/juce_ios_CameraDevice.h"
+
+ #if JUCE_CLANG
+ #pragma clang diagnostic pop
+ #endif
#elif JUCE_ANDROID
#include "../native/juce_android_CameraDevice.h"
#endif
+#if JUCE_ANDROID || JUCE_IOS
+//==============================================================================
+class CameraDevice::CameraFactory
+{
+public:
+ static CameraFactory& getInstance()
+ {
+ static CameraFactory factory;
+ return factory;
+ }
+
+ void openCamera (int index, OpenCameraResultCallback resultCallback,
+ int minWidth, int minHeight, int maxWidth, int maxHeight, bool useHighQuality)
+ {
+ auto cameraId = getAvailableDevices()[index];
+
+ if (getCameraIndex (cameraId) != -1)
+ {
+ // You are trying to open the same camera twice.
+ jassertfalse;
+ return;
+ }
+
+ std::unique_ptr device (new CameraDevice (cameraId, index,
+ minWidth, minHeight, maxWidth,
+ maxHeight, useHighQuality));
+
+ camerasToOpen.add ({ nextRequestId++,
+ std::unique_ptr (device.release()),
+ resultCallback });
+
+ auto& pendingOpen = camerasToOpen.getReference (camerasToOpen.size() - 1);
+
+ pendingOpen.device->pimpl->open ([this](const String& deviceId, const String& error)
+ {
+ int index = getCameraIndex (deviceId);
+
+ if (index == -1)
+ return;
+
+ auto& pendingOpen = camerasToOpen.getReference (index);
+
+ if (error.isEmpty())
+ pendingOpen.resultCallback (pendingOpen.device.release(), error);
+ else
+ pendingOpen.resultCallback (nullptr, error);
+
+ int id = pendingOpen.requestId;
+
+ MessageManager::callAsync ([this, id]() { removeRequestWithId (id); });
+ });
+ }
+
+private:
+ int getCameraIndex (const String& cameraId) const
+ {
+ for (int i = 0; i < camerasToOpen.size(); ++i)
+ {
+ auto& pendingOpen = camerasToOpen.getReference (i);
+
+ if (pendingOpen.device->pimpl->getCameraId() == cameraId)
+ return i;
+ }
+
+ return -1;
+ }
+
+ void removeRequestWithId (int id)
+ {
+ for (int i = camerasToOpen.size(); --i >= 0;)
+ {
+ if (camerasToOpen.getReference (i).requestId == id)
+ {
+ camerasToOpen.remove (i);
+ return;
+ }
+ }
+ }
+
+ struct PendingCameraOpen
+ {
+ int requestId;
+ std::unique_ptr device;
+ OpenCameraResultCallback resultCallback;
+ };
+
+ Array camerasToOpen;
+ static int nextRequestId;
+};
+
+int CameraDevice::CameraFactory::nextRequestId = 0;
+
+#endif
+
//==============================================================================
CameraDevice::CameraDevice (const String& nm, int index, int minWidth, int minHeight, int maxWidth, int maxHeight, bool useHighQuality)
- : name (nm), pimpl (new Pimpl (name, index, minWidth, minHeight, maxWidth, maxHeight, useHighQuality))
+ : name (nm), pimpl (new Pimpl (*this, name, index, minWidth, minHeight, maxWidth, maxHeight, useHighQuality))
{
}
CameraDevice::~CameraDevice()
{
+ jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
+
stopRecording();
pimpl.reset();
}
@@ -52,6 +159,11 @@ Component* CameraDevice::createViewerComponent()
return new ViewerComponent (*this);
}
+void CameraDevice::takeStillPicture (std::function pictureTakenCallback)
+{
+ pimpl->takeStillPicture (pictureTakenCallback);
+}
+
void CameraDevice::startRecordingToFile (const File& file, int quality)
{
stopRecording();
@@ -68,18 +180,6 @@ void CameraDevice::stopRecording()
pimpl->stopRecording();
}
-void CameraDevice::addListener (Listener* listenerToAdd)
-{
- if (listenerToAdd != nullptr)
- pimpl->addListener (listenerToAdd);
-}
-
-void CameraDevice::removeListener (Listener* listenerToRemove)
-{
- if (listenerToRemove != nullptr)
- pimpl->removeListener (listenerToRemove);
-}
-
//==============================================================================
StringArray CameraDevice::getAvailableDevices()
{
@@ -94,12 +194,44 @@ CameraDevice* CameraDevice::openDevice (int index,
int maxWidth, int maxHeight,
bool useHighQuality)
{
+ jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
+
+ #if ! JUCE_ANDROID && ! JUCE_IOS
std::unique_ptr d (new CameraDevice (getAvailableDevices() [index], index,
minWidth, minHeight, maxWidth, maxHeight, useHighQuality));
if (d != nullptr && d->pimpl->openedOk())
return d.release();
+ #else
+ ignoreUnused (index, minWidth, minHeight);
+ ignoreUnused (maxWidth, maxHeight, useHighQuality);
+
+ // Use openDeviceAsync to open a camera device on iOS or Android.
+ jassertfalse;
+ #endif
return nullptr;
}
+void CameraDevice::openDeviceAsync (int index, OpenCameraResultCallback resultCallback,
+ int minWidth, int minHeight, int maxWidth, int maxHeight, bool useHighQuality)
+{
+ jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
+
+ if (resultCallback == nullptr)
+ {
+ // A valid callback must be passed.
+ jassertfalse;
+ return;
+ }
+
+ #if JUCE_ANDROID || JUCE_IOS
+ CameraFactory::getInstance().openCamera (index, static_cast (resultCallback),
+ minWidth, minHeight, maxWidth, maxHeight, useHighQuality);
+ #else
+ auto* device = openDevice (index, minWidth, minHeight, maxWidth, maxHeight, useHighQuality);
+
+ resultCallback (device, device != nullptr ? String() : "Could not open camera device");
+ #endif
+}
+
} // namespace juce
diff --git a/modules/juce_video/capture/juce_CameraDevice.h b/modules/juce_video/capture/juce_CameraDevice.h
index 5f62674b10..751990e7ef 100644
--- a/modules/juce_video/capture/juce_CameraDevice.h
+++ b/modules/juce_video/capture/juce_CameraDevice.h
@@ -35,9 +35,9 @@ namespace juce
Controls any video capture devices that might be available.
Use getAvailableDevices() to list the devices that are attached to the
- system, then call openDevice to open one for use. Once you have a CameraDevice
- object, you can get a viewer component from it, and use its methods to
- stream to a file or capture still-frames.
+ system, then call openDevice() or openDeviceAsync() to open one for use.
+ Once you have a CameraDevice object, you can get a viewer component from it,
+ and use its methods to stream to a file or capture still-frames.
@tags{Video}
*/
@@ -50,17 +50,18 @@ public:
//==============================================================================
/** Returns a list of the available cameras on this machine.
- You can open one of these devices by calling openDevice().
+ You can open one of these devices by calling openDevice() or openDeviceAsync().
*/
static StringArray getAvailableDevices();
- /** Opens a camera device.
+ /** Synchronously opens a camera device. This function should not be used on iOS or
+ Android, use openDeviceAsync() instead.
The index parameter indicates which of the items returned by getAvailableDevices()
to open.
The size constraints allow the method to choose between different resolutions if
- the camera supports this. If the resolution cam't be specified (e.g. on the Mac)
+ the camera supports this. If the resolution can't be specified (e.g. on the Mac)
then these will be ignored.
On Mac, if highQuality is false, then the camera will be opened in preview mode
@@ -72,16 +73,62 @@ public:
int maxWidth = 1024, int maxHeight = 768,
bool highQuality = true);
+ using OpenCameraResultCallback = std::function;
+
+ /** Asynchronously opens a camera device on iOS (iOS 7+) or Android (API 21+).
+ On other platforms, the function will simply call openDevice(). Upon completion,
+ resultCallback will be invoked with valid CameraDevice* and an empty error
+ String on success, or nullptr CameraDevice and a non-empty error String on failure.
+
+ This is the preferred method of opening a camera device, because it works on all
+ platforms, whereas synchronous openDevice() does not work on iOS & Android.
+
+ The index parameter indicates which of the items returned by getAvailableDevices()
+ to open.
+
+ The size constraints allow the method to choose between different resolutions if
+ the camera supports this. If the resolution can't be specified then these will be
+ ignored.
+
+ On iOS, if you want to switch a device, it is more efficient to open a new device
+ before closing the older one, because this way both devices can share the same
+ underlying camera session. Otherwise, the session needs to be close first, and this
+ is a lengthy process that can take several seconds.
+
+ The Android implementation currently supports a maximum recording resolution of
+ 1080p. Choosing a larger size will result in larger pictures taken, but the video
+ will be capped at 1080p.
+ */
+ static void openDeviceAsync (int deviceIndex,
+ OpenCameraResultCallback resultCallback,
+ int minWidth = 128, int minHeight = 64,
+ int maxWidth = 1024, int maxHeight = 768,
+ bool highQuality = true);
+
//==============================================================================
/** Returns the name of this device */
const String& getName() const noexcept { return name; }
/** Creates a component that can be used to display a preview of the
video from this camera.
+
+ Note: while you can change the size of the preview component, the actual
+ preview display may be smaller than the size requested, because the correct
+ aspect ratio is maintained automatically.
*/
Component* createViewerComponent();
//==============================================================================
+ /** Triggers a still picture capture. Upon completion, pictureTakenCallback will
+ be invoked on a message thread.
+
+ On Android, before calling takeStillPicture(), you need to create a preview with
+ createViewerComponent() and you need to make it visible on screen.
+
+ Android does not support simultaneous video recording and still picture capture.
+ */
+ void takeStillPicture (std::function pictureTakenCallback);
+
/** Starts recording video to the specified file.
You should use getFileExtension() to find out the correct extension to
@@ -95,6 +142,16 @@ public:
The quality parameter can be 0, 1, or 2, to indicate low, medium, or high. It may
or may not be used, depending on the driver.
+
+ On Android, before calling startRecordingToFile(), you need to create a preview with
+ createViewerComponent() and you need to make it visible on screen.
+
+ The Android camera also requires exclusive access to the audio device, so make sure
+ you close any open audio devices with AudioDeviceManager::closeAudioDevice() first.
+
+ Android does not support simultaneous video recording and still picture capture.
+
+ @see AudioDeviceManager::closeAudioDevice, AudioDeviceManager::restartLastAudioDevice
*/
void startRecordingToFile (const File& file, int quality = 2);
@@ -113,36 +170,9 @@ public:
*/
Time getTimeOfFirstRecordedFrame() const;
- //==============================================================================
- /**
- Receives callbacks with images from a CameraDevice.
-
- @see CameraDevice::addListener
- */
- class JUCE_API Listener
- {
- public:
- Listener() {}
- virtual ~Listener() {}
-
- /** This method is called when a new image arrives.
-
- This may be called by any thread, so be careful about thread-safety,
- and make sure that you process the data as quickly as possible to
- avoid glitching!
- */
- virtual void imageReceived (const Image& image) = 0;
- };
-
- /** Adds a listener to receive images from the camera.
-
- Be very careful not to delete the listener without first removing it by calling
- removeListener().
- */
- void addListener (Listener* listenerToAdd);
-
- /** Removes a listener that was previously added with addListener(). */
- void removeListener (Listener* listenerToRemove);
+ /** Set this callback to be notified whenever an error occurs. You may need to close
+ and reopen the device to be able to use it further. */
+ std::function onErrorOccurred;
private:
String name;
@@ -158,6 +188,32 @@ private:
CameraDevice (const String& name, int index,
int minWidth, int minHeight, int maxWidth, int maxHeight, bool highQuality);
+ #if JUCE_ANDROID || JUCE_IOS
+ class CameraFactory;
+ #endif
+
+ #if JUCE_ANDROID
+ friend void juce_cameraDeviceStateClosed (int64);
+ friend void juce_cameraDeviceStateDisconnected (int64);
+ friend void juce_cameraDeviceStateError (int64, int);
+ friend void juce_cameraDeviceStateOpened (int64, void*);
+
+ friend void juce_cameraCaptureSessionActive (int64, void*);
+ friend void juce_cameraCaptureSessionClosed (int64, void*);
+ friend void juce_cameraCaptureSessionConfigureFailed (int64, void*);
+ friend void juce_cameraCaptureSessionConfigured (int64, void*);
+ friend void juce_cameraCaptureSessionReady (int64, void*);
+
+ friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int);
+ friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64);
+ friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64);
+
+ friend void juce_deviceOrientationChanged (int64, int);
+ #endif
+
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CameraDevice)
};
diff --git a/modules/juce_video/juce_video.h b/modules/juce_video/juce_video.h
index 4333e43db4..ec04bc6ab8 100644
--- a/modules/juce_video/juce_video.h
+++ b/modules/juce_video/juce_video.h
@@ -59,13 +59,23 @@
//=============================================================================
/** Config: JUCE_USE_CAMERA
- Enables web-cam support using the CameraDevice class (Mac and Windows).
+ Enables camera support using the CameraDevice class (Mac, Windows, iOS, Android).
*/
#ifndef JUCE_USE_CAMERA
#define JUCE_USE_CAMERA 0
#endif
-#if ! (JUCE_MAC || JUCE_WINDOWS)
+#ifndef JUCE_CAMERA_LOG_ENABLED
+ #define JUCE_CAMERA_LOG_ENABLED 0
+#endif
+
+#if JUCE_CAMERA_LOG_ENABLED
+ #define JUCE_CAMERA_LOG(x) DBG(x)
+#else
+ #define JUCE_CAMERA_LOG(x) {}
+#endif
+
+#if ! (JUCE_MAC || JUCE_WINDOWS || JUCE_IOS || JUCE_ANDROID)
#undef JUCE_USE_CAMERA
#endif
diff --git a/modules/juce_video/native/juce_android_CameraDevice.h b/modules/juce_video/native/juce_android_CameraDevice.h
index 18e402ae76..58c7413b19 100644
--- a/modules/juce_video/native/juce_android_CameraDevice.h
+++ b/modules/juce_video/native/juce_android_CameraDevice.h
@@ -24,62 +24,3356 @@
==============================================================================
*/
-struct CameraDevice::Pimpl
+#if __ANDROID_API__ >= 21
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ STATICMETHOD (valueOf, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$CompressFormat;")
+
+DECLARE_JNI_CLASS (AndroidBitmapCompressFormat, "android/graphics/Bitmap$CompressFormat");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (close, "close", "()V") \
+ METHOD (createCaptureRequest, "createCaptureRequest", "(I)Landroid/hardware/camera2/CaptureRequest$Builder;") \
+ METHOD (createCaptureSession, "createCaptureSession", "(Ljava/util/List;Landroid/hardware/camera2/CameraCaptureSession$StateCallback;Landroid/os/Handler;)V")
+
+DECLARE_JNI_CLASS (AndroidCameraDevice, "android/hardware/camera2/CameraDevice");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (close, "close", "()V") \
+ METHOD (getPlanes, "getPlanes", "()[Landroid/media/Image$Plane;")
+
+DECLARE_JNI_CLASS (AndroidImage, "android/media/Image");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (getBuffer, "getBuffer", "()Ljava/nio/ByteBuffer;")
+
+DECLARE_JNI_CLASS (AndroidImagePlane, "android/media/Image$Plane");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (acquireLatestImage, "acquireLatestImage", "()Landroid/media/Image;") \
+ METHOD (close, "close", "()V") \
+ METHOD (getSurface, "getSurface", "()Landroid/view/Surface;") \
+ METHOD (setOnImageAvailableListener, "setOnImageAvailableListener", "(Landroid/media/ImageReader$OnImageAvailableListener;Landroid/os/Handler;)V") \
+ STATICMETHOD (newInstance, "newInstance", "(IIII)Landroid/media/ImageReader;")
+
+DECLARE_JNI_CLASS (AndroidImageReader, "android/media/ImageReader");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (constructor, "", "()V") \
+ METHOD (getSurface, "getSurface", "()Landroid/view/Surface;") \
+ METHOD (prepare, "prepare", "()V") \
+ METHOD (release, "release", "()V") \
+ METHOD (setAudioEncoder, "setAudioEncoder", "(I)V") \
+ METHOD (setAudioSource, "setAudioSource", "(I)V") \
+ METHOD (setOnErrorListener, "setOnErrorListener", "(Landroid/media/MediaRecorder$OnErrorListener;)V") \
+ METHOD (setOnInfoListener, "setOnInfoListener", "(Landroid/media/MediaRecorder$OnInfoListener;)V") \
+ METHOD (setOrientationHint, "setOrientationHint", "(I)V") \
+ METHOD (setOutputFile, "setOutputFile", "(Ljava/lang/String;)V") \
+ METHOD (setOutputFormat, "setOutputFormat", "(I)V") \
+ METHOD (setVideoEncoder, "setVideoEncoder", "(I)V") \
+ METHOD (setVideoEncodingBitRate, "setVideoEncodingBitRate", "(I)V") \
+ METHOD (setVideoFrameRate, "setVideoFrameRate", "(I)V") \
+ METHOD (setVideoSize, "setVideoSize", "(II)V") \
+ METHOD (setVideoSource, "setVideoSource", "(I)V") \
+ METHOD (start, "start", "()V") \
+ METHOD (stop, "stop", "()V")
+
+DECLARE_JNI_CLASS (AndroidMediaRecorder, "android/media/MediaRecorder");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (constructor, "", "(Landroid/content/Context;)V") \
+ METHOD (getSurfaceTexture, "getSurfaceTexture", "()Landroid/graphics/SurfaceTexture;") \
+ METHOD (isAvailable, "isAvailable", "()Z") \
+ METHOD (setSurfaceTextureListener, "setSurfaceTextureListener", "(Landroid/view/TextureView$SurfaceTextureListener;)V") \
+ METHOD (setTransform, "setTransform", "(Landroid/graphics/Matrix;)V")
+
+DECLARE_JNI_CLASS (AndroidTextureView, "android/view/TextureView");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (constructor, "", "(Landroid/graphics/SurfaceTexture;)V")
+
+DECLARE_JNI_CLASS (AndroidSurface, "android/view/Surface");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (setDefaultBufferSize, "setDefaultBufferSize", "(II)V")
+
+DECLARE_JNI_CLASS (AndroidSurfaceTexture, "android/graphics/SurfaceTexture");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (getOutputSizesForClass, "getOutputSizes", "(Ljava/lang/Class;)[Landroid/util/Size;") \
+ METHOD (getOutputSizesForFormat, "getOutputSizes", "(I)[Landroid/util/Size;") \
+ METHOD (isOutputSupportedFor, "isOutputSupportedFor", "(I)Z") \
+ METHOD (isOutputSupportedForSurface, "isOutputSupportedFor", "(Landroid/view/Surface;)Z")
+
+DECLARE_JNI_CLASS (AndroidStreamConfigurationMap, "android/hardware/camera2/params/StreamConfigurationMap");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (constructor, "", "()V") \
+ METHOD (toByteArray, "toByteArray", "()[B") \
+ METHOD (size, "size", "()I")
+
+DECLARE_JNI_CLASS (ByteArrayOutputStream, "java/io/ByteArrayOutputStream");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (abortCaptures, "abortCaptures", "()V") \
+ METHOD (capture, "capture", "(Landroid/hardware/camera2/CaptureRequest;Landroid/hardware/camera2/CameraCaptureSession$CaptureCallback;Landroid/os/Handler;)I") \
+ METHOD (close, "close", "()V") \
+ METHOD (setRepeatingRequest, "setRepeatingRequest", "(Landroid/hardware/camera2/CaptureRequest;Landroid/hardware/camera2/CameraCaptureSession$CaptureCallback;Landroid/os/Handler;)I") \
+ METHOD (stopRepeating, "stopRepeating", "()V")
+
+DECLARE_JNI_CLASS (CameraCaptureSession, "android/hardware/camera2/CameraCaptureSession")
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";JZ)V")
+
+DECLARE_JNI_CLASS (CameraCaptureSessionCaptureCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$CameraCaptureSessionCaptureCallback");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V")
+
+DECLARE_JNI_CLASS (CameraCaptureSessionStateCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$CameraCaptureSessionStateCallback");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (get, "get", "(Landroid/hardware/camera2/CameraCharacteristics$Key;)Ljava/lang/Object;") \
+ METHOD (getKeys, "getKeys", "()Ljava/util/List;") \
+ STATICFIELD (CONTROL_AF_AVAILABLE_MODES, "CONTROL_AF_AVAILABLE_MODES", "Landroid/hardware/camera2/CameraCharacteristics$Key;") \
+ STATICFIELD (LENS_FACING, "LENS_FACING", "Landroid/hardware/camera2/CameraCharacteristics$Key;") \
+ STATICFIELD (SCALER_STREAM_CONFIGURATION_MAP, "SCALER_STREAM_CONFIGURATION_MAP", "Landroid/hardware/camera2/CameraCharacteristics$Key;") \
+ STATICFIELD (SENSOR_ORIENTATION, "SENSOR_ORIENTATION", "Landroid/hardware/camera2/CameraCharacteristics$Key;")
+
+DECLARE_JNI_CLASS (CameraCharacteristics, "android/hardware/camera2/CameraCharacteristics");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (getName, "getName", "()Ljava/lang/String;")
+
+DECLARE_JNI_CLASS (CameraCharacteristicsKey, "android/hardware/camera2/CameraCharacteristics$Key");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V")
+
+DECLARE_JNI_CLASS (CameraDeviceStateCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$CameraDeviceStateCallback");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (getCameraCharacteristics, "getCameraCharacteristics", "(Ljava/lang/String;)Landroid/hardware/camera2/CameraCharacteristics;") \
+ METHOD (getCameraIdList, "getCameraIdList", "()[Ljava/lang/String;") \
+ METHOD (openCamera, "openCamera", "(Ljava/lang/String;Landroid/hardware/camera2/CameraDevice$StateCallback;Landroid/os/Handler;)V")
+
+DECLARE_JNI_CLASS (CameraManager, "android/hardware/camera2/CameraManager");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ STATICFIELD (CONTROL_AE_PRECAPTURE_TRIGGER, "CONTROL_AE_PRECAPTURE_TRIGGER", "Landroid/hardware/camera2/CaptureRequest$Key;") \
+ STATICFIELD (CONTROL_AF_MODE, "CONTROL_AF_MODE", "Landroid/hardware/camera2/CaptureRequest$Key;") \
+ STATICFIELD (CONTROL_AF_TRIGGER, "CONTROL_AF_TRIGGER", "Landroid/hardware/camera2/CaptureRequest$Key;") \
+ STATICFIELD (CONTROL_MODE, "CONTROL_MODE", "Landroid/hardware/camera2/CaptureRequest$Key;")
+
+DECLARE_JNI_CLASS (CaptureRequest, "android/hardware/camera2/CaptureRequest");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (addTarget, "addTarget", "(Landroid/view/Surface;)V") \
+ METHOD (build, "build", "()Landroid/hardware/camera2/CaptureRequest;") \
+ METHOD (set, "set", "(Landroid/hardware/camera2/CaptureRequest$Key;Ljava/lang/Object;)V")
+
+DECLARE_JNI_CLASS (CaptureRequestBuilder, "android/hardware/camera2/CaptureRequest$Builder");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (get, "get", "(Landroid/hardware/camera2/CaptureResult$Key;)Ljava/lang/Object;") \
+ STATICFIELD (CONTROL_AE_STATE, "CONTROL_AE_STATE", "Landroid/hardware/camera2/CaptureResult$Key;") \
+ STATICFIELD (CONTROL_AF_STATE, "CONTROL_AF_STATE", "Landroid/hardware/camera2/CaptureResult$Key;")
+
+DECLARE_JNI_CLASS (CaptureResult, "android/hardware/camera2/CaptureResult");
+#undef JNI_CLASS_MEMBERS
+
+#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
+ METHOD (canDetectOrientation, "canDetectOrientation", "()Z") \
+ METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";JLandroid/content/Context;I)V") \
+ METHOD (disable, "disable", "()V") \
+ METHOD (enable, "enable", "()V")
+
+DECLARE_JNI_CLASS (OrientationEventListener, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceOrientationEventListener");
+#undef JNI_CLASS_MEMBERS
+#endif
+
+//==============================================================================
+class AndroidRunnable : public juce::AndroidInterfaceImplementer
{
- Pimpl (const String&, int /*index*/, int /*minWidth*/, int /*minHeight*/, int /*maxWidth*/, int /*maxHeight*/)
+public:
+ struct Owner
{
+ virtual ~Owner() {}
+
+ virtual void run() = 0;
+ };
+
+ AndroidRunnable (Owner& ownerToUse)
+ : owner (ownerToUse)
+ {}
+
+private:
+ Owner& owner;
+
+ jobject invoke (jobject proxy, jobject method, jobjectArray args) override
+ {
+ auto* env = getEnv();
+ auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
+
+ if (methodName == "run")
+ {
+ owner.run();
+ return nullptr;
+ }
+
+ // invoke base class
+ return AndroidInterfaceImplementer::invoke (proxy, method, args);
+ }
+};
+
+//==============================================================================
+class TextureViewSurfaceTextureListener : public AndroidInterfaceImplementer
+{
+public:
+ struct Owner
+ {
+ virtual ~Owner() {}
+
+ virtual void onSurfaceTextureAvailable (LocalRef& surface, int width, int height) = 0;
+ virtual bool onSurfaceTextureDestroyed (LocalRef& surface) = 0;
+ virtual void onSurfaceTextureSizeChanged (LocalRef& surface, int width, int height) = 0;
+ virtual void onSurfaceTextureUpdated (LocalRef& surface) = 0;
+ };
+
+ TextureViewSurfaceTextureListener (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 == "onSurfaceTextureAvailable" && numArgs == 3)
+ {
+ auto surface = LocalRef (env->GetObjectArrayElement (args, 0));
+ auto width = LocalRef (env->GetObjectArrayElement (args, 1));
+ auto height = LocalRef (env->GetObjectArrayElement (args, 2));
+
+ auto widthInt = env->CallIntMethod (width, JavaInteger.intValue);
+ auto heightInt = env->CallIntMethod (height, JavaInteger.intValue);
+
+ owner.onSurfaceTextureAvailable (surface, widthInt, heightInt);
+ return nullptr;
+ }
+ else if (methodName == "onSurfaceTextureDestroyed" && numArgs == 1)
+ {
+ auto surface = LocalRef (env->GetObjectArrayElement (args, 0));
+ auto result = owner.onSurfaceTextureDestroyed (surface);
+
+ return env->CallStaticObjectMethod (JavaBoolean, JavaBoolean.valueOf, result);
+ }
+ else if (methodName == "onSurfaceTextureSizeChanged" && numArgs == 3)
+ {
+ auto surface = LocalRef (env->GetObjectArrayElement (args, 0));
+ auto width = LocalRef (env->GetObjectArrayElement (args, 1));
+ auto height = LocalRef (env->GetObjectArrayElement (args, 2));
+
+ auto widthInt = env->CallIntMethod (width, JavaInteger.intValue);
+ auto heightInt = env->CallIntMethod (height, JavaInteger.intValue);
+
+ owner.onSurfaceTextureSizeChanged (surface, widthInt, heightInt);
+ return nullptr;
+ }
+ else if (methodName == "onSurfaceTextureUpdated" && numArgs == 1)
+ {
+ auto surface = LocalRef (env->GetObjectArrayElement (args, 0));
+
+ owner.onSurfaceTextureUpdated (surface);
+ return nullptr;
+ }
+
+ return AndroidInterfaceImplementer::invoke (proxy, method, args);
+ }
+
+private:
+ Owner& owner;
+};
+
+//==============================================================================
+class ImageReaderOnImageAvailableListener : public AndroidInterfaceImplementer
+{
+public:
+ struct Owner
+ {
+ virtual ~Owner() {}
+
+ virtual void onImageAvailable (LocalRef& imageReader) = 0;
+ };
+
+ ImageReaderOnImageAvailableListener (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 == "onImageAvailable" && numArgs == 1)
+ {
+ auto imageReader = LocalRef (env->GetObjectArrayElement (args, 0));
+
+ owner.onImageAvailable (imageReader);
+ return nullptr;
+ }
+
+ return AndroidInterfaceImplementer::invoke (proxy, method, args);
+ }
+
+private:
+ Owner& owner;
+};
+
+//==============================================================================
+class MediaRecorderOnInfoListener : public AndroidInterfaceImplementer
+{
+public:
+ struct Owner
+ {
+ virtual ~Owner() {}
+
+ virtual void onInfo (LocalRef& mediaRecorder, int what, int extra) = 0;
+ };
+
+ MediaRecorderOnInfoListener (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 == "onInfo" && numArgs == 3)
+ {
+ auto mediaRecorder = LocalRef (env->GetObjectArrayElement (args, 0));
+ auto what = LocalRef (env->GetObjectArrayElement (args, 1));
+ auto extra = LocalRef (env->GetObjectArrayElement (args, 2));
+
+ auto whatInt = (int) env->CallIntMethod (what, JavaInteger.intValue);
+ auto extraInt = (int) env->CallIntMethod (extra, JavaInteger.intValue);
+
+ owner.onInfo (mediaRecorder, whatInt, extraInt);
+ return nullptr;
+ }
+
+ return AndroidInterfaceImplementer::invoke (proxy, method, args);
+ }
+
+private:
+ Owner& owner;
+};
+
+//==============================================================================
+class MediaRecorderOnErrorListener : public AndroidInterfaceImplementer
+{
+public:
+ struct Owner
+ {
+ virtual ~Owner() {}
+
+ virtual void onError (LocalRef& mediaRecorder, int what, int extra) = 0;
+ };
+
+ MediaRecorderOnErrorListener (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 == "onError" && numArgs == 3)
+ {
+ auto mediaRecorder = LocalRef (env->GetObjectArrayElement (args, 0));
+ auto what = LocalRef (env->GetObjectArrayElement (args, 1));
+ auto extra = LocalRef (env->GetObjectArrayElement (args, 2));
+
+ auto whatInt = (int) env->CallIntMethod (what, JavaInteger.intValue);
+ auto extraInt = (int) env->CallIntMethod (extra, JavaInteger.intValue);
+
+ owner.onError (mediaRecorder, whatInt, extraInt);
+ return nullptr;
+ }
+
+ return AndroidInterfaceImplementer::invoke (proxy, method, args);
+ }
+
+private:
+ 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
+#if __ANDROID_API__ >= 21
+ : private AppPausedResumedListener::Owner
+#endif
+{
+ using InternalOpenCameraResultCallback = std::function;
+
+ Pimpl (CameraDevice& ownerToUse, const String& cameraIdToUse, int /*index*/,
+ int minWidthToUse, int minHeightToUse, int maxWidthToUse, int maxHeightToUse,
+ bool /*useHighQuality*/)
+ #if __ANDROID_API__ >= 21
+ : owner (ownerToUse),
+ minWidth (minWidthToUse),
+ minHeight (minHeightToUse),
+ maxWidth (maxWidthToUse),
+ maxHeight (maxHeightToUse),
+ cameraId (cameraIdToUse),
+ appPausedResumedListener (*this),
+ appPausedResumedListenerNative (CreateJavaInterface (&appPausedResumedListener,
+ JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener").get()),
+
+ cameraManager (initialiseCameraManager()),
+ cameraCharacteristics (initialiseCameraCharacteristics (cameraManager, cameraId)),
+ streamConfigurationMap (cameraCharacteristics),
+ previewDisplay (streamConfigurationMap.getPreviewBufferSize()),
+ deviceOrientationChangeListener (previewDisplay)
+ #endif
+ {
+ #if __ANDROID_API__ >= 21
+ startBackgroundThread();
+ #endif
}
~Pimpl()
{
+ #if __ANDROID_API__ >= 21
+ getEnv()->CallVoidMethod (android.activity, JuceAppActivity.removeAppPausedResumedListener,
+ appPausedResumedListenerNative.get(), reinterpret_cast(this));
+ #endif
}
- void startRecordingToFile (const File&, int /*quality*/)
+ #if __ANDROID_API__ < 21
+ // Dummy implementations for unsupported API levels.
+ void open (InternalOpenCameraResultCallback) {}
+ void takeStillPicture (std::function) {}
+ void startRecordingToFile (const File&, int) {}
+ void stopRecording() {}
+
+ String getCameraId() const noexcept { return {}; }
+ bool openedOk() const noexcept { return false; }
+ Time getTimeOfFirstRecordedFrame() const { return {}; }
+ static StringArray getAvailableDevices()
{
+ // Camera on Android requires API 21 or above.
+ jassertfalse;
+ return {};
+ }
+ #else
+ JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
+
+ String getCameraId() const noexcept { return cameraId; }
+
+ void open (InternalOpenCameraResultCallback cameraOpenCallbackToUse)
+ {
+ cameraOpenCallback = static_cast (cameraOpenCallbackToUse);
+
+ // A valid camera open callback must be passed.
+ jassert (cameraOpenCallback != nullptr);
+
+ // The same camera can be opened only once!
+ jassert (scopedCameraDevice == nullptr);
+
+ if (cameraOpenCallback == nullptr || scopedCameraDevice != nullptr)
+ return;
+
+ WeakReference safeThis (this);
+ RuntimePermissions::request (RuntimePermissions::camera, [safeThis] (bool granted) mutable
+ {
+ if (safeThis != nullptr)
+ safeThis->continueOpenRequest (granted);
+ });
+ }
+
+ void continueOpenRequest (bool granted)
+ {
+ if (granted)
+ {
+ getEnv()->CallVoidMethod (android.activity, JuceAppActivity.addAppPausedResumedListener,
+ appPausedResumedListenerNative.get(), reinterpret_cast (this));
+ scopedCameraDevice.reset (new ScopedCameraDevice (*this, cameraId, cameraManager, handler, getAutoFocusModeToUse()));
+ }
+ else
+ {
+ invokeCameraOpenCallback ("Camera permission not granted");
+ }
+ }
+
+ bool openedOk() const noexcept { return scopedCameraDevice->openedOk(); }
+
+ void takeStillPicture (std::function pictureTakenCallbackToUse)
+ {
+ if (pictureTakenCallbackToUse == nullptr)
+ {
+ jassertfalse;
+ return;
+ }
+
+ if (currentCaptureSessionMode->isVideoRecordSession())
+ {
+ // Taking still pictures while recording video is not supported on Android.
+ jassertfalse;
+ return;
+ }
+
+ pictureTakenCallback = static_cast&&> (pictureTakenCallbackToUse);
+
+ triggerStillPictureCapture();
+ }
+
+ void startRecordingToFile (const File& file, int /*quality*/)
+ {
+ if (! openedOk())
+ {
+ jassertfalse;
+ return;
+ }
+
+ if (! previewDisplay.isReady())
+ {
+ // Did you remember to create and show a preview display?
+ jassertfalse;
+ return;
+ }
+
+ file.deleteFile();
+ file.create();
+ jassert (file.existsAsFile());
+
+ // MediaRecorder can't handle videos larger than 1080p
+ auto videoSize = chooseBestSize (minWidth, minHeight, jmin (maxWidth, 1080), maxHeight,
+ streamConfigurationMap.getSupportedVideoRecordingOutputSizes());
+
+ mediaRecorder.reset (new MediaRecorder (file.getFullPathName(), videoSize.getWidth(), videoSize.getHeight(),
+ getCameraSensorOrientation(), getCameraLensFacing()));
+
+ firstRecordedFrameTimeMs = Time::getCurrentTime();
+
+ currentCaptureSessionMode.reset();
+ startVideoRecordingMode (*mediaRecorder);
}
void stopRecording()
{
+ currentCaptureSessionMode.reset();
+ mediaRecorder.reset();
+
+ startPreviewMode (*imageReader);
}
Time getTimeOfFirstRecordedFrame() const
{
- return {};
- }
-
- void addListener (CameraDevice::Listener* listenerToAdd)
- {
- const ScopedLock sl (listenerLock);
- listeners.addIfNotAlreadyThere (listenerToAdd);
- }
-
- void removeListener (CameraDevice::Listener* listenerToRemove)
- {
- const ScopedLock sl (listenerLock);
- listeners.removeFirstMatchingValue (listenerToRemove);
+ return firstRecordedFrameTimeMs;
}
static StringArray getAvailableDevices()
{
StringArray results;
+ auto* env = getEnv();
+
+ auto cameraManagerToUse = initialiseCameraManager();
+ auto cameraIdArray = LocalRef ((jobjectArray) env->CallObjectMethod (cameraManagerToUse,
+ CameraManager.getCameraIdList));
+
+ results = javaStringArrayToJuce (cameraIdArray);
+
+ for (auto& result : results)
+ printDebugCameraInfo (cameraManagerToUse, result);
+
return results;
}
private:
+ enum
+ {
+ ERROR_CAMERA_IN_USE = 1,
+ ERROR_MAX_CAMERAS_IN_USE = 2,
+ ERROR_CAMERA_DISABLED = 3,
+ ERROR_CAMERA_DEVICE = 4,
+ ERROR_CAMERA_SERVICE = 5
+ };
+
+ static String cameraErrorCodeToString (int errorCode)
+ {
+ switch (errorCode)
+ {
+ case ERROR_CAMERA_IN_USE: return "Camera already in use.";
+ case ERROR_MAX_CAMERAS_IN_USE: return "Too many opened camera devices.";
+ case ERROR_CAMERA_DISABLED: return "Camera disabled.";
+ case ERROR_CAMERA_DEVICE: return "Fatal error.";
+ case ERROR_CAMERA_SERVICE: return "Fatal error. Reboot required or persistent hardware problem.";
+ default: return "Unknown error.";
+ }
+ }
+
+ static LocalRef initialiseCameraManager()
+ {
+ return LocalRef (getEnv()->CallObjectMethod (android.activity, JuceAppActivity.getSystemService,
+ javaString ("camera").get()));
+ }
+
+ static LocalRef initialiseCameraCharacteristics (const GlobalRef& cameraManager, const String& cameraId)
+ {
+ return LocalRef (getEnv()->CallObjectMethod (cameraManager,
+ CameraManager.getCameraCharacteristics,
+ javaString (cameraId).get()));
+ }
+
+ static void printDebugCameraInfo (const LocalRef& cameraManagerToUse, const String& cameraId)
+ {
+ auto* env = getEnv();
+
+ auto characteristics = LocalRef (env->CallObjectMethod (cameraManagerToUse,
+ CameraManager.getCameraCharacteristics,
+ javaString (cameraId).get()));
+
+ auto keysList = LocalRef (env->CallObjectMethod (characteristics, CameraCharacteristics.getKeys));
+
+ const int size = env->CallIntMethod (keysList, JavaList.size);
+
+ JUCE_CAMERA_LOG ("Camera id: " + cameraId + ", characteristics keys num: " + String (size));
+
+ for (int i = 0; i < size; ++i)
+ {
+ auto key = LocalRef (env->CallObjectMethod (keysList, JavaList.get, i));
+ auto jKeyName = LocalRef ((jstring) env->CallObjectMethod (key, CameraCharacteristicsKey.getName));
+ auto keyName = juceString (jKeyName);
+
+ auto keyValue = LocalRef (env->CallObjectMethod (characteristics, CameraCharacteristics.get, key.get()));
+ auto jKeyValueString = LocalRef ((jstring) env->CallObjectMethod (keyValue, JavaObject.toString));
+ auto keyValueString = juceString (jKeyValueString);
+
+ auto &kvs = keyValueString;
+
+ if (kvs.startsWith ("[I") || kvs.startsWith ("[F") || kvs.startsWith ("[Z") || kvs.startsWith ("[B"))
+ {
+ printPrimitiveArrayElements (keyValue, keyName, keyValueString);
+ }
+ else if (kvs.startsWith ("[Landroid.util.Range"))
+ {
+ printRangeArrayElements (keyValue, keyName);
+ }
+ else
+ {
+ int chunkSize = 256;
+
+ if (keyValueString.length() > chunkSize)
+ {
+ JUCE_CAMERA_LOG ("Key: " + keyName);
+
+ for (int i = 0, j = 1; i < keyValueString.length(); i += chunkSize, ++j)
+ JUCE_CAMERA_LOG ("value part " + String (j) + ": " + keyValueString.substring (i, i + chunkSize));
+ }
+ else
+ {
+ JUCE_CAMERA_LOG ("Key: " + keyName + ", value: " + keyValueString);
+ }
+ }
+
+ ignoreUnused (keyName);
+ }
+ }
+
+ static void printPrimitiveArrayElements (const LocalRef& keyValue, const String& keyName,
+ const String& keyValueString)
+ {
+ ignoreUnused (keyName);
+
+ String result = "[";
+
+ auto* env = getEnv();
+
+ #define PRINT_ELEMENTS(elem_type, array_type, fun_name_middle) \
+ { \
+ elem_type* elements = env->Get##fun_name_middle##ArrayElements ((array_type) keyValue.get(), 0); \
+ int size = env->GetArrayLength ((array_type) keyValue.get()); \
+ \
+ for (int i = 0; i < size - 1; ++i) \
+ result << String (elements[i]) << " "; \
+ \
+ if (size > 0) \
+ result << String (elements[size - 1]); \
+ \
+ env->Release##fun_name_middle##ArrayElements ((array_type) keyValue.get(), elements, 0); \
+ }
+
+ if (keyValueString.startsWith ("[I"))
+ PRINT_ELEMENTS (jint, jintArray, Int)
+ else if (keyValueString.startsWith ("[F"))
+ PRINT_ELEMENTS (float, jfloatArray, Float)
+ else if (keyValueString.startsWith ("[Z"))
+ PRINT_ELEMENTS (jboolean, jbooleanArray, Boolean)
+ else if (keyValueString.startsWith ("[B"))
+ PRINT_ELEMENTS (jbyte, jbyteArray, Byte);
+
+ #undef PRINT_ELEMENTS
+
+ result << "]";
+ JUCE_CAMERA_LOG ("Key: " + keyName + ", value: " + result);
+ }
+
+ static void printRangeArrayElements (const LocalRef& rangeArray, const String& keyName)
+ {
+ auto* env = getEnv();
+
+ jobjectArray ranges = static_cast (rangeArray.get());
+
+ int numRanges = env->GetArrayLength (ranges);
+
+ String result;
+
+ for (int i = 0; i < numRanges; ++i)
+ {
+ auto range = LocalRef (env->GetObjectArrayElement (ranges, i));
+
+ auto jRangeString = LocalRef ((jstring) env->CallObjectMethod (range, AndroidRange.toString));
+
+ result << juceString (jRangeString) << " ";
+ }
+
+ JUCE_CAMERA_LOG ("Key: " + keyName + ", value: " + result);
+ }
+
+ //==============================================================================
+ class StreamConfigurationMap
+ {
+ public:
+ StreamConfigurationMap (const GlobalRef& cameraCharacteristics)
+ : scalerStreamConfigurationMap (getStreamConfigurationMap (cameraCharacteristics)),
+ supportedPreviewOutputSizes (retrieveOutputSizes (scalerStreamConfigurationMap,
+ getClassForName ("android.graphics.SurfaceTexture"),
+ -1)),
+ supportedStillImageOutputSizes (retrieveOutputSizes (scalerStreamConfigurationMap,
+ LocalRef(),
+ jpegImageFormat)),
+ supportedVideoRecordingOutputSizes (retrieveOutputSizes (scalerStreamConfigurationMap,
+ getClassForName ("android.media.MediaRecorder"),
+ -1)),
+ defaultPreviewSize (getSmallestSize (supportedPreviewOutputSizes)),
+ previewBufferSize (getLargestSize (supportedPreviewOutputSizes))
+ {
+ printSizesLog (supportedPreviewOutputSizes, "SurfaceTexture");
+ printSizesLog (supportedStillImageOutputSizes, "JPEG");
+ printSizesLog (supportedVideoRecordingOutputSizes, "MediaRecorder");
+ }
+
+ Array> getSupportedPreviewOutputSizes() const noexcept { return supportedPreviewOutputSizes; }
+ Array> getSupportedStillImageOutputSizes() const noexcept { return supportedStillImageOutputSizes; }
+ Array> getSupportedVideoRecordingOutputSizes() const noexcept { return supportedVideoRecordingOutputSizes; }
+
+ Rectangle getDefaultPreviewSize() const noexcept { return defaultPreviewSize; }
+ Rectangle getPreviewBufferSize() const noexcept { return previewBufferSize; }
+
+ bool isOutputSupportedForSurface (const LocalRef& surface) const
+ {
+ return getEnv()->CallBooleanMethod (scalerStreamConfigurationMap, AndroidStreamConfigurationMap.isOutputSupportedForSurface, surface.get());
+ }
+
+ static constexpr int jpegImageFormat = 256;
+
+ private:
+ GlobalRef scalerStreamConfigurationMap;
+
+ Array> supportedPreviewOutputSizes;
+ Array> supportedStillImageOutputSizes;
+ Array> supportedVideoRecordingOutputSizes;
+ Rectangle defaultPreviewSize, previewBufferSize;
+
+ GlobalRef getStreamConfigurationMap (const GlobalRef& cameraCharacteristics)
+ {
+ auto* env = getEnv();
+
+ auto scalerStreamConfigurationMapKey = LocalRef (env->GetStaticObjectField (CameraCharacteristics,
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP));
+
+ return GlobalRef (LocalRef (env->CallObjectMethod (cameraCharacteristics,
+ CameraCharacteristics.get,
+ scalerStreamConfigurationMapKey.get())));
+ }
+
+ static Array> retrieveOutputSizes (GlobalRef& scalerStreamConfigurationMap,
+ const LocalRef& outputClass,
+ int format)
+ {
+ Array> result;
+
+ auto* env = getEnv();
+
+ auto outputSizes = outputClass.get() != nullptr
+ ? LocalRef ((jobjectArray) env->CallObjectMethod (scalerStreamConfigurationMap,
+ AndroidStreamConfigurationMap.getOutputSizesForClass,
+ outputClass.get()))
+ : LocalRef ((jobjectArray) env->CallObjectMethod (scalerStreamConfigurationMap,
+ AndroidStreamConfigurationMap.getOutputSizesForFormat,
+ (jint) format));
+
+ if (format != -1)
+ {
+ auto supported = (env->CallBooleanMethod (scalerStreamConfigurationMap, AndroidStreamConfigurationMap.isOutputSupportedFor, (jint) format) != 0);
+
+ if (! supported)
+ {
+ // The output format is not supported by this device, still image capture will not work!
+ jassertfalse;
+ return {};
+ }
+ }
+
+ int numSizes = env->GetArrayLength (outputSizes);
+
+ jassert (numSizes > 0);
+
+ for (int i = 0; i < numSizes; ++i)
+ {
+ auto size = LocalRef (env->GetObjectArrayElement (outputSizes, i));
+
+ auto width = env->CallIntMethod (size, AndroidSize.getWidth);
+ auto height = env->CallIntMethod (size, AndroidSize.getHeight);
+
+ result.add (Rectangle (0, 0, width, height));
+ }
+
+ return result;
+ }
+
+ static LocalRef getClassForName (const String& name)
+ {
+ return LocalRef (getEnv()->CallStaticObjectMethod (JavaClass, JavaClass.forName,
+ javaString (name).get()));
+ }
+
+ static void printSizesLog (const Array>& sizes, const String& className)
+ {
+ ignoreUnused (sizes, className);
+
+ JUCE_CAMERA_LOG ("Sizes for class " + className);
+
+ #if JUCE_CAMERA_LOG_ENABLED
+ for (auto& s : sizes)
+ JUCE_CAMERA_LOG (s.toString() + "\n");
+ #endif
+ }
+
+ Rectangle getSmallestSize (const Array>& sizes) const
+ {
+ if (sizes.size() == 0)
+ return {};
+
+ auto smallestSize = sizes[0];
+
+ for (auto& size : sizes)
+ {
+ if (size.getWidth() < smallestSize.getWidth() && size.getHeight() < smallestSize.getHeight())
+ smallestSize = size;
+ }
+
+ return smallestSize;
+ }
+
+ Rectangle getLargestSize (const Array>& sizes) const
+ {
+ if (sizes.size() == 0)
+ return {};
+
+ auto largestSize = sizes[0];
+
+ for (auto& size : sizes)
+ {
+ if (size.getWidth() > largestSize.getWidth() && size.getHeight() > largestSize.getHeight())
+ largestSize = size;
+ }
+
+ return largestSize;
+ }
+ };
+
+ //==============================================================================
+ class PreviewDisplay : private TextureViewSurfaceTextureListener::Owner
+ {
+ public:
+ struct Listener
+ {
+ virtual ~Listener() {}
+
+ virtual void previewDisplayReady() = 0;
+ virtual void previewDisplayAboutToBeDestroyed() = 0;
+ };
+
+ PreviewDisplay (Rectangle bufferSize)
+ : textureViewSurfaceTextureListener (*this),
+ textureView (getEnv()->NewObject (AndroidTextureView, AndroidTextureView.constructor,
+ android.activity.get())),
+ bufferWidth (bufferSize.getWidth()),
+ bufferHeight (bufferSize.getHeight())
+ {
+ auto* env = getEnv();
+
+ if (! isReady())
+ env->CallVoidMethod (textureView, AndroidTextureView.setSurfaceTextureListener,
+ CreateJavaInterface (&textureViewSurfaceTextureListener,
+ "android/view/TextureView$SurfaceTextureListener").get());
+ }
+
+ ~PreviewDisplay()
+ {
+ getEnv()->CallVoidMethod (textureView, AndroidTextureView.setSurfaceTextureListener, nullptr);
+ }
+
+ void addListener (Listener* l)
+ {
+ if (l == nullptr)
+ {
+ jassertfalse;
+ return;
+ }
+
+ listeners.add (l);
+
+ if (isReady())
+ l->previewDisplayReady();
+ }
+
+ void removeListener (Listener* l)
+ {
+ if (l == nullptr)
+ {
+ jassertfalse;
+ return;
+ }
+
+ listeners.remove (l);
+ }
+
+ bool isReady() const
+ {
+ return (getEnv()->CallBooleanMethod (textureView, AndroidTextureView.isAvailable) != 0)
+ && width > 0 && height > 0;
+ }
+
+ LocalRef createSurface()
+ {
+ // Surface may get destroyed while session is being configured, if
+ // the preview gets hidden in the meantime, so bailout.
+ if (! isReady())
+ return LocalRef (nullptr);
+
+ auto* env = getEnv();
+
+ auto surfaceTexture = LocalRef (env->CallObjectMethod (textureView,
+ AndroidTextureView.getSurfaceTexture));
+
+ // NB: too small buffer will result in pixelated preview. A buffer with wrong aspect ratio
+ // can result in a cropped preview.
+ env->CallVoidMethod (surfaceTexture, AndroidSurfaceTexture.setDefaultBufferSize, (jint) bufferWidth, (jint) bufferHeight);
+
+ auto surface = LocalRef (env->NewObject (AndroidSurface, AndroidSurface.constructor, surfaceTexture.get()));
+
+ return surface;
+ }
+
+ const GlobalRef& getNativeView() { return textureView; }
+
+ void updateSurfaceTransform()
+ {
+ auto* env = getEnv();
+
+ auto windowManager = LocalRef (env->CallObjectMethod (android.activity, JuceAppActivity.getWindowManager));
+ auto display = LocalRef (env->CallObjectMethod (windowManager, AndroidWindowManager.getDefaultDisplay));
+ auto rotation = env->CallIntMethod (display, AndroidDisplay.getRotation);
+
+ static constexpr int rotation90 = 1;
+ static constexpr int rotation270 = 3;
+
+ auto matrix = LocalRef (env->NewObject (AndroidMatrix, AndroidMatrix.constructor));
+
+ if (rotation == rotation90 || rotation == rotation270)
+ {
+ env->CallBooleanMethod (matrix, AndroidMatrix.postScale, jfloat (height / (float) width), jfloat (width / (float) height), (jfloat) 0, (jfloat) 0);
+ env->CallBooleanMethod (matrix, AndroidMatrix.postRotate, (jfloat) 90 * (rotation - 2), (jfloat) 0, (jfloat) 0);
+ env->CallBooleanMethod (matrix, AndroidMatrix.postTranslate, (jfloat) (rotation == 3 ? width : 0), (jfloat) (rotation == 1 ? height : 0));
+ }
+
+ env->CallVoidMethod (textureView, AndroidTextureView.setTransform, matrix.get());
+ }
+
+ private:
+ ListenerList listeners;
+
+ TextureViewSurfaceTextureListener textureViewSurfaceTextureListener;
+ GlobalRef textureView;
+ int width = -1, height = -1;
+ int bufferWidth, bufferHeight;
+
+ void onSurfaceTextureAvailable (LocalRef& /*surface*/, int widthToUse, int heightToUse) override
+ {
+ JUCE_CAMERA_LOG ("onSurfaceTextureAvailable()");
+
+ width = widthToUse;
+ height = heightToUse;
+
+ updateSurfaceTransform();
+
+ listeners.call (&Listener::previewDisplayReady);
+ }
+
+ bool onSurfaceTextureDestroyed (LocalRef& /*surface*/) override
+ {
+ JUCE_CAMERA_LOG ("onSurfaceTextureDestroyed()");
+
+ listeners.call (&Listener::previewDisplayAboutToBeDestroyed);
+
+ return true;
+ }
+
+ void onSurfaceTextureSizeChanged (LocalRef& /*surface*/, int widthToUse, int heightToUse) override
+ {
+ JUCE_CAMERA_LOG ("onSurfaceTextureSizeChanged()");
+
+ width = widthToUse;
+ height = heightToUse;
+
+ updateSurfaceTransform();
+ }
+
+ void onSurfaceTextureUpdated (LocalRef& /*surface*/) override
+ {
+ JUCE_CAMERA_LOG ("onSurfaceTextureUpdated()");
+ }
+
+ JUCE_DECLARE_NON_COPYABLE (PreviewDisplay)
+ };
+
+ //==============================================================================
+ class ImageReader : private ImageReaderOnImageAvailableListener::Owner
+ {
+ public:
+ ImageReader (Pimpl& ownerToUse, GlobalRef& handlerToUse,
+ int imageWidth, int imageHeight, int cameraSensorOrientationToUse)
+ : owner (ownerToUse),
+ cameraSensorOrientation (cameraSensorOrientationToUse),
+ imageReader (getEnv()->CallStaticObjectMethod (AndroidImageReader, AndroidImageReader.newInstance,
+ imageWidth, imageHeight, StreamConfigurationMap::jpegImageFormat,
+ numImagesToKeep)),
+ onImageAvailableListener (*this)
+ {
+ getEnv()->CallVoidMethod (imageReader, AndroidImageReader.setOnImageAvailableListener,
+ CreateJavaInterface (&onImageAvailableListener,
+ "android/media/ImageReader$OnImageAvailableListener").get(),
+ handlerToUse.get());
+ }
+
+ ~ImageReader()
+ {
+ getEnv()->CallVoidMethod (imageReader, AndroidImageReader.close);
+ }
+
+ LocalRef getSurface() const
+ {
+ return LocalRef (getEnv()->CallObjectMethod (imageReader, AndroidImageReader.getSurface));
+ }
+
+ void resetNotificationFlag()
+ {
+ hasNotifiedListeners.set (0);
+ }
+
+ private:
+ Pimpl& owner;
+ int cameraSensorOrientation;
+
+ GlobalRef imageReader;
+ ImageReaderOnImageAvailableListener onImageAvailableListener;
+ static constexpr int numImagesToKeep = 2;
+ Atomic hasNotifiedListeners { 0 };
+
+ JUCE_DECLARE_WEAK_REFERENCEABLE (ImageReader)
+
+ void onImageAvailable (LocalRef& /*imageReader*/) override
+ {
+ JUCE_CAMERA_LOG ("onImageAvailable()");
+
+ auto* env = getEnv();
+
+ auto jImage = LocalRef (env->CallObjectMethod (imageReader, AndroidImageReader.acquireLatestImage));
+
+ if (jImage.get() == nullptr)
+ return;
+
+ auto cameraLensFrontFacing = owner.getCameraLensFacing() == 0;
+
+ // NB: could use sensor orientation here to get real-world orientation, but then the resulting
+ // image could not match the UI orientation.
+ auto image = androidImageToJuceWithFixedOrientation (jImage, owner.deviceOrientationChangeListener.getDeviceOrientation(),
+ Desktop::getInstance().getCurrentOrientation(),
+ cameraLensFrontFacing,
+ cameraSensorOrientation);
+
+ env->CallVoidMethod (jImage, AndroidImage.close);
+
+ WeakReference safeThis (this);
+
+ // Android may take multiple pictures before it handles a request to stop.
+ if (hasNotifiedListeners.compareAndSetBool (1, 0))
+ MessageManager::callAsync ([safeThis, image]() mutable { if (safeThis != nullptr) safeThis->owner.notifyImageReceived (image); });
+ }
+
+ struct ImageBuffer
+ {
+ LocalRef byteArray;
+ int size;
+ };
+
+ static Image androidImageToJuceWithFixedOrientation (const LocalRef& androidImage,
+ Desktop::DisplayOrientation deviceOrientationFromAccelerometerSensor,
+ Desktop::DisplayOrientation targetOrientation,
+ bool cameraLensFrontFacing,
+ int cameraSensorOrientation)
+ {
+ auto* env = getEnv();
+
+ auto planes = LocalRef ((jobjectArray) env->CallObjectMethod (androidImage, AndroidImage.getPlanes));
+ jassert (env->GetArrayLength (planes) > 0);
+
+ auto plane = LocalRef (env->GetObjectArrayElement (planes, 0));
+ auto byteBuffer = LocalRef (env->CallObjectMethod (plane, AndroidImagePlane.getBuffer));
+
+ ImageBuffer correctedBuffer = getImageBufferWithCorrectedOrientationFrom (byteBuffer, deviceOrientationFromAccelerometerSensor,
+ targetOrientation, cameraLensFrontFacing, cameraSensorOrientation);
+
+ jbyte* rawBytes = env->GetByteArrayElements (correctedBuffer.byteArray, nullptr);
+
+ Image result = ImageFileFormat::loadFrom (rawBytes, (size_t) correctedBuffer.size);
+
+ env->ReleaseByteArrayElements (correctedBuffer.byteArray, rawBytes, 0);
+
+ return result;
+ }
+
+ static ImageBuffer getImageBufferWithCorrectedOrientationFrom (const LocalRef& imagePlaneBuffer,
+ Desktop::DisplayOrientation deviceOrientationFromAccelerometerSensor,
+ Desktop::DisplayOrientation targetOrientation,
+ bool cameraLensFrontFacing,
+ int cameraSensorOrientation)
+ {
+ auto* env = getEnv();
+
+ auto bufferSize = env->CallIntMethod (imagePlaneBuffer, JavaByteBuffer.remaining);
+ auto byteArray = LocalRef (env->NewByteArray (bufferSize));
+ env->CallObjectMethod (imagePlaneBuffer, JavaByteBuffer.get, byteArray.get());
+
+ auto orientationsEnabled = Desktop::getInstance().getOrientationsEnabled() & ~Desktop::upsideDown;
+
+ auto rotationAngle = getRotationAngle (deviceOrientationFromAccelerometerSensor, targetOrientation,
+ cameraLensFrontFacing, cameraSensorOrientation);
+
+ if (rotationAngle == 0)
+ {
+ // Nothing to do, just get the bytes
+ return { byteArray, bufferSize };
+ }
+
+ auto origBitmap = LocalRef (env->CallStaticObjectMethod (AndroidBitmapFactory,
+ AndroidBitmapFactory.decodeByteArray,
+ byteArray.get(), (jint) 0, (jint) bufferSize));
+
+ auto correctedBitmap = getBitmapWithCorrectOrientationFrom (origBitmap, rotationAngle);
+
+ auto byteArrayOutputStream = LocalRef (env->NewObject (ByteArrayOutputStream,
+ ByteArrayOutputStream.constructor));
+
+ auto jCompressFormatString = javaString ("JPEG");
+ auto compressFormat = LocalRef (env->CallStaticObjectMethod (AndroidBitmapCompressFormat,
+ AndroidBitmapCompressFormat.valueOf,
+ jCompressFormatString.get()));
+
+ if (env->CallBooleanMethod (correctedBitmap, AndroidBitmap.compress, compressFormat.get(),
+ (jint) 100, byteArrayOutputStream.get()) != 0)
+ {
+ auto correctedByteArray = LocalRef ((jbyteArray) env->CallObjectMethod (byteArrayOutputStream,
+ ByteArrayOutputStream.toByteArray));
+
+ int correctedByteArraySize = env->CallIntMethod (byteArrayOutputStream, ByteArrayOutputStream.size);
+
+ return { correctedByteArray, correctedByteArraySize };
+ }
+
+ jassertfalse;
+ // fallback, return original bitmap
+ return { byteArray, bufferSize };
+ }
+
+ static int getRotationAngle (Desktop::DisplayOrientation deviceOrientationFromAccelerometerSensor,
+ Desktop::DisplayOrientation targetOrientation,
+ bool cameraLensFrontFacing,
+ int cameraSensorOrientation)
+ {
+ auto orientationsEnabled = Desktop::getInstance().getOrientationsEnabled() & ~Desktop::upsideDown;
+
+ auto isSensorOrientationHorizontal = deviceOrientationFromAccelerometerSensor == Desktop::rotatedAntiClockwise
+ || deviceOrientationFromAccelerometerSensor == Desktop::rotatedClockwise;
+
+ if (cameraLensFrontFacing && isSensorOrientationHorizontal)
+ {
+ // flip angles for front camera
+ return getRotationAngle (deviceOrientationFromAccelerometerSensor, targetOrientation, false, (cameraSensorOrientation + 180) % 360);
+ }
+
+ switch (targetOrientation)
+ {
+ case Desktop::rotatedAntiClockwise:
+ return cameraSensorOrientation == 90 ? 0 : 180;
+ case Desktop::rotatedClockwise:
+ return cameraSensorOrientation == 90 ? 180 : 0;
+ case Desktop::upright:
+ case Desktop::upsideDown:
+ if ((targetOrientation == Desktop::upright && ! cameraLensFrontFacing)
+ || (targetOrientation == Desktop::upsideDown && cameraLensFrontFacing))
+ {
+ return cameraSensorOrientation;
+ }
+ else
+ {
+ if (deviceOrientationFromAccelerometerSensor == Desktop::upright || deviceOrientationFromAccelerometerSensor == Desktop::upsideDown)
+ return cameraSensorOrientation;
+ else
+ return (cameraSensorOrientation + 180) % 360;
+ }
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ static LocalRef getBitmapWithCorrectOrientationFrom (LocalRef& origBitmap, int rotationAngle)
+ {
+ auto* env = getEnv();
+
+ auto origBitmapWidth = env->CallIntMethod (origBitmap, AndroidBitmap.getWidth);
+ auto origBitmapHeight = env->CallIntMethod (origBitmap, AndroidBitmap.getHeight);
+
+ auto orientationsEnabled = Desktop::getInstance().getOrientationsEnabled() & ~Desktop::upsideDown;
+
+ auto matrix = LocalRef (env->NewObject (AndroidMatrix, AndroidMatrix.constructor));
+ env->CallBooleanMethod (matrix, AndroidMatrix.postRotate, (jfloat) rotationAngle, (jfloat) 0, (jfloat) 0);
+
+ auto rotatedBitmap = LocalRef (env->CallStaticObjectMethod (AndroidBitmap, AndroidBitmap.createBitmapFrom,
+ origBitmap.get(), (jint) 0, (jint) 0,
+ (jint) origBitmapWidth, (jint) origBitmapHeight,
+ matrix.get(), true));
+
+ env->CallVoidMethod (origBitmap, AndroidBitmap.recycle);
+
+ return rotatedBitmap;
+ }
+ };
+
+ //==============================================================================
+ class MediaRecorder : private MediaRecorderOnInfoListener::Owner,
+ private MediaRecorderOnErrorListener::Owner
+ {
+ public:
+ MediaRecorder (const String& outputFilePath, int videoWidth, int videoHeight,
+ int sensorOrientation, int cameraLensFacing)
+ : onInfoListener (*this),
+ onErrorListener (*this),
+ mediaRecorder (LocalRef (getEnv()->NewObject (AndroidMediaRecorder,
+ AndroidMediaRecorder.constructor)))
+ {
+ auto* env = getEnv();
+
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setOnInfoListener,
+ CreateJavaInterface (&onInfoListener,
+ "android/media/MediaRecorder$OnInfoListener").get());
+
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setOnErrorListener,
+ CreateJavaInterface (&onErrorListener,
+ "android/media/MediaRecorder$OnErrorListener").get());
+
+ // NB: the order of function calls here is enforced, and exceptions will be thrown if
+ // the order is changed.
+ static constexpr int audioSourceMic = 1;
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setAudioSource, (jint) audioSourceMic);
+
+ static constexpr int videoSourceSurface = 2;
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setVideoSource, (jint) videoSourceSurface);
+
+ static constexpr int outputFormatMPEG4 = 2;
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setOutputFormat, (jint) outputFormatMPEG4);
+
+ static constexpr int audioEncoderAAC = 3;
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setAudioEncoder, (jint) audioEncoderAAC);
+
+ static constexpr int videoEncoderH264 = 2;
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setVideoEncoder, (jint) videoEncoderH264);
+
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setVideoEncodingBitRate, (jint) 10000000);
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setVideoFrameRate, (jint) 30);
+
+ auto frontFacing = cameraLensFacing == 0;
+
+ auto useInverseDegrees = frontFacing && sensorOrientation == 90;
+
+ int orientationHint = getOrientationHint (useInverseDegrees, sensorOrientation);
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setOrientationHint, (jint) orientationHint);
+
+ getEnv()->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setVideoSize, (jint) videoWidth, (jint) videoHeight);
+ getEnv()->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.setOutputFile, javaString (outputFilePath).get());
+ getEnv()->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.prepare);
+ }
+
+ ~MediaRecorder()
+ {
+ getEnv()->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.release);
+ }
+
+ LocalRef getSurface()
+ {
+ return LocalRef (getEnv()->CallObjectMethod (mediaRecorder, AndroidMediaRecorder.getSurface));
+ }
+
+ void start()
+ {
+ lockScreenOrientation();
+
+ getEnv()->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.start);
+
+ hasStartedRecording = true;
+ }
+
+ void stop()
+ {
+ // A request to stop can be sent before recording has had a chance to start, so
+ // ignore the request rather than calling AndroidMediaRecorder.stop because
+ // otherwise MediaRecorder will throw an exception and...
+ if (! hasStartedRecording)
+ return;
+
+ hasStartedRecording = false;
+
+ auto* env = getEnv();
+ env->CallVoidMethod (mediaRecorder, AndroidMediaRecorder.stop);
+
+ // ... 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.
+ auto exception = LocalRef (env->ExceptionOccurred());
+
+ if (exception != 0)
+ env->ExceptionClear();
+
+ unlockScreenOrientation();
+ }
+
+ private:
+ MediaRecorderOnInfoListener onInfoListener;
+ MediaRecorderOnErrorListener onErrorListener;
+ GlobalRef mediaRecorder;
+ bool hasStartedRecording = false;
+ int orientationsEnabled = -1;
+
+ void lockScreenOrientation()
+ {
+ orientationsEnabled = Desktop::getInstance().getOrientationsEnabled();
+
+ auto o = Desktop::getInstance().getCurrentOrientation();
+ Desktop::getInstance().setOrientationsEnabled (o);
+ }
+
+ static jint juceOrientationToNativeOrientation (int orientations) noexcept
+ {
+ enum
+ {
+ SCREEN_ORIENTATION_LANDSCAPE = 0,
+ SCREEN_ORIENTATION_PORTRAIT = 1,
+ SCREEN_ORIENTATION_USER = 2,
+ SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8,
+ SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9,
+ SCREEN_ORIENTATION_USER_LANDSCAPE = 11,
+ SCREEN_ORIENTATION_USER_PORTRAIT = 12,
+ };
+
+ switch (orientations)
+ {
+ case Desktop::upright: return (jint) SCREEN_ORIENTATION_PORTRAIT;
+ case Desktop::upsideDown: return (jint) SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ case Desktop::upright + Desktop::upsideDown: return (jint) SCREEN_ORIENTATION_USER_PORTRAIT;
+ case Desktop::rotatedAntiClockwise: return (jint) SCREEN_ORIENTATION_LANDSCAPE;
+ case Desktop::rotatedClockwise: return (jint) SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ case Desktop::rotatedClockwise + Desktop::rotatedAntiClockwise: return (jint) SCREEN_ORIENTATION_USER_LANDSCAPE;
+ default: return (jint) SCREEN_ORIENTATION_USER;
+ }
+ }
+
+ void unlockScreenOrientation()
+ {
+ Desktop::getInstance().setOrientationsEnabled (orientationsEnabled);
+ }
+
+ void onInfo (LocalRef& recorder, int what, int extra) override
+ {
+ ignoreUnused (recorder, what, extra);
+
+ JUCE_CAMERA_LOG ("MediaRecorder::OnInfo: " + getInfoStringFromCode (what)
+ + ", extra code = " + String (extra));
+ }
+
+ void onError (LocalRef& recorder, int what, int extra) override
+ {
+ ignoreUnused (recorder, what, extra);
+
+ JUCE_CAMERA_LOG ("MediaRecorder::onError: " + getErrorStringFromCode (what)
+ + ", extra code = " + String (extra));
+ }
+
+ static String getInfoStringFromCode (int what)
+ {
+ enum
+ {
+ MEDIA_RECORDER_INFO_UNKNOWN = 1,
+ MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800,
+ MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801,
+ MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING = 802,
+ MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED = 803
+ };
+
+ switch (what)
+ {
+ case MEDIA_RECORDER_INFO_UNKNOWN: return { "Unknown info" };
+ case MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: return { "Max duration reached" };
+ case MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED: return { "Max filesize reached" };
+ case MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING: return { "Max filesize approaching" };
+ case MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED: return { "Next output file started" };
+ default: return String (what);
+ };
+ }
+
+ static String getErrorStringFromCode (int what)
+ {
+ enum
+ {
+ MEDIA_RECORDER_ERROR_UNKNOWN = 1,
+ MEDIA_ERROR_SERVER_DIED = 100
+ };
+
+ switch (what)
+ {
+ case MEDIA_RECORDER_ERROR_UNKNOWN: return { "Unknown error" };
+ case MEDIA_ERROR_SERVER_DIED: return { "Server died" };
+ default: return String (what);
+ };
+ }
+
+ static int getOrientationHint (bool useInverseDegrees, int cameraSensorOrientation)
+ {
+ auto* env = getEnv();
+
+ auto windowManager = LocalRef (env->CallObjectMethod (android.activity, JuceAppActivity.getWindowManager));
+ auto display = LocalRef (env->CallObjectMethod (windowManager, AndroidWindowManager.getDefaultDisplay));
+ auto rotation = env->CallIntMethod (display, AndroidDisplay.getRotation);
+
+ enum
+ {
+ ROTATION_0 = 0,
+ ROTATION_90,
+ ROTATION_180,
+ ROTATION_270
+ };
+
+ int hint = 0;
+
+ switch (rotation)
+ {
+ case ROTATION_0: hint = cameraSensorOrientation; break;
+ case ROTATION_90: hint = useInverseDegrees ? 180 : 0; break;
+ case ROTATION_180: hint = cameraSensorOrientation + 180; break;
+ case ROTATION_270: hint = useInverseDegrees ? 0 : 180; break;
+ default: jassertfalse;
+ }
+
+ return (hint + 360) % 360;
+ }
+ };
+
+ //==============================================================================
+ class ScopedCameraDevice
+ {
+ public:
+ //==============================================================================
+ class CaptureSession
+ {
+ public:
+ struct ConfiguredCallback
+ {
+ virtual ~ConfiguredCallback() {}
+
+ virtual void captureSessionConfigured (CaptureSession*) = 0;
+ };
+
+ ~CaptureSession()
+ {
+ bool calledClose = false;
+
+ auto* env = getEnv();
+
+ {
+ const ScopedLock lock (captureSessionLock);
+
+ if (captureSession.get() != nullptr)
+ {
+ calledClose = true;
+
+ env->CallVoidMethod (captureSession, CameraCaptureSession.close);
+ }
+ }
+
+ auto exception = LocalRef (env->ExceptionOccurred());
+
+ // When exception occurs, CameraCaptureSession.close will never finish, so
+ // we should not wait for it. For fatal error an exception does occur, but
+ // it is catched internally in Java...
+ if (exception != 0 || scopedCameraDevice.fatalErrorOccurred.get())
+ {
+ JUCE_CAMERA_LOG ("Exception or fatal error occurred while closing Capture Session, closing by force");
+
+ env->ExceptionClear();
+ }
+ else if (calledClose)
+ {
+ pendingClose.set (1);
+ closedEvent.wait (-1);
+ }
+ }
+
+ bool openedOk() const noexcept { return captureSession != nullptr; }
+
+ const GlobalRef& getNativeSession() const { return captureSession; }
+
+ bool start (const LocalRef& targetSurfacesList, GlobalRef& handlerToUse)
+ {
+ if (! openedOk())
+ {
+ jassertfalse;
+ return false;
+ }
+
+ auto* env = getEnv();
+
+ auto numSurfaces = env->CallIntMethod (targetSurfacesList, JavaArrayList.size);
+
+ for (int i = 0; i < numSurfaces; ++i)
+ {
+ auto surface = LocalRef (env->CallObjectMethod (targetSurfacesList, JavaArrayList.get, (jint) i));
+ env->CallVoidMethod (captureRequestBuilder, CaptureRequestBuilder.addTarget, surface.get());
+ }
+
+ previewCaptureRequest = GlobalRef (env->CallObjectMethod (captureRequestBuilder, CaptureRequestBuilder.build));
+
+ env->CallIntMethod (captureSession, CameraCaptureSession.setRepeatingRequest,
+ previewCaptureRequest.get(), nullptr, handlerToUse.get());
+
+ return true;
+ }
+
+ void takeStillPicture (jobject targetSurface)
+ {
+ if (stillPictureTaker == nullptr)
+ {
+ // Can only take picture once session was successfully configured!
+ jassertfalse;
+ return;
+ }
+
+ auto* env = getEnv();
+
+ static constexpr int templateStillCapture = 2;
+ auto builder = LocalRef (env->CallObjectMethod (scopedCameraDevice.cameraDevice,
+ AndroidCameraDevice.createCaptureRequest,
+ (jint) templateStillCapture));
+
+ env->CallVoidMethod (builder, CaptureRequestBuilder.addTarget, targetSurface);
+
+ setCaptureRequestBuilderIntegerKey (builder.get(), CaptureRequest.CONTROL_AF_MODE, autoFocusMode);
+
+ auto stillPictureCaptureRequest = LocalRef (env->CallObjectMethod (builder, CaptureRequestBuilder.build));
+
+ stillPictureTaker->takePicture (stillPictureCaptureRequest.get());
+ }
+
+ private:
+ //==============================================================================
+ class StillPictureTaker : private AndroidRunnable::Owner
+ {
+ public:
+ StillPictureTaker (GlobalRef& captureSessionToUse, GlobalRef& captureRequestBuilderToUse,
+ GlobalRef& previewCaptureRequestToUse, GlobalRef& handlerToUse,
+ int autoFocusModeToUse)
+ : captureSession (captureSessionToUse),
+ captureRequestBuilder (captureRequestBuilderToUse),
+ previewCaptureRequest (previewCaptureRequestToUse),
+ handler (handlerToUse),
+ runnable (*this),
+ captureSessionPreviewCaptureCallback (LocalRef (getEnv()->NewObject (CameraCaptureSessionCaptureCallback,
+ CameraCaptureSessionCaptureCallback.constructor,
+ android.activity.get(),
+ reinterpret_cast (this),
+ true))),
+ captureSessionStillPictureCaptureCallback (LocalRef (getEnv()->NewObject (CameraCaptureSessionCaptureCallback,
+ CameraCaptureSessionCaptureCallback.constructor,
+ android.activity.get(),
+ reinterpret_cast (this),
+ false))),
+ autoFocusMode (autoFocusModeToUse)
+ {
+ }
+
+ void takePicture (jobject stillPictureCaptureRequestToUse)
+ {
+ JUCE_CAMERA_LOG ("Taking picture...");
+
+ stillPictureCaptureRequest = GlobalRef (stillPictureCaptureRequestToUse);
+
+ lockFocus();
+ }
+
+ private:
+ GlobalRef& captureSession;
+ GlobalRef& captureRequestBuilder;
+ GlobalRef& previewCaptureRequest;
+ GlobalRef& handler;
+
+ AndroidRunnable runnable;
+ GlobalRef delayedCaptureRunnable;
+
+ GlobalRef captureSessionPreviewCaptureCallback;
+
+ GlobalRef stillPictureCaptureRequest;
+ GlobalRef captureSessionStillPictureCaptureCallback;
+
+ int autoFocusMode;
+
+ enum class State
+ {
+ idle = 0,
+ pendingFocusLock,
+ pendingExposurePrecapture,
+ pendingExposurePostPrecapture,
+ pictureTaken
+ };
+
+ State currentState = State::idle;
+
+ void lockFocus()
+ {
+ if (Pimpl::checkHasExceptionOccurred())
+ return;
+
+ JUCE_CAMERA_LOG ("Performing auto-focus if possible...");
+
+ currentState = State::pendingFocusLock;
+
+ auto* env = getEnv();
+
+ // NB: auto-focus may be unavailable on a device, in which case it may have already
+ // automatically adjusted the exposure. We check for that in updateState().
+ static constexpr int controlAfTriggerStart = 1;
+ CaptureSession::setCaptureRequestBuilderIntegerKey (captureRequestBuilder.get(),
+ CaptureRequest.CONTROL_AF_TRIGGER,
+ controlAfTriggerStart);
+
+ auto previewRequest = LocalRef (env->CallObjectMethod (captureRequestBuilder,
+ CaptureRequestBuilder.build));
+
+ env->CallIntMethod (captureSession, CameraCaptureSession.capture, previewRequest.get(),
+ captureSessionPreviewCaptureCallback.get(), handler.get());
+ }
+
+ void updateState (jobject captureResult)
+ {
+ // IllegalStateException can be thrown when accessing CaptureSession,
+ // claiming that capture session was already closed but we may not
+ // get relevant callback yet, so check for this and bailout when needed.
+ if (Pimpl::checkHasExceptionOccurred())
+ return;
+
+ auto* env = getEnv();
+
+ switch (currentState)
+ {
+ case State::pendingFocusLock:
+ {
+ JUCE_CAMERA_LOG ("Still picture capture, updateState(), State::pendingFocusLock...");
+
+ auto controlAfStateValue = getCaptureResultIntegerKeyValue (CaptureResult.CONTROL_AF_STATE, captureResult);
+
+ if (controlAfStateValue.get() == nullptr)
+ {
+ captureStillPictureDelayed();
+ return;
+ }
+
+ auto autoToFocusNotAvailable = autoFocusMode == 0;
+
+ if (autoToFocusNotAvailable || autoFocusHasFinished (controlAfStateValue))
+ {
+ auto controlAeStateIntValue = getControlAEState (captureResult);
+ static constexpr int controlAeStateConverged = 2;
+
+ if (controlAeStateIntValue == -1 || controlAeStateIntValue == controlAeStateConverged)
+ {
+ currentState = State::pictureTaken;
+ captureStillPictureDelayed();
+ }
+ else
+ {
+ runPrecaptureSequence();
+ }
+ }
+
+ break;
+ }
+
+ case State::pendingExposurePrecapture:
+ {
+ JUCE_CAMERA_LOG ("Still picture capture, updateState(), State::pendingExposurePrecapture...");
+
+ auto controlAeStateIntValue = getControlAEState (captureResult);
+ static constexpr int controlAeStateFlashRequired = 4;
+ static constexpr int controlAeStatePrecapture = 5;
+
+ if (controlAeStateIntValue == -1 || controlAeStateIntValue == controlAeStateFlashRequired
+ || controlAeStateIntValue == controlAeStatePrecapture)
+ {
+ currentState = State::pendingExposurePostPrecapture;
+ }
+
+ break;
+ }
+
+ case State::pendingExposurePostPrecapture:
+ {
+ JUCE_CAMERA_LOG ("Still picture capture, updateState(), State::pendingExposurePostPrecapture...");
+
+ auto controlAeStateIntValue = getControlAEState (captureResult);
+ static constexpr int controlAeStatePrecapture = 5;
+
+ if (controlAeStateIntValue == -1 || controlAeStateIntValue != controlAeStatePrecapture)
+ {
+ currentState = State::pictureTaken;
+ captureStillPictureDelayed();
+ }
+
+ break;
+ }
+ case State::idle:
+ case State::pictureTaken:
+ { /* do nothing */ break; }
+ };
+ }
+
+ static int getControlAEState (jobject captureResult)
+ {
+ auto controlAeStateValue = getCaptureResultIntegerKeyValue (CaptureResult.CONTROL_AE_STATE, captureResult);
+
+ return controlAeStateValue.get() != nullptr
+ ? getEnv()->CallIntMethod (controlAeStateValue, JavaInteger.intValue) : -1;
+ }
+
+ static bool autoFocusHasFinished (const LocalRef& controlAfStateValue)
+ {
+ static constexpr int controlAfStateFocusedLocked = 4;
+ static constexpr int controlAfStateNotFocusedLocked = 5;
+
+ auto controlAfStateIntValue = getEnv()->CallIntMethod (controlAfStateValue, JavaInteger.intValue);
+
+ return controlAfStateIntValue == controlAfStateFocusedLocked || controlAfStateIntValue == controlAfStateNotFocusedLocked;
+ }
+
+ static LocalRef getCaptureResultIntegerKeyValue (jfieldID key, jobject captureResult)
+ {
+ auto* env = getEnv();
+
+ auto jKey = LocalRef (env->GetStaticObjectField (CaptureResult, key));
+ return LocalRef (env->CallObjectMethod (captureResult, CaptureResult.get, jKey.get()));
+ }
+
+ void captureStillPictureDelayed()
+ {
+ if (Pimpl::checkHasExceptionOccurred())
+ return;
+
+ JUCE_CAMERA_LOG ("Still picture capture, device ready, capturing now...");
+
+ auto* env = getEnv();
+
+ env->CallVoidMethod (captureSession, CameraCaptureSession.stopRepeating);
+
+ if (Pimpl::checkHasExceptionOccurred())
+ return;
+
+ env->CallVoidMethod (captureSession, CameraCaptureSession.abortCaptures);
+
+ if (Pimpl::checkHasExceptionOccurred())
+ return;
+
+ // Delay still picture capture for devices that can't handle it right after
+ // stopRepeating/abortCaptures calls.
+ delayedCaptureRunnable = GlobalRef (CreateJavaInterface (&runnable, "java/lang/Runnable").get());
+ env->CallBooleanMethod (handler, AndroidHandler.postDelayed, delayedCaptureRunnable.get(), (jlong) 200);
+ }
+
+ void runPrecaptureSequence()
+ {
+ if (Pimpl::checkHasExceptionOccurred())
+ return;
+
+ auto* env = getEnv();
+
+ static constexpr int controlAePrecaptureTriggerStart = 1;
+ CaptureSession::setCaptureRequestBuilderIntegerKey (captureRequestBuilder.get(),
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ controlAePrecaptureTriggerStart);
+
+ currentState = State::pendingExposurePrecapture;
+
+ auto previewRequest = LocalRef (env->CallObjectMethod (captureRequestBuilder,
+ CaptureRequestBuilder.build));
+
+ env->CallIntMethod (captureSession, CameraCaptureSession.capture, previewRequest.get(),
+ captureSessionPreviewCaptureCallback.get(), handler.get());
+ }
+
+ void unlockFocus()
+ {
+ if (Pimpl::checkHasExceptionOccurred())
+ return;
+
+ JUCE_CAMERA_LOG ("Unlocking focus...");
+
+ currentState = State::idle;
+
+ auto* env = getEnv();
+
+ static constexpr int controlAfTriggerCancel = 2;
+ CaptureSession::setCaptureRequestBuilderIntegerKey (captureRequestBuilder.get(),
+ CaptureRequest.CONTROL_AF_TRIGGER,
+ controlAfTriggerCancel);
+
+ auto resetAutoFocusRequest = LocalRef (env->CallObjectMethod (captureRequestBuilder,
+ CaptureRequestBuilder.build));
+
+ env->CallIntMethod (captureSession, CameraCaptureSession.capture, resetAutoFocusRequest.get(),
+ nullptr, handler.get());
+
+ if (Pimpl::checkHasExceptionOccurred())
+ return;
+
+ delayedCaptureRunnable.clear();
+
+ // NB: for preview, using preview capture request again
+ env->CallIntMethod (captureSession, CameraCaptureSession.setRepeatingRequest, previewCaptureRequest.get(),
+ nullptr, handler.get());
+ }
+ //==============================================================================
+ void run() override
+ {
+ captureStillPicture();
+ }
+
+ void captureStillPicture()
+ {
+ getEnv()->CallIntMethod (captureSession, CameraCaptureSession.capture,
+ stillPictureCaptureRequest.get(), captureSessionStillPictureCaptureCallback.get(),
+ nullptr);
+ }
+
+ //==============================================================================
+ void cameraCaptureSessionCaptureCompleted (bool isPreview, jobject session, jobject request, jobject result)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionCaptureCompleted()");
+
+ ignoreUnused (session, request);
+
+ if (isPreview)
+ updateState (result);
+ else if (currentState != State::idle)
+ unlockFocus();
+ }
+
+ void cameraCaptureSessionCaptureFailed (bool isPreview, jobject session, jobject request, jobject failure)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionCaptureFailed()");
+
+ ignoreUnused (isPreview, session, request, failure);
+ }
+
+ void cameraCaptureSessionCaptureProgressed (bool isPreview, jobject session, jobject request, jobject partialResult)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionCaptureProgressed()");
+
+ ignoreUnused (session, request);
+
+ if (isPreview)
+ updateState (partialResult);
+ }
+
+ void cameraCaptureSessionCaptureSequenceAborted (bool isPreview, jobject session, int sequenceId)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionCaptureSequenceAborted()");
+
+ ignoreUnused (isPreview, isPreview, session, sequenceId);
+ }
+
+ void cameraCaptureSessionCaptureSequenceCompleted (bool isPreview, jobject session, int sequenceId, int64 frameNumber)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionCaptureSequenceCompleted()");
+
+ ignoreUnused (isPreview, session, sequenceId, frameNumber);
+ }
+
+ void cameraCaptureSessionCaptureStarted (bool isPreview, jobject session, jobject request, int64 timestamp, int64 frameNumber)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionCaptureStarted()");
+
+ ignoreUnused (isPreview, session, request, timestamp, frameNumber);
+ }
+
+ friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int);
+ friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64);
+ friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64);
+ };
+
+ //==============================================================================
+ ScopedCameraDevice& scopedCameraDevice;
+ ConfiguredCallback& configuredCallback;
+ GlobalRef& handler;
+
+ GlobalRef captureRequestBuilder;
+ GlobalRef previewCaptureRequest;
+
+ GlobalRef captureSessionStateCallback;
+ int autoFocusMode;
+
+ GlobalRef captureSession;
+ CriticalSection captureSessionLock;
+
+ Atomic pendingClose { 0 };
+
+ std::unique_ptr stillPictureTaker;
+
+ WaitableEvent closedEvent;
+
+ JUCE_DECLARE_WEAK_REFERENCEABLE (CaptureSession)
+
+ //==============================================================================
+ CaptureSession (ScopedCameraDevice& scopedCameraDeviceToUse, ConfiguredCallback& configuredCallbackToUse,
+ const LocalRef& surfacesList, GlobalRef& handlerToUse,
+ int captureSessionTemplate, int autoFocusModeToUse)
+ : scopedCameraDevice (scopedCameraDeviceToUse),
+ configuredCallback (configuredCallbackToUse),
+ handler (handlerToUse),
+ captureRequestBuilder (LocalRef (getEnv()->CallObjectMethod (scopedCameraDevice.cameraDevice,
+ AndroidCameraDevice.createCaptureRequest,
+ (jint) captureSessionTemplate))),
+ captureSessionStateCallback (LocalRef (getEnv()->NewObject (CameraCaptureSessionStateCallback,
+ CameraCaptureSessionStateCallback.constructor,
+ android.activity.get(),
+ reinterpret_cast (this)))),
+ autoFocusMode (autoFocusModeToUse)
+ {
+ auto* env = getEnv();
+
+ env->CallVoidMethod (scopedCameraDevice.cameraDevice, AndroidCameraDevice.createCaptureSession,
+ surfacesList.get(), captureSessionStateCallback.get(), handler.get());
+
+ static constexpr int controlModeAuto = 1;
+ setCaptureRequestBuilderIntegerKey (captureRequestBuilder.get(), CaptureRequest.CONTROL_MODE, controlModeAuto);
+
+ setCaptureRequestBuilderIntegerKey (captureRequestBuilder.get(), CaptureRequest.CONTROL_AF_MODE, autoFocusMode);
+ }
+
+ static void setCaptureRequestBuilderIntegerKey (jobject captureRequestBuilder, jfieldID key, int value)
+ {
+ auto* env = getEnv();
+
+ auto jKey = LocalRef (env->GetStaticObjectField (CaptureRequest, key));
+ auto jValue = LocalRef (env->CallStaticObjectMethod (JavaInteger, JavaInteger.valueOf, (jint) value));
+
+ env->CallVoidMethod (captureRequestBuilder, CaptureRequestBuilder.set, jKey.get(), jValue.get());
+ }
+
+ void cameraCaptureSessionActive (jobject session)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionActive()");
+ ignoreUnused (session);
+ }
+
+ void cameraCaptureSessionClosed (jobject session)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionClosed()");
+ ignoreUnused (session);
+
+ closedEvent.signal();
+ }
+
+ void cameraCaptureSessionConfigureFailed (jobject session)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionConfigureFailed()");
+ ignoreUnused (session);
+
+ WeakReference weakRef (this);
+
+ MessageManager::callAsync ([this, weakRef]()
+ {
+ if (weakRef == nullptr)
+ return;
+
+ configuredCallback.captureSessionConfigured (nullptr);
+ });
+ }
+
+ void cameraCaptureSessionConfigured (jobject session)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionConfigured()");
+
+ if (pendingClose.get() == 1)
+ {
+ // Already closing, bailout.
+ closedEvent.signal();
+
+ GlobalRef s (session);
+
+ MessageManager::callAsync ([s]()
+ {
+ getEnv()->CallVoidMethod (s, CameraCaptureSession.close);
+ });
+
+ return;
+ }
+
+ {
+ const ScopedLock lock (captureSessionLock);
+ captureSession = GlobalRef (session);
+ }
+
+ WeakReference weakRef (this);
+
+ MessageManager::callAsync ([this, weakRef]()
+ {
+ if (weakRef == nullptr)
+ return;
+
+ stillPictureTaker.reset (new StillPictureTaker (captureSession, captureRequestBuilder,
+ previewCaptureRequest, handler, autoFocusMode));
+
+ configuredCallback.captureSessionConfigured (this);
+ });
+ }
+
+ void cameraCaptureSessionReady (jobject session)
+ {
+ JUCE_CAMERA_LOG ("cameraCaptureSessionReady()");
+ ignoreUnused (session);
+ }
+
+ friend class ScopedCameraDevice;
+
+ friend void juce_cameraCaptureSessionActive (int64, void*);
+ friend void juce_cameraCaptureSessionClosed (int64, void*);
+ friend void juce_cameraCaptureSessionConfigureFailed (int64, void*);
+ friend void juce_cameraCaptureSessionConfigured (int64, void*);
+ friend void juce_cameraCaptureSessionReady (int64, void*);
+
+ friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int);
+ friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64);
+ friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64);
+
+ JUCE_DECLARE_NON_COPYABLE (CaptureSession)
+ };
+
+ //==============================================================================
+ ScopedCameraDevice (Pimpl& ownerToUse, const String& cameraIdToUse, GlobalRef& cameraManagerToUse,
+ GlobalRef& handlerToUse, int autoFocusModeToUse)
+ : owner (ownerToUse),
+ cameraId (cameraIdToUse),
+ cameraManager (cameraManagerToUse),
+ handler (handlerToUse),
+ cameraStateCallback (LocalRef (getEnv()->NewObject (CameraDeviceStateCallback,
+ CameraDeviceStateCallback.constructor,
+ android.activity.get(),
+ reinterpret_cast (this)))),
+ autoFocusMode (autoFocusModeToUse)
+ {
+ open();
+ }
+
+ ~ScopedCameraDevice()
+ {
+ close();
+ }
+
+ void open()
+ {
+ pendingOpen.set (1);
+
+ auto* env = getEnv();
+
+ env->CallVoidMethod (cameraManager, CameraManager.openCamera,
+ javaString (cameraId).get(),
+ cameraStateCallback.get(), handler.get());
+
+ // If something went wrong we will be pinged in cameraDeviceStateError()
+ // callback, silence the redundant exception.
+ auto exception = LocalRef (env->ExceptionOccurred());
+
+ if (exception != 0)
+ env->ExceptionClear();
+ }
+
+ void close()
+ {
+ if (pendingClose.compareAndSetBool (1, 0))
+ {
+ auto* env = getEnv();
+
+ if (cameraDevice.get() != nullptr)
+ {
+ env->CallVoidMethod (cameraDevice, AndroidCameraDevice.close);
+ closedEvent.wait (-1);
+ }
+
+ pendingClose.set (0);
+ pendingOpen .set (0);
+ cameraDevice.clear();
+ }
+ }
+
+ bool openedOk() const { return cameraDevice != nullptr; }
+
+ bool hasErrorOccurred() const { return fatalErrorOccurred.get(); }
+
+ CaptureSession* createCaptureSession (CaptureSession::ConfiguredCallback& cc,
+ const LocalRef& surfacesList,
+ GlobalRef& handlerToUse,
+ int captureSessionTemplate)
+ {
+ if (! openedOk())
+ {
+ jassertfalse;
+ return nullptr;
+ }
+
+ return new CaptureSession (*this, cc, surfacesList, handlerToUse, captureSessionTemplate, autoFocusMode);
+ }
+
+ private:
+ Pimpl& owner;
+ const String cameraId;
+ GlobalRef& cameraManager;
+ GlobalRef& handler;
+
+ GlobalRef cameraStateCallback;
+ int autoFocusMode;
+
+ GlobalRef cameraDevice;
+ Atomic pendingOpen { 0 };
+ Atomic pendingClose { 0 };
+ Atomic fatalErrorOccurred { 0 };
+ String openError;
+
+ WaitableEvent closedEvent;
+
+ void cameraDeviceStateClosed()
+ {
+ JUCE_CAMERA_LOG ("cameraDeviceStateClosed()");
+
+ closedEvent.signal();
+ }
+
+ void cameraDeviceStateDisconnected()
+ {
+ JUCE_CAMERA_LOG ("cameraDeviceStateDisconnected()");
+
+ if (pendingOpen.compareAndSetBool (0, 1))
+ {
+ openError = "Device disconnected";
+
+ notifyOpenResult();
+ }
+
+ MessageManager::callAsync ([this]() { close(); });
+ }
+
+ void cameraDeviceStateError (int errorCode)
+ {
+ String error = cameraErrorCodeToString (errorCode);
+
+ JUCE_CAMERA_LOG ("cameraDeviceStateError(), error: " + error);
+
+ if (pendingOpen.compareAndSetBool (0, 1))
+ {
+ openError = error;
+
+ notifyOpenResult();
+ }
+
+ fatalErrorOccurred.set (1);
+
+ MessageManager::callAsync ([this, error]()
+ {
+ owner.cameraDeviceError (error);
+ close();
+ });
+ }
+
+ void cameraDeviceStateOpened (jobject cameraDeviceToUse)
+ {
+ JUCE_CAMERA_LOG ("cameraDeviceStateOpened()");
+
+ pendingOpen.set (0);
+
+ cameraDevice = GlobalRef (cameraDeviceToUse);
+
+ notifyOpenResult();
+ }
+
+ void notifyOpenResult()
+ {
+ MessageManager::callAsync ([this]() { owner.cameraOpenFinished (openError); });
+ }
+
+ friend void juce_cameraDeviceStateClosed (int64);
+ friend void juce_cameraDeviceStateDisconnected (int64);
+ friend void juce_cameraDeviceStateError (int64, int);
+ friend void juce_cameraDeviceStateOpened (int64, void*);
+
+ friend void juce_cameraCaptureSessionActive (int64, void*);
+ friend void juce_cameraCaptureSessionClosed (int64, void*);
+ friend void juce_cameraCaptureSessionConfigureFailed (int64, void*);
+ friend void juce_cameraCaptureSessionConfigured (int64, void*);
+ friend void juce_cameraCaptureSessionReady (int64, void*);
+
+ friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*);
+ friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int);
+ friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64);
+ friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64);
+ };
+
+ //==============================================================================
+ struct CaptureSessionModeBase
+ {
+ virtual ~CaptureSessionModeBase() { }
+
+ virtual bool isVideoRecordSession() const = 0;
+
+ virtual void triggerStillPictureCapture() = 0;
+ };
+
+ //==============================================================================
+ template
+ struct CaptureSessionMode : public CaptureSessionModeBase,
+ private PreviewDisplay::Listener,
+ private ScopedCameraDevice::CaptureSession::ConfiguredCallback
+ {
+ ~CaptureSessionMode()
+ {
+ captureSession.reset();
+
+ previewDisplay.removeListener (this);
+ }
+
+ bool isVideoRecordSession() const override
+ {
+ return Mode::isVideoRecord();
+ }
+
+ void triggerStillPictureCapture() override
+ {
+ if (captureSession == nullptr)
+ {
+ // The capture session must be ready before taking a still picture.
+ // Did you remember to create and show a preview display?
+ jassertfalse;
+ return;
+ }
+
+ crtp().takeStillPicture();
+ }
+
+ protected:
+ CaptureSessionMode (Pimpl& ownerToUse, ScopedCameraDevice& cameraDeviceToUse,
+ GlobalRef& handlerToUse, PreviewDisplay& pd, int cameraSensorOrientationToUse,
+ int cameraLensFacingToUse, StreamConfigurationMap& streamConfigurationMapToUse)
+ : owner (ownerToUse),
+ scopedCameraDevice (cameraDeviceToUse),
+ handler (handlerToUse),
+ previewDisplay (pd),
+ cameraSensorOrientation (cameraSensorOrientationToUse),
+ cameraLensFacing (cameraLensFacingToUse),
+ streamConfigurationMap (streamConfigurationMapToUse)
+ {
+ WeakReference> weakRef (this);
+
+ if (weakRef == nullptr)
+ return;
+
+ // async so that the object is fully constructed before the callback gets invoked
+ MessageManager::callAsync ([this, weakRef]()
+ {
+ if (weakRef == nullptr)
+ return;
+
+ previewDisplay.addListener (this);
+ });
+ }
+
+ Mode& crtp() { return static_cast (*this); }
+
+ void previewDisplayReady() override
+ {
+ jassert (previewDisplay.isReady());
+
+ JUCE_CAMERA_LOG ("previewDisplayReady()");
+
+ // close previous capture session first
+ captureSession.reset();
+
+ if (scopedCameraDevice.hasErrorOccurred())
+ {
+ JUCE_CAMERA_LOG ("Device error detected, not recreating a new camera session. The device needs to be reopened.");
+ return;
+ }
+
+ captureSession.reset (scopedCameraDevice.createCaptureSession (*this, crtp().getCaptureSessionSurfaces(),
+ handler, Mode::getTemplate()));
+ }
+
+ void previewDisplayAboutToBeDestroyed() override
+ {
+ JUCE_CAMERA_LOG ("previewDisplayAboutToBeDestroyed()");
+
+ stopPreview();
+ }
+
+ void captureSessionConfigured (ScopedCameraDevice::CaptureSession* session) override
+ {
+ if (session == nullptr)
+ {
+ owner.cameraDeviceError ("Failed to configure camera session.");
+ return;
+ }
+
+ jassert (session == captureSession.get());
+
+ startSession();
+ }
+
+ void startSession()
+ {
+ if (! captureSession->start (crtp().getTargetSurfaces(), handler))
+ {
+ jassertfalse;
+ JUCE_CAMERA_LOG ("Could not start capture session");
+ }
+
+ crtp().sessionStarted();
+ }
+
+ void stopPreview()
+ {
+ if (captureSession != nullptr)
+ {
+ auto session = captureSession->getNativeSession();
+
+ auto* env = getEnv();
+
+ env->CallVoidMethod (session, CameraCaptureSession.stopRepeating);
+
+ if (Pimpl::checkHasExceptionOccurred())
+ return;
+
+ env->CallVoidMethod (session, CameraCaptureSession.abortCaptures);
+
+ Pimpl::checkHasExceptionOccurred();
+ }
+ }
+
+ Pimpl& owner;
+ ScopedCameraDevice& scopedCameraDevice;
+ GlobalRef& handler;
+ PreviewDisplay& previewDisplay;
+ int cameraSensorOrientation;
+ int cameraLensFacing;
+ StreamConfigurationMap& streamConfigurationMap;
+
+ std::unique_ptr captureSession;
+
+ JUCE_DECLARE_WEAK_REFERENCEABLE (CaptureSessionMode)
+ };
+
+ //==============================================================================
+ struct CaptureSessionPreviewMode : public CaptureSessionMode
+ {
+ CaptureSessionPreviewMode (Pimpl& ownerToUse, ScopedCameraDevice& cameraDeviceToUse, GlobalRef& handlerToUse,
+ PreviewDisplay& pd, ImageReader& ir, int cameraSensorOrientation,
+ int cameraLensFacingToUse, StreamConfigurationMap& streamConfigurationMapToUse)
+ : CaptureSessionMode (ownerToUse, cameraDeviceToUse, handlerToUse, pd,
+ cameraSensorOrientation, cameraLensFacingToUse, streamConfigurationMapToUse),
+ imageReader (ir)
+ {
+ }
+
+ // Surfaces passed to newly created capture session.
+ LocalRef getCaptureSessionSurfaces() const
+ {
+ auto* env = getEnv();
+
+ auto previewSurface = LocalRef (previewDisplay.createSurface());
+ auto imageSurface = LocalRef (imageReader.getSurface());
+
+ auto arrayList = LocalRef (env->NewObject (JavaArrayList, JavaArrayList.constructor, 2));
+ env->CallBooleanMethod (arrayList, JavaArrayList.add, previewSurface.get());
+ env->CallBooleanMethod (arrayList, JavaArrayList.add, imageSurface.get());
+
+ auto supported = streamConfigurationMap.isOutputSupportedForSurface (imageSurface);
+
+ // Output surface is not supported by this device, still image capture will not work!
+ jassert (supported);
+
+ return arrayList;
+ }
+
+ // Surfaces set as target during capture.
+ LocalRef getTargetSurfaces() const
+ {
+ auto* env = getEnv();
+
+ auto previewSurface = LocalRef (previewDisplay.createSurface());
+
+ auto arrayList = LocalRef (env->NewObject (JavaArrayList, JavaArrayList.constructor, 1));
+ env->CallBooleanMethod (arrayList, JavaArrayList.add, previewSurface.get());
+
+ return arrayList;
+ }
+
+ static int getTemplate()
+ {
+ static constexpr int templatePreview = 1;
+ return templatePreview;
+ }
+
+ static bool isVideoRecord() { return false; }
+
+ void sessionStarted() {}
+
+ void takeStillPicture()
+ {
+ imageReader.resetNotificationFlag();
+ captureSession->takeStillPicture (imageReader.getSurface());
+ }
+
+ private:
+ ImageReader& imageReader;
+ };
+
+ //==============================================================================
+ struct CaptureSessionVideoRecordingMode : public CaptureSessionMode
+ {
+ CaptureSessionVideoRecordingMode (Pimpl& ownerToUse, ScopedCameraDevice& cameraDeviceToUse, GlobalRef& handlerToUse,
+ PreviewDisplay& pd, MediaRecorder& mr, int cameraSensorOrientation,
+ int cameraLensFacingToUse, StreamConfigurationMap& streamConfigurationMapToUse)
+ : CaptureSessionMode (ownerToUse, cameraDeviceToUse, handlerToUse, pd,
+ cameraSensorOrientation, cameraLensFacingToUse, streamConfigurationMapToUse),
+ mediaRecorder (mr)
+ {
+ }
+
+ ~CaptureSessionVideoRecordingMode()
+ {
+ // We need to explicitly stop the preview before stopping the media recorder,
+ // because legacy devices can't handle recording stop before stopping the preview.
+ stopPreview();
+
+ mediaRecorder.stop();
+ }
+
+ // Surfaces passed to newly created capture session.
+ LocalRef