mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-16 00:34:19 +00:00
Add video playback support for Android and iOS. Update VideoComponent API to support building custom UIs.
This commit is contained in:
parent
dc7217fbbb
commit
315326477d
45 changed files with 4293 additions and 308 deletions
|
|
@ -1467,6 +1467,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_android_Video.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"
|
||||
|
|
@ -2932,6 +2933,7 @@ set_source_files_properties("../../../../../modules/juce_product_unlocking/juce_
|
|||
set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/native/juce_android_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/native/juce_android_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/native/juce_ios_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/native/juce_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)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ import android.content.res.Configuration;
|
|||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.camera2.*;
|
||||
import android.database.ContentObserver;
|
||||
import android.media.session.*;
|
||||
import android.media.MediaMetadata;
|
||||
import android.net.http.SslError;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -94,8 +97,11 @@ public class DemoRunner extends Activity
|
|||
//==============================================================================
|
||||
public boolean isPermissionDeclaredInManifest (int permissionID)
|
||||
{
|
||||
String permissionToCheck = getAndroidPermissionName(permissionID);
|
||||
return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
|
||||
}
|
||||
|
||||
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
|
||||
{
|
||||
try
|
||||
{
|
||||
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||
|
|
@ -1997,11 +2003,13 @@ public class DemoRunner extends Activity
|
|||
implements SurfaceHolder.Callback
|
||||
{
|
||||
private long nativeContext = 0;
|
||||
private boolean forVideo;
|
||||
|
||||
NativeSurfaceView (Context context, long nativeContextPtr)
|
||||
NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
|
||||
{
|
||||
super (context);
|
||||
nativeContext = nativeContextPtr;
|
||||
forVideo = createdForVideo;
|
||||
}
|
||||
|
||||
public Surface getNativeSurface()
|
||||
|
|
@ -2019,38 +2027,51 @@ public class DemoRunner extends Activity
|
|||
@Override
|
||||
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
|
||||
{
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
if (forVideo)
|
||||
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
|
||||
else
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated (SurfaceHolder holder)
|
||||
{
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
if (forVideo)
|
||||
surfaceCreatedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed (SurfaceHolder holder)
|
||||
{
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
if (forVideo)
|
||||
surfaceDestroyedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw (Canvas canvas)
|
||||
{
|
||||
super.dispatchDraw (canvas);
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
|
||||
if (forVideo)
|
||||
dispatchDrawNativeVideo (nativeContext, canvas);
|
||||
else
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@Override
|
||||
protected void onAttachedToWindow ()
|
||||
protected void onAttachedToWindow()
|
||||
{
|
||||
super.onAttachedToWindow();
|
||||
getHolder().addCallback (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow ()
|
||||
protected void onDetachedFromWindow()
|
||||
{
|
||||
super.onDetachedFromWindow();
|
||||
getHolder().removeCallback (this);
|
||||
|
|
@ -2062,11 +2083,17 @@ public class DemoRunner extends Activity
|
|||
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
|
||||
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
|
||||
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
}
|
||||
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
|
||||
{
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr);
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -2826,6 +2853,151 @@ public class DemoRunner extends Activity
|
|||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
public class MediaControllerCallback extends MediaController.Callback
|
||||
{
|
||||
private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info);
|
||||
private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata);
|
||||
private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);
|
||||
private native void mediaControllerSessionDestroyed (long host);
|
||||
|
||||
MediaControllerCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioInfoChanged (MediaController.PlaybackInfo info)
|
||||
{
|
||||
mediaControllerAudioInfoChanged (host, info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged (MediaMetadata metadata)
|
||||
{
|
||||
mediaControllerMetadataChanged (host, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged (PlaybackState state)
|
||||
{
|
||||
mediaControllerPlaybackStateChanged (host, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueChanged (List<MediaSession.QueueItem> queue) {}
|
||||
|
||||
@Override
|
||||
public void onSessionDestroyed()
|
||||
{
|
||||
mediaControllerSessionDestroyed (host);
|
||||
}
|
||||
|
||||
private long host;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public class MediaSessionCallback extends MediaSession.Callback
|
||||
{
|
||||
private native void mediaSessionPause (long host);
|
||||
private native void mediaSessionPlay (long host);
|
||||
private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);
|
||||
private native void mediaSessionSeekTo (long host, long pos);
|
||||
private native void mediaSessionStop (long host);
|
||||
|
||||
|
||||
MediaSessionCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
mediaSessionPause (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlay()
|
||||
{
|
||||
mediaSessionPlay (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayFromMediaId (String mediaId, Bundle extras)
|
||||
{
|
||||
mediaSessionPlayFromMediaId (host, mediaId, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekTo (long pos)
|
||||
{
|
||||
mediaSessionSeekTo (host, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
mediaSessionStop (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFastForward() {}
|
||||
|
||||
@Override
|
||||
public boolean onMediaButtonEvent (Intent mediaButtonIntent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewind() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToNext() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToPrevious() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToQueueItem (long id) {}
|
||||
|
||||
private long host;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public class SystemVolumeObserver extends ContentObserver
|
||||
{
|
||||
private native void mediaSessionSystemVolumeChanged (long host);
|
||||
|
||||
SystemVolumeObserver (Activity activityToUse, long hostToUse)
|
||||
{
|
||||
super (null);
|
||||
|
||||
activity = activityToUse;
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
void setEnabled (boolean shouldBeEnabled)
|
||||
{
|
||||
if (shouldBeEnabled)
|
||||
activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);
|
||||
else
|
||||
activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange (boolean selfChange, Uri uri)
|
||||
{
|
||||
if (uri.toString().startsWith ("content://settings/system/volume_music"))
|
||||
mediaSessionSystemVolumeChanged (host);
|
||||
}
|
||||
|
||||
private Activity activity;
|
||||
private long host;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
public static final String getLocaleValue (boolean isRegion)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2840,6 +2840,7 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>
|
||||
|
|
|
|||
|
|
@ -4827,6 +4827,9 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -2840,6 +2840,7 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>
|
||||
|
|
|
|||
|
|
@ -4827,6 +4827,9 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -2840,6 +2840,7 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>
|
||||
|
|
|
|||
|
|
@ -4827,6 +4827,9 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -291,6 +291,10 @@
|
|||
#ifndef JUCE_USE_CAMERA
|
||||
#define JUCE_USE_CAMERA 1
|
||||
#endif
|
||||
|
||||
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
||||
//#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
|
||||
#endif
|
||||
//==============================================================================
|
||||
#ifndef JUCE_STANDALONE_APPLICATION
|
||||
#if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
#include "../../../GUI/OpenGLDemo2D.h"
|
||||
#endif
|
||||
#include "../../../GUI/PropertiesDemo.h"
|
||||
#if JUCE_MAC || JUCE_WINDOWS
|
||||
#if ! JUCE_LINUX
|
||||
#include "../../../GUI/VideoDemo.h"
|
||||
#endif
|
||||
#include "../../../GUI/WebBrowserDemo.h"
|
||||
|
|
@ -100,7 +100,7 @@ void registerDemos_Two() noexcept
|
|||
REGISTER_DEMO_WITH_FILENAME (OpenGLDemoClasses::OpenGLDemo, GUI, OpenGLDemo, true)
|
||||
#endif
|
||||
REGISTER_DEMO (PropertiesDemo, GUI, false)
|
||||
#if JUCE_MAC || JUCE_WINDOWS
|
||||
#if ! JUCE_LINUX
|
||||
REGISTER_DEMO (VideoDemo, GUI, true)
|
||||
#endif
|
||||
REGISTER_DEMO (WebBrowserDemo, GUI, true)
|
||||
|
|
|
|||
|
|
@ -159,6 +159,18 @@ public:
|
|||
}
|
||||
|
||||
setSize (500, 500);
|
||||
|
||||
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
||||
[] (bool granted)
|
||||
{
|
||||
if (! granted)
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||
"Permissions warning",
|
||||
"External storage access permission not granted, some files"
|
||||
" may be inaccessible.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
#if JUCE_MAC || JUCE_WINDOWS
|
||||
//==============================================================================
|
||||
// so that we can easily have two video windows each with a file browser, wrap this up as a class..
|
||||
class MovieComponentWithFileBrowser : public Component,
|
||||
|
|
@ -54,6 +55,7 @@ class MovieComponentWithFileBrowser : public Component,
|
|||
{
|
||||
public:
|
||||
MovieComponentWithFileBrowser()
|
||||
: videoComp (true)
|
||||
{
|
||||
addAndMakeVisible (videoComp);
|
||||
|
||||
|
|
@ -110,8 +112,16 @@ private:
|
|||
|
||||
void filenameComponentChanged (FilenameComponent*) override
|
||||
{
|
||||
auto url = URL (fileChooser.getCurrentFile());
|
||||
|
||||
// this is called when the user changes the filename in the file chooser box
|
||||
auto result = videoComp.load (fileChooser.getCurrentFile());
|
||||
auto result = videoComp.load (url);
|
||||
videoLoadingFinished (url, result);
|
||||
}
|
||||
|
||||
void videoLoadingFinished (const URL& url, Result result)
|
||||
{
|
||||
ignoreUnused (url);
|
||||
|
||||
if (result.wasOk())
|
||||
{
|
||||
|
|
@ -209,6 +219,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileChooser> fileChooser;
|
||||
WildcardFileFilter moviesWildcardFilter { "*", "*", "Movies File Filter" };
|
||||
TimeSliceThread directoryThread { "Movie File Scanner Thread" };
|
||||
DirectoryContentsList movieList { &moviesWildcardFilter, directoryThread };
|
||||
|
|
@ -231,5 +242,462 @@ private:
|
|||
void fileDoubleClicked (const File&) override {}
|
||||
void browserRootChanged (const File&) override {}
|
||||
|
||||
void selectVideoFile()
|
||||
{
|
||||
fileChooser.reset (new FileChooser ("Choose a file to open...", File::getCurrentWorkingDirectory(),
|
||||
"*", false));
|
||||
|
||||
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
|
||||
[this] (const FileChooser& chooser)
|
||||
{
|
||||
String chosen;
|
||||
auto results = chooser.getURLResults();
|
||||
|
||||
// TODO: support non local files too
|
||||
if (results.size() > 0)
|
||||
movieCompLeft.setFile (results[0].getLocalFile());
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoDemo)
|
||||
};
|
||||
#elif JUCE_IOS || JUCE_ANDROID
|
||||
//==============================================================================
|
||||
class VideoDemo : public Component,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
VideoDemo()
|
||||
: videoCompWithNativeControls (true),
|
||||
videoCompNoNativeControls (false)
|
||||
{
|
||||
loadLocalButton .onClick = [this] { selectVideoFile(); };
|
||||
loadUrlButton .onClick = [this] { showVideoUrlPrompt(); };
|
||||
seekToStartButton.onClick = [this] { seekVideoToStart(); };
|
||||
playButton .onClick = [this] { playVideo(); };
|
||||
pauseButton .onClick = [this] { pauseVideo(); };
|
||||
unloadButton .onClick = [this] { unloadVideoFile(); };
|
||||
|
||||
volumeLabel .setColour (Label::textColourId, Colours::white);
|
||||
currentPositionLabel.setColour (Label::textColourId, Colours::white);
|
||||
|
||||
volumeLabel .setJustificationType (Justification::right);
|
||||
currentPositionLabel.setJustificationType (Justification::right);
|
||||
|
||||
volumeSlider .setRange (0.0, 1.0);
|
||||
positionSlider.setRange (0.0, 1.0);
|
||||
|
||||
volumeSlider .setSliderSnapsToMousePosition (false);
|
||||
positionSlider.setSliderSnapsToMousePosition (false);
|
||||
|
||||
volumeSlider.setSkewFactor (1.5);
|
||||
volumeSlider.setValue (1.0, dontSendNotification);
|
||||
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
||||
curVideoComp->onGlobalMediaVolumeChanged = [this]() { volumeSlider.setValue (curVideoComp->getAudioVolume(), dontSendNotification); };
|
||||
#endif
|
||||
|
||||
volumeSlider .onValueChange = [this]() { curVideoComp->setAudioVolume ((float) volumeSlider.getValue()); };
|
||||
positionSlider.onValueChange = [this]() { seekVideoToNormalisedPosition (positionSlider.getValue()); };
|
||||
|
||||
positionSlider.onDragStart = [this]()
|
||||
{
|
||||
positionSliderDragging = true;
|
||||
wasPlayingBeforeDragStart = curVideoComp->isPlaying();
|
||||
|
||||
if (wasPlayingBeforeDragStart)
|
||||
curVideoComp->stop();
|
||||
};
|
||||
|
||||
positionSlider.onDragEnd = [this]()
|
||||
{
|
||||
if (wasPlayingBeforeDragStart)
|
||||
curVideoComp->play();
|
||||
|
||||
wasPlayingBeforeDragStart = false;
|
||||
|
||||
// Ensure the slider does not temporarily jump back on consecutive timer callback.
|
||||
Timer::callAfterDelay (500, [this]() { positionSliderDragging = false; });
|
||||
};
|
||||
|
||||
playSpeedComboBox.addItem ("25%", 25);
|
||||
playSpeedComboBox.addItem ("50%", 50);
|
||||
playSpeedComboBox.addItem ("100%", 100);
|
||||
playSpeedComboBox.addItem ("200%", 200);
|
||||
playSpeedComboBox.addItem ("400%", 400);
|
||||
playSpeedComboBox.setSelectedId (100, dontSendNotification);
|
||||
playSpeedComboBox.onChange = [this]() { curVideoComp->setPlaySpeed (playSpeedComboBox.getSelectedId() / 100.0); };
|
||||
|
||||
setTransportControlsEnabled (false);
|
||||
|
||||
addAndMakeVisible (loadLocalButton);
|
||||
addAndMakeVisible (loadUrlButton);
|
||||
addAndMakeVisible (volumeLabel);
|
||||
addAndMakeVisible (volumeSlider);
|
||||
addChildComponent (videoCompWithNativeControls);
|
||||
addChildComponent (videoCompNoNativeControls);
|
||||
addAndMakeVisible (positionSlider);
|
||||
addAndMakeVisible (currentPositionLabel);
|
||||
|
||||
addAndMakeVisible (playSpeedComboBox);
|
||||
addAndMakeVisible (seekToStartButton);
|
||||
addAndMakeVisible (playButton);
|
||||
addAndMakeVisible (unloadButton);
|
||||
addChildComponent (pauseButton);
|
||||
|
||||
setSize (500, 500);
|
||||
|
||||
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
||||
[] (bool granted)
|
||||
{
|
||||
if (! granted)
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||
"Permissions warning",
|
||||
"External storage access permission not granted, some files"
|
||||
" may be inaccessible.");
|
||||
}
|
||||
});
|
||||
|
||||
setPortraitOrientationEnabled (true);
|
||||
}
|
||||
|
||||
~VideoDemo()
|
||||
{
|
||||
curVideoComp->onPlaybackStarted = nullptr;
|
||||
curVideoComp->onPlaybackStopped = nullptr;
|
||||
curVideoComp->onErrorOccurred = nullptr;
|
||||
curVideoComp->onGlobalMediaVolumeChanged = nullptr;
|
||||
|
||||
setPortraitOrientationEnabled (false);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto area = getLocalBounds();
|
||||
|
||||
int marginSize = 5;
|
||||
int buttonHeight = 20;
|
||||
|
||||
area.reduce (0, marginSize);
|
||||
|
||||
auto topArea = area.removeFromTop (buttonHeight);
|
||||
loadLocalButton.setBounds (topArea.removeFromLeft (topArea.getWidth() / 6));
|
||||
loadUrlButton.setBounds (topArea.removeFromLeft (loadLocalButton.getWidth()));
|
||||
volumeLabel.setBounds (topArea.removeFromLeft (loadLocalButton.getWidth()));
|
||||
volumeSlider.setBounds (topArea.reduced (10, 0));
|
||||
|
||||
auto transportArea = area.removeFromBottom (buttonHeight);
|
||||
auto positionArea = area.removeFromBottom (buttonHeight).reduced (marginSize, 0);
|
||||
|
||||
playSpeedComboBox.setBounds (transportArea.removeFromLeft (jmax (50, transportArea.getWidth() / 5)));
|
||||
|
||||
auto controlWidth = transportArea.getWidth() / 3;
|
||||
|
||||
currentPositionLabel.setBounds (positionArea.removeFromRight (jmax (150, controlWidth)));
|
||||
positionSlider.setBounds (positionArea);
|
||||
|
||||
seekToStartButton.setBounds (transportArea.removeFromLeft (controlWidth));
|
||||
playButton .setBounds (transportArea.removeFromLeft (controlWidth));
|
||||
unloadButton .setBounds (transportArea.removeFromLeft (controlWidth));
|
||||
pauseButton.setBounds (playButton.getBounds());
|
||||
|
||||
area.removeFromTop (marginSize);
|
||||
area.removeFromBottom (marginSize);
|
||||
|
||||
videoCompWithNativeControls.setBounds (area);
|
||||
videoCompNoNativeControls.setBounds (area);
|
||||
|
||||
if (positionSlider.getWidth() > 0)
|
||||
positionSlider.setMouseDragSensitivity (positionSlider.getWidth());
|
||||
}
|
||||
|
||||
private:
|
||||
TextButton loadLocalButton { "Load Local" };
|
||||
TextButton loadUrlButton { "Load URL" };
|
||||
Label volumeLabel { "volumeLabel", "Vol:" };
|
||||
Slider volumeSlider { Slider::LinearHorizontal, Slider::NoTextBox };
|
||||
|
||||
VideoComponent videoCompWithNativeControls;
|
||||
VideoComponent videoCompNoNativeControls;
|
||||
#if JUCE_IOS || JUCE_MAC
|
||||
VideoComponent* curVideoComp = &videoCompWithNativeControls;
|
||||
#else
|
||||
VideoComponent* curVideoComp = &videoCompNoNativeControls;
|
||||
#endif
|
||||
bool isFirstSetup = true;
|
||||
|
||||
Slider positionSlider { Slider::LinearHorizontal, Slider::NoTextBox };
|
||||
bool positionSliderDragging = false;
|
||||
bool wasPlayingBeforeDragStart = false;
|
||||
|
||||
Label currentPositionLabel { "currentPositionLabel", "-:- / -:-" };
|
||||
|
||||
ComboBox playSpeedComboBox { "playSpeedComboBox" };
|
||||
TextButton seekToStartButton { "|<" };
|
||||
TextButton playButton { "Play" };
|
||||
TextButton pauseButton { "Pause" };
|
||||
TextButton unloadButton { "Unload" };
|
||||
|
||||
std::unique_ptr<FileChooser> fileChooser;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoDemo)
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (VideoDemo)
|
||||
|
||||
//==============================================================================
|
||||
void setPortraitOrientationEnabled (bool shouldBeEnabled)
|
||||
{
|
||||
auto allowedOrientations = Desktop::getInstance().getOrientationsEnabled();
|
||||
|
||||
if (shouldBeEnabled)
|
||||
allowedOrientations |= Desktop::upright;
|
||||
else
|
||||
allowedOrientations &= ~Desktop::upright;
|
||||
|
||||
Desktop::getInstance().setOrientationsEnabled (allowedOrientations);
|
||||
}
|
||||
|
||||
void setTransportControlsEnabled (bool shouldBeEnabled)
|
||||
{
|
||||
positionSlider .setEnabled (shouldBeEnabled);
|
||||
playSpeedComboBox.setEnabled (shouldBeEnabled);
|
||||
seekToStartButton.setEnabled (shouldBeEnabled);
|
||||
playButton .setEnabled (shouldBeEnabled);
|
||||
unloadButton .setEnabled (shouldBeEnabled);
|
||||
pauseButton .setEnabled (shouldBeEnabled);
|
||||
}
|
||||
|
||||
void selectVideoFile()
|
||||
{
|
||||
fileChooser.reset (new FileChooser ("Choose a video file to open...", File::getCurrentWorkingDirectory(),
|
||||
"*", true));
|
||||
|
||||
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
|
||||
[this] (const FileChooser& chooser)
|
||||
{
|
||||
auto results = chooser.getURLResults();
|
||||
|
||||
if (results.size() > 0)
|
||||
loadVideo (results[0]);
|
||||
});
|
||||
}
|
||||
|
||||
void loadVideo (const URL& url)
|
||||
{
|
||||
unloadVideoFile();
|
||||
|
||||
#if JUCE_IOS || JUCE_MAC
|
||||
askIfUseNativeControls (url);
|
||||
#else
|
||||
loadUrl (url);
|
||||
setupVideoComp (false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void askIfUseNativeControls (const URL& url)
|
||||
{
|
||||
auto* aw = new AlertWindow ("Choose viewer type", {}, AlertWindow::NoIcon);
|
||||
|
||||
aw->addButton ("Yes", 1, KeyPress (KeyPress::returnKey));
|
||||
aw->addButton ("No", 0, KeyPress (KeyPress::escapeKey));
|
||||
aw->addTextBlock ("Do you want to use the viewer with native controls?");
|
||||
|
||||
auto callback = ModalCallbackFunction::forComponent (videoViewerTypeChosen, this, url);
|
||||
aw->enterModalState (true, callback, true);
|
||||
}
|
||||
|
||||
static void videoViewerTypeChosen (int result, VideoDemo* owner, URL url)
|
||||
{
|
||||
if (owner != nullptr)
|
||||
{
|
||||
owner->setupVideoComp (result != 0);
|
||||
owner->loadUrl (url);
|
||||
}
|
||||
}
|
||||
|
||||
void setupVideoComp (bool useNativeViewerWithNativeControls)
|
||||
{
|
||||
auto* oldVideoComp = curVideoComp;
|
||||
|
||||
if (useNativeViewerWithNativeControls)
|
||||
curVideoComp = &videoCompWithNativeControls;
|
||||
else
|
||||
curVideoComp = &videoCompNoNativeControls;
|
||||
|
||||
if (isFirstSetup || oldVideoComp != curVideoComp)
|
||||
{
|
||||
oldVideoComp->onPlaybackStarted = nullptr;
|
||||
oldVideoComp->onPlaybackStopped = nullptr;
|
||||
oldVideoComp->onErrorOccurred = nullptr;
|
||||
oldVideoComp->setVisible (false);
|
||||
|
||||
curVideoComp->onPlaybackStarted = [this]() { processPlaybackStarted(); };
|
||||
curVideoComp->onPlaybackStopped = [this]() { processPlaybackPaused(); };
|
||||
curVideoComp->onErrorOccurred = [this](const String& errorMessage) { errorOccurred (errorMessage); };
|
||||
curVideoComp->setVisible (true);
|
||||
|
||||
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
||||
oldVideoComp->onGlobalMediaVolumeChanged = nullptr;
|
||||
curVideoComp->onGlobalMediaVolumeChanged = [this]() { volumeSlider.setValue (curVideoComp->getAudioVolume(), dontSendNotification); };
|
||||
#endif
|
||||
}
|
||||
|
||||
isFirstSetup = false;
|
||||
}
|
||||
|
||||
void loadUrl (const URL& url)
|
||||
{
|
||||
curVideoComp->loadAsync (url, [this] (const URL& u, Result r) { videoLoadingFinished (u, r); });
|
||||
}
|
||||
|
||||
void showVideoUrlPrompt()
|
||||
{
|
||||
auto* aw = new AlertWindow ("Enter URL for video to load", {}, AlertWindow::NoIcon);
|
||||
|
||||
aw->addButton ("OK", 1, KeyPress (KeyPress::returnKey));
|
||||
aw->addButton ("Cancel", 0, KeyPress (KeyPress::escapeKey));
|
||||
aw->addTextEditor ("videoUrlTextEditor", "https://www.rmp-streaming.com/media/bbb-360p.mp4");
|
||||
|
||||
auto callback = ModalCallbackFunction::forComponent (videoUrlPromptClosed, this, Component::SafePointer<AlertWindow> (aw));
|
||||
aw->enterModalState (true, callback, true);
|
||||
}
|
||||
|
||||
static void videoUrlPromptClosed (int result, VideoDemo* owner, Component::SafePointer<AlertWindow> aw)
|
||||
{
|
||||
if (result != 0 && owner != nullptr && aw != nullptr)
|
||||
{
|
||||
auto url = aw->getTextEditorContents ("videoUrlTextEditor");
|
||||
|
||||
if (url.isNotEmpty())
|
||||
owner->loadVideo (url);
|
||||
}
|
||||
}
|
||||
|
||||
void videoLoadingFinished (const URL& url, Result result)
|
||||
{
|
||||
ignoreUnused (url);
|
||||
|
||||
if (result.wasOk())
|
||||
{
|
||||
resized(); // update to reflect the video's aspect ratio
|
||||
|
||||
setTransportControlsEnabled (true);
|
||||
|
||||
currentPositionLabel.setText (getPositionString (0.0, curVideoComp->getVideoDuration()), sendNotification);
|
||||
positionSlider.setValue (0.0, dontSendNotification);
|
||||
playSpeedComboBox.setSelectedId (100, dontSendNotification);
|
||||
}
|
||||
else
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||
"Couldn't load the file!",
|
||||
result.getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
static String getPositionString (double playPositionSeconds, double durationSeconds)
|
||||
{
|
||||
auto positionMs = static_cast<int> (1000 * playPositionSeconds);
|
||||
int posMinutes = positionMs / 60000;
|
||||
int posSeconds = (positionMs % 60000) / 1000;
|
||||
int posMillis = positionMs % 1000;
|
||||
|
||||
auto totalMs = static_cast<int> (1000 * durationSeconds);
|
||||
int totMinutes = totalMs / 60000;
|
||||
int totSeconds = (totalMs % 60000) / 1000;
|
||||
int totMillis = totalMs % 1000;
|
||||
|
||||
return String::formatted ("%02d:%02d:%03d / %02d:%02d:%03d",
|
||||
posMinutes, posSeconds, posMillis,
|
||||
totMinutes, totSeconds, totMillis);
|
||||
}
|
||||
|
||||
void updatePositionSliderAndLabel()
|
||||
{
|
||||
auto position = curVideoComp->getPlayPosition();
|
||||
auto duration = curVideoComp->getVideoDuration();
|
||||
|
||||
currentPositionLabel.setText (getPositionString (position, duration), sendNotification);
|
||||
|
||||
if (! positionSliderDragging)
|
||||
positionSlider.setValue (duration != 0 ? (position / duration) : 0.0, dontSendNotification);
|
||||
}
|
||||
|
||||
void seekVideoToStart()
|
||||
{
|
||||
seekVideoToNormalisedPosition (0.0);
|
||||
}
|
||||
|
||||
void seekVideoToNormalisedPosition (double normalisedPos)
|
||||
{
|
||||
normalisedPos = jlimit (0.0, 1.0, normalisedPos);
|
||||
|
||||
auto duration = curVideoComp->getVideoDuration();
|
||||
auto newPos = jlimit (0.0, duration, duration * normalisedPos);
|
||||
|
||||
curVideoComp->setPlayPosition (newPos);
|
||||
currentPositionLabel.setText (getPositionString (newPos, curVideoComp->getVideoDuration()), sendNotification);
|
||||
positionSlider.setValue (normalisedPos, dontSendNotification);
|
||||
}
|
||||
|
||||
void playVideo()
|
||||
{
|
||||
curVideoComp->play();
|
||||
}
|
||||
|
||||
void processPlaybackStarted()
|
||||
{
|
||||
playButton.setVisible (false);
|
||||
pauseButton.setVisible (true);
|
||||
|
||||
startTimer (20);
|
||||
}
|
||||
|
||||
void pauseVideo()
|
||||
{
|
||||
curVideoComp->stop();
|
||||
}
|
||||
|
||||
void processPlaybackPaused()
|
||||
{
|
||||
// On seeking to a new pos, the playback may be temporarily paused.
|
||||
if (positionSliderDragging)
|
||||
return;
|
||||
|
||||
pauseButton.setVisible (false);
|
||||
playButton.setVisible (true);
|
||||
}
|
||||
|
||||
void errorOccurred (const String& errorMessage)
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
|
||||
"An error has occurred",
|
||||
errorMessage + ", video will be unloaded.");
|
||||
|
||||
unloadVideoFile();
|
||||
}
|
||||
|
||||
void unloadVideoFile()
|
||||
{
|
||||
curVideoComp->closeVideo();
|
||||
|
||||
setTransportControlsEnabled (false);
|
||||
stopTimer();
|
||||
|
||||
pauseButton.setVisible (false);
|
||||
playButton.setVisible (true);
|
||||
|
||||
currentPositionLabel.setText ("-:- / -:-", sendNotification);
|
||||
positionSlider.setValue (0.0, dontSendNotification);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
updatePositionSliderAndLabel();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ import android.content.res.Configuration;
|
|||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.camera2.*;
|
||||
import android.database.ContentObserver;
|
||||
import android.media.session.*;
|
||||
import android.media.MediaMetadata;
|
||||
import android.net.http.SslError;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -94,8 +97,11 @@ public class AudioPerformanceTest extends Activity
|
|||
//==============================================================================
|
||||
public boolean isPermissionDeclaredInManifest (int permissionID)
|
||||
{
|
||||
String permissionToCheck = getAndroidPermissionName(permissionID);
|
||||
return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
|
||||
}
|
||||
|
||||
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
|
||||
{
|
||||
try
|
||||
{
|
||||
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||
|
|
@ -1997,11 +2003,13 @@ public class AudioPerformanceTest extends Activity
|
|||
implements SurfaceHolder.Callback
|
||||
{
|
||||
private long nativeContext = 0;
|
||||
private boolean forVideo;
|
||||
|
||||
NativeSurfaceView (Context context, long nativeContextPtr)
|
||||
NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
|
||||
{
|
||||
super (context);
|
||||
nativeContext = nativeContextPtr;
|
||||
forVideo = createdForVideo;
|
||||
}
|
||||
|
||||
public Surface getNativeSurface()
|
||||
|
|
@ -2019,38 +2027,51 @@ public class AudioPerformanceTest extends Activity
|
|||
@Override
|
||||
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
|
||||
{
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
if (forVideo)
|
||||
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
|
||||
else
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated (SurfaceHolder holder)
|
||||
{
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
if (forVideo)
|
||||
surfaceCreatedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed (SurfaceHolder holder)
|
||||
{
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
if (forVideo)
|
||||
surfaceDestroyedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw (Canvas canvas)
|
||||
{
|
||||
super.dispatchDraw (canvas);
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
|
||||
if (forVideo)
|
||||
dispatchDrawNativeVideo (nativeContext, canvas);
|
||||
else
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@Override
|
||||
protected void onAttachedToWindow ()
|
||||
protected void onAttachedToWindow()
|
||||
{
|
||||
super.onAttachedToWindow();
|
||||
getHolder().addCallback (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow ()
|
||||
protected void onDetachedFromWindow()
|
||||
{
|
||||
super.onDetachedFromWindow();
|
||||
getHolder().removeCallback (this);
|
||||
|
|
@ -2062,11 +2083,17 @@ public class AudioPerformanceTest extends Activity
|
|||
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
|
||||
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
|
||||
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
}
|
||||
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
|
||||
{
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr);
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -2826,6 +2853,151 @@ public class AudioPerformanceTest extends Activity
|
|||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
public class MediaControllerCallback extends MediaController.Callback
|
||||
{
|
||||
private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info);
|
||||
private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata);
|
||||
private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);
|
||||
private native void mediaControllerSessionDestroyed (long host);
|
||||
|
||||
MediaControllerCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioInfoChanged (MediaController.PlaybackInfo info)
|
||||
{
|
||||
mediaControllerAudioInfoChanged (host, info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged (MediaMetadata metadata)
|
||||
{
|
||||
mediaControllerMetadataChanged (host, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged (PlaybackState state)
|
||||
{
|
||||
mediaControllerPlaybackStateChanged (host, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueChanged (List<MediaSession.QueueItem> queue) {}
|
||||
|
||||
@Override
|
||||
public void onSessionDestroyed()
|
||||
{
|
||||
mediaControllerSessionDestroyed (host);
|
||||
}
|
||||
|
||||
private long host;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public class MediaSessionCallback extends MediaSession.Callback
|
||||
{
|
||||
private native void mediaSessionPause (long host);
|
||||
private native void mediaSessionPlay (long host);
|
||||
private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);
|
||||
private native void mediaSessionSeekTo (long host, long pos);
|
||||
private native void mediaSessionStop (long host);
|
||||
|
||||
|
||||
MediaSessionCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
mediaSessionPause (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlay()
|
||||
{
|
||||
mediaSessionPlay (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayFromMediaId (String mediaId, Bundle extras)
|
||||
{
|
||||
mediaSessionPlayFromMediaId (host, mediaId, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekTo (long pos)
|
||||
{
|
||||
mediaSessionSeekTo (host, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
mediaSessionStop (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFastForward() {}
|
||||
|
||||
@Override
|
||||
public boolean onMediaButtonEvent (Intent mediaButtonIntent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewind() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToNext() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToPrevious() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToQueueItem (long id) {}
|
||||
|
||||
private long host;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public class SystemVolumeObserver extends ContentObserver
|
||||
{
|
||||
private native void mediaSessionSystemVolumeChanged (long host);
|
||||
|
||||
SystemVolumeObserver (Activity activityToUse, long hostToUse)
|
||||
{
|
||||
super (null);
|
||||
|
||||
activity = activityToUse;
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
void setEnabled (boolean shouldBeEnabled)
|
||||
{
|
||||
if (shouldBeEnabled)
|
||||
activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);
|
||||
else
|
||||
activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange (boolean selfChange, Uri uri)
|
||||
{
|
||||
if (uri.toString().startsWith ("content://settings/system/volume_music"))
|
||||
mediaSessionSystemVolumeChanged (host);
|
||||
}
|
||||
|
||||
private Activity activity;
|
||||
private long host;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
public static final String getLocaleValue (boolean isRegion)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1251,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_android_Video.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"
|
||||
|
|
@ -2491,6 +2492,7 @@ set_source_files_properties("../../../../../modules/juce_opengl/juce_opengl.h" P
|
|||
set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.cpp" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/capture/juce_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/native/juce_android_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/native/juce_android_Video.h" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/native/juce_ios_CameraDevice.h" PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
set_source_files_properties("../../../../../modules/juce_video/native/juce_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)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ import android.content.res.Configuration;
|
|||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.camera2.*;
|
||||
import android.database.ContentObserver;
|
||||
import android.media.session.*;
|
||||
import android.media.MediaMetadata;
|
||||
import android.net.http.SslError;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -94,8 +97,11 @@ public class AudioPluginHost extends Activity
|
|||
//==============================================================================
|
||||
public boolean isPermissionDeclaredInManifest (int permissionID)
|
||||
{
|
||||
String permissionToCheck = getAndroidPermissionName(permissionID);
|
||||
return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
|
||||
}
|
||||
|
||||
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
|
||||
{
|
||||
try
|
||||
{
|
||||
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||
|
|
@ -1997,11 +2003,13 @@ public class AudioPluginHost extends Activity
|
|||
implements SurfaceHolder.Callback
|
||||
{
|
||||
private long nativeContext = 0;
|
||||
private boolean forVideo;
|
||||
|
||||
NativeSurfaceView (Context context, long nativeContextPtr)
|
||||
NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
|
||||
{
|
||||
super (context);
|
||||
nativeContext = nativeContextPtr;
|
||||
forVideo = createdForVideo;
|
||||
}
|
||||
|
||||
public Surface getNativeSurface()
|
||||
|
|
@ -2019,38 +2027,51 @@ public class AudioPluginHost extends Activity
|
|||
@Override
|
||||
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
|
||||
{
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
if (forVideo)
|
||||
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
|
||||
else
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated (SurfaceHolder holder)
|
||||
{
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
if (forVideo)
|
||||
surfaceCreatedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed (SurfaceHolder holder)
|
||||
{
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
if (forVideo)
|
||||
surfaceDestroyedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw (Canvas canvas)
|
||||
{
|
||||
super.dispatchDraw (canvas);
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
|
||||
if (forVideo)
|
||||
dispatchDrawNativeVideo (nativeContext, canvas);
|
||||
else
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@Override
|
||||
protected void onAttachedToWindow ()
|
||||
protected void onAttachedToWindow()
|
||||
{
|
||||
super.onAttachedToWindow();
|
||||
getHolder().addCallback (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow ()
|
||||
protected void onDetachedFromWindow()
|
||||
{
|
||||
super.onDetachedFromWindow();
|
||||
getHolder().removeCallback (this);
|
||||
|
|
@ -2062,11 +2083,17 @@ public class AudioPluginHost extends Activity
|
|||
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
|
||||
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
|
||||
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
}
|
||||
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
|
||||
{
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr);
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -2826,6 +2853,151 @@ public class AudioPluginHost extends Activity
|
|||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
public class MediaControllerCallback extends MediaController.Callback
|
||||
{
|
||||
private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info);
|
||||
private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata);
|
||||
private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);
|
||||
private native void mediaControllerSessionDestroyed (long host);
|
||||
|
||||
MediaControllerCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioInfoChanged (MediaController.PlaybackInfo info)
|
||||
{
|
||||
mediaControllerAudioInfoChanged (host, info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged (MediaMetadata metadata)
|
||||
{
|
||||
mediaControllerMetadataChanged (host, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged (PlaybackState state)
|
||||
{
|
||||
mediaControllerPlaybackStateChanged (host, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueChanged (List<MediaSession.QueueItem> queue) {}
|
||||
|
||||
@Override
|
||||
public void onSessionDestroyed()
|
||||
{
|
||||
mediaControllerSessionDestroyed (host);
|
||||
}
|
||||
|
||||
private long host;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public class MediaSessionCallback extends MediaSession.Callback
|
||||
{
|
||||
private native void mediaSessionPause (long host);
|
||||
private native void mediaSessionPlay (long host);
|
||||
private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);
|
||||
private native void mediaSessionSeekTo (long host, long pos);
|
||||
private native void mediaSessionStop (long host);
|
||||
|
||||
|
||||
MediaSessionCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
mediaSessionPause (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlay()
|
||||
{
|
||||
mediaSessionPlay (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayFromMediaId (String mediaId, Bundle extras)
|
||||
{
|
||||
mediaSessionPlayFromMediaId (host, mediaId, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekTo (long pos)
|
||||
{
|
||||
mediaSessionSeekTo (host, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
mediaSessionStop (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFastForward() {}
|
||||
|
||||
@Override
|
||||
public boolean onMediaButtonEvent (Intent mediaButtonIntent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewind() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToNext() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToPrevious() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToQueueItem (long id) {}
|
||||
|
||||
private long host;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public class SystemVolumeObserver extends ContentObserver
|
||||
{
|
||||
private native void mediaSessionSystemVolumeChanged (long host);
|
||||
|
||||
SystemVolumeObserver (Activity activityToUse, long hostToUse)
|
||||
{
|
||||
super (null);
|
||||
|
||||
activity = activityToUse;
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
void setEnabled (boolean shouldBeEnabled)
|
||||
{
|
||||
if (shouldBeEnabled)
|
||||
activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);
|
||||
else
|
||||
activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange (boolean selfChange, Uri uri)
|
||||
{
|
||||
if (uri.toString().startsWith ("content://settings/system/volume_music"))
|
||||
mediaSessionSystemVolumeChanged (host);
|
||||
}
|
||||
|
||||
private Activity activity;
|
||||
private long host;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
public static final String getLocaleValue (boolean isRegion)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2436,6 +2436,7 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>
|
||||
|
|
|
|||
|
|
@ -4065,6 +4065,9 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -2436,6 +2436,7 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>
|
||||
|
|
|
|||
|
|
@ -4065,6 +4065,9 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -2436,6 +2436,7 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>
|
||||
|
|
|
|||
|
|
@ -4065,6 +4065,9 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -264,6 +264,10 @@
|
|||
#ifndef JUCE_USE_CAMERA
|
||||
#define JUCE_USE_CAMERA 0
|
||||
#endif
|
||||
|
||||
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
||||
//#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
|
||||
#endif
|
||||
//==============================================================================
|
||||
#ifndef JUCE_STANDALONE_APPLICATION
|
||||
#if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)
|
||||
|
|
|
|||
|
|
@ -87,8 +87,11 @@ public class JUCENetworkGraphicsDemo extends Activity
|
|||
//==============================================================================
|
||||
public boolean isPermissionDeclaredInManifest (int permissionID)
|
||||
{
|
||||
String permissionToCheck = getAndroidPermissionName(permissionID);
|
||||
return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
|
||||
}
|
||||
|
||||
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
|
||||
{
|
||||
try
|
||||
{
|
||||
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||
|
|
@ -1064,11 +1067,13 @@ public class JUCENetworkGraphicsDemo extends Activity
|
|||
implements SurfaceHolder.Callback
|
||||
{
|
||||
private long nativeContext = 0;
|
||||
private boolean forVideo;
|
||||
|
||||
NativeSurfaceView (Context context, long nativeContextPtr)
|
||||
NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
|
||||
{
|
||||
super (context);
|
||||
nativeContext = nativeContextPtr;
|
||||
forVideo = createdForVideo;
|
||||
}
|
||||
|
||||
public Surface getNativeSurface()
|
||||
|
|
@ -1086,38 +1091,51 @@ public class JUCENetworkGraphicsDemo extends Activity
|
|||
@Override
|
||||
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
|
||||
{
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
if (forVideo)
|
||||
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
|
||||
else
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated (SurfaceHolder holder)
|
||||
{
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
if (forVideo)
|
||||
surfaceCreatedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed (SurfaceHolder holder)
|
||||
{
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
if (forVideo)
|
||||
surfaceDestroyedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw (Canvas canvas)
|
||||
{
|
||||
super.dispatchDraw (canvas);
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
|
||||
if (forVideo)
|
||||
dispatchDrawNativeVideo (nativeContext, canvas);
|
||||
else
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@Override
|
||||
protected void onAttachedToWindow ()
|
||||
protected void onAttachedToWindow()
|
||||
{
|
||||
super.onAttachedToWindow();
|
||||
getHolder().addCallback (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow ()
|
||||
protected void onDetachedFromWindow()
|
||||
{
|
||||
super.onDetachedFromWindow();
|
||||
getHolder().removeCallback (this);
|
||||
|
|
@ -1129,11 +1147,17 @@ public class JUCENetworkGraphicsDemo extends Activity
|
|||
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
|
||||
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
|
||||
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
}
|
||||
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
|
||||
{
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr);
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -1050,6 +1050,7 @@ private:
|
|||
auto midiCode = getMidiCode (javaSourceFolder, className);
|
||||
auto webViewCode = getWebViewCode (javaSourceFolder);
|
||||
auto cameraCode = getCameraCode (javaSourceFolder);
|
||||
auto videoCode = getVideoCode (javaSourceFolder);
|
||||
|
||||
auto javaSourceFile = javaSourceFolder.getChildFile ("JuceAppActivity.java");
|
||||
auto javaSourceLines = StringArray::fromLines (javaSourceFile.loadFileAsString());
|
||||
|
|
@ -1075,6 +1076,10 @@ private:
|
|||
newFile << cameraCode.imports;
|
||||
else if (line.contains ("$$JuceAndroidCameraCode$$"))
|
||||
newFile << cameraCode.main;
|
||||
else if (line.contains ("$$JuceAndroidVideoImports$$"))
|
||||
newFile << videoCode.imports;
|
||||
else if (line.contains ("$$JuceAndroidVideoCode$$"))
|
||||
newFile << videoCode.main;
|
||||
else
|
||||
newFile << line.replace ("$$JuceAppActivityBaseClass$$", androidActivityBaseClassName.get().toString())
|
||||
.replace ("JuceAppActivity", className)
|
||||
|
|
@ -1203,13 +1208,12 @@ private:
|
|||
String juceCameraImports, juceCameraCode;
|
||||
|
||||
if (static_cast<int> (androidMinimumSDK.get()) >= 21)
|
||||
{
|
||||
juceCameraImports << "import android.hardware.camera2.*;" << newLine;
|
||||
|
||||
auto javaCameraFile = javaSourceFolder.getChildFile ("AndroidCamera.java");
|
||||
auto juceCameraCodeAll = javaCameraFile.loadFileAsString();
|
||||
auto javaCameraFile = javaSourceFolder.getChildFile ("AndroidCamera.java");
|
||||
auto juceCameraCodeAll = javaCameraFile.loadFileAsString();
|
||||
|
||||
if (static_cast<int> (androidMinimumSDK.get()) >= 21)
|
||||
{
|
||||
juceCameraCode << juceCameraCodeAll.fromFirstOccurrenceOf ("$$CameraApi21", false, false)
|
||||
.upToFirstOccurrenceOf ("CameraApi21$$", false, false);
|
||||
}
|
||||
|
|
@ -1217,6 +1221,32 @@ private:
|
|||
return { juceCameraImports, juceCameraCode };
|
||||
}
|
||||
|
||||
struct VideoCode
|
||||
{
|
||||
String imports;
|
||||
String main;
|
||||
};
|
||||
|
||||
VideoCode getVideoCode (const File& javaSourceFolder) const
|
||||
{
|
||||
String juceVideoImports, juceVideoCode;
|
||||
|
||||
if (static_cast<int> (androidMinimumSDK.get()) >= 21)
|
||||
{
|
||||
juceVideoImports << "import android.database.ContentObserver;" << newLine;
|
||||
juceVideoImports << "import android.media.session.*;" << newLine;
|
||||
juceVideoImports << "import android.media.MediaMetadata;" << newLine;
|
||||
|
||||
auto javaVideoFile = javaSourceFolder.getChildFile ("AndroidVideo.java");
|
||||
auto juceVideoCodeAll = javaVideoFile.loadFileAsString();
|
||||
|
||||
juceVideoCode << juceVideoCodeAll.fromFirstOccurrenceOf ("$$VideoApi21", false, false)
|
||||
.upToFirstOccurrenceOf ("VideoApi21$$", false, false);
|
||||
}
|
||||
|
||||
return { juceVideoImports, juceVideoCode };
|
||||
}
|
||||
|
||||
void copyAdditionalJavaFiles (const File& sourceFolder, const File& targetFolder) const
|
||||
{
|
||||
auto inAppBillingJavaFileName = String ("IInAppBillingService.java");
|
||||
|
|
|
|||
|
|
@ -2642,6 +2642,7 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_product_unlocking\juce_product_unlocking.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>
|
||||
|
|
|
|||
|
|
@ -4473,6 +4473,9 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -290,6 +290,10 @@
|
|||
#ifndef JUCE_USE_CAMERA
|
||||
//#define JUCE_USE_CAMERA 0
|
||||
#endif
|
||||
|
||||
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
||||
//#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
|
||||
#endif
|
||||
//==============================================================================
|
||||
#ifndef JUCE_STANDALONE_APPLICATION
|
||||
#if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)
|
||||
|
|
|
|||
|
|
@ -2423,6 +2423,7 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_opengl\juce_opengl.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\capture\juce_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_CameraDevice.h"/>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_mac_Video.h"/>
|
||||
|
|
|
|||
|
|
@ -4014,6 +4014,9 @@
|
|||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_android_Video.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\..\modules\juce_video\native\juce_ios_CameraDevice.h">
|
||||
<Filter>JUCE Modules\juce_video\native</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -262,6 +262,10 @@
|
|||
#ifndef JUCE_USE_CAMERA
|
||||
//#define JUCE_USE_CAMERA 0
|
||||
#endif
|
||||
|
||||
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
||||
//#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
|
||||
#endif
|
||||
//==============================================================================
|
||||
#ifndef JUCE_STANDALONE_APPLICATION
|
||||
#if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)
|
||||
|
|
|
|||
146
modules/juce_core/native/java/AndroidVideo.java
Normal file
146
modules/juce_core/native/java/AndroidVideo.java
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
$$VideoApi21
|
||||
//==============================================================================
|
||||
public class MediaControllerCallback extends MediaController.Callback
|
||||
{
|
||||
private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info);
|
||||
private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata);
|
||||
private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state);
|
||||
private native void mediaControllerSessionDestroyed (long host);
|
||||
|
||||
MediaControllerCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioInfoChanged (MediaController.PlaybackInfo info)
|
||||
{
|
||||
mediaControllerAudioInfoChanged (host, info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged (MediaMetadata metadata)
|
||||
{
|
||||
mediaControllerMetadataChanged (host, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged (PlaybackState state)
|
||||
{
|
||||
mediaControllerPlaybackStateChanged (host, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueChanged (List<MediaSession.QueueItem> queue) {}
|
||||
|
||||
@Override
|
||||
public void onSessionDestroyed()
|
||||
{
|
||||
mediaControllerSessionDestroyed (host);
|
||||
}
|
||||
|
||||
private long host;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public class MediaSessionCallback extends MediaSession.Callback
|
||||
{
|
||||
private native void mediaSessionPause (long host);
|
||||
private native void mediaSessionPlay (long host);
|
||||
private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras);
|
||||
private native void mediaSessionSeekTo (long host, long pos);
|
||||
private native void mediaSessionStop (long host);
|
||||
|
||||
|
||||
MediaSessionCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
mediaSessionPause (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlay()
|
||||
{
|
||||
mediaSessionPlay (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayFromMediaId (String mediaId, Bundle extras)
|
||||
{
|
||||
mediaSessionPlayFromMediaId (host, mediaId, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekTo (long pos)
|
||||
{
|
||||
mediaSessionSeekTo (host, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
mediaSessionStop (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFastForward() {}
|
||||
|
||||
@Override
|
||||
public boolean onMediaButtonEvent (Intent mediaButtonIntent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewind() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToNext() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToPrevious() {}
|
||||
|
||||
@Override
|
||||
public void onSkipToQueueItem (long id) {}
|
||||
|
||||
private long host;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public class SystemVolumeObserver extends ContentObserver
|
||||
{
|
||||
private native void mediaSessionSystemVolumeChanged (long host);
|
||||
|
||||
SystemVolumeObserver (Activity activityToUse, long hostToUse)
|
||||
{
|
||||
super (null);
|
||||
|
||||
activity = activityToUse;
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
void setEnabled (boolean shouldBeEnabled)
|
||||
{
|
||||
if (shouldBeEnabled)
|
||||
activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this);
|
||||
else
|
||||
activity.getApplicationContext().getContentResolver().unregisterContentObserver (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange (boolean selfChange, Uri uri)
|
||||
{
|
||||
if (uri.toString().startsWith ("content://settings/system/volume_music"))
|
||||
mediaSessionSystemVolumeChanged (host);
|
||||
}
|
||||
|
||||
private Activity activity;
|
||||
private long host;
|
||||
}
|
||||
|
||||
VideoApi21$$
|
||||
|
|
@ -30,7 +30,8 @@ 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!
|
||||
$$JuceAndroidCameraImports$$ // If you get an error here, you need to re-save your project with the Projucer!
|
||||
$$JuceAndroidVideoImports$$ // If you get an error here, you need to re-save your project with the Projucer!
|
||||
import android.net.http.SslError;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -87,10 +88,13 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
public boolean isPermissionDeclaredInManifest (int permissionID)
|
||||
public boolean isPermissionDeclaredInManifest (int permissionID)
|
||||
{
|
||||
return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID));
|
||||
}
|
||||
|
||||
public boolean isPermissionDeclaredInManifest (String permissionToCheck)
|
||||
{
|
||||
String permissionToCheck = getAndroidPermissionName(permissionID);
|
||||
|
||||
try
|
||||
{
|
||||
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||
|
|
@ -982,12 +986,14 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
|
|||
public static class NativeSurfaceView extends SurfaceView
|
||||
implements SurfaceHolder.Callback
|
||||
{
|
||||
private long nativeContext = 0;
|
||||
private long nativeContext = 0;
|
||||
private boolean forVideo;
|
||||
|
||||
NativeSurfaceView (Context context, long nativeContextPtr)
|
||||
NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo)
|
||||
{
|
||||
super (context);
|
||||
nativeContext = nativeContextPtr;
|
||||
nativeContext = nativeContextPtr;
|
||||
forVideo = createdForVideo;
|
||||
}
|
||||
|
||||
public Surface getNativeSurface()
|
||||
|
|
@ -1004,39 +1010,52 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
|
|||
//==============================================================================
|
||||
@Override
|
||||
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
|
||||
{
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
{
|
||||
if (forVideo)
|
||||
surfaceChangedNativeVideo (nativeContext, holder, format, width, height);
|
||||
else
|
||||
surfaceChangedNative (nativeContext, holder, format, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated (SurfaceHolder holder)
|
||||
{
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
{
|
||||
if (forVideo)
|
||||
surfaceCreatedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceCreatedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed (SurfaceHolder holder)
|
||||
{
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
{
|
||||
if (forVideo)
|
||||
surfaceDestroyedNativeVideo (nativeContext, holder);
|
||||
else
|
||||
surfaceDestroyedNative (nativeContext, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw (Canvas canvas)
|
||||
{
|
||||
super.dispatchDraw (canvas);
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
super.dispatchDraw (canvas);
|
||||
|
||||
if (forVideo)
|
||||
dispatchDrawNativeVideo (nativeContext, canvas);
|
||||
else
|
||||
dispatchDrawNative (nativeContext, canvas);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@Override
|
||||
protected void onAttachedToWindow ()
|
||||
protected void onAttachedToWindow()
|
||||
{
|
||||
super.onAttachedToWindow();
|
||||
getHolder().addCallback (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow ()
|
||||
protected void onDetachedFromWindow()
|
||||
{
|
||||
super.onDetachedFromWindow();
|
||||
getHolder().removeCallback (this);
|
||||
|
|
@ -1047,12 +1066,18 @@ public class JuceAppActivity extends $$JuceAppActivityBaseClass$$
|
|||
private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
int format, int width, int height);
|
||||
|
||||
private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas);
|
||||
private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder);
|
||||
private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder,
|
||||
int format, int width, int height);
|
||||
}
|
||||
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
|
||||
public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo)
|
||||
{
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr);
|
||||
return new NativeSurfaceView (this, nativeSurfacePtr, forVideo);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -1610,7 +1635,8 @@ $$JuceAndroidWebViewNativeCode$$ // If you get an error here, you need to re-sav
|
|||
private final Object hostLock = new Object();
|
||||
}
|
||||
|
||||
$$JuceAndroidCameraCode$$ // If you get an error here, you need to re-save your project with the Projucer!
|
||||
$$JuceAndroidCameraCode$$ // If you get an error here, you need to re-save your project with the Projucer!
|
||||
$$JuceAndroidVideoCode$$ // If you get an error here, you need to re-save your project with the Projucer!
|
||||
|
||||
//==============================================================================
|
||||
public static final String getLocaleValue (boolean isRegion)
|
||||
|
|
|
|||
|
|
@ -188,6 +188,13 @@ private:
|
|||
uri.get(), projection.get(), jSelection.get(),
|
||||
args.get(), nullptr));
|
||||
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission?
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (cursor)
|
||||
{
|
||||
if (env->CallBooleanMethod (cursor.get(), AndroidCursor.moveToFirst) != 0)
|
||||
|
|
@ -380,6 +387,13 @@ private:
|
|||
uri.get(), projection.get(), nullptr,
|
||||
nullptr, nullptr));
|
||||
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission?
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (cursor == 0)
|
||||
return {};
|
||||
|
||||
|
|
|
|||
|
|
@ -250,60 +250,79 @@ extern AndroidSystem android;
|
|||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \
|
||||
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") \
|
||||
METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \
|
||||
METHOD (renderGlyph, "renderGlyph", "(CCLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \
|
||||
STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \
|
||||
METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \
|
||||
METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \
|
||||
METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;)V") \
|
||||
METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \
|
||||
STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \
|
||||
STATICMETHOD (getDocumentsFolder, "getDocumentsFolder", "()Ljava/lang/String;") \
|
||||
STATICMETHOD (getPicturesFolder, "getPicturesFolder", "()Ljava/lang/String;") \
|
||||
STATICMETHOD (getMusicFolder, "getMusicFolder", "()Ljava/lang/String;") \
|
||||
STATICMETHOD (getDownloadsFolder, "getDownloadsFolder", "()Ljava/lang/String;") \
|
||||
STATICMETHOD (getMoviesFolder, "getMoviesFolder", "()Ljava/lang/String;") \
|
||||
METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
|
||||
METHOD (getTypeFaceFromByteArray, "getTypeFaceFromByteArray", "([B)Landroid/graphics/Typeface;") \
|
||||
METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \
|
||||
METHOD (getScreenSaver, "getScreenSaver", "()Z") \
|
||||
METHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager;") \
|
||||
METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \
|
||||
STATICMETHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \
|
||||
METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \
|
||||
METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \
|
||||
METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \
|
||||
METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \
|
||||
METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \
|
||||
METHOD (getAssets, "getAssets", "()Landroid/content/res/AssetManager;") \
|
||||
METHOD (getSystemService, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;") \
|
||||
METHOD (getPackageManager, "getPackageManager", "()Landroid/content/pm/PackageManager;") \
|
||||
METHOD (getPackageName, "getPackageName", "()Ljava/lang/String;") \
|
||||
METHOD (getResources, "getResources", "()Landroid/content/res/Resources;") \
|
||||
METHOD (createInvocationHandler, "createInvocationHandler", "(J)Ljava/lang/reflect/InvocationHandler;") \
|
||||
METHOD (invocationHandlerContextDeleted, "invocationHandlerContextDeleted", "(Ljava/lang/reflect/InvocationHandler;)V") \
|
||||
METHOD (bindService, "bindService", "(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z") \
|
||||
METHOD (unbindService, "unbindService", "(Landroid/content/ServiceConnection;)V") \
|
||||
METHOD (startIntentSenderForResult, "startIntentSenderForResult", "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V") \
|
||||
METHOD (moveTaskToBack, "moveTaskToBack", "(Z)Z") \
|
||||
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")
|
||||
METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \
|
||||
METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \
|
||||
METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(JZ)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") \
|
||||
METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \
|
||||
METHOD (renderGlyph, "renderGlyph", "(CCLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \
|
||||
STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \
|
||||
METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \
|
||||
METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \
|
||||
METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;)V") \
|
||||
METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \
|
||||
STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \
|
||||
STATICMETHOD (getDocumentsFolder, "getDocumentsFolder", "()Ljava/lang/String;") \
|
||||
STATICMETHOD (getPicturesFolder, "getPicturesFolder", "()Ljava/lang/String;") \
|
||||
STATICMETHOD (getMusicFolder, "getMusicFolder", "()Ljava/lang/String;") \
|
||||
STATICMETHOD (getDownloadsFolder, "getDownloadsFolder", "()Ljava/lang/String;") \
|
||||
STATICMETHOD (getMoviesFolder, "getMoviesFolder", "()Ljava/lang/String;") \
|
||||
METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
|
||||
METHOD (getTypeFaceFromByteArray, "getTypeFaceFromByteArray", "([B)Landroid/graphics/Typeface;") \
|
||||
METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \
|
||||
METHOD (getScreenSaver, "getScreenSaver", "()Z") \
|
||||
METHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager;") \
|
||||
METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \
|
||||
STATICMETHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \
|
||||
METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \
|
||||
METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \
|
||||
METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \
|
||||
METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \
|
||||
METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \
|
||||
METHOD (isPermissionDeclaredInManifestString, "isPermissionDeclaredInManifest", "(Ljava/lang/String;)Z") \
|
||||
METHOD (getAssets, "getAssets", "()Landroid/content/res/AssetManager;") \
|
||||
METHOD (getSystemService, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;") \
|
||||
METHOD (getPackageManager, "getPackageManager", "()Landroid/content/pm/PackageManager;") \
|
||||
METHOD (getPackageName, "getPackageName", "()Ljava/lang/String;") \
|
||||
METHOD (getResources, "getResources", "()Landroid/content/res/Resources;") \
|
||||
METHOD (createInvocationHandler, "createInvocationHandler", "(J)Ljava/lang/reflect/InvocationHandler;") \
|
||||
METHOD (invocationHandlerContextDeleted, "invocationHandlerContextDeleted", "(Ljava/lang/reflect/InvocationHandler;)V") \
|
||||
METHOD (bindService, "bindService", "(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z") \
|
||||
METHOD (unbindService, "unbindService", "(Landroid/content/ServiceConnection;)V") \
|
||||
METHOD (startIntentSenderForResult, "startIntentSenderForResult", "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V") \
|
||||
METHOD (moveTaskToBack, "moveTaskToBack", "(Z)Z") \
|
||||
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
|
||||
|
||||
//==============================================================================
|
||||
#if __ANDROID_API__ >= 21
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (build, "build", "()Landroid/media/AudioAttributes;") \
|
||||
METHOD (constructor, "<init>", "()V") \
|
||||
METHOD (setContentType, "setContentType", "(I)Landroid/media/AudioAttributes$Builder;") \
|
||||
METHOD (setUsage, "setUsage", "(I)Landroid/media/AudioAttributes$Builder;")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidAudioAttributesBuilder, "android/media/AudioAttributes$Builder")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
#endif
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (abandonAudioFocus, "abandonAudioFocus", "(Landroid/media/AudioManager$OnAudioFocusChangeListener;)I") \
|
||||
METHOD (requestAudioFocus, "requestAudioFocus", "(Landroid/media/AudioManager$OnAudioFocusChangeListener;II)I")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidAudioManager, "android/media/AudioManager");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
STATICMETHOD (createBitmap, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;") \
|
||||
STATICMETHOD (createBitmapFrom, "createBitmap", "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)Landroid/graphics/Bitmap;") \
|
||||
|
|
@ -741,6 +760,21 @@ namespace
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool jniCheckHasExceptionOccurredAndClear()
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> exception (env->ExceptionOccurred());
|
||||
|
||||
if (exception != nullptr)
|
||||
{
|
||||
env->ExceptionClear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -778,4 +812,24 @@ LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
|
|||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
|
||||
const String& interfaceName);
|
||||
|
||||
//==============================================================================
|
||||
class AppPausedResumedListener : public AndroidInterfaceImplementer
|
||||
{
|
||||
public:
|
||||
struct Owner
|
||||
{
|
||||
virtual ~Owner() {}
|
||||
|
||||
virtual void appPaused() = 0;
|
||||
virtual void appResumed() = 0;
|
||||
};
|
||||
|
||||
AppPausedResumedListener (Owner&);
|
||||
|
||||
jobject invoke (jobject proxy, jobject method, jobjectArray args) override;
|
||||
|
||||
private:
|
||||
Owner& owner;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -200,6 +200,35 @@ JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024Nativ
|
|||
juce_dispatchDelete (env, thisPtr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AppPausedResumedListener::AppPausedResumedListener (Owner& ownerToUse)
|
||||
: owner (ownerToUse)
|
||||
{
|
||||
}
|
||||
|
||||
jobject AppPausedResumedListener::invoke (jobject proxy, jobject method, jobjectArray args)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
|
||||
|
||||
int numArgs = args != nullptr ? env->GetArrayLength (args) : 0;
|
||||
|
||||
if (methodName == "appPaused" && numArgs == 0)
|
||||
{
|
||||
owner.appPaused();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (methodName == "appResumed" && numArgs == 0)
|
||||
{
|
||||
owner.appResumed();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return AndroidInterfaceImplementer::invoke (proxy, method, args);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JavaVM* androidJNIJavaVM = nullptr;
|
||||
|
||||
|
|
|
|||
|
|
@ -307,14 +307,10 @@ private:
|
|||
auto inputStream = StreamCloser (LocalRef<jobject> (env->CallObjectMethod (assetFd,
|
||||
AssetFileDescriptor.createInputStream)));
|
||||
|
||||
auto exception = LocalRef<jobject> (env->ExceptionOccurred());
|
||||
|
||||
if (exception != 0)
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to open file stream for resource
|
||||
jassertfalse;
|
||||
|
||||
env->ExceptionClear();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -326,14 +322,10 @@ private:
|
|||
JavaFileOutputStream.constructor,
|
||||
javaString (tempFile.getFullPathName()).get())));
|
||||
|
||||
exception = LocalRef<jobject> (env->ExceptionOccurred());
|
||||
|
||||
if (exception != 0)
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to open file stream for temporary file
|
||||
jassertfalse;
|
||||
|
||||
env->ExceptionClear();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -347,14 +339,10 @@ private:
|
|||
|
||||
bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get());
|
||||
|
||||
exception = LocalRef<jobject> (env->ExceptionOccurred());
|
||||
|
||||
if (exception != 0)
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to read from resource file.
|
||||
jassertfalse;
|
||||
|
||||
env->ExceptionClear();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -363,12 +351,10 @@ private:
|
|||
|
||||
env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead);
|
||||
|
||||
if (exception != 0)
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to write to temporary file.
|
||||
jassertfalse;
|
||||
|
||||
env->ExceptionClear();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
@ -714,14 +700,10 @@ private:
|
|||
ParcelFileDescriptor.open,
|
||||
javaFile.get(), modeReadOnly));
|
||||
|
||||
auto exception = LocalRef<jobject> (env->ExceptionOccurred());
|
||||
|
||||
if (exception != 0)
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to create file descriptor. Have you provided a valid file path/resource name?
|
||||
jassertfalse;
|
||||
|
||||
env->ExceptionClear();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,17 +27,6 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
#if __ANDROID_API__ >= 21
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (build, "build", "()Landroid/media/AudioAttributes;") \
|
||||
METHOD (constructor, "<init>", "()V") \
|
||||
METHOD (setContentType, "setContentType", "(I)Landroid/media/AudioAttributes$Builder;") \
|
||||
METHOD (setUsage, "setUsage", "(I)Landroid/media/AudioAttributes$Builder;")
|
||||
|
||||
DECLARE_JNI_CLASS (AudioAttributesBuilder, "android/media/AudioAttributes$Builder")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
#endif
|
||||
|
||||
#if __ANDROID_API__ >= 26
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \
|
||||
|
|
@ -1504,12 +1493,12 @@ struct PushNotifications::Pimpl
|
|||
env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration);
|
||||
}
|
||||
|
||||
auto audioAttributesBuilder = LocalRef<jobject> (env->NewObject (AudioAttributesBuilder, AudioAttributesBuilder.constructor));
|
||||
auto AndroidAudioAttributesBuilder = LocalRef<jobject> (env->NewObject (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.constructor));
|
||||
const int contentTypeSonification = 4;
|
||||
const int usageNotification = 5;
|
||||
env->CallObjectMethod (audioAttributesBuilder, AudioAttributesBuilder.setContentType, contentTypeSonification);
|
||||
env->CallObjectMethod (audioAttributesBuilder, AudioAttributesBuilder.setUsage, usageNotification);
|
||||
auto audioAttributes = LocalRef<jobject> (env->CallObjectMethod (audioAttributesBuilder, AudioAttributesBuilder.build));
|
||||
env->CallObjectMethod (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.setContentType, contentTypeSonification);
|
||||
env->CallObjectMethod (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.setUsage, usageNotification);
|
||||
auto audioAttributes = LocalRef<jobject> (env->CallObjectMethod (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.build));
|
||||
env->CallVoidMethod (channel, NotificationChannel.setSound, juceUrlToAndroidUri (c.soundToPlay).get(), audioAttributes.get());
|
||||
|
||||
env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannel, channel.get());
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ public:
|
|||
// create a native surface view
|
||||
surfaceView = GlobalRef (env->CallObjectMethod (android.activity.get(),
|
||||
JuceAppActivity.createNativeSurfaceView,
|
||||
reinterpret_cast<jlong> (this)));
|
||||
reinterpret_cast<jlong> (this),
|
||||
false));
|
||||
if (surfaceView.get() == nullptr)
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,25 @@
|
|||
#undef JUCE_USE_CAMERA
|
||||
#endif
|
||||
|
||||
//=============================================================================
|
||||
/** Config: JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
||||
Enables synchronisation between video playback volume and OS media volume.
|
||||
Currently supported on Android only.
|
||||
*/
|
||||
#ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
||||
#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1
|
||||
#endif
|
||||
|
||||
#ifndef JUCE_VIDEO_LOG_ENABLED
|
||||
#define JUCE_VIDEO_LOG_ENABLED 1
|
||||
#endif
|
||||
|
||||
#if JUCE_VIDEO_LOG_ENABLED
|
||||
#define JUCE_VIDEO_LOG(x) DBG(x)
|
||||
#else
|
||||
#define JUCE_VIDEO_LOG(x) {}
|
||||
#endif
|
||||
|
||||
//=============================================================================
|
||||
#include "playback/juce_VideoComponent.h"
|
||||
#include "capture/juce_CameraDevice.h"
|
||||
|
|
|
|||
|
|
@ -442,49 +442,6 @@ 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
|
||||
|
|
@ -506,7 +463,6 @@ struct CameraDevice::Pimpl
|
|||
appPausedResumedListener (*this),
|
||||
appPausedResumedListenerNative (CreateJavaInterface (&appPausedResumedListener,
|
||||
JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener").get()),
|
||||
|
||||
cameraManager (initialiseCameraManager()),
|
||||
cameraCharacteristics (initialiseCameraCharacteristics (cameraManager, cameraId)),
|
||||
streamConfigurationMap (cameraCharacteristics),
|
||||
|
|
@ -869,7 +825,7 @@ private:
|
|||
|
||||
bool isOutputSupportedForSurface (const LocalRef<jobject>& surface) const
|
||||
{
|
||||
return getEnv()->CallBooleanMethod (scalerStreamConfigurationMap, AndroidStreamConfigurationMap.isOutputSupportedForSurface, surface.get());
|
||||
return getEnv()->CallBooleanMethod (scalerStreamConfigurationMap, AndroidStreamConfigurationMap.isOutputSupportedForSurface, surface.get()) != 0;
|
||||
}
|
||||
|
||||
static constexpr int jpegImageFormat = 256;
|
||||
|
|
@ -1460,10 +1416,7 @@ private:
|
|||
|
||||
// ... 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<jobject> (env->ExceptionOccurred());
|
||||
|
||||
if (exception != 0)
|
||||
env->ExceptionClear();
|
||||
jniCheckHasExceptionOccurredAndClear();
|
||||
|
||||
unlockScreenOrientation();
|
||||
}
|
||||
|
|
@ -1630,16 +1583,12 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
auto exception = LocalRef<jobject> (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())
|
||||
if (jniCheckHasExceptionOccurredAndClear() || scopedCameraDevice.fatalErrorOccurred.get())
|
||||
{
|
||||
JUCE_CAMERA_LOG ("Exception or fatal error occurred while closing Capture Session, closing by force");
|
||||
|
||||
env->ExceptionClear();
|
||||
}
|
||||
else if (calledClose)
|
||||
{
|
||||
|
|
@ -1768,7 +1717,7 @@ private:
|
|||
|
||||
void lockFocus()
|
||||
{
|
||||
if (Pimpl::checkHasExceptionOccurred())
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
return;
|
||||
|
||||
JUCE_CAMERA_LOG ("Performing auto-focus if possible...");
|
||||
|
|
@ -1796,7 +1745,7 @@ private:
|
|||
// 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())
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
return;
|
||||
|
||||
auto* env = getEnv();
|
||||
|
|
@ -1902,7 +1851,7 @@ private:
|
|||
|
||||
void captureStillPictureDelayed()
|
||||
{
|
||||
if (Pimpl::checkHasExceptionOccurred())
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
return;
|
||||
|
||||
JUCE_CAMERA_LOG ("Still picture capture, device ready, capturing now...");
|
||||
|
|
@ -1911,12 +1860,12 @@ private:
|
|||
|
||||
env->CallVoidMethod (captureSession, CameraCaptureSession.stopRepeating);
|
||||
|
||||
if (Pimpl::checkHasExceptionOccurred())
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
return;
|
||||
|
||||
env->CallVoidMethod (captureSession, CameraCaptureSession.abortCaptures);
|
||||
|
||||
if (Pimpl::checkHasExceptionOccurred())
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
return;
|
||||
|
||||
// Delay still picture capture for devices that can't handle it right after
|
||||
|
|
@ -1929,7 +1878,7 @@ private:
|
|||
|
||||
void runPrecaptureSequence()
|
||||
{
|
||||
if (Pimpl::checkHasExceptionOccurred())
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
return;
|
||||
|
||||
auto* env = getEnv();
|
||||
|
|
@ -1950,7 +1899,7 @@ private:
|
|||
|
||||
void unlockFocus()
|
||||
{
|
||||
if (Pimpl::checkHasExceptionOccurred())
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
return;
|
||||
|
||||
JUCE_CAMERA_LOG ("Unlocking focus...");
|
||||
|
|
@ -1970,7 +1919,7 @@ private:
|
|||
env->CallIntMethod (captureSession, CameraCaptureSession.capture, resetAutoFocusRequest.get(),
|
||||
nullptr, handler.get());
|
||||
|
||||
if (Pimpl::checkHasExceptionOccurred())
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
return;
|
||||
|
||||
// NB: for preview, using preview capture request again
|
||||
|
|
@ -2233,10 +2182,7 @@ private:
|
|||
|
||||
// If something went wrong we will be pinged in cameraDeviceStateError()
|
||||
// callback, silence the redundant exception.
|
||||
auto exception = LocalRef<jobject> (env->ExceptionOccurred());
|
||||
|
||||
if (exception != 0)
|
||||
env->ExceptionClear();
|
||||
jniCheckHasExceptionOccurredAndClear();
|
||||
}
|
||||
|
||||
void close()
|
||||
|
|
@ -2500,12 +2446,12 @@ private:
|
|||
|
||||
env->CallVoidMethod (session, CameraCaptureSession.stopRepeating);
|
||||
|
||||
if (Pimpl::checkHasExceptionOccurred())
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
return;
|
||||
|
||||
env->CallVoidMethod (session, CameraCaptureSession.abortCaptures);
|
||||
|
||||
Pimpl::checkHasExceptionOccurred();
|
||||
jniCheckHasExceptionOccurredAndClear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3064,29 +3010,11 @@ private:
|
|||
env->CallBooleanMethod (handlerThread, AndroidHandlerThread.quitSafely);
|
||||
env->CallVoidMethod (handlerThread, AndroidHandlerThread.join);
|
||||
|
||||
auto exception = LocalRef<jobject> (env->ExceptionOccurred());
|
||||
|
||||
if (exception != 0)
|
||||
env->ExceptionClear();
|
||||
jniCheckHasExceptionOccurredAndClear();
|
||||
|
||||
handlerThread.clear();
|
||||
handler.clear();
|
||||
}
|
||||
|
||||
static bool checkHasExceptionOccurred()
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
auto exception = LocalRef<jobject> (env->ExceptionOccurred());
|
||||
|
||||
if (exception != 0)
|
||||
{
|
||||
env->ExceptionClear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
friend struct CameraDevice::ViewerComponent;
|
||||
|
|
|
|||
1918
modules/juce_video/native/juce_android_Video.h
Normal file
1918
modules/juce_video/native/juce_android_Video.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@
|
|||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2015 - ROLI Ltd.
|
||||
Copyright (c) 2018 - ROLI Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
|
|
@ -22,34 +22,26 @@
|
|||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_IOS
|
||||
using BaseClass = UIViewComponent;
|
||||
#if JUCE_MAC
|
||||
using Base = NSViewComponent;
|
||||
#else
|
||||
using BaseClass = NSViewComponent;
|
||||
using Base = UIViewComponent;
|
||||
#endif
|
||||
|
||||
struct VideoComponent::Pimpl : public BaseClass
|
||||
struct VideoComponent::Pimpl : public Base
|
||||
{
|
||||
Pimpl()
|
||||
Pimpl (VideoComponent& ownerToUse, bool useNativeControlsIfAvailable)
|
||||
: owner (ownerToUse),
|
||||
playerController (*this, useNativeControlsIfAvailable)
|
||||
{
|
||||
setVisible (true);
|
||||
|
||||
#if JUCE_MAC && JUCE_32BIT
|
||||
auto view = [[NSView alloc] init]; // 32-bit builds don't have AVPlayerView, so need to use a layer
|
||||
controller = [[AVPlayerLayer alloc] init];
|
||||
auto* view = playerController.getView();
|
||||
setView (view);
|
||||
|
||||
#if JUCE_MAC
|
||||
[view setNextResponder: [view superview]];
|
||||
[view setWantsLayer: YES];
|
||||
[view setLayer: controller];
|
||||
[view release];
|
||||
#elif JUCE_MAC
|
||||
controller = [[AVPlayerView alloc] init];
|
||||
setView (controller);
|
||||
[controller setNextResponder: [controller superview]];
|
||||
[controller setWantsLayer: YES];
|
||||
#else
|
||||
controller = [[AVPlayerViewController alloc] init];
|
||||
setView ([controller view]);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +49,6 @@ struct VideoComponent::Pimpl : public BaseClass
|
|||
{
|
||||
close();
|
||||
setView (nil);
|
||||
[controller release];
|
||||
}
|
||||
|
||||
Result load (const File& file)
|
||||
|
|
@ -85,34 +76,46 @@ struct VideoComponent::Pimpl : public BaseClass
|
|||
if (url != nil)
|
||||
{
|
||||
close();
|
||||
|
||||
if (auto* player = [AVPlayer playerWithURL: url])
|
||||
{
|
||||
[controller setPlayer: player];
|
||||
return Result::ok();
|
||||
}
|
||||
return playerController.load (url);
|
||||
}
|
||||
|
||||
return Result::fail ("Couldn't open movie");
|
||||
}
|
||||
|
||||
void loadAsync (const URL& url, std::function<void (const URL&, Result)> callback)
|
||||
{
|
||||
if (url.isEmpty())
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
currentURL = url;
|
||||
|
||||
jassert (callback != nullptr);
|
||||
|
||||
loadFinishedCallback = std::move (callback);
|
||||
|
||||
playerController.loadAsync (url);
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
stop();
|
||||
[controller setPlayer: nil];
|
||||
playerController.close();
|
||||
currentFile = File();
|
||||
currentURL = {};
|
||||
}
|
||||
|
||||
bool isOpen() const noexcept { return getAVPlayer() != nil; }
|
||||
bool isOpen() const noexcept { return playerController.getPlayer() != nil; }
|
||||
bool isPlaying() const noexcept { return getSpeed() != 0; }
|
||||
|
||||
void play() noexcept { [getAVPlayer() play]; }
|
||||
void stop() noexcept { [getAVPlayer() pause]; }
|
||||
void play() noexcept { [playerController.getPlayer() play]; setSpeed (playSpeedMult); }
|
||||
void stop() noexcept { [playerController.getPlayer() pause]; }
|
||||
|
||||
void setPosition (double newPosition)
|
||||
{
|
||||
if (auto* p = getAVPlayer())
|
||||
if (auto* p = playerController.getPlayer())
|
||||
{
|
||||
CMTime t = { (CMTimeValue) (100000.0 * newPosition),
|
||||
(CMTimeScale) 100000, kCMTimeFlags_Valid };
|
||||
|
|
@ -125,7 +128,7 @@ struct VideoComponent::Pimpl : public BaseClass
|
|||
|
||||
double getPosition() const
|
||||
{
|
||||
if (auto* p = getAVPlayer())
|
||||
if (auto* p = playerController.getPlayer())
|
||||
return toSeconds ([p currentTime]);
|
||||
|
||||
return 0.0;
|
||||
|
|
@ -133,12 +136,16 @@ struct VideoComponent::Pimpl : public BaseClass
|
|||
|
||||
void setSpeed (double newSpeed)
|
||||
{
|
||||
[getAVPlayer() setRate: (float) newSpeed];
|
||||
playSpeedMult = newSpeed;
|
||||
|
||||
// Calling non 0.0 speed on a paused player would start it...
|
||||
if (isPlaying())
|
||||
[playerController.getPlayer() setRate: (float) playSpeedMult];
|
||||
}
|
||||
|
||||
double getSpeed() const
|
||||
{
|
||||
if (auto* p = getAVPlayer())
|
||||
if (auto* p = playerController.getPlayer())
|
||||
return [p rate];
|
||||
|
||||
return 0.0;
|
||||
|
|
@ -146,9 +153,9 @@ struct VideoComponent::Pimpl : public BaseClass
|
|||
|
||||
Rectangle<int> getNativeSize() const
|
||||
{
|
||||
if (auto* player = getAVPlayer())
|
||||
if (auto* p = playerController.getPlayer())
|
||||
{
|
||||
auto s = [[player currentItem] presentationSize];
|
||||
auto s = [[p currentItem] presentationSize];
|
||||
return { (int) s.width, (int) s.height };
|
||||
}
|
||||
|
||||
|
|
@ -157,20 +164,20 @@ struct VideoComponent::Pimpl : public BaseClass
|
|||
|
||||
double getDuration() const
|
||||
{
|
||||
if (auto* player = getAVPlayer())
|
||||
return toSeconds ([[player currentItem] duration]);
|
||||
if (auto* p = playerController.getPlayer())
|
||||
return toSeconds ([[p currentItem] duration]);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void setVolume (float newVolume)
|
||||
{
|
||||
[getAVPlayer() setVolume: newVolume];
|
||||
[playerController.getPlayer() setVolume: newVolume];
|
||||
}
|
||||
|
||||
float getVolume() const
|
||||
{
|
||||
if (auto* p = getAVPlayer())
|
||||
if (auto* p = playerController.getPlayer())
|
||||
return [p volume];
|
||||
|
||||
return 0.0f;
|
||||
|
|
@ -180,20 +187,633 @@ struct VideoComponent::Pimpl : public BaseClass
|
|||
URL currentURL;
|
||||
|
||||
private:
|
||||
#if JUCE_IOS
|
||||
AVPlayerViewController* controller = nil;
|
||||
#elif JUCE_32BIT
|
||||
AVPlayerLayer* controller = nil;
|
||||
//==============================================================================
|
||||
template <typename Derived>
|
||||
class PlayerControllerBase
|
||||
{
|
||||
public:
|
||||
~PlayerControllerBase()
|
||||
{
|
||||
detachPlayerStatusObserver();
|
||||
detachPlaybackObserver();
|
||||
}
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
struct JucePlayerStatusObserverClass : public ObjCClass<NSObject>
|
||||
{
|
||||
JucePlayerStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerStatusObserverClass_")
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged, "v@:@@@?");
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
addIvar<PlayerAsyncInitialiser*> ("owner");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static PlayerControllerBase& getOwner (id self) { return *getIvar<PlayerControllerBase*> (self, "owner"); }
|
||||
static void setOwner (id self, PlayerControllerBase* p) { object_setInstanceVariable (self, "owner", p); }
|
||||
|
||||
private:
|
||||
static void valueChanged (id self, SEL, NSString* keyPath, id,
|
||||
NSDictionary<NSKeyValueChangeKey, id>* change, void*)
|
||||
{
|
||||
auto& owner = getOwner (self);
|
||||
|
||||
if ([keyPath isEqualToString: nsStringLiteral ("rate")])
|
||||
{
|
||||
auto oldRate = [change[NSKeyValueChangeOldKey] floatValue];
|
||||
auto newRate = [change[NSKeyValueChangeNewKey] floatValue];
|
||||
|
||||
if (oldRate == 0 && newRate != 0)
|
||||
owner.playbackStarted();
|
||||
else if (oldRate != 0 && newRate == 0)
|
||||
owner.playbackStopped();
|
||||
}
|
||||
else if ([keyPath isEqualToString: nsStringLiteral ("status")])
|
||||
{
|
||||
auto status = [change[NSKeyValueChangeNewKey] intValue];
|
||||
|
||||
if (status == AVPlayerStatusFailed)
|
||||
owner.errorOccurred();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JucePlayerItemPlaybackStatusObserverClass : public ObjCClass<NSObject>
|
||||
{
|
||||
JucePlayerItemPlaybackStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerItemPlaybackStatusObserverClass_")
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
addMethod (@selector (processNotification:), notificationReceived, "v@:@");
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
addIvar<PlayerControllerBase*> ("owner");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static PlayerControllerBase& getOwner (id self) { return *getIvar<PlayerControllerBase*> (self, "owner"); }
|
||||
static void setOwner (id self, PlayerControllerBase* p) { object_setInstanceVariable (self, "owner", p); }
|
||||
|
||||
private:
|
||||
static void notificationReceived (id self, SEL, NSNotification* notification)
|
||||
{
|
||||
if ([notification.name isEqualToString: AVPlayerItemDidPlayToEndTimeNotification])
|
||||
getOwner (self).playbackReachedEndTime();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class PlayerAsyncInitialiser
|
||||
{
|
||||
public:
|
||||
PlayerAsyncInitialiser (PlayerControllerBase& ownerToUse)
|
||||
: owner (ownerToUse),
|
||||
assetKeys ([[NSArray alloc] initWithObjects: nsStringLiteral ("duration"), nsStringLiteral ("tracks"),
|
||||
nsStringLiteral ("playable"), nil])
|
||||
{
|
||||
static JucePlayerItemPreparationStatusObserverClass cls;
|
||||
playerItemPreparationStatusObserver.reset ([cls.createInstance() init]);
|
||||
JucePlayerItemPreparationStatusObserverClass::setOwner (playerItemPreparationStatusObserver.get(), this);
|
||||
}
|
||||
|
||||
~PlayerAsyncInitialiser()
|
||||
{
|
||||
detachPreparationStatusObserver();
|
||||
}
|
||||
|
||||
void loadAsync (URL url)
|
||||
{
|
||||
auto* nsUrl = [NSURL URLWithString: juceStringToNS (url.toString (true))];
|
||||
asset.reset ([[AVURLAsset alloc] initWithURL: nsUrl options: nil]);
|
||||
|
||||
[asset.get() loadValuesAsynchronouslyForKeys: assetKeys.get()
|
||||
completionHandler: ^() { checkAllKeysReadyFor (asset.get(), url); }];
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct JucePlayerItemPreparationStatusObserverClass : public ObjCClass<NSObject>
|
||||
{
|
||||
JucePlayerItemPreparationStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerItemStatusObserverClass_")
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged, "v@:@@@?");
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
addIvar<PlayerAsyncInitialiser*> ("owner");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static PlayerAsyncInitialiser& getOwner (id self) { return *getIvar<PlayerAsyncInitialiser*> (self, "owner"); }
|
||||
static void setOwner (id self, PlayerAsyncInitialiser* p) { object_setInstanceVariable (self, "owner", p); }
|
||||
|
||||
private:
|
||||
static void valueChanged (id self, SEL, NSString*, id object,
|
||||
NSDictionary<NSKeyValueChangeKey, id>* change, void* context)
|
||||
{
|
||||
auto& owner = getOwner (self);
|
||||
|
||||
if (context == &owner)
|
||||
{
|
||||
auto* playerItem = (AVPlayerItem*) object;
|
||||
auto* urlAsset = (AVURLAsset*) playerItem.asset;
|
||||
|
||||
URL url (nsStringToJuce (urlAsset.URL.absoluteString));
|
||||
auto oldStatus = [change[NSKeyValueChangeOldKey] intValue];
|
||||
auto newStatus = [change[NSKeyValueChangeNewKey] intValue];
|
||||
|
||||
// Ignore spurious notifications
|
||||
if (oldStatus == newStatus)
|
||||
return;
|
||||
|
||||
if (newStatus == AVPlayerItemStatusFailed)
|
||||
{
|
||||
auto errorMessage = playerItem.error != nil
|
||||
? nsStringToJuce (playerItem.error.localizedDescription)
|
||||
: String();
|
||||
|
||||
owner.notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
|
||||
}
|
||||
else if (newStatus == AVPlayerItemStatusReadyToPlay)
|
||||
{
|
||||
owner.notifyOwnerPreparationFinished (url, Result::ok(), owner.player.release());
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
PlayerControllerBase& owner;
|
||||
|
||||
std::unique_ptr<AVURLAsset, NSObjectDeleter> asset;
|
||||
std::unique_ptr<NSArray<NSString*>, NSObjectDeleter> assetKeys;
|
||||
std::unique_ptr<AVPlayerItem, NSObjectDeleter> playerItem;
|
||||
std::unique_ptr<NSObject, NSObjectDeleter> playerItemPreparationStatusObserver;
|
||||
std::unique_ptr<AVPlayer, NSObjectDeleter> player;
|
||||
|
||||
//==============================================================================
|
||||
void checkAllKeysReadyFor (AVAsset* assetToCheck, const URL& url)
|
||||
{
|
||||
NSError* error = nil;
|
||||
|
||||
int successCount = 0;
|
||||
|
||||
for (NSString* key : assetKeys.get())
|
||||
{
|
||||
switch ([assetToCheck statusOfValueForKey: key error: &error])
|
||||
{
|
||||
case AVKeyValueStatusLoaded:
|
||||
{
|
||||
++successCount;
|
||||
break;
|
||||
}
|
||||
case AVKeyValueStatusCancelled:
|
||||
{
|
||||
notifyOwnerPreparationFinished (url, Result::fail ("Loading cancelled"), nullptr);
|
||||
return;
|
||||
}
|
||||
case AVKeyValueStatusFailed:
|
||||
{
|
||||
auto errorMessage = error != nil ? nsStringToJuce (error.localizedDescription) : String();
|
||||
notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
jassert (successCount == (int) [assetKeys.get() count]);
|
||||
preparePlayerItem();
|
||||
}
|
||||
|
||||
void preparePlayerItem()
|
||||
{
|
||||
playerItem.reset ([[AVPlayerItem alloc] initWithAsset: asset.get()]);
|
||||
|
||||
attachPreparationStatusObserver();
|
||||
|
||||
player.reset ([[AVPlayer alloc] initWithPlayerItem: playerItem.get()]);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void attachPreparationStatusObserver()
|
||||
{
|
||||
[playerItem.get() addObserver: playerItemPreparationStatusObserver.get()
|
||||
forKeyPath: nsStringLiteral ("status")
|
||||
options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
|
||||
context: this];
|
||||
}
|
||||
|
||||
void detachPreparationStatusObserver()
|
||||
{
|
||||
if (playerItem != nullptr && playerItemPreparationStatusObserver != nullptr)
|
||||
{
|
||||
[playerItem.get() removeObserver: playerItemPreparationStatusObserver.get()
|
||||
forKeyPath: nsStringLiteral ("status")
|
||||
context: this];
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void notifyOwnerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
|
||||
{
|
||||
WeakReference<PlayerAsyncInitialiser> safeThis (this);
|
||||
|
||||
MessageManager::callAsync ([safeThis, url, r, preparedPlayer]() mutable
|
||||
{
|
||||
if (safeThis != nullptr)
|
||||
safeThis->owner.playerPreparationFinished (url, r, preparedPlayer);
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerAsyncInitialiser)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Pimpl& owner;
|
||||
bool useNativeControls;
|
||||
|
||||
PlayerAsyncInitialiser playerAsyncInitialiser;
|
||||
std::unique_ptr<NSObject, NSObjectDeleter> playerStatusObserver;
|
||||
std::unique_ptr<NSObject, NSObjectDeleter> playerItemPlaybackStatusObserver;
|
||||
|
||||
//==============================================================================
|
||||
PlayerControllerBase (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
|
||||
: owner (ownerToUse),
|
||||
useNativeControls (useNativeControlsIfAvailable),
|
||||
playerAsyncInitialiser (*this)
|
||||
{
|
||||
static JucePlayerStatusObserverClass playerObserverClass;
|
||||
playerStatusObserver.reset ([playerObserverClass.createInstance() init]);
|
||||
JucePlayerStatusObserverClass::setOwner (playerStatusObserver.get(), this);
|
||||
|
||||
static JucePlayerItemPlaybackStatusObserverClass itemObserverClass;
|
||||
playerItemPlaybackStatusObserver.reset ([itemObserverClass.createInstance() init]);
|
||||
JucePlayerItemPlaybackStatusObserverClass::setOwner (playerItemPlaybackStatusObserver.get(), this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void attachPlayerStatusObserver()
|
||||
{
|
||||
[crtp().getPlayer() addObserver: playerStatusObserver.get()
|
||||
forKeyPath: nsStringLiteral ("rate")
|
||||
options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
|
||||
context: this];
|
||||
|
||||
[crtp().getPlayer() addObserver: playerStatusObserver.get()
|
||||
forKeyPath: nsStringLiteral ("status")
|
||||
options: NSKeyValueObservingOptionNew
|
||||
context: this];
|
||||
}
|
||||
|
||||
void detachPlayerStatusObserver()
|
||||
{
|
||||
if (crtp().getPlayer() != nullptr && playerStatusObserver != nullptr)
|
||||
{
|
||||
[crtp().getPlayer() removeObserver: playerStatusObserver.get()
|
||||
forKeyPath: nsStringLiteral ("rate")
|
||||
context: this];
|
||||
|
||||
[crtp().getPlayer() removeObserver: playerStatusObserver.get()
|
||||
forKeyPath: nsStringLiteral ("status")
|
||||
context: this];
|
||||
}
|
||||
}
|
||||
|
||||
void attachPlaybackObserver()
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
[[NSNotificationCenter defaultCenter] addObserver: playerItemPlaybackStatusObserver.get()
|
||||
selector: @selector (processNotification:)
|
||||
name: AVPlayerItemDidPlayToEndTimeNotification
|
||||
object: [crtp().getPlayer() currentItem]];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
void detachPlaybackObserver()
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: playerItemPlaybackStatusObserver.get()];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Derived& crtp() { return static_cast<Derived&> (*this); }
|
||||
|
||||
//==============================================================================
|
||||
void playerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
|
||||
{
|
||||
if (preparedPlayer != nil)
|
||||
crtp().setPlayer (preparedPlayer);
|
||||
|
||||
owner.playerPreparationFinished (url, r);
|
||||
}
|
||||
|
||||
void playbackReachedEndTime()
|
||||
{
|
||||
WeakReference<PlayerControllerBase> safeThis (this);
|
||||
|
||||
MessageManager::callAsync ([safeThis]() mutable
|
||||
{
|
||||
if (safeThis != nullptr)
|
||||
safeThis->owner.playbackReachedEndTime();
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void errorOccurred()
|
||||
{
|
||||
auto errorMessage = (crtp().getPlayer() != nil && crtp().getPlayer().error != nil)
|
||||
? nsStringToJuce (crtp().getPlayer().error.localizedDescription)
|
||||
: String();
|
||||
|
||||
owner.errorOccurred (errorMessage);
|
||||
}
|
||||
|
||||
void playbackStarted()
|
||||
{
|
||||
owner.playbackStarted();
|
||||
}
|
||||
|
||||
void playbackStopped()
|
||||
{
|
||||
owner.playbackStopped();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerControllerBase)
|
||||
};
|
||||
|
||||
#if JUCE_MAC
|
||||
//==============================================================================
|
||||
class PlayerController : public PlayerControllerBase<PlayerController>
|
||||
{
|
||||
public:
|
||||
PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
|
||||
: PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
|
||||
{
|
||||
#if JUCE_32BIT
|
||||
// 32-bit builds don't have AVPlayerView, so need to use a layer
|
||||
useNativeControls = false;
|
||||
#endif
|
||||
|
||||
if (useNativeControls)
|
||||
{
|
||||
#if ! JUCE_32BIT
|
||||
playerView = [[AVPlayerView alloc] init];
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
view = [[NSView alloc] init];
|
||||
playerLayer = [[AVPlayerLayer alloc] init];
|
||||
[view setLayer: playerLayer];
|
||||
}
|
||||
}
|
||||
|
||||
~PlayerController()
|
||||
{
|
||||
#if JUCE_32BIT
|
||||
[view release];
|
||||
[playerLayer release];
|
||||
#else
|
||||
[playerView release];
|
||||
#endif
|
||||
}
|
||||
|
||||
NSView* getView()
|
||||
{
|
||||
#if ! JUCE_32BIT
|
||||
if (useNativeControls)
|
||||
return playerView;
|
||||
#endif
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
Result load (NSURL* url)
|
||||
{
|
||||
if (auto* player = [AVPlayer playerWithURL: url])
|
||||
{
|
||||
setPlayer (player);
|
||||
return Result::ok();
|
||||
}
|
||||
|
||||
return Result::fail ("Couldn't open movie");
|
||||
}
|
||||
|
||||
void loadAsync (URL url)
|
||||
{
|
||||
playerAsyncInitialiser.loadAsync (url);
|
||||
}
|
||||
|
||||
void close() { setPlayer (nil); }
|
||||
|
||||
void setPlayer (AVPlayer* player)
|
||||
{
|
||||
#if ! JUCE_32BIT
|
||||
if (useNativeControls)
|
||||
{
|
||||
[playerView setPlayer: player];
|
||||
attachPlayerStatusObserver();
|
||||
attachPlaybackObserver();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
[playerLayer setPlayer: player];
|
||||
attachPlayerStatusObserver();
|
||||
attachPlaybackObserver();
|
||||
}
|
||||
|
||||
AVPlayer* getPlayer() const
|
||||
{
|
||||
#if ! JUCE_32BIT
|
||||
if (useNativeControls)
|
||||
return [playerView player];
|
||||
#endif
|
||||
|
||||
return [playerLayer player];
|
||||
}
|
||||
|
||||
private:
|
||||
NSView* view = nil;
|
||||
AVPlayerLayer* playerLayer = nil;
|
||||
#if ! JUCE_32BIT
|
||||
// 32-bit builds don't have AVPlayerView
|
||||
AVPlayerView* playerView = nil;
|
||||
#endif
|
||||
};
|
||||
#else
|
||||
AVPlayerView* controller = nil;
|
||||
//==============================================================================
|
||||
class PlayerController : public PlayerControllerBase<PlayerController>
|
||||
{
|
||||
public:
|
||||
PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
|
||||
: PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
|
||||
{
|
||||
if (useNativeControls)
|
||||
{
|
||||
playerViewController.reset ([[AVPlayerViewController alloc] init]);
|
||||
}
|
||||
else
|
||||
{
|
||||
static JuceVideoViewerClass cls;
|
||||
playerView.reset ([cls.createInstance() init]);
|
||||
|
||||
playerLayer.reset ([[AVPlayerLayer alloc] init]);
|
||||
[playerView.get().layer addSublayer: playerLayer.get()];
|
||||
}
|
||||
}
|
||||
|
||||
UIView* getView()
|
||||
{
|
||||
if (useNativeControls)
|
||||
return [playerViewController.get() view];
|
||||
|
||||
// Should call getView() only once.
|
||||
jassert (playerView != nil);
|
||||
return playerView.release();
|
||||
}
|
||||
|
||||
Result load (NSURL*)
|
||||
{
|
||||
jassertfalse;
|
||||
return Result::fail ("Synchronous loading is not supported on iOS, use loadAsync()");
|
||||
}
|
||||
|
||||
void loadAsync (URL url)
|
||||
{
|
||||
playerAsyncInitialiser.loadAsync (url);
|
||||
}
|
||||
|
||||
void close() { setPlayer (nil); }
|
||||
|
||||
AVPlayer* getPlayer() const
|
||||
{
|
||||
if (useNativeControls)
|
||||
return [playerViewController.get() player];
|
||||
|
||||
return [playerLayer.get() player];
|
||||
}
|
||||
|
||||
void setPlayer (AVPlayer* playerToUse)
|
||||
{
|
||||
if (useNativeControls)
|
||||
[playerViewController.get() setPlayer: playerToUse];
|
||||
else
|
||||
[playerLayer.get() setPlayer: playerToUse];
|
||||
|
||||
attachPlayerStatusObserver();
|
||||
attachPlaybackObserver();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct JuceVideoViewerClass : public ObjCClass<UIView>
|
||||
{
|
||||
JuceVideoViewerClass() : ObjCClass<UIView> ("JuceVideoViewerClass_")
|
||||
{
|
||||
addMethod (@selector (layoutSubviews), layoutSubviews, "v@:");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static void layoutSubviews (id self, SEL)
|
||||
{
|
||||
sendSuperclassMessage (self, @selector (layoutSubviews));
|
||||
|
||||
UIView* asUIView = (UIView*) self;
|
||||
|
||||
if (auto* previewLayer = getPreviewLayer (self))
|
||||
previewLayer.frame = asUIView.bounds;
|
||||
}
|
||||
|
||||
static AVPlayerLayer* getPreviewLayer (id self)
|
||||
{
|
||||
UIView* asUIView = (UIView*) self;
|
||||
|
||||
if (asUIView.layer.sublayers != nil && [asUIView.layer.sublayers count] > 0)
|
||||
if ([asUIView.layer.sublayers[0] isKindOfClass: [AVPlayerLayer class]])
|
||||
return (AVPlayerLayer*) asUIView.layer.sublayers[0];
|
||||
|
||||
return nil;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AVPlayerViewController, NSObjectDeleter> playerViewController;
|
||||
|
||||
std::unique_ptr<UIView, NSObjectDeleter> playerView;
|
||||
std::unique_ptr<AVPlayerLayer, NSObjectDeleter> playerLayer;
|
||||
};
|
||||
#endif
|
||||
|
||||
AVPlayer* getAVPlayer() const noexcept { return [controller player]; }
|
||||
//==============================================================================
|
||||
VideoComponent& owner;
|
||||
|
||||
PlayerController playerController;
|
||||
|
||||
std::function<void (const URL&, Result)> loadFinishedCallback;
|
||||
|
||||
double playSpeedMult = 1.0;
|
||||
|
||||
static double toSeconds (const CMTime& t) noexcept
|
||||
{
|
||||
return t.timescale != 0 ? (t.value / (double) t.timescale) : 0.0;
|
||||
}
|
||||
|
||||
void playerPreparationFinished (const URL& url, Result r)
|
||||
{
|
||||
owner.resized();
|
||||
|
||||
loadFinishedCallback (url, r);
|
||||
loadFinishedCallback = nullptr;
|
||||
}
|
||||
|
||||
void errorOccurred (const String& errorMessage)
|
||||
{
|
||||
if (owner.onErrorOccurred != nullptr)
|
||||
owner.onErrorOccurred (errorMessage);
|
||||
}
|
||||
|
||||
void playbackStarted()
|
||||
{
|
||||
if (owner.onPlaybackStarted != nullptr)
|
||||
owner.onPlaybackStarted();
|
||||
}
|
||||
|
||||
void playbackStopped()
|
||||
{
|
||||
if (owner.onPlaybackStopped != nullptr)
|
||||
owner.onPlaybackStopped();
|
||||
}
|
||||
|
||||
void playbackReachedEndTime()
|
||||
{
|
||||
stop();
|
||||
setPosition (0.0);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -160,7 +160,9 @@ namespace VideoRenderers
|
|||
//==============================================================================
|
||||
struct VideoComponent::Pimpl : public Component
|
||||
{
|
||||
Pimpl() : videoLoaded (false)
|
||||
Pimpl (VideoComponent& ownerToUse, bool)
|
||||
: owner (ownerToUse),
|
||||
videoLoaded (false)
|
||||
{
|
||||
setOpaque (true);
|
||||
context.reset (new DirectShowContext (*this));
|
||||
|
|
@ -257,6 +259,11 @@ struct VideoComponent::Pimpl : public Component
|
|||
context->setSpeed (newSpeed);
|
||||
}
|
||||
|
||||
double getSpeed() const
|
||||
{
|
||||
return videoLoaded ? context->getSpeed() : 0.0;
|
||||
}
|
||||
|
||||
Rectangle<int> getNativeSize() const
|
||||
{
|
||||
return videoLoaded ? context->getVideoSize()
|
||||
|
|
@ -307,10 +314,30 @@ struct VideoComponent::Pimpl : public Component
|
|||
repaint();
|
||||
}
|
||||
|
||||
void playbackStarted()
|
||||
{
|
||||
if (owner.onPlaybackStarted != nullptr)
|
||||
owner.onPlaybackStarted();
|
||||
}
|
||||
|
||||
void playbackStopped()
|
||||
{
|
||||
if (owner.onPlaybackStopped != nullptr)
|
||||
owner.onPlaybackStopped();
|
||||
}
|
||||
|
||||
void errorOccurred (const String& errorMessage)
|
||||
{
|
||||
if (owner.onErrorOccurred != nullptr)
|
||||
owner.onErrorOccurred (errorMessage);
|
||||
}
|
||||
|
||||
File currentFile;
|
||||
URL currentURL;
|
||||
|
||||
private:
|
||||
VideoComponent& owner;
|
||||
|
||||
bool videoLoaded;
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -395,12 +422,15 @@ private:
|
|||
deleteNativeWindow();
|
||||
|
||||
mediaEvent->SetNotifyWindow (0, 0, 0);
|
||||
|
||||
if (videoRenderer != nullptr)
|
||||
videoRenderer->setVideoWindow (nullptr);
|
||||
|
||||
createNativeWindow();
|
||||
|
||||
mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
|
||||
mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
|
||||
|
||||
if (videoRenderer != nullptr)
|
||||
videoRenderer->setVideoWindow (hwnd);
|
||||
}
|
||||
|
|
@ -510,7 +540,10 @@ private:
|
|||
|
||||
// set window to receive events
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
|
||||
hr = mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
|
||||
}
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
|
|
@ -586,22 +619,33 @@ private:
|
|||
|
||||
switch (ec)
|
||||
{
|
||||
case EC_REPAINT:
|
||||
component.repaint();
|
||||
break;
|
||||
case EC_REPAINT:
|
||||
component.repaint();
|
||||
break;
|
||||
|
||||
case EC_COMPLETE:
|
||||
component.stop();
|
||||
break;
|
||||
case EC_COMPLETE:
|
||||
component.stop();
|
||||
component.setPosition (0.0);
|
||||
break;
|
||||
|
||||
case EC_USERABORT:
|
||||
case EC_ERRORABORT:
|
||||
case EC_ERRORABORTEX:
|
||||
component.close();
|
||||
break;
|
||||
case EC_ERRORABORT:
|
||||
case EC_ERRORABORTEX:
|
||||
component.errorOccurred (getErrorMessageFromResult ((HRESULT) p1).getErrorMessage());
|
||||
// intentional fallthrough
|
||||
case EC_USERABORT:
|
||||
component.close();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
case EC_STATE_CHANGE:
|
||||
switch (p1)
|
||||
{
|
||||
case State_Paused: component.playbackStopped(); break;
|
||||
case State_Running: component.playbackStarted(); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -644,6 +688,13 @@ private:
|
|||
return duration;
|
||||
}
|
||||
|
||||
double getSpeed() const
|
||||
{
|
||||
double speed;
|
||||
mediaPosition->get_Rate (&speed);
|
||||
return speed;
|
||||
}
|
||||
|
||||
double getPosition() const
|
||||
{
|
||||
REFTIME seconds;
|
||||
|
|
|
|||
|
|
@ -25,16 +25,19 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MAC || JUCE_IOS || JUCE_MSVC
|
||||
#if ! JUCE_LINUX
|
||||
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
#include "../native/juce_mac_Video.h"
|
||||
#elif JUCE_WINDOWS
|
||||
#include "../native/juce_win32_Video.h"
|
||||
#elif JUCE_ANDROID
|
||||
#include "../native/juce_android_Video.h"
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
VideoComponent::VideoComponent() : pimpl (new Pimpl())
|
||||
VideoComponent::VideoComponent (bool useNativeControlsIfAvailable)
|
||||
: pimpl (new Pimpl (*this, useNativeControlsIfAvailable))
|
||||
{
|
||||
addAndMakeVisible (pimpl.get());
|
||||
}
|
||||
|
|
@ -46,22 +49,55 @@ VideoComponent::~VideoComponent()
|
|||
|
||||
Result VideoComponent::load (const File& file)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
ignoreUnused (file);
|
||||
jassertfalse;
|
||||
return Result::fail ("load() is not supported on this platform. Use loadAsync() instead.");
|
||||
#else
|
||||
auto r = pimpl->load (file);
|
||||
resized();
|
||||
return r;
|
||||
#endif
|
||||
}
|
||||
|
||||
Result VideoComponent::load (const URL& url)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
// You need to use loadAsync on Android & iOS.
|
||||
ignoreUnused (url);
|
||||
jassertfalse;
|
||||
return Result::fail ("load() is not supported on this platform. Use loadAsync() instead.");
|
||||
#else
|
||||
auto r = pimpl->load (url);
|
||||
resized();
|
||||
return r;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VideoComponent::loadAsync (const URL& url, std::function<void (const URL&, Result)> callback)
|
||||
{
|
||||
if (callback == nullptr)
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->loadAsync (url, callback);
|
||||
#else
|
||||
auto result = load (url);
|
||||
callback (url, result);
|
||||
#endif
|
||||
}
|
||||
|
||||
void VideoComponent::closeVideo()
|
||||
{
|
||||
pimpl->close();
|
||||
// Closing on Android is async and resized() will be called internally by pimpl once
|
||||
// close operation finished.
|
||||
#if ! JUCE_ANDROID// TODO JUCE_IOS too?
|
||||
resized();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VideoComponent::isVideoOpen() const { return pimpl->isOpen(); }
|
||||
|
|
@ -81,6 +117,8 @@ void VideoComponent::setPlayPosition (double newPos) { pimpl->setPosition
|
|||
double VideoComponent::getPlayPosition() const { return pimpl->getPosition(); }
|
||||
|
||||
void VideoComponent::setPlaySpeed (double newSpeed) { pimpl->setSpeed (newSpeed); }
|
||||
double VideoComponent::getPlaySpeed() const { return pimpl->getSpeed(); }
|
||||
|
||||
void VideoComponent::setAudioVolume (float newVolume) { pimpl->setVolume (newVolume); }
|
||||
float VideoComponent::getAudioVolume() const { return pimpl->getVolume(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -44,25 +44,55 @@ public:
|
|||
//==============================================================================
|
||||
/** Creates an empty VideoComponent.
|
||||
|
||||
Use the load() method to open a video once you've added this component to
|
||||
a parent (or put it on the desktop).
|
||||
Use the loadAsync() or load() method to open a video once you've added
|
||||
this component to a parent (or put it on the desktop).
|
||||
|
||||
If useNativeControlsIfAvailable is enabled and a target OS has a video view with
|
||||
dedicated controls for transport etc, that view will be used. In opposite
|
||||
case a bare video view without any controls will be presented, allowing you to
|
||||
tailor your own UI. Currently this flag is used on iOS and 64bit macOS.
|
||||
Android, Windows and 32bit macOS will always use plain video views without
|
||||
dedicated controls.
|
||||
*/
|
||||
VideoComponent();
|
||||
VideoComponent (bool useNativeControlsIfAvailable);
|
||||
|
||||
/** Destructor. */
|
||||
~VideoComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to load a video from a local file.
|
||||
|
||||
This function is supported on macOS and Windows. For iOS and Android, use
|
||||
loadAsync() instead.
|
||||
|
||||
@returns an error if the file failed to be loaded correctly
|
||||
|
||||
@see loadAsync
|
||||
*/
|
||||
Result load (const File& file);
|
||||
|
||||
/** Tries to load a video from a URL.
|
||||
|
||||
This function is supported on macOS and Windows. For iOS and Android, use
|
||||
loadAsync() instead.
|
||||
|
||||
@returns an error if the file failed to be loaded correctly
|
||||
|
||||
@see loadAsync
|
||||
*/
|
||||
Result load (const URL& url);
|
||||
|
||||
/** Tries to load a video from a URL asynchronously. When finished, invokes the
|
||||
callback supplied to the function on the message thread.
|
||||
|
||||
This is the preferred way of loading content, since it works not only on
|
||||
macOS and Windows, but also on iOS and Android. On Windows, it will internally
|
||||
call load().
|
||||
|
||||
@see load
|
||||
*/
|
||||
void loadAsync (const URL& url, std::function<void (const URL&, Result)> loadFinishedCallback);
|
||||
|
||||
/** Closes the video and resets the component. */
|
||||
void closeVideo();
|
||||
|
||||
|
|
@ -109,6 +139,9 @@ public:
|
|||
*/
|
||||
void setPlaySpeed (double newSpeed);
|
||||
|
||||
/** Returns the current play speed of the video. */
|
||||
double getPlaySpeed() const;
|
||||
|
||||
/** Changes the video's playback volume.
|
||||
@param newVolume the volume in the range 0 (silent) to 1.0 (full)
|
||||
*/
|
||||
|
|
@ -119,6 +152,23 @@ public:
|
|||
*/
|
||||
float getAudioVolume() const;
|
||||
|
||||
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
||||
/** Set this callback to be notified whenever OS global media volume changes.
|
||||
Currently used on Android only.
|
||||
*/
|
||||
std::function<void()> onGlobalMediaVolumeChanged;
|
||||
#endif
|
||||
|
||||
/** Set this callback to be notified whenever the playback starts. */
|
||||
std::function<void()> onPlaybackStarted;
|
||||
|
||||
/** Set this callback to be notified whenever the playback stops. */
|
||||
std::function<void()> onPlaybackStopped;
|
||||
|
||||
/** Set this callback to be notified whenever an error occurs. Upon error, you
|
||||
may need to load the video again. */
|
||||
std::function<void (const String& /*error*/)> onErrorOccurred;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct Pimpl;
|
||||
|
|
@ -129,6 +179,24 @@ private:
|
|||
void resized() override;
|
||||
void timerCallback() override;
|
||||
|
||||
#if JUCE_ANDROID
|
||||
friend void juce_surfaceChangedNativeVideo (int64, void*);
|
||||
friend void juce_surfaceDestroyedNativeVideo (int64, void*);
|
||||
|
||||
friend void juce_mediaSessionPause (int64);
|
||||
friend void juce_mediaSessionPlay (int64);
|
||||
friend void juce_mediaSessionPlayFromMediaId (int64, void*, void*);
|
||||
friend void juce_mediaSessionSeekTo (int64, int64);
|
||||
friend void juce_mediaSessionStop (int64);
|
||||
|
||||
friend void juce_mediaControllerAudioInfoChanged (int64, void*);
|
||||
friend void juce_mediaControllerMetadataChanged (int64, void*);
|
||||
friend void juce_mediaControllerPlaybackStateChanged (int64, void*);
|
||||
friend void juce_mediaControllerSessionDestroyed (int64);
|
||||
|
||||
friend void juce_mediaSessionSystemVolumeChanged (int64);
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoComponent)
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue