git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
243
deps/juce/modules/juce_video/capture/juce_CameraDevice.cpp
vendored
Normal file
243
deps/juce/modules/juce_video/capture/juce_CameraDevice.cpp
vendored
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MAC
|
||||
#include "../native/juce_mac_CameraDevice.h"
|
||||
#elif JUCE_WINDOWS
|
||||
#include "../native/juce_win32_CameraDevice.h"
|
||||
#elif JUCE_IOS
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new")
|
||||
|
||||
#include "../native/juce_ios_CameraDevice.h"
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
#elif JUCE_ANDROID
|
||||
#include "../native/juce_android_CameraDevice.h"
|
||||
#endif
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
//==============================================================================
|
||||
class CameraDevice::CameraFactory
|
||||
{
|
||||
public:
|
||||
static CameraFactory& getInstance()
|
||||
{
|
||||
static CameraFactory factory;
|
||||
return factory;
|
||||
}
|
||||
|
||||
void openCamera (int index, OpenCameraResultCallback resultCallback,
|
||||
int minWidth, int minHeight, int maxWidth, int maxHeight, bool useHighQuality)
|
||||
{
|
||||
auto cameraId = getAvailableDevices()[index];
|
||||
|
||||
if (getCameraIndex (cameraId) != -1)
|
||||
{
|
||||
// You are trying to open the same camera twice.
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<CameraDevice> device (new CameraDevice (cameraId, index,
|
||||
minWidth, minHeight, maxWidth,
|
||||
maxHeight, useHighQuality));
|
||||
|
||||
camerasToOpen.add ({ nextRequestId++,
|
||||
std::unique_ptr<CameraDevice> (device.release()),
|
||||
resultCallback });
|
||||
|
||||
auto& pendingOpen = camerasToOpen.getReference (camerasToOpen.size() - 1);
|
||||
|
||||
pendingOpen.device->pimpl->open ([this] (const String& deviceId, const String& error)
|
||||
{
|
||||
int cIndex = getCameraIndex (deviceId);
|
||||
|
||||
if (cIndex == -1)
|
||||
return;
|
||||
|
||||
auto& cameraPendingOpen = camerasToOpen.getReference (cIndex);
|
||||
|
||||
if (error.isEmpty())
|
||||
cameraPendingOpen.resultCallback (cameraPendingOpen.device.release(), error);
|
||||
else
|
||||
cameraPendingOpen.resultCallback (nullptr, error);
|
||||
|
||||
int id = cameraPendingOpen.requestId;
|
||||
|
||||
MessageManager::callAsync ([this, id]() { removeRequestWithId (id); });
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
int getCameraIndex (const String& cameraId) const
|
||||
{
|
||||
for (int i = 0; i < camerasToOpen.size(); ++i)
|
||||
{
|
||||
auto& pendingOpen = camerasToOpen.getReference (i);
|
||||
|
||||
if (pendingOpen.device->pimpl->getCameraId() == cameraId)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void removeRequestWithId (int id)
|
||||
{
|
||||
for (int i = camerasToOpen.size(); --i >= 0;)
|
||||
{
|
||||
if (camerasToOpen.getReference (i).requestId == id)
|
||||
{
|
||||
camerasToOpen.remove (i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingCameraOpen
|
||||
{
|
||||
int requestId;
|
||||
std::unique_ptr<CameraDevice> device;
|
||||
OpenCameraResultCallback resultCallback;
|
||||
};
|
||||
|
||||
Array<PendingCameraOpen> camerasToOpen;
|
||||
static int nextRequestId;
|
||||
};
|
||||
|
||||
int CameraDevice::CameraFactory::nextRequestId = 0;
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
CameraDevice::CameraDevice (const String& nm, int index, int minWidth, int minHeight, int maxWidth, int maxHeight, bool useHighQuality)
|
||||
: name (nm), pimpl (new Pimpl (*this, name, index, minWidth, minHeight, maxWidth, maxHeight, useHighQuality))
|
||||
{
|
||||
}
|
||||
|
||||
CameraDevice::~CameraDevice()
|
||||
{
|
||||
jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
stopRecording();
|
||||
pimpl.reset();
|
||||
}
|
||||
|
||||
Component* CameraDevice::createViewerComponent()
|
||||
{
|
||||
return new ViewerComponent (*this);
|
||||
}
|
||||
|
||||
void CameraDevice::takeStillPicture (std::function<void (const Image&)> pictureTakenCallback)
|
||||
{
|
||||
pimpl->takeStillPicture (pictureTakenCallback);
|
||||
}
|
||||
|
||||
void CameraDevice::startRecordingToFile (const File& file, int quality)
|
||||
{
|
||||
stopRecording();
|
||||
pimpl->startRecordingToFile (file, quality);
|
||||
}
|
||||
|
||||
Time CameraDevice::getTimeOfFirstRecordedFrame() const
|
||||
{
|
||||
return pimpl->getTimeOfFirstRecordedFrame();
|
||||
}
|
||||
|
||||
void CameraDevice::stopRecording()
|
||||
{
|
||||
pimpl->stopRecording();
|
||||
}
|
||||
|
||||
void CameraDevice::addListener (Listener* listenerToAdd)
|
||||
{
|
||||
if (listenerToAdd != nullptr)
|
||||
pimpl->addListener (listenerToAdd);
|
||||
}
|
||||
|
||||
void CameraDevice::removeListener (Listener* listenerToRemove)
|
||||
{
|
||||
if (listenerToRemove != nullptr)
|
||||
pimpl->removeListener (listenerToRemove);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StringArray CameraDevice::getAvailableDevices()
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
return Pimpl::getAvailableDevices();
|
||||
}
|
||||
}
|
||||
|
||||
CameraDevice* CameraDevice::openDevice (int index,
|
||||
int minWidth, int minHeight,
|
||||
int maxWidth, int maxHeight,
|
||||
bool useHighQuality)
|
||||
{
|
||||
jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
#if ! JUCE_ANDROID && ! JUCE_IOS
|
||||
std::unique_ptr<CameraDevice> d (new CameraDevice (getAvailableDevices() [index], index,
|
||||
minWidth, minHeight, maxWidth, maxHeight, useHighQuality));
|
||||
if (d != nullptr && d->pimpl->openedOk())
|
||||
return d.release();
|
||||
#else
|
||||
ignoreUnused (index, minWidth, minHeight);
|
||||
ignoreUnused (maxWidth, maxHeight, useHighQuality);
|
||||
|
||||
// Use openDeviceAsync to open a camera device on iOS or Android.
|
||||
jassertfalse;
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CameraDevice::openDeviceAsync (int index, OpenCameraResultCallback resultCallback,
|
||||
int minWidth, int minHeight, int maxWidth, int maxHeight, bool useHighQuality)
|
||||
{
|
||||
jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
if (resultCallback == nullptr)
|
||||
{
|
||||
// A valid callback must be passed.
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
CameraFactory::getInstance().openCamera (index, std::move (resultCallback),
|
||||
minWidth, minHeight, maxWidth, maxHeight, useHighQuality);
|
||||
#else
|
||||
auto* device = openDevice (index, minWidth, minHeight, maxWidth, maxHeight, useHighQuality);
|
||||
|
||||
resultCallback (device, device != nullptr ? String() : "Could not open camera device");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace juce
|
260
deps/juce/modules/juce_video/capture/juce_CameraDevice.h
vendored
Normal file
260
deps/juce/modules/juce_video/capture/juce_CameraDevice.h
vendored
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_USE_CAMERA || DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Controls any video capture devices that might be available.
|
||||
|
||||
Use getAvailableDevices() to list the devices that are attached to the
|
||||
system, then call openDevice() or openDeviceAsync() to open one for use.
|
||||
Once you have a CameraDevice object, you can get a viewer component from it,
|
||||
and use its methods to stream to a file or capture still-frames.
|
||||
|
||||
@tags{Video}
|
||||
*/
|
||||
class JUCE_API CameraDevice
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~CameraDevice();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a list of the available cameras on this machine.
|
||||
|
||||
You can open one of these devices by calling openDevice() or openDeviceAsync().
|
||||
*/
|
||||
static StringArray getAvailableDevices();
|
||||
|
||||
/** Synchronously opens a camera device. This function should not be used on iOS or
|
||||
Android, use openDeviceAsync() instead.
|
||||
|
||||
The index parameter indicates which of the items returned by getAvailableDevices()
|
||||
to open.
|
||||
|
||||
The size constraints allow the method to choose between different resolutions if
|
||||
the camera supports this. If the resolution can't be specified (e.g. on the Mac)
|
||||
then these will be ignored.
|
||||
|
||||
On Mac, if highQuality is false, then the camera will be opened in preview mode
|
||||
which will allow the OS to drop frames if the computer cannot keep up in processing
|
||||
the frames.
|
||||
*/
|
||||
static CameraDevice* openDevice (int deviceIndex,
|
||||
int minWidth = 128, int minHeight = 64,
|
||||
int maxWidth = 1024, int maxHeight = 768,
|
||||
bool highQuality = true);
|
||||
|
||||
using OpenCameraResultCallback = std::function<void (CameraDevice*, const String& /*error*/)>;
|
||||
|
||||
/** Asynchronously opens a camera device on iOS (iOS 7+) or Android (API 21+).
|
||||
On other platforms, the function will simply call openDevice(). Upon completion,
|
||||
resultCallback will be invoked with valid CameraDevice* and an empty error
|
||||
String on success, or nullptr CameraDevice and a non-empty error String on failure.
|
||||
|
||||
This is the preferred method of opening a camera device, because it works on all
|
||||
platforms, whereas synchronous openDevice() does not work on iOS & Android.
|
||||
|
||||
The index parameter indicates which of the items returned by getAvailableDevices()
|
||||
to open.
|
||||
|
||||
The size constraints allow the method to choose between different resolutions if
|
||||
the camera supports this. If the resolution can't be specified then these will be
|
||||
ignored.
|
||||
|
||||
On iOS, if you want to switch a device, it is more efficient to open a new device
|
||||
before closing the older one, because this way both devices can share the same
|
||||
underlying camera session. Otherwise, the session needs to be close first, and this
|
||||
is a lengthy process that can take several seconds.
|
||||
|
||||
The Android implementation currently supports a maximum recording resolution of
|
||||
1080p. Choosing a larger size will result in larger pictures taken, but the video
|
||||
will be capped at 1080p.
|
||||
*/
|
||||
static void openDeviceAsync (int deviceIndex,
|
||||
OpenCameraResultCallback resultCallback,
|
||||
int minWidth = 128, int minHeight = 64,
|
||||
int maxWidth = 1024, int maxHeight = 768,
|
||||
bool highQuality = true);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the name of this device */
|
||||
const String& getName() const noexcept { return name; }
|
||||
|
||||
/** Creates a component that can be used to display a preview of the
|
||||
video from this camera.
|
||||
|
||||
Note: While you can change the size of the preview component, the actual
|
||||
preview display may be smaller than the size requested, because the correct
|
||||
aspect ratio is maintained automatically.
|
||||
*/
|
||||
Component* createViewerComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Triggers a still picture capture. Upon completion, pictureTakenCallback will
|
||||
be invoked on a message thread.
|
||||
|
||||
On Android, before calling takeStillPicture(), you need to create a preview with
|
||||
createViewerComponent() and you need to make it visible on screen.
|
||||
|
||||
Android does not support simultaneous video recording and still picture capture.
|
||||
*/
|
||||
void takeStillPicture (std::function<void (const Image&)> pictureTakenCallback);
|
||||
|
||||
/** Starts recording video to the specified file.
|
||||
|
||||
You should use getFileExtension() to find out the correct extension to
|
||||
use for your filename.
|
||||
|
||||
If the file exists, it will be deleted before the recording starts.
|
||||
|
||||
This method may not start recording instantly, so if you need to know the
|
||||
exact time at which the file begins, you can call getTimeOfFirstRecordedFrame()
|
||||
after the recording has finished.
|
||||
|
||||
The quality parameter can be 0, 1, or 2, to indicate low, medium, or high. It may
|
||||
or may not be used, depending on the driver.
|
||||
|
||||
On Android, before calling startRecordingToFile(), you need to create a preview with
|
||||
createViewerComponent() and you need to make it visible on screen.
|
||||
|
||||
The Android camera also requires exclusive access to the audio device, so make sure
|
||||
you close any open audio devices with AudioDeviceManager::closeAudioDevice() first.
|
||||
|
||||
Android does not support simultaneous video recording and still picture capture.
|
||||
|
||||
@see AudioDeviceManager::closeAudioDevice, AudioDeviceManager::restartLastAudioDevice
|
||||
*/
|
||||
void startRecordingToFile (const File& file, int quality = 2);
|
||||
|
||||
/** Stops recording, after a call to startRecordingToFile(). */
|
||||
void stopRecording();
|
||||
|
||||
/** Returns the file extension that should be used for the files
|
||||
that you pass to startRecordingToFile().
|
||||
|
||||
This may be platform-specific, e.g. ".mov" or ".avi".
|
||||
*/
|
||||
static String getFileExtension();
|
||||
|
||||
/** After calling stopRecording(), this method can be called to return the timestamp
|
||||
of the first frame that was written to the file.
|
||||
*/
|
||||
Time getTimeOfFirstRecordedFrame() const;
|
||||
|
||||
/** Set this callback to be notified whenever an error occurs. You may need to close
|
||||
and reopen the device to be able to use it further. */
|
||||
std::function<void (const String& /*error*/)> onErrorOccurred;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Receives callbacks with individual frames from a CameraDevice. It is mainly
|
||||
useful for processing multiple frames that has to be done as quickly as
|
||||
possible. The callbacks can be called from any thread.
|
||||
|
||||
If you just need to take one picture, you should use takeStillPicture() instead.
|
||||
|
||||
@see CameraDevice::addListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
Listener() {}
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** This method is called when a new image arrives.
|
||||
|
||||
This may be called by any thread, so be careful about thread-safety,
|
||||
and make sure that you process the data as quickly as possible to
|
||||
avoid glitching!
|
||||
|
||||
Simply add a listener to be continuously notified about new frames becoming
|
||||
available and remove the listener when you no longer need new frames.
|
||||
|
||||
If you just need to take one picture, use takeStillPicture() instead.
|
||||
|
||||
@see CameraDevice::takeStillPicture
|
||||
*/
|
||||
virtual void imageReceived (const Image& image) = 0;
|
||||
};
|
||||
|
||||
/** Adds a listener to receive images from the camera.
|
||||
|
||||
Be very careful not to delete the listener without first removing it by calling
|
||||
removeListener().
|
||||
*/
|
||||
void addListener (Listener* listenerToAdd);
|
||||
|
||||
/** Removes a listener that was previously added with addListener(). */
|
||||
void removeListener (Listener* listenerToRemove);
|
||||
|
||||
private:
|
||||
String name;
|
||||
|
||||
struct Pimpl;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
struct ViewerComponent;
|
||||
friend struct ViewerComponent;
|
||||
|
||||
CameraDevice (const String& name, int index,
|
||||
int minWidth, int minHeight, int maxWidth, int maxHeight, bool highQuality);
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
class CameraFactory;
|
||||
#endif
|
||||
|
||||
#if JUCE_ANDROID
|
||||
friend void juce_cameraDeviceStateClosed (int64);
|
||||
friend void juce_cameraDeviceStateDisconnected (int64);
|
||||
friend void juce_cameraDeviceStateError (int64, int);
|
||||
friend void juce_cameraDeviceStateOpened (int64, void*);
|
||||
|
||||
friend void juce_cameraCaptureSessionActive (int64, void*);
|
||||
friend void juce_cameraCaptureSessionClosed (int64, void*);
|
||||
friend void juce_cameraCaptureSessionConfigureFailed (int64, void*);
|
||||
friend void juce_cameraCaptureSessionConfigured (int64, void*);
|
||||
friend void juce_cameraCaptureSessionReady (int64, void*);
|
||||
|
||||
friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*);
|
||||
friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*);
|
||||
friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*);
|
||||
friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int);
|
||||
friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64);
|
||||
friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64);
|
||||
|
||||
friend void juce_deviceOrientationChanged (int64, int);
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CameraDevice)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
73
deps/juce/modules/juce_video/juce_video.cpp
vendored
Normal file
73
deps/juce/modules/juce_video/juce_video.cpp
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifdef JUCE_VIDEO_H_INCLUDED
|
||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've
|
||||
already included any other headers - just put it inside a file on its own, possibly with your config
|
||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
|
||||
header files that the compiler may be using.
|
||||
*/
|
||||
#error "Incorrect use of JUCE cpp file"
|
||||
#endif
|
||||
|
||||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1
|
||||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
|
||||
|
||||
#include "juce_video.h"
|
||||
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AVKit/AVKit.h>
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_WINDOWS && ! JUCE_MINGW
|
||||
/* If you're using the camera classes, you'll need access to a few DirectShow headers.
|
||||
These files are provided in the normal Windows SDK. */
|
||||
#include <dshow.h>
|
||||
#include <dshowasf.h>
|
||||
#include <evr.h>
|
||||
|
||||
#if ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
|
||||
#pragma comment (lib, "strmiids.lib")
|
||||
|
||||
#if JUCE_USE_CAMERA
|
||||
#pragma comment (lib, "Strmiids.lib")
|
||||
#pragma comment (lib, "wmvcore.lib")
|
||||
#endif
|
||||
|
||||
#if JUCE_MEDIAFOUNDATION
|
||||
#pragma comment (lib, "mfuuid.lib")
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#include "playback/juce_VideoComponent.cpp"
|
||||
|
||||
#if JUCE_USE_CAMERA
|
||||
#include "capture/juce_CameraDevice.cpp"
|
||||
#endif
|
103
deps/juce/modules/juce_video/juce_video.h
vendored
Normal file
103
deps/juce/modules/juce_video/juce_video.h
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this module, and is read by
|
||||
the Projucer to automatically generate project code that uses it.
|
||||
For details about the syntax and how to create or use a module, see the
|
||||
JUCE Module Format.md file.
|
||||
|
||||
|
||||
BEGIN_JUCE_MODULE_DECLARATION
|
||||
|
||||
ID: juce_video
|
||||
vendor: juce
|
||||
version: 6.1.2
|
||||
name: JUCE video playback and capture classes
|
||||
description: Classes for playing video and capturing camera input.
|
||||
website: http://www.juce.com/juce
|
||||
license: GPL/Commercial
|
||||
minimumCppStandard: 14
|
||||
|
||||
dependencies: juce_gui_extra
|
||||
OSXFrameworks: AVKit AVFoundation CoreMedia
|
||||
iOSFrameworks: AVKit AVFoundation CoreMedia
|
||||
|
||||
END_JUCE_MODULE_DECLARATION
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
#define JUCE_VIDEO_H_INCLUDED
|
||||
|
||||
//==============================================================================
|
||||
#include <juce_gui_extra/juce_gui_extra.h>
|
||||
|
||||
//==============================================================================
|
||||
/** Config: JUCE_USE_CAMERA
|
||||
Enables camera support using the CameraDevice class (Mac, Windows, iOS, Android).
|
||||
*/
|
||||
#ifndef JUCE_USE_CAMERA
|
||||
#define JUCE_USE_CAMERA 0
|
||||
#endif
|
||||
|
||||
#ifndef JUCE_CAMERA_LOG_ENABLED
|
||||
#define JUCE_CAMERA_LOG_ENABLED 0
|
||||
#endif
|
||||
|
||||
#if JUCE_CAMERA_LOG_ENABLED
|
||||
#define JUCE_CAMERA_LOG(x) DBG(x)
|
||||
#else
|
||||
#define JUCE_CAMERA_LOG(x) {}
|
||||
#endif
|
||||
|
||||
#if ! (JUCE_MAC || JUCE_WINDOWS || JUCE_IOS || JUCE_ANDROID)
|
||||
#undef JUCE_USE_CAMERA
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** 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"
|
26
deps/juce/modules/juce_video/juce_video.mm
vendored
Normal file
26
deps/juce/modules/juce_video/juce_video.mm
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "juce_video.cpp"
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.hardware.camera2.CameraCaptureSession;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.TotalCaptureResult;
|
||||
import android.hardware.camera2.CaptureFailure;
|
||||
import android.hardware.camera2.CaptureResult;
|
||||
|
||||
public class CameraCaptureSessionCaptureCallback extends CameraCaptureSession.CaptureCallback
|
||||
{
|
||||
private native void cameraCaptureSessionCaptureCompleted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result);
|
||||
private native void cameraCaptureSessionCaptureFailed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureFailure failure);
|
||||
private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult);
|
||||
private native void cameraCaptureSessionCaptureStarted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber);
|
||||
private native void cameraCaptureSessionCaptureSequenceAborted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId);
|
||||
private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber);
|
||||
|
||||
CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview)
|
||||
{
|
||||
host = hostToUse;
|
||||
preview = shouldBePreview;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request,
|
||||
TotalCaptureResult result)
|
||||
{
|
||||
cameraCaptureSessionCaptureCompleted (host, preview, session, request, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)
|
||||
{
|
||||
cameraCaptureSessionCaptureFailed (host, preview, session, request, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request,
|
||||
CaptureResult partialResult)
|
||||
{
|
||||
cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId)
|
||||
{
|
||||
cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber)
|
||||
{
|
||||
cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp,
|
||||
long frameNumber)
|
||||
{
|
||||
cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber);
|
||||
}
|
||||
|
||||
private long host;
|
||||
private boolean preview;
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.hardware.camera2.CameraCaptureSession;
|
||||
|
||||
public class CameraCaptureSessionStateCallback extends CameraCaptureSession.StateCallback
|
||||
{
|
||||
private native void cameraCaptureSessionActive (long host, CameraCaptureSession session);
|
||||
|
||||
private native void cameraCaptureSessionClosed (long host, CameraCaptureSession session);
|
||||
|
||||
private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session);
|
||||
|
||||
private native void cameraCaptureSessionConfigured (long host, CameraCaptureSession session);
|
||||
|
||||
private native void cameraCaptureSessionReady (long host, CameraCaptureSession session);
|
||||
|
||||
CameraCaptureSessionStateCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActive (CameraCaptureSession session)
|
||||
{
|
||||
cameraCaptureSessionActive (host, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed (CameraCaptureSession session)
|
||||
{
|
||||
cameraCaptureSessionClosed (host, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigureFailed (CameraCaptureSession session)
|
||||
{
|
||||
cameraCaptureSessionConfigureFailed (host, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigured (CameraCaptureSession session)
|
||||
{
|
||||
cameraCaptureSessionConfigured (host, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady (CameraCaptureSession session)
|
||||
{
|
||||
cameraCaptureSessionReady (host, session);
|
||||
}
|
||||
|
||||
private long host;
|
||||
}
|
67
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/CameraDeviceStateCallback.java
vendored
Normal file
67
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/CameraDeviceStateCallback.java
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.hardware.camera2.CameraDevice;
|
||||
|
||||
public class CameraDeviceStateCallback extends CameraDevice.StateCallback
|
||||
{
|
||||
private native void cameraDeviceStateClosed (long host, CameraDevice camera);
|
||||
private native void cameraDeviceStateDisconnected (long host, CameraDevice camera);
|
||||
private native void cameraDeviceStateError (long host, CameraDevice camera, int error);
|
||||
private native void cameraDeviceStateOpened (long host, CameraDevice camera);
|
||||
|
||||
CameraDeviceStateCallback (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed (CameraDevice camera)
|
||||
{
|
||||
cameraDeviceStateClosed (host, camera);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected (CameraDevice camera)
|
||||
{
|
||||
cameraDeviceStateDisconnected (host, camera);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError (CameraDevice camera, int error)
|
||||
{
|
||||
cameraDeviceStateError (host, camera, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpened (CameraDevice camera)
|
||||
{
|
||||
cameraDeviceStateOpened (host, camera);
|
||||
}
|
||||
|
||||
private long host;
|
||||
}
|
49
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/JuceOrientationEventListener.java
vendored
Normal file
49
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/JuceOrientationEventListener.java
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.view.OrientationEventListener;
|
||||
import android.content.Context;
|
||||
|
||||
public class JuceOrientationEventListener extends OrientationEventListener
|
||||
{
|
||||
private native void deviceOrientationChanged (long host, int orientation);
|
||||
|
||||
public JuceOrientationEventListener (long hostToUse, Context context, int rate)
|
||||
{
|
||||
super (context, rate);
|
||||
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrientationChanged (int orientation)
|
||||
{
|
||||
deviceOrientationChanged (host, orientation);
|
||||
}
|
||||
|
||||
private long host;
|
||||
}
|
78
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/MediaControllerCallback.java
vendored
Normal file
78
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/MediaControllerCallback.java
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.PlaybackState;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
//==============================================================================
|
||||
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;
|
||||
}
|
103
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/MediaSessionCallback.java
vendored
Normal file
103
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/MediaSessionCallback.java
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.media.session.MediaSession;
|
||||
|
||||
import java.lang.String;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.content.Intent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
//==============================================================================
|
||||
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;
|
||||
}
|
62
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/SystemVolumeObserver.java
vendored
Normal file
62
deps/juce/modules/juce_video/native/java/app/com/rmsl/juce/SystemVolumeObserver.java
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.database.ContentObserver;
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
|
||||
//==============================================================================
|
||||
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;
|
||||
}
|
3285
deps/juce/modules/juce_video/native/juce_android_CameraDevice.h
vendored
Normal file
3285
deps/juce/modules/juce_video/native/juce_android_CameraDevice.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1853
deps/juce/modules/juce_video/native/juce_android_Video.h
vendored
Normal file
1853
deps/juce/modules/juce_video/native/juce_android_Video.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1340
deps/juce/modules/juce_video/native/juce_ios_CameraDevice.h
vendored
Normal file
1340
deps/juce/modules/juce_video/native/juce_ios_CameraDevice.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
582
deps/juce/modules/juce_video/native/juce_mac_CameraDevice.h
vendored
Normal file
582
deps/juce/modules/juce_video/native/juce_mac_CameraDevice.h
vendored
Normal file
@ -0,0 +1,582 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
struct CameraDevice::Pimpl
|
||||
{
|
||||
#if defined (MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15
|
||||
#define JUCE_USE_NEW_APPLE_CAMERA_API 1
|
||||
#else
|
||||
#define JUCE_USE_NEW_APPLE_CAMERA_API 0
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_NEW_APPLE_CAMERA_API
|
||||
class PostCatalinaPhotoOutput
|
||||
{
|
||||
public:
|
||||
PostCatalinaPhotoOutput()
|
||||
{
|
||||
static PhotoOutputDelegateClass cls;
|
||||
delegate.reset ([cls.createInstance() init]);
|
||||
}
|
||||
|
||||
void addImageCapture (AVCaptureSession* s)
|
||||
{
|
||||
if (imageOutput != nil)
|
||||
return;
|
||||
|
||||
imageOutput = [[AVCapturePhotoOutput alloc] init];
|
||||
[s addOutput: imageOutput];
|
||||
}
|
||||
|
||||
void removeImageCapture (AVCaptureSession* s)
|
||||
{
|
||||
if (imageOutput == nil)
|
||||
return;
|
||||
|
||||
[s removeOutput: imageOutput];
|
||||
[imageOutput release];
|
||||
imageOutput = nil;
|
||||
}
|
||||
|
||||
NSArray<AVCaptureConnection*>* getConnections() const
|
||||
{
|
||||
if (imageOutput != nil)
|
||||
return imageOutput.connections;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
void triggerImageCapture (Pimpl& p)
|
||||
{
|
||||
if (imageOutput == nil)
|
||||
return;
|
||||
|
||||
PhotoOutputDelegateClass::setOwner (delegate.get(), &p);
|
||||
|
||||
[imageOutput capturePhotoWithSettings: [AVCapturePhotoSettings photoSettings]
|
||||
delegate: id<AVCapturePhotoCaptureDelegate> (delegate.get())];
|
||||
}
|
||||
|
||||
static NSArray* getAvailableDevices()
|
||||
{
|
||||
auto* discovery = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes: @[AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
||||
AVCaptureDeviceTypeExternalUnknown]
|
||||
mediaType: AVMediaTypeVideo
|
||||
position: AVCaptureDevicePositionUnspecified];
|
||||
return [discovery devices];
|
||||
}
|
||||
|
||||
private:
|
||||
class PhotoOutputDelegateClass : public ObjCClass<NSObject>
|
||||
{
|
||||
public:
|
||||
PhotoOutputDelegateClass() : ObjCClass<NSObject> ("PhotoOutputDelegateClass_")
|
||||
{
|
||||
addMethod (@selector (captureOutput:didFinishProcessingPhoto:error:), didFinishProcessingPhoto, "v@:@@@");
|
||||
addIvar<Pimpl*> ("owner");
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void didFinishProcessingPhoto (id self, SEL, AVCapturePhotoOutput*, AVCapturePhoto* photo, NSError* error)
|
||||
{
|
||||
if (error != nil)
|
||||
{
|
||||
String errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
|
||||
ignoreUnused (errorString);
|
||||
|
||||
JUCE_CAMERA_LOG ("Still picture capture failed, error: " + errorString);
|
||||
jassertfalse;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto* imageData = [photo fileDataRepresentation];
|
||||
auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
|
||||
|
||||
getOwner (self).imageCaptureFinished (image);
|
||||
}
|
||||
|
||||
static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
|
||||
static void setOwner (id self, Pimpl* t) { object_setInstanceVariable (self, "owner", t); }
|
||||
};
|
||||
|
||||
AVCapturePhotoOutput* imageOutput = nil;
|
||||
std::unique_ptr<NSObject, NSObjectDeleter> delegate;
|
||||
};
|
||||
#else
|
||||
struct PreCatalinaStillImageOutput
|
||||
{
|
||||
public:
|
||||
void addImageCapture (AVCaptureSession* s)
|
||||
{
|
||||
if (imageOutput != nil)
|
||||
return;
|
||||
|
||||
const auto codecType =
|
||||
#if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13
|
||||
AVVideoCodecTypeJPEG;
|
||||
#else
|
||||
AVVideoCodecJPEG;
|
||||
#endif
|
||||
|
||||
imageOutput = [[AVCaptureStillImageOutput alloc] init];
|
||||
auto imageSettings = [[NSDictionary alloc] initWithObjectsAndKeys: codecType, AVVideoCodecKey, nil];
|
||||
[imageOutput setOutputSettings: imageSettings];
|
||||
[imageSettings release];
|
||||
[s addOutput: imageOutput];
|
||||
}
|
||||
|
||||
void removeImageCapture (AVCaptureSession* s)
|
||||
{
|
||||
if (imageOutput == nil)
|
||||
return;
|
||||
|
||||
[s removeOutput: imageOutput];
|
||||
[imageOutput release];
|
||||
imageOutput = nil;
|
||||
}
|
||||
|
||||
NSArray<AVCaptureConnection*>* getConnections() const
|
||||
{
|
||||
if (imageOutput != nil)
|
||||
return imageOutput.connections;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
void triggerImageCapture (Pimpl& p)
|
||||
{
|
||||
if (auto* videoConnection = p.getVideoConnection())
|
||||
{
|
||||
[imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection
|
||||
completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error)
|
||||
{
|
||||
if (error != nil)
|
||||
{
|
||||
JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: sampleBuffer];
|
||||
auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
|
||||
p.imageCaptureFinished (image);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
static NSArray* getAvailableDevices()
|
||||
{
|
||||
return [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo];
|
||||
}
|
||||
|
||||
private:
|
||||
AVCaptureStillImageOutput* imageOutput = nil;
|
||||
};
|
||||
#endif
|
||||
|
||||
Pimpl (CameraDevice& ownerToUse, const String& deviceNameToUse, int /*index*/,
|
||||
int /*minWidth*/, int /*minHeight*/,
|
||||
int /*maxWidth*/, int /*maxHeight*/,
|
||||
bool useHighQuality)
|
||||
: owner (ownerToUse),
|
||||
deviceName (deviceNameToUse)
|
||||
{
|
||||
session = [[AVCaptureSession alloc] init];
|
||||
|
||||
session.sessionPreset = useHighQuality ? AVCaptureSessionPresetHigh
|
||||
: AVCaptureSessionPresetMedium;
|
||||
|
||||
refreshConnections();
|
||||
|
||||
static DelegateClass cls;
|
||||
callbackDelegate = (id<AVCaptureFileOutputRecordingDelegate>) [cls.createInstance() init];
|
||||
DelegateClass::setOwner (callbackDelegate, this);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[[NSNotificationCenter defaultCenter] addObserver: callbackDelegate
|
||||
selector: @selector (captureSessionRuntimeError:)
|
||||
name: AVCaptureSessionRuntimeErrorNotification
|
||||
object: session];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: callbackDelegate];
|
||||
|
||||
[session stopRunning];
|
||||
removeInput();
|
||||
removeImageCapture();
|
||||
removeMovieCapture();
|
||||
[session release];
|
||||
[callbackDelegate release];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool openedOk() const noexcept { return openingError.isEmpty(); }
|
||||
|
||||
void startSession()
|
||||
{
|
||||
if (! [session isRunning])
|
||||
[session startRunning];
|
||||
}
|
||||
|
||||
void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse)
|
||||
{
|
||||
if (pictureTakenCallbackToUse == nullptr)
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
pictureTakenCallback = std::move (pictureTakenCallbackToUse);
|
||||
|
||||
triggerImageCapture();
|
||||
}
|
||||
|
||||
void startRecordingToFile (const File& file, int /*quality*/)
|
||||
{
|
||||
stopRecording();
|
||||
refreshIfNeeded();
|
||||
firstPresentationTime = Time::getCurrentTime();
|
||||
file.deleteFile();
|
||||
|
||||
startSession();
|
||||
isRecording = true;
|
||||
[fileOutput startRecordingToOutputFileURL: createNSURLFromFile (file)
|
||||
recordingDelegate: callbackDelegate];
|
||||
}
|
||||
|
||||
void stopRecording()
|
||||
{
|
||||
if (isRecording)
|
||||
{
|
||||
[fileOutput stopRecording];
|
||||
isRecording = false;
|
||||
}
|
||||
}
|
||||
|
||||
Time getTimeOfFirstRecordedFrame() const
|
||||
{
|
||||
return firstPresentationTime;
|
||||
}
|
||||
|
||||
void addListener (CameraDevice::Listener* listenerToAdd)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
listeners.add (listenerToAdd);
|
||||
|
||||
if (listeners.size() == 1)
|
||||
triggerImageCapture();
|
||||
}
|
||||
|
||||
void removeListener (CameraDevice::Listener* listenerToRemove)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
listeners.remove (listenerToRemove);
|
||||
}
|
||||
|
||||
static StringArray getAvailableDevices()
|
||||
{
|
||||
auto* devices = decltype (imageOutput)::getAvailableDevices();
|
||||
|
||||
StringArray results;
|
||||
|
||||
for (AVCaptureDevice* device : devices)
|
||||
results.add (nsStringToJuce ([device localizedName]));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
AVCaptureSession* getCaptureSession()
|
||||
{
|
||||
return session;
|
||||
}
|
||||
|
||||
NSView* createVideoCapturePreview()
|
||||
{
|
||||
// The video preview must be created before the capture session is
|
||||
// started. Make sure you haven't called `addListener`,
|
||||
// `startRecordingToFile`, or `takeStillPicture` before calling this
|
||||
// function.
|
||||
jassert (! [session isRunning]);
|
||||
startSession();
|
||||
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
NSView* view = [[NSView alloc] init];
|
||||
[view setLayer: [AVCaptureVideoPreviewLayer layerWithSession: getCaptureSession()]];
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct DelegateClass : public ObjCClass<NSObject>
|
||||
{
|
||||
DelegateClass() : ObjCClass<NSObject> ("JUCECameraDelegate_")
|
||||
{
|
||||
addIvar<Pimpl*> ("owner");
|
||||
addProtocol (@protocol (AVCaptureFileOutputRecordingDelegate));
|
||||
|
||||
addMethod (@selector (captureOutput:didStartRecordingToOutputFileAtURL: fromConnections:), didStartRecordingToOutputFileAtURL, "v@:@@@");
|
||||
addMethod (@selector (captureOutput:didPauseRecordingToOutputFileAtURL: fromConnections:), didPauseRecordingToOutputFileAtURL, "v@:@@@");
|
||||
addMethod (@selector (captureOutput:didResumeRecordingToOutputFileAtURL: fromConnections:), didResumeRecordingToOutputFileAtURL, "v@:@@@");
|
||||
addMethod (@selector (captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:), willFinishRecordingToOutputFileAtURL, "v@:@@@@");
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (captureSessionRuntimeError:), sessionRuntimeError, "v@:@");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
|
||||
|
||||
private:
|
||||
static void didStartRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
|
||||
static void didPauseRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
|
||||
static void didResumeRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
|
||||
static void willFinishRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*, NSError*) {}
|
||||
|
||||
static void sessionRuntimeError (id self, SEL, NSNotification* notification)
|
||||
{
|
||||
JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
|
||||
|
||||
NSError* error = notification.userInfo[AVCaptureSessionErrorKey];
|
||||
auto errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
|
||||
getOwner (self).cameraSessionRuntimeError (errorString);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void addImageCapture()
|
||||
{
|
||||
imageOutput.addImageCapture (session);
|
||||
}
|
||||
|
||||
void addMovieCapture()
|
||||
{
|
||||
if (fileOutput == nil)
|
||||
{
|
||||
fileOutput = [[AVCaptureMovieFileOutput alloc] init];
|
||||
[session addOutput: fileOutput];
|
||||
}
|
||||
}
|
||||
|
||||
void removeImageCapture()
|
||||
{
|
||||
imageOutput.removeImageCapture (session);
|
||||
}
|
||||
|
||||
void removeMovieCapture()
|
||||
{
|
||||
if (fileOutput != nil)
|
||||
{
|
||||
[session removeOutput: fileOutput];
|
||||
[fileOutput release];
|
||||
fileOutput = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void removeCurrentSessionVideoInputs()
|
||||
{
|
||||
if (session != nil)
|
||||
{
|
||||
NSArray<AVCaptureDeviceInput*>* inputs = session.inputs;
|
||||
|
||||
for (AVCaptureDeviceInput* input : inputs)
|
||||
if ([input.device hasMediaType: AVMediaTypeVideo])
|
||||
[session removeInput:input];
|
||||
}
|
||||
}
|
||||
|
||||
void addInput()
|
||||
{
|
||||
if (currentInput == nil)
|
||||
{
|
||||
auto* availableDevices = decltype (imageOutput)::getAvailableDevices();
|
||||
|
||||
for (AVCaptureDevice* device : availableDevices)
|
||||
{
|
||||
if (deviceName == nsStringToJuce ([device localizedName]))
|
||||
{
|
||||
removeCurrentSessionVideoInputs();
|
||||
|
||||
NSError* err = nil;
|
||||
AVCaptureDeviceInput* inputDevice = [[AVCaptureDeviceInput alloc] initWithDevice: device
|
||||
error: &err];
|
||||
|
||||
jassert (err == nil);
|
||||
|
||||
if ([session canAddInput: inputDevice])
|
||||
{
|
||||
[session addInput: inputDevice];
|
||||
currentInput = inputDevice;
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
[inputDevice release];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeInput()
|
||||
{
|
||||
if (currentInput != nil)
|
||||
{
|
||||
[session removeInput: currentInput];
|
||||
[currentInput release];
|
||||
currentInput = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void refreshConnections()
|
||||
{
|
||||
[session beginConfiguration];
|
||||
removeInput();
|
||||
removeImageCapture();
|
||||
removeMovieCapture();
|
||||
addInput();
|
||||
addImageCapture();
|
||||
addMovieCapture();
|
||||
[session commitConfiguration];
|
||||
}
|
||||
|
||||
void refreshIfNeeded()
|
||||
{
|
||||
if (getVideoConnection() == nullptr)
|
||||
refreshConnections();
|
||||
}
|
||||
|
||||
AVCaptureConnection* getVideoConnection() const
|
||||
{
|
||||
auto* connections = imageOutput.getConnections();
|
||||
|
||||
if (connections != nil)
|
||||
for (AVCaptureConnection* connection in connections)
|
||||
if ([connection isActive] && [connection isEnabled])
|
||||
for (AVCaptureInputPort* port in [connection inputPorts])
|
||||
if ([[port mediaType] isEqual: AVMediaTypeVideo])
|
||||
return connection;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
void imageCaptureFinished (const Image& image)
|
||||
{
|
||||
handleImageCapture (image);
|
||||
|
||||
MessageManager::callAsync ([weakRef = WeakReference<Pimpl> { this }, image]() mutable
|
||||
{
|
||||
if (weakRef != nullptr && weakRef->pictureTakenCallback != nullptr)
|
||||
weakRef->pictureTakenCallback (image);
|
||||
});
|
||||
}
|
||||
|
||||
void handleImageCapture (const Image& image)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
listeners.call ([=] (Listener& l) { l.imageReceived (image); });
|
||||
|
||||
if (! listeners.isEmpty())
|
||||
triggerImageCapture();
|
||||
}
|
||||
|
||||
void triggerImageCapture()
|
||||
{
|
||||
refreshIfNeeded();
|
||||
|
||||
startSession();
|
||||
|
||||
if (auto* videoConnection = getVideoConnection())
|
||||
imageOutput.triggerImageCapture (*this);
|
||||
}
|
||||
|
||||
void cameraSessionRuntimeError (const String& error)
|
||||
{
|
||||
JUCE_CAMERA_LOG ("cameraSessionRuntimeError(), error = " + error);
|
||||
|
||||
if (owner.onErrorOccurred != nullptr)
|
||||
owner.onErrorOccurred (error);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CameraDevice& owner;
|
||||
String deviceName;
|
||||
|
||||
AVCaptureSession* session = nil;
|
||||
AVCaptureMovieFileOutput* fileOutput = nil;
|
||||
#if JUCE_USE_NEW_APPLE_CAMERA_API
|
||||
PostCatalinaPhotoOutput imageOutput;
|
||||
#else
|
||||
PreCatalinaStillImageOutput imageOutput;
|
||||
#endif
|
||||
AVCaptureDeviceInput* currentInput = nil;
|
||||
|
||||
id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil;
|
||||
String openingError;
|
||||
Time firstPresentationTime;
|
||||
bool isRecording = false;
|
||||
|
||||
CriticalSection listenerLock;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
std::function<void (const Image&)> pictureTakenCallback = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
|
||||
JUCE_DECLARE_NON_COPYABLE (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct CameraDevice::ViewerComponent : public NSViewComponent
|
||||
{
|
||||
ViewerComponent (CameraDevice& device)
|
||||
{
|
||||
setView (device.pimpl->createVideoCapturePreview());
|
||||
}
|
||||
|
||||
~ViewerComponent()
|
||||
{
|
||||
setView (nil);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
|
||||
};
|
||||
|
||||
String CameraDevice::getFileExtension()
|
||||
{
|
||||
return ".mov";
|
||||
}
|
||||
|
||||
#undef JUCE_USE_NEW_APPLE_CAMERA_API
|
831
deps/juce/modules/juce_video/native/juce_mac_Video.h
vendored
Normal file
831
deps/juce/modules/juce_video/native/juce_mac_Video.h
vendored
Normal file
@ -0,0 +1,831 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_MAC
|
||||
using Base = NSViewComponent;
|
||||
#else
|
||||
using Base = UIViewComponent;
|
||||
#endif
|
||||
|
||||
struct VideoComponent::Pimpl : public Base
|
||||
{
|
||||
Pimpl (VideoComponent& ownerToUse, bool useNativeControlsIfAvailable)
|
||||
: owner (ownerToUse),
|
||||
playerController (*this, useNativeControlsIfAvailable)
|
||||
{
|
||||
setVisible (true);
|
||||
|
||||
auto* view = playerController.getView();
|
||||
setView (view);
|
||||
|
||||
#if JUCE_MAC
|
||||
[view setNextResponder: [view superview]];
|
||||
[view setWantsLayer: YES];
|
||||
#endif
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
close();
|
||||
setView (nil);
|
||||
}
|
||||
|
||||
Result load (const File& file)
|
||||
{
|
||||
auto r = load (createNSURLFromFile (file));
|
||||
|
||||
if (r.wasOk())
|
||||
currentFile = file;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Result load (const URL& url)
|
||||
{
|
||||
auto r = load ([NSURL URLWithString: juceStringToNS (url.toString (true))]);
|
||||
|
||||
if (r.wasOk())
|
||||
currentURL = url;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Result load (NSURL* url)
|
||||
{
|
||||
if (url != nil)
|
||||
{
|
||||
close();
|
||||
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();
|
||||
playerController.close();
|
||||
currentFile = File();
|
||||
currentURL = {};
|
||||
}
|
||||
|
||||
bool isOpen() const noexcept { return playerController.getPlayer() != nil; }
|
||||
bool isPlaying() const noexcept { return getSpeed() != 0; }
|
||||
|
||||
void play() noexcept { [playerController.getPlayer() play]; setSpeed (playSpeedMult); }
|
||||
void stop() noexcept { [playerController.getPlayer() pause]; }
|
||||
|
||||
void setPosition (double newPosition)
|
||||
{
|
||||
if (auto* p = playerController.getPlayer())
|
||||
{
|
||||
CMTime t = { (CMTimeValue) (100000.0 * newPosition),
|
||||
(CMTimeScale) 100000, kCMTimeFlags_Valid, {} };
|
||||
|
||||
[p seekToTime: t
|
||||
toleranceBefore: kCMTimeZero
|
||||
toleranceAfter: kCMTimeZero];
|
||||
}
|
||||
}
|
||||
|
||||
double getPosition() const
|
||||
{
|
||||
if (auto* p = playerController.getPlayer())
|
||||
return toSeconds ([p currentTime]);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void setSpeed (double 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 = playerController.getPlayer())
|
||||
return [p rate];
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Rectangle<int> getNativeSize() const
|
||||
{
|
||||
if (auto* p = playerController.getPlayer())
|
||||
{
|
||||
auto s = [[p currentItem] presentationSize];
|
||||
return { (int) s.width, (int) s.height };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
double getDuration() const
|
||||
{
|
||||
if (auto* p = playerController.getPlayer())
|
||||
return toSeconds ([[p currentItem] duration]);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void setVolume (float newVolume)
|
||||
{
|
||||
[playerController.getPlayer() setVolume: newVolume];
|
||||
}
|
||||
|
||||
float getVolume() const
|
||||
{
|
||||
if (auto* p = playerController.getPlayer())
|
||||
return [p volume];
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
File currentFile;
|
||||
URL currentURL;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
template <typename Derived>
|
||||
class PlayerControllerBase
|
||||
{
|
||||
public:
|
||||
~PlayerControllerBase()
|
||||
{
|
||||
detachPlayerStatusObserver();
|
||||
detachPlaybackObserver();
|
||||
}
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
struct JucePlayerStatusObserverClass : public ObjCClass<NSObject>
|
||||
{
|
||||
JucePlayerStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerStatusObserverClass_")
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged, "v@:@@@?");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
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<NSString*, 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_")
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (processNotification:), notificationReceived, "v@:@");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
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_")
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged, "v@:@@@?");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
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<NSString*, 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.get());
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
case AVKeyValueStatusUnknown:
|
||||
case AVKeyValueStatusLoading:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
MessageManager::callAsync ([url, preparedPlayer, r,
|
||||
safeThis = WeakReference<PlayerAsyncInitialiser> { this }]() 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()
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[[NSNotificationCenter defaultCenter] addObserver: playerItemPlaybackStatusObserver.get()
|
||||
selector: @selector (processNotification:)
|
||||
name: AVPlayerItemDidPlayToEndTimeNotification
|
||||
object: [crtp().getPlayer() currentItem]];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
void detachPlaybackObserver()
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: playerItemPlaybackStatusObserver.get()];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
MessageManager::callAsync ([safeThis = WeakReference<PlayerControllerBase> { this }]() 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 (getPlayer() != nil && player != getPlayer()) {
|
||||
// must detach from this player properly
|
||||
detachPlayerStatusObserver();
|
||||
detachPlaybackObserver();
|
||||
}
|
||||
|
||||
#if ! JUCE_32BIT
|
||||
if (useNativeControls)
|
||||
[playerView setPlayer: player];
|
||||
else
|
||||
#endif
|
||||
[playerLayer setPlayer: player];
|
||||
|
||||
if (player != nil)
|
||||
{
|
||||
attachPlayerStatusObserver();
|
||||
attachPlaybackObserver();
|
||||
}
|
||||
else
|
||||
{
|
||||
detachPlayerStatusObserver();
|
||||
detachPlaybackObserver();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
//==============================================================================
|
||||
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 (getPlayer() != nil && playerToUse != getPlayer()) {
|
||||
// must detach from this player properly
|
||||
detachPlayerStatusObserver();
|
||||
detachPlaybackObserver();
|
||||
}
|
||||
|
||||
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<void> (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
|
||||
|
||||
//==============================================================================
|
||||
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)
|
||||
};
|
840
deps/juce/modules/juce_video/native/juce_win32_CameraDevice.h
vendored
Normal file
840
deps/juce/modules/juce_video/native/juce_win32_CameraDevice.h
vendored
Normal file
@ -0,0 +1,840 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
interface ISampleGrabberCB : public IUnknown
|
||||
{
|
||||
JUCE_COMCALL SampleCB (double, IMediaSample*) = 0;
|
||||
JUCE_COMCALL BufferCB (double, BYTE*, long) = 0;
|
||||
};
|
||||
|
||||
interface ISampleGrabber : public IUnknown
|
||||
{
|
||||
JUCE_COMCALL SetOneShot (BOOL) = 0;
|
||||
JUCE_COMCALL SetMediaType (const AM_MEDIA_TYPE*) = 0;
|
||||
JUCE_COMCALL GetConnectedMediaType (AM_MEDIA_TYPE*) = 0;
|
||||
JUCE_COMCALL SetBufferSamples (BOOL) = 0;
|
||||
JUCE_COMCALL GetCurrentBuffer (long*, long*) = 0;
|
||||
JUCE_COMCALL GetCurrentSample (IMediaSample**) = 0;
|
||||
JUCE_COMCALL SetCallback (ISampleGrabberCB*, long) = 0;
|
||||
};
|
||||
|
||||
static const IID IID_ISampleGrabberCB = { 0x0579154A, 0x2B53, 0x4994, { 0xB0, 0xD0, 0xE7, 0x73, 0x14, 0x8E, 0xFF, 0x85 } };
|
||||
static const IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce, { 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } };
|
||||
static const CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
|
||||
static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
|
||||
|
||||
|
||||
struct CameraDevice::Pimpl : public ChangeBroadcaster
|
||||
{
|
||||
Pimpl (CameraDevice& ownerToUse, const String&, int index,
|
||||
int minWidth, int minHeight, int maxWidth, int maxHeight,
|
||||
bool /*highQuality*/)
|
||||
: owner (ownerToUse)
|
||||
{
|
||||
HRESULT hr = captureGraphBuilder.CoCreateInstance (CLSID_CaptureGraphBuilder2);
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
filter = enumerateCameras (nullptr, index);
|
||||
if (filter == nullptr)
|
||||
return;
|
||||
|
||||
hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
hr = captureGraphBuilder->SetFiltergraph (graphBuilder);
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
mediaControl = graphBuilder.getInterface<IMediaControl>();
|
||||
if (mediaControl == nullptr)
|
||||
return;
|
||||
|
||||
{
|
||||
ComSmartPtr<IAMStreamConfig> streamConfig;
|
||||
|
||||
hr = captureGraphBuilder->FindInterface (&PIN_CATEGORY_CAPTURE, nullptr, filter,
|
||||
IID_IAMStreamConfig, (void**) streamConfig.resetAndGetPointerAddress());
|
||||
|
||||
if (streamConfig != nullptr)
|
||||
{
|
||||
getVideoSizes (streamConfig);
|
||||
|
||||
if (! selectVideoSize (streamConfig, minWidth, minHeight, maxWidth, maxHeight))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
hr = graphBuilder->AddFilter (filter, _T("Video Capture"));
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
hr = smartTee.CoCreateInstance (CLSID_SmartTee);
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
hr = graphBuilder->AddFilter (smartTee, _T("Smart Tee"));
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
if (! connectFilters (filter, smartTee))
|
||||
return;
|
||||
|
||||
ComSmartPtr<IBaseFilter> sampleGrabberBase;
|
||||
hr = sampleGrabberBase.CoCreateInstance (CLSID_SampleGrabber);
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
hr = sampleGrabberBase.QueryInterface (IID_ISampleGrabber, sampleGrabber);
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
{
|
||||
AM_MEDIA_TYPE mt = {};
|
||||
mt.majortype = MEDIATYPE_Video;
|
||||
mt.subtype = MEDIASUBTYPE_RGB24;
|
||||
mt.formattype = FORMAT_VideoInfo;
|
||||
sampleGrabber->SetMediaType (&mt);
|
||||
}
|
||||
|
||||
callback = new GrabberCallback (*this);
|
||||
hr = sampleGrabber->SetCallback (callback, 1);
|
||||
|
||||
hr = graphBuilder->AddFilter (sampleGrabberBase, _T("Sample Grabber"));
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
ComSmartPtr<IPin> grabberInputPin;
|
||||
if (! (getPin (smartTee, PINDIR_OUTPUT, smartTeeCaptureOutputPin, "capture")
|
||||
&& getPin (smartTee, PINDIR_OUTPUT, smartTeePreviewOutputPin, "preview")
|
||||
&& getPin (sampleGrabberBase, PINDIR_INPUT, grabberInputPin)))
|
||||
return;
|
||||
|
||||
hr = graphBuilder->Connect (smartTeePreviewOutputPin, grabberInputPin);
|
||||
if (FAILED (hr))
|
||||
return;
|
||||
|
||||
AM_MEDIA_TYPE mt = {};
|
||||
hr = sampleGrabber->GetConnectedMediaType (&mt);
|
||||
|
||||
if (auto* pVih = unalignedPointerCast<VIDEOINFOHEADER*> (mt.pbFormat))
|
||||
{
|
||||
width = pVih->bmiHeader.biWidth;
|
||||
height = pVih->bmiHeader.biHeight;
|
||||
}
|
||||
|
||||
ComSmartPtr<IBaseFilter> nullFilter;
|
||||
hr = nullFilter.CoCreateInstance (CLSID_NullRenderer);
|
||||
hr = graphBuilder->AddFilter (nullFilter, _T("Null Renderer"));
|
||||
|
||||
if (connectFilters (sampleGrabberBase, nullFilter)
|
||||
&& addGraphToRot())
|
||||
{
|
||||
activeImage = Image (Image::RGB, width, height, true);
|
||||
loadingImage = Image (Image::RGB, width, height, true);
|
||||
|
||||
openedSuccessfully = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
if (mediaControl != nullptr)
|
||||
mediaControl->Stop();
|
||||
|
||||
removeGraphFromRot();
|
||||
disconnectAnyViewers();
|
||||
|
||||
if (sampleGrabber != nullptr)
|
||||
{
|
||||
sampleGrabber->SetCallback (nullptr, 0);
|
||||
sampleGrabber = nullptr;
|
||||
}
|
||||
|
||||
callback = nullptr;
|
||||
graphBuilder = nullptr;
|
||||
mediaControl = nullptr;
|
||||
filter = nullptr;
|
||||
captureGraphBuilder = nullptr;
|
||||
smartTee = nullptr;
|
||||
smartTeePreviewOutputPin = nullptr;
|
||||
smartTeeCaptureOutputPin = nullptr;
|
||||
asfWriter = nullptr;
|
||||
}
|
||||
|
||||
bool openedOk() const noexcept { return openedSuccessfully; }
|
||||
|
||||
void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse)
|
||||
{
|
||||
{
|
||||
const ScopedLock sl (pictureTakenCallbackLock);
|
||||
|
||||
jassert (pictureTakenCallbackToUse != nullptr);
|
||||
|
||||
if (pictureTakenCallbackToUse == nullptr)
|
||||
return;
|
||||
|
||||
pictureTakenCallback = std::move (pictureTakenCallbackToUse);
|
||||
}
|
||||
|
||||
addUser();
|
||||
}
|
||||
|
||||
void startRecordingToFile (const File& file, int quality)
|
||||
{
|
||||
addUser();
|
||||
isRecording = createFileCaptureFilter (file, quality);
|
||||
}
|
||||
|
||||
void stopRecording()
|
||||
{
|
||||
if (isRecording)
|
||||
{
|
||||
removeFileCaptureFilter();
|
||||
removeUser();
|
||||
isRecording = false;
|
||||
}
|
||||
}
|
||||
|
||||
Time getTimeOfFirstRecordedFrame() const
|
||||
{
|
||||
return firstRecordedTime;
|
||||
}
|
||||
|
||||
void addListener (CameraDevice::Listener* listenerToAdd)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
|
||||
if (listeners.size() == 0)
|
||||
addUser();
|
||||
|
||||
listeners.add (listenerToAdd);
|
||||
}
|
||||
|
||||
void removeListener (CameraDevice::Listener* listenerToRemove)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
listeners.remove (listenerToRemove);
|
||||
|
||||
if (listeners.size() == 0)
|
||||
removeUser();
|
||||
}
|
||||
|
||||
void callListeners (const Image& image)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
listeners.call ([=] (Listener& l) { l.imageReceived (image); });
|
||||
}
|
||||
|
||||
void notifyPictureTakenIfNeeded (const Image& image)
|
||||
{
|
||||
{
|
||||
const ScopedLock sl (pictureTakenCallbackLock);
|
||||
|
||||
if (pictureTakenCallback == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
MessageManager::callAsync ([weakRef = WeakReference<Pimpl> { this }, image]() mutable
|
||||
{
|
||||
if (weakRef == nullptr)
|
||||
return;
|
||||
|
||||
if (weakRef->pictureTakenCallback != nullptr)
|
||||
weakRef->pictureTakenCallback (image);
|
||||
|
||||
weakRef->pictureTakenCallback = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void addUser()
|
||||
{
|
||||
if (openedSuccessfully && activeUsers++ == 0)
|
||||
mediaControl->Run();
|
||||
}
|
||||
|
||||
void removeUser()
|
||||
{
|
||||
if (openedSuccessfully && --activeUsers == 0)
|
||||
mediaControl->Stop();
|
||||
}
|
||||
|
||||
void handleFrame (double /*time*/, BYTE* buffer, long /*bufferSize*/)
|
||||
{
|
||||
if (recordNextFrameTime)
|
||||
{
|
||||
const double defaultCameraLatency = 0.1;
|
||||
|
||||
firstRecordedTime = Time::getCurrentTime() - RelativeTime (defaultCameraLatency);
|
||||
recordNextFrameTime = false;
|
||||
|
||||
ComSmartPtr<IPin> pin;
|
||||
if (getPin (filter, PINDIR_OUTPUT, pin))
|
||||
{
|
||||
if (auto pushSource = pin.getInterface<IAMPushSource>())
|
||||
{
|
||||
REFERENCE_TIME latency = 0;
|
||||
pushSource->GetLatency (&latency);
|
||||
|
||||
firstRecordedTime = firstRecordedTime - RelativeTime ((double) latency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const int lineStride = width * 3;
|
||||
const ScopedLock sl (imageSwapLock);
|
||||
|
||||
{
|
||||
loadingImage.duplicateIfShared();
|
||||
const Image::BitmapData destData (loadingImage, 0, 0, width, height, Image::BitmapData::writeOnly);
|
||||
|
||||
for (int i = 0; i < height; ++i)
|
||||
memcpy (destData.getLinePointer ((height - 1) - i),
|
||||
buffer + lineStride * i,
|
||||
(size_t) lineStride);
|
||||
}
|
||||
|
||||
imageNeedsFlipping = true;
|
||||
}
|
||||
|
||||
if (listeners.size() > 0)
|
||||
callListeners (loadingImage);
|
||||
|
||||
notifyPictureTakenIfNeeded (loadingImage);
|
||||
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
void drawCurrentImage (Graphics& g, Rectangle<int> area)
|
||||
{
|
||||
if (imageNeedsFlipping)
|
||||
{
|
||||
const ScopedLock sl (imageSwapLock);
|
||||
std::swap (loadingImage, activeImage);
|
||||
imageNeedsFlipping = false;
|
||||
}
|
||||
|
||||
Rectangle<int> centred (RectanglePlacement (RectanglePlacement::centred)
|
||||
.appliedTo (Rectangle<int> (width, height), area));
|
||||
|
||||
RectangleList<int> borders (area);
|
||||
borders.subtract (centred);
|
||||
g.setColour (Colours::black);
|
||||
g.fillRectList (borders);
|
||||
|
||||
g.drawImage (activeImage, centred.getX(), centred.getY(),
|
||||
centred.getWidth(), centred.getHeight(), 0, 0, width, height);
|
||||
}
|
||||
|
||||
bool createFileCaptureFilter (const File& file, int quality)
|
||||
{
|
||||
removeFileCaptureFilter();
|
||||
file.deleteFile();
|
||||
mediaControl->Stop();
|
||||
firstRecordedTime = Time();
|
||||
recordNextFrameTime = true;
|
||||
previewMaxFPS = 60;
|
||||
|
||||
HRESULT hr = asfWriter.CoCreateInstance (CLSID_WMAsfWriter);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
if (auto fileSink = asfWriter.getInterface<IFileSinkFilter>())
|
||||
{
|
||||
hr = fileSink->SetFileName (file.getFullPathName().toWideCharPointer(), nullptr);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
hr = graphBuilder->AddFilter (asfWriter, _T("AsfWriter"));
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
if (auto asfConfig = asfWriter.getInterface<IConfigAsfWriter>())
|
||||
{
|
||||
asfConfig->SetIndexMode (true);
|
||||
ComSmartPtr<IWMProfileManager> profileManager;
|
||||
hr = WMCreateProfileManager (profileManager.resetAndGetPointerAddress());
|
||||
|
||||
// This gibberish is the DirectShow profile for a video-only wmv file.
|
||||
String prof ("<profile version=\"589824\" storageformat=\"1\" name=\"Quality\" description=\"Quality type for output.\">"
|
||||
"<streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" streamnumber=\"1\" "
|
||||
"streamname=\"Video Stream\" inputname=\"Video409\" bitrate=\"894960\" "
|
||||
"bufferwindow=\"0\" reliabletransport=\"1\" decodercomplexity=\"AU\" rfc1766langid=\"en-us\">"
|
||||
"<videomediaprops maxkeyframespacing=\"50000000\" quality=\"90\"/>"
|
||||
"<wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" bfixedsizesamples=\"0\" "
|
||||
"btemporalcompression=\"1\" lsamplesize=\"0\">"
|
||||
"<videoinfoheader dwbitrate=\"894960\" dwbiterrorrate=\"0\" avgtimeperframe=\"$AVGTIMEPERFRAME\">"
|
||||
"<rcsource left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>"
|
||||
"<rctarget left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>"
|
||||
"<bitmapinfoheader biwidth=\"$WIDTH\" biheight=\"$HEIGHT\" biplanes=\"1\" bibitcount=\"24\" "
|
||||
"bicompression=\"WMV3\" bisizeimage=\"0\" bixpelspermeter=\"0\" biypelspermeter=\"0\" "
|
||||
"biclrused=\"0\" biclrimportant=\"0\"/>"
|
||||
"</videoinfoheader>"
|
||||
"</wmmediatype>"
|
||||
"</streamconfig>"
|
||||
"</profile>");
|
||||
|
||||
const int fps[] = { 10, 15, 30 };
|
||||
int maxFramesPerSecond = fps[jlimit (0, numElementsInArray (fps) - 1, quality & 0xff)];
|
||||
|
||||
if (((uint32_t) quality & 0xff000000) != 0) // (internal hacky way to pass explicit frame rates for testing)
|
||||
maxFramesPerSecond = (quality >> 24) & 0xff;
|
||||
|
||||
prof = prof.replace ("$WIDTH", String (width))
|
||||
.replace ("$HEIGHT", String (height))
|
||||
.replace ("$AVGTIMEPERFRAME", String (10000000 / maxFramesPerSecond));
|
||||
|
||||
ComSmartPtr<IWMProfile> currentProfile;
|
||||
hr = profileManager->LoadProfileByData (prof.toWideCharPointer(), currentProfile.resetAndGetPointerAddress());
|
||||
hr = asfConfig->ConfigureFilterUsingProfile (currentProfile);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
ComSmartPtr<IPin> asfWriterInputPin;
|
||||
|
||||
if (getPin (asfWriter, PINDIR_INPUT, asfWriterInputPin, "Video Input 01"))
|
||||
{
|
||||
hr = graphBuilder->Connect (smartTeeCaptureOutputPin, asfWriterInputPin);
|
||||
|
||||
if (SUCCEEDED (hr) && openedSuccessfully && activeUsers > 0
|
||||
&& SUCCEEDED (mediaControl->Run()))
|
||||
{
|
||||
previewMaxFPS = (quality < 2) ? 15 : 25; // throttle back the preview comps to try to leave the cpu free for encoding
|
||||
|
||||
if ((quality & 0x00ff0000) != 0) // (internal hacky way to pass explicit frame rates for testing)
|
||||
previewMaxFPS = (quality >> 16) & 0xff;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeFileCaptureFilter();
|
||||
|
||||
if (openedSuccessfully && activeUsers > 0)
|
||||
mediaControl->Run();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void removeFileCaptureFilter()
|
||||
{
|
||||
mediaControl->Stop();
|
||||
|
||||
if (asfWriter != nullptr)
|
||||
{
|
||||
graphBuilder->RemoveFilter (asfWriter);
|
||||
asfWriter = nullptr;
|
||||
}
|
||||
|
||||
if (openedSuccessfully && activeUsers > 0)
|
||||
mediaControl->Run();
|
||||
|
||||
previewMaxFPS = 60;
|
||||
}
|
||||
|
||||
static ComSmartPtr<IBaseFilter> enumerateCameras (StringArray* names, const int deviceIndexToOpen)
|
||||
{
|
||||
int index = 0;
|
||||
ComSmartPtr<ICreateDevEnum> pDevEnum;
|
||||
|
||||
struct Deleter
|
||||
{
|
||||
void operator() (IUnknown* ptr) const noexcept { ptr->Release(); }
|
||||
};
|
||||
|
||||
using ContextPtr = std::unique_ptr<IBindCtx, Deleter>;
|
||||
|
||||
if (SUCCEEDED (pDevEnum.CoCreateInstance (CLSID_SystemDeviceEnum)))
|
||||
{
|
||||
ComSmartPtr<IEnumMoniker> enumerator;
|
||||
HRESULT hr = pDevEnum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, enumerator.resetAndGetPointerAddress(), 0);
|
||||
|
||||
if (SUCCEEDED (hr) && enumerator != nullptr)
|
||||
{
|
||||
ComSmartPtr<IMoniker> moniker;
|
||||
ULONG fetched;
|
||||
|
||||
while (enumerator->Next (1, moniker.resetAndGetPointerAddress(), &fetched) == S_OK)
|
||||
{
|
||||
auto context = []
|
||||
{
|
||||
IBindCtx* ptr = nullptr;
|
||||
ignoreUnused (CreateBindCtx (0, &ptr));
|
||||
return ContextPtr (ptr);
|
||||
}();
|
||||
|
||||
ComSmartPtr<IBaseFilter> captureFilter;
|
||||
hr = moniker->BindToObject (context.get(), nullptr, IID_IBaseFilter, (void**) captureFilter.resetAndGetPointerAddress());
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
ComSmartPtr<IPropertyBag> propertyBag;
|
||||
hr = moniker->BindToStorage (context.get(), nullptr, IID_IPropertyBag, (void**) propertyBag.resetAndGetPointerAddress());
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
VARIANT var;
|
||||
var.vt = VT_BSTR;
|
||||
|
||||
hr = propertyBag->Read (_T("FriendlyName"), &var, nullptr);
|
||||
propertyBag = nullptr;
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
if (names != nullptr)
|
||||
names->add (var.bstrVal);
|
||||
|
||||
if (index == deviceIndexToOpen)
|
||||
return captureFilter;
|
||||
|
||||
++index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static StringArray getAvailableDevices()
|
||||
{
|
||||
StringArray devs;
|
||||
enumerateCameras (&devs, -1);
|
||||
return devs;
|
||||
}
|
||||
|
||||
struct GrabberCallback : public ComBaseClassHelperBase<ISampleGrabberCB>
|
||||
{
|
||||
GrabberCallback (Pimpl& p)
|
||||
: ComBaseClassHelperBase (0), owner (p) {}
|
||||
|
||||
JUCE_COMRESULT QueryInterface (REFIID refId, void** result)
|
||||
{
|
||||
if (refId == IID_ISampleGrabberCB)
|
||||
return castToType<ISampleGrabberCB> (result);
|
||||
|
||||
return ComBaseClassHelperBase<ISampleGrabberCB>::QueryInterface (refId, result);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT SampleCB (double, IMediaSample*) { return E_FAIL; }
|
||||
|
||||
JUCE_COMRESULT BufferCB (double time, BYTE* buffer, long bufferSize)
|
||||
{
|
||||
owner.handleFrame (time, buffer, bufferSize);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
Pimpl& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (GrabberCallback)
|
||||
};
|
||||
|
||||
CameraDevice& owner;
|
||||
|
||||
ComSmartPtr<GrabberCallback> callback;
|
||||
|
||||
CriticalSection listenerLock;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
CriticalSection pictureTakenCallbackLock;
|
||||
std::function<void (const Image&)> pictureTakenCallback;
|
||||
|
||||
bool isRecording = false, openedSuccessfully = false;
|
||||
int width = 0, height = 0;
|
||||
Time firstRecordedTime;
|
||||
|
||||
Array<ViewerComponent*> viewerComps;
|
||||
|
||||
ComSmartPtr<ICaptureGraphBuilder2> captureGraphBuilder;
|
||||
ComSmartPtr<IBaseFilter> filter, smartTee, asfWriter;
|
||||
ComSmartPtr<IGraphBuilder> graphBuilder;
|
||||
ComSmartPtr<ISampleGrabber> sampleGrabber;
|
||||
ComSmartPtr<IMediaControl> mediaControl;
|
||||
ComSmartPtr<IPin> smartTeePreviewOutputPin, smartTeeCaptureOutputPin;
|
||||
int activeUsers = 0;
|
||||
Array<int> widths, heights;
|
||||
DWORD graphRegistrationID;
|
||||
|
||||
CriticalSection imageSwapLock;
|
||||
bool imageNeedsFlipping = false;
|
||||
Image loadingImage, activeImage;
|
||||
|
||||
bool recordNextFrameTime = false;
|
||||
int previewMaxFPS = 60;
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
|
||||
|
||||
private:
|
||||
void getVideoSizes (IAMStreamConfig* const streamConfig)
|
||||
{
|
||||
widths.clear();
|
||||
heights.clear();
|
||||
|
||||
int count = 0, size = 0;
|
||||
streamConfig->GetNumberOfCapabilities (&count, &size);
|
||||
|
||||
if (size == sizeof (VIDEO_STREAM_CONFIG_CAPS))
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
VIDEO_STREAM_CONFIG_CAPS scc;
|
||||
AM_MEDIA_TYPE* config;
|
||||
|
||||
HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
const int w = scc.InputSize.cx;
|
||||
const int h = scc.InputSize.cy;
|
||||
|
||||
bool duplicate = false;
|
||||
|
||||
for (int j = widths.size(); --j >= 0;)
|
||||
{
|
||||
if (w == widths.getUnchecked (j) && h == heights.getUnchecked (j))
|
||||
{
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! duplicate)
|
||||
{
|
||||
widths.add (w);
|
||||
heights.add (h);
|
||||
}
|
||||
|
||||
deleteMediaType (config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool selectVideoSize (IAMStreamConfig* const streamConfig,
|
||||
const int minWidth, const int minHeight,
|
||||
const int maxWidth, const int maxHeight)
|
||||
{
|
||||
int count = 0, size = 0, bestArea = 0, bestIndex = -1;
|
||||
streamConfig->GetNumberOfCapabilities (&count, &size);
|
||||
|
||||
if (size == sizeof (VIDEO_STREAM_CONFIG_CAPS))
|
||||
{
|
||||
AM_MEDIA_TYPE* config;
|
||||
VIDEO_STREAM_CONFIG_CAPS scc;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
if (scc.InputSize.cx >= minWidth
|
||||
&& scc.InputSize.cy >= minHeight
|
||||
&& scc.InputSize.cx <= maxWidth
|
||||
&& scc.InputSize.cy <= maxHeight)
|
||||
{
|
||||
int area = scc.InputSize.cx * scc.InputSize.cy;
|
||||
if (area > bestArea)
|
||||
{
|
||||
bestIndex = i;
|
||||
bestArea = area;
|
||||
}
|
||||
}
|
||||
|
||||
deleteMediaType (config);
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex >= 0)
|
||||
{
|
||||
HRESULT hr = streamConfig->GetStreamCaps (bestIndex, &config, (BYTE*) &scc);
|
||||
|
||||
hr = streamConfig->SetFormat (config);
|
||||
deleteMediaType (config);
|
||||
return SUCCEEDED (hr);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool getPin (IBaseFilter* filter, const PIN_DIRECTION wantedDirection,
|
||||
ComSmartPtr<IPin>& result, const char* pinName = nullptr)
|
||||
{
|
||||
ComSmartPtr<IEnumPins> enumerator;
|
||||
ComSmartPtr<IPin> pin;
|
||||
|
||||
filter->EnumPins (enumerator.resetAndGetPointerAddress());
|
||||
|
||||
while (enumerator->Next (1, pin.resetAndGetPointerAddress(), nullptr) == S_OK)
|
||||
{
|
||||
PIN_DIRECTION dir;
|
||||
pin->QueryDirection (&dir);
|
||||
|
||||
if (wantedDirection == dir)
|
||||
{
|
||||
PIN_INFO info = {};
|
||||
pin->QueryPinInfo (&info);
|
||||
|
||||
if (pinName == nullptr || String (pinName).equalsIgnoreCase (String (info.achName)))
|
||||
{
|
||||
result = pin;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool connectFilters (IBaseFilter* const first, IBaseFilter* const second) const
|
||||
{
|
||||
ComSmartPtr<IPin> in, out;
|
||||
|
||||
return getPin (first, PINDIR_OUTPUT, out)
|
||||
&& getPin (second, PINDIR_INPUT, in)
|
||||
&& SUCCEEDED (graphBuilder->Connect (out, in));
|
||||
}
|
||||
|
||||
bool addGraphToRot()
|
||||
{
|
||||
ComSmartPtr<IRunningObjectTable> rot;
|
||||
if (FAILED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
|
||||
return false;
|
||||
|
||||
ComSmartPtr<IMoniker> moniker;
|
||||
WCHAR buffer[128]{};
|
||||
HRESULT hr = CreateItemMoniker (_T("!"), buffer, moniker.resetAndGetPointerAddress());
|
||||
if (FAILED (hr))
|
||||
return false;
|
||||
|
||||
graphRegistrationID = 0;
|
||||
return SUCCEEDED (rot->Register (0, graphBuilder, moniker, &graphRegistrationID));
|
||||
}
|
||||
|
||||
void removeGraphFromRot()
|
||||
{
|
||||
ComSmartPtr<IRunningObjectTable> rot;
|
||||
|
||||
if (SUCCEEDED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
|
||||
rot->Revoke (graphRegistrationID);
|
||||
}
|
||||
|
||||
void disconnectAnyViewers();
|
||||
|
||||
static void deleteMediaType (AM_MEDIA_TYPE* const pmt)
|
||||
{
|
||||
if (pmt->cbFormat != 0)
|
||||
CoTaskMemFree ((PVOID) pmt->pbFormat);
|
||||
|
||||
if (pmt->pUnk != nullptr)
|
||||
pmt->pUnk->Release();
|
||||
|
||||
CoTaskMemFree (pmt);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct CameraDevice::ViewerComponent : public Component,
|
||||
public ChangeListener
|
||||
{
|
||||
ViewerComponent (CameraDevice& d)
|
||||
: owner (d.pimpl.get()), maxFPS (15), lastRepaintTime (0)
|
||||
{
|
||||
setOpaque (true);
|
||||
owner->addChangeListener (this);
|
||||
owner->addUser();
|
||||
owner->viewerComps.add (this);
|
||||
setSize (owner->width, owner->height);
|
||||
}
|
||||
|
||||
~ViewerComponent() override
|
||||
{
|
||||
if (owner != nullptr)
|
||||
{
|
||||
owner->viewerComps.removeFirstMatchingValue (this);
|
||||
owner->removeUser();
|
||||
owner->removeChangeListener (this);
|
||||
}
|
||||
}
|
||||
|
||||
void ownerDeleted()
|
||||
{
|
||||
owner = nullptr;
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.setColour (Colours::black);
|
||||
g.setImageResamplingQuality (Graphics::lowResamplingQuality);
|
||||
|
||||
if (owner != nullptr)
|
||||
owner->drawCurrentImage (g, getLocalBounds());
|
||||
else
|
||||
g.fillAll();
|
||||
}
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster*) override
|
||||
{
|
||||
const int64 now = Time::currentTimeMillis();
|
||||
|
||||
if (now >= lastRepaintTime + (1000 / maxFPS))
|
||||
{
|
||||
lastRepaintTime = now;
|
||||
repaint();
|
||||
|
||||
if (owner != nullptr)
|
||||
maxFPS = owner->previewMaxFPS;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Pimpl* owner;
|
||||
int maxFPS;
|
||||
int64 lastRepaintTime;
|
||||
};
|
||||
|
||||
void CameraDevice::Pimpl::disconnectAnyViewers()
|
||||
{
|
||||
for (int i = viewerComps.size(); --i >= 0;)
|
||||
viewerComps.getUnchecked(i)->ownerDeleted();
|
||||
}
|
||||
|
||||
String CameraDevice::getFileExtension()
|
||||
{
|
||||
return ".wmv";
|
||||
}
|
959
deps/juce/modules/juce_video/native/juce_win32_Video.h
vendored
Normal file
959
deps/juce/modules/juce_video/native/juce_win32_Video.h
vendored
Normal file
@ -0,0 +1,959 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace VideoRenderers
|
||||
{
|
||||
//==============================================================================
|
||||
struct Base
|
||||
{
|
||||
virtual ~Base() {}
|
||||
|
||||
virtual HRESULT create (ComSmartPtr<IGraphBuilder>&, ComSmartPtr<IBaseFilter>&, HWND) = 0;
|
||||
virtual void setVideoWindow (HWND) = 0;
|
||||
virtual void setVideoPosition (HWND) = 0;
|
||||
virtual void repaintVideo (HWND, HDC) = 0;
|
||||
virtual void displayModeChanged() = 0;
|
||||
virtual HRESULT getVideoSize (long& videoWidth, long& videoHeight) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct VMR7 : public Base
|
||||
{
|
||||
VMR7() {}
|
||||
|
||||
HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder,
|
||||
ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) override
|
||||
{
|
||||
ComSmartPtr<IVMRFilterConfig> filterConfig;
|
||||
|
||||
HRESULT hr = baseFilter.CoCreateInstance (CLSID_VideoMixingRenderer);
|
||||
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder->AddFilter (baseFilter, L"VMR-7");
|
||||
if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (filterConfig);
|
||||
if (SUCCEEDED (hr)) hr = filterConfig->SetRenderingMode (VMRMode_Windowless);
|
||||
if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (windowlessControl);
|
||||
if (SUCCEEDED (hr)) hr = windowlessControl->SetVideoClippingWindow (hwnd);
|
||||
if (SUCCEEDED (hr)) hr = windowlessControl->SetAspectRatioMode (VMR_ARMODE_LETTER_BOX);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
void setVideoWindow (HWND hwnd) override
|
||||
{
|
||||
windowlessControl->SetVideoClippingWindow (hwnd);
|
||||
}
|
||||
|
||||
void setVideoPosition (HWND hwnd) override
|
||||
{
|
||||
long videoWidth = 0, videoHeight = 0;
|
||||
windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr);
|
||||
|
||||
RECT src, dest;
|
||||
SetRect (&src, 0, 0, videoWidth, videoHeight);
|
||||
GetClientRect (hwnd, &dest);
|
||||
|
||||
windowlessControl->SetVideoPosition (&src, &dest);
|
||||
}
|
||||
|
||||
void repaintVideo (HWND hwnd, HDC hdc) override
|
||||
{
|
||||
windowlessControl->RepaintVideo (hwnd, hdc);
|
||||
}
|
||||
|
||||
void displayModeChanged() override
|
||||
{
|
||||
windowlessControl->DisplayModeChanged();
|
||||
}
|
||||
|
||||
HRESULT getVideoSize (long& videoWidth, long& videoHeight) override
|
||||
{
|
||||
return windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr);
|
||||
}
|
||||
|
||||
ComSmartPtr<IVMRWindowlessControl> windowlessControl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VMR7)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
struct EVR : public Base
|
||||
{
|
||||
EVR() {}
|
||||
|
||||
HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder,
|
||||
ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) override
|
||||
{
|
||||
ComSmartPtr<IMFGetService> getService;
|
||||
|
||||
HRESULT hr = baseFilter.CoCreateInstance (CLSID_EnhancedVideoRenderer);
|
||||
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder->AddFilter (baseFilter, L"EVR");
|
||||
if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (getService);
|
||||
if (SUCCEEDED (hr)) hr = getService->GetService (MR_VIDEO_RENDER_SERVICE, IID_IMFVideoDisplayControl,
|
||||
(void**) videoDisplayControl.resetAndGetPointerAddress());
|
||||
if (SUCCEEDED (hr)) hr = videoDisplayControl->SetVideoWindow (hwnd);
|
||||
if (SUCCEEDED (hr)) hr = videoDisplayControl->SetAspectRatioMode (MFVideoARMode_PreservePicture);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
void setVideoWindow (HWND hwnd) override
|
||||
{
|
||||
videoDisplayControl->SetVideoWindow (hwnd);
|
||||
}
|
||||
|
||||
void setVideoPosition (HWND hwnd) override
|
||||
{
|
||||
const MFVideoNormalizedRect src = { 0.0f, 0.0f, 1.0f, 1.0f };
|
||||
|
||||
RECT dest;
|
||||
GetClientRect (hwnd, &dest);
|
||||
|
||||
videoDisplayControl->SetVideoPosition (&src, &dest);
|
||||
}
|
||||
|
||||
void repaintVideo (HWND, HDC) override
|
||||
{
|
||||
videoDisplayControl->RepaintVideo();
|
||||
}
|
||||
|
||||
void displayModeChanged() override {}
|
||||
|
||||
HRESULT getVideoSize (long& videoWidth, long& videoHeight) override
|
||||
{
|
||||
SIZE sz = { 0, 0 };
|
||||
HRESULT hr = videoDisplayControl->GetNativeVideoSize (&sz, nullptr);
|
||||
videoWidth = sz.cx;
|
||||
videoHeight = sz.cy;
|
||||
return hr;
|
||||
}
|
||||
|
||||
ComSmartPtr<IMFVideoDisplayControl> videoDisplayControl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EVR)
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct VideoComponent::Pimpl : public Component,
|
||||
private ComponentPeer::ScaleFactorListener
|
||||
{
|
||||
Pimpl (VideoComponent& ownerToUse, bool)
|
||||
: owner (ownerToUse)
|
||||
{
|
||||
setOpaque (true);
|
||||
context.reset (new DirectShowContext (*this));
|
||||
componentWatcher.reset (new ComponentWatcher (*this));
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
close();
|
||||
context = nullptr;
|
||||
componentWatcher = nullptr;
|
||||
|
||||
if (currentPeer != nullptr)
|
||||
currentPeer->removeScaleFactorListener (this);
|
||||
}
|
||||
|
||||
Result loadFromString (const String& fileOrURLPath)
|
||||
{
|
||||
close();
|
||||
auto r = context->loadFile (fileOrURLPath);
|
||||
|
||||
if (r.wasOk())
|
||||
{
|
||||
videoLoaded = true;
|
||||
context->updateVideoPosition();
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Result load (const File& file)
|
||||
{
|
||||
auto r = loadFromString (file.getFullPathName());
|
||||
|
||||
if (r.wasOk())
|
||||
currentFile = file;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Result load (const URL& url)
|
||||
{
|
||||
auto r = loadFromString (URL::removeEscapeChars (url.toString (true)));
|
||||
|
||||
if (r.wasOk())
|
||||
currentURL = url;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
stop();
|
||||
context->release();
|
||||
|
||||
videoLoaded = false;
|
||||
currentFile = File();
|
||||
currentURL = {};
|
||||
}
|
||||
|
||||
bool isOpen() const
|
||||
{
|
||||
return videoLoaded;
|
||||
}
|
||||
|
||||
bool isPlaying() const
|
||||
{
|
||||
return context->state == DirectShowContext::runningState;
|
||||
}
|
||||
|
||||
void play()
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->play();
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->pause();
|
||||
}
|
||||
|
||||
void setPosition (double newPosition)
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->setPosition (newPosition);
|
||||
}
|
||||
|
||||
double getPosition() const
|
||||
{
|
||||
return videoLoaded ? context->getPosition() : 0.0;
|
||||
}
|
||||
|
||||
void setSpeed (double newSpeed)
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->setSpeed (newSpeed);
|
||||
}
|
||||
|
||||
double getSpeed() const
|
||||
{
|
||||
return videoLoaded ? context->getSpeed() : 0.0;
|
||||
}
|
||||
|
||||
Rectangle<int> getNativeSize() const
|
||||
{
|
||||
return videoLoaded ? context->getVideoSize()
|
||||
: Rectangle<int>();
|
||||
}
|
||||
|
||||
double getDuration() const
|
||||
{
|
||||
return videoLoaded ? context->getDuration() : 0.0;
|
||||
}
|
||||
|
||||
void setVolume (float newVolume)
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->setVolume (newVolume);
|
||||
}
|
||||
|
||||
float getVolume() const
|
||||
{
|
||||
return videoLoaded ? context->getVolume() : 0.0f;
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->handleUpdateNowIfNeeded();
|
||||
else
|
||||
g.fillAll (Colours::grey);
|
||||
}
|
||||
|
||||
void updateContextPosition()
|
||||
{
|
||||
context->updateContextPosition();
|
||||
|
||||
if (getWidth() > 0 && getHeight() > 0)
|
||||
if (auto* peer = getTopLevelComponent()->getPeer())
|
||||
context->updateWindowPosition ((peer->getAreaCoveredBy (*this).toDouble()
|
||||
* peer->getPlatformScaleFactor()).toNearestInt());
|
||||
}
|
||||
|
||||
void updateContextVisibility()
|
||||
{
|
||||
context->showWindow (isShowing());
|
||||
}
|
||||
|
||||
void recreateNativeWindowAsync()
|
||||
{
|
||||
context->recreateNativeWindowAsync();
|
||||
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;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
bool videoLoaded = false;
|
||||
|
||||
//==============================================================================
|
||||
void nativeScaleFactorChanged (double /*newScaleFactor*/) override
|
||||
{
|
||||
if (videoLoaded)
|
||||
updateContextPosition();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ComponentWatcher : public ComponentMovementWatcher
|
||||
{
|
||||
ComponentWatcher (Pimpl& c) : ComponentMovementWatcher (&c), owner (c)
|
||||
{
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool, bool) override
|
||||
{
|
||||
if (owner.videoLoaded)
|
||||
owner.updateContextPosition();
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
if (owner.currentPeer != nullptr)
|
||||
owner.currentPeer->removeScaleFactorListener (&owner);
|
||||
|
||||
if (owner.videoLoaded)
|
||||
owner.recreateNativeWindowAsync();
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
if (owner.videoLoaded)
|
||||
owner.updateContextVisibility();
|
||||
}
|
||||
|
||||
Pimpl& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentWatcher)
|
||||
};
|
||||
|
||||
std::unique_ptr<ComponentWatcher> componentWatcher;
|
||||
|
||||
//==============================================================================
|
||||
struct DirectShowContext : public AsyncUpdater
|
||||
{
|
||||
DirectShowContext (Pimpl& c) : component (c)
|
||||
{
|
||||
ignoreUnused (CoInitialize (nullptr));
|
||||
}
|
||||
|
||||
~DirectShowContext() override
|
||||
{
|
||||
release();
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void updateWindowPosition (const Rectangle<int>& newBounds)
|
||||
{
|
||||
nativeWindow->setWindowPosition (newBounds);
|
||||
}
|
||||
|
||||
void showWindow (bool shouldBeVisible)
|
||||
{
|
||||
nativeWindow->showWindow (shouldBeVisible);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void repaint()
|
||||
{
|
||||
if (hasVideo)
|
||||
videoRenderer->repaintVideo (nativeWindow->hwnd, nativeWindow->hdc);
|
||||
}
|
||||
|
||||
void updateVideoPosition()
|
||||
{
|
||||
if (hasVideo)
|
||||
videoRenderer->setVideoPosition (nativeWindow->hwnd);
|
||||
}
|
||||
|
||||
void displayResolutionChanged()
|
||||
{
|
||||
if (hasVideo)
|
||||
videoRenderer->displayModeChanged();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void peerChanged()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
if (hwnd != nullptr)
|
||||
{
|
||||
if (needToRecreateNativeWindow)
|
||||
{
|
||||
peerChanged();
|
||||
needToRecreateNativeWindow = false;
|
||||
}
|
||||
|
||||
if (needToUpdateViewport)
|
||||
{
|
||||
updateVideoPosition();
|
||||
needToUpdateViewport = false;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
else
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void recreateNativeWindowAsync()
|
||||
{
|
||||
needToRecreateNativeWindow = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void updateContextPosition()
|
||||
{
|
||||
needToUpdateViewport = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Result loadFile (const String& fileOrURLPath)
|
||||
{
|
||||
jassert (state == uninitializedState);
|
||||
|
||||
if (! createNativeWindow())
|
||||
return Result::fail ("Can't create window");
|
||||
|
||||
HRESULT hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
|
||||
|
||||
// basic playback interfaces
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaControl);
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaPosition);
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaEvent);
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (basicAudio);
|
||||
|
||||
// video renderer interface
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
|
||||
{
|
||||
videoRenderer.reset (new VideoRenderers::EVR());
|
||||
hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
|
||||
|
||||
if (FAILED (hr))
|
||||
videoRenderer = nullptr;
|
||||
}
|
||||
|
||||
if (videoRenderer == nullptr)
|
||||
{
|
||||
videoRenderer.reset (new VideoRenderers::VMR7());
|
||||
hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
// build filter graph
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
hr = graphBuilder->RenderFile (fileOrURLPath.toWideCharPointer(), nullptr);
|
||||
|
||||
if (FAILED (hr))
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
// Annoyingly, if we don't run the msg loop between failing and deleting the window, the
|
||||
// whole OS message-dispatch system gets itself into a state, and refuses to deliver any
|
||||
// more messages for the whole app. (That's what happens in Win7, anyway)
|
||||
MessageManager::getInstance()->runDispatchLoopUntil (200);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// remove video renderer if not connected (no video)
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
if (isRendererConnected())
|
||||
{
|
||||
hasVideo = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasVideo = false;
|
||||
graphBuilder->RemoveFilter (baseFilter);
|
||||
videoRenderer = nullptr;
|
||||
baseFilter = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// set window to receive events
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
|
||||
hr = mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
|
||||
}
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
state = stoppedState;
|
||||
pause();
|
||||
return Result::ok();
|
||||
}
|
||||
|
||||
// Note that if you're trying to open a file and this method fails, you may
|
||||
// just need to install a suitable codec. It seems that by default DirectShow
|
||||
// doesn't support a very good range of formats.
|
||||
release();
|
||||
return getErrorMessageFromResult (hr);
|
||||
}
|
||||
|
||||
static Result getErrorMessageFromResult (HRESULT hr)
|
||||
{
|
||||
switch (hr)
|
||||
{
|
||||
case VFW_E_INVALID_FILE_FORMAT: return Result::fail ("Invalid file format");
|
||||
case VFW_E_NOT_FOUND: return Result::fail ("File not found");
|
||||
case VFW_E_UNKNOWN_FILE_TYPE: return Result::fail ("Unknown file type");
|
||||
case VFW_E_UNSUPPORTED_STREAM: return Result::fail ("Unsupported stream");
|
||||
case VFW_E_CANNOT_CONNECT: return Result::fail ("Cannot connect");
|
||||
case VFW_E_CANNOT_LOAD_SOURCE_FILTER: return Result::fail ("Cannot load source filter");
|
||||
}
|
||||
|
||||
TCHAR messageBuffer[512] = { 0 };
|
||||
|
||||
FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr);
|
||||
|
||||
return Result::fail (String (messageBuffer));
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
if (mediaControl != nullptr)
|
||||
mediaControl->Stop();
|
||||
|
||||
if (mediaEvent != nullptr)
|
||||
mediaEvent->SetNotifyWindow (0, 0, 0);
|
||||
|
||||
if (videoRenderer != nullptr)
|
||||
videoRenderer->setVideoWindow (nullptr);
|
||||
|
||||
hasVideo = false;
|
||||
videoRenderer = nullptr;
|
||||
baseFilter = nullptr;
|
||||
basicAudio = nullptr;
|
||||
mediaEvent = nullptr;
|
||||
mediaPosition = nullptr;
|
||||
mediaControl = nullptr;
|
||||
graphBuilder = nullptr;
|
||||
|
||||
state = uninitializedState;
|
||||
|
||||
if (nativeWindow != nullptr)
|
||||
deleteNativeWindow();
|
||||
}
|
||||
|
||||
void graphEventProc()
|
||||
{
|
||||
LONG ec = 0;
|
||||
LONG_PTR p1 = {}, p2 = {};
|
||||
|
||||
jassert (mediaEvent != nullptr);
|
||||
|
||||
while (SUCCEEDED (mediaEvent->GetEvent (&ec, &p1, &p2, 0)))
|
||||
{
|
||||
mediaEvent->FreeEventParams (ec, p1, p2);
|
||||
|
||||
switch (ec)
|
||||
{
|
||||
case EC_REPAINT:
|
||||
component.repaint();
|
||||
break;
|
||||
|
||||
case EC_COMPLETE:
|
||||
component.stop();
|
||||
component.setPosition (0.0);
|
||||
break;
|
||||
|
||||
case EC_ERRORABORT:
|
||||
case EC_ERRORABORTEX:
|
||||
component.errorOccurred (getErrorMessageFromResult ((HRESULT) p1).getErrorMessage());
|
||||
// intentional fallthrough
|
||||
case EC_USERABORT:
|
||||
component.close();
|
||||
break;
|
||||
|
||||
case EC_STATE_CHANGE:
|
||||
switch (p1)
|
||||
{
|
||||
case State_Paused: component.playbackStopped(); break;
|
||||
case State_Running: component.playbackStarted(); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void play()
|
||||
{
|
||||
mediaControl->Run();
|
||||
state = runningState;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
mediaControl->Stop();
|
||||
state = stoppedState;
|
||||
}
|
||||
|
||||
void pause()
|
||||
{
|
||||
mediaControl->Pause();
|
||||
state = pausedState;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Rectangle<int> getVideoSize() const noexcept
|
||||
{
|
||||
long width = 0, height = 0;
|
||||
|
||||
if (hasVideo)
|
||||
videoRenderer->getVideoSize (width, height);
|
||||
|
||||
return { (int) width, (int) height };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
double getDuration() const
|
||||
{
|
||||
REFTIME duration;
|
||||
mediaPosition->get_Duration (&duration);
|
||||
return duration;
|
||||
}
|
||||
|
||||
double getSpeed() const
|
||||
{
|
||||
double speed;
|
||||
mediaPosition->get_Rate (&speed);
|
||||
return speed;
|
||||
}
|
||||
|
||||
double getPosition() const
|
||||
{
|
||||
REFTIME seconds;
|
||||
mediaPosition->get_CurrentPosition (&seconds);
|
||||
return seconds;
|
||||
}
|
||||
|
||||
void setSpeed (double newSpeed) { mediaPosition->put_Rate (newSpeed); }
|
||||
void setPosition (double seconds) { mediaPosition->put_CurrentPosition (seconds); }
|
||||
void setVolume (float newVolume) { basicAudio->put_Volume (convertToDShowVolume (newVolume)); }
|
||||
|
||||
// in DirectShow, full volume is 0, silence is -10000
|
||||
static long convertToDShowVolume (float vol) noexcept
|
||||
{
|
||||
if (vol >= 1.0f) return 0;
|
||||
if (vol <= 0.0f) return -10000;
|
||||
|
||||
return roundToInt ((vol * 10000.0f) - 10000.0f);
|
||||
}
|
||||
|
||||
float getVolume() const
|
||||
{
|
||||
long volume;
|
||||
basicAudio->get_Volume (&volume);
|
||||
return (float) (volume + 10000) / 10000.0f;
|
||||
}
|
||||
|
||||
enum State { uninitializedState, runningState, pausedState, stoppedState };
|
||||
State state = uninitializedState;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
enum { graphEventID = WM_APP + 0x43f0 };
|
||||
|
||||
Pimpl& component;
|
||||
HWND hwnd = {};
|
||||
HDC hdc = {};
|
||||
|
||||
ComSmartPtr<IGraphBuilder> graphBuilder;
|
||||
ComSmartPtr<IMediaControl> mediaControl;
|
||||
ComSmartPtr<IMediaPosition> mediaPosition;
|
||||
ComSmartPtr<IMediaEventEx> mediaEvent;
|
||||
ComSmartPtr<IBasicAudio> basicAudio;
|
||||
ComSmartPtr<IBaseFilter> baseFilter;
|
||||
|
||||
std::unique_ptr<VideoRenderers::Base> videoRenderer;
|
||||
|
||||
bool hasVideo = false, needToUpdateViewport = true, needToRecreateNativeWindow = false;
|
||||
|
||||
//==============================================================================
|
||||
bool createNativeWindow()
|
||||
{
|
||||
jassert (nativeWindow == nullptr);
|
||||
|
||||
if (auto* topLevelPeer = component.getTopLevelComponent()->getPeer())
|
||||
{
|
||||
nativeWindow.reset (new NativeWindow ((HWND) topLevelPeer->getNativeHandle(), this));
|
||||
|
||||
hwnd = nativeWindow->hwnd;
|
||||
component.currentPeer = topLevelPeer;
|
||||
component.currentPeer->addScaleFactorListener (&component);
|
||||
|
||||
if (hwnd != nullptr)
|
||||
{
|
||||
hdc = GetDC (hwnd);
|
||||
component.updateContextPosition();
|
||||
component.updateContextVisibility();
|
||||
return true;
|
||||
}
|
||||
|
||||
nativeWindow = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void deleteNativeWindow()
|
||||
{
|
||||
jassert (nativeWindow != nullptr);
|
||||
ReleaseDC (hwnd, hdc);
|
||||
hwnd = {};
|
||||
hdc = {};
|
||||
nativeWindow = nullptr;
|
||||
}
|
||||
|
||||
bool isRendererConnected()
|
||||
{
|
||||
ComSmartPtr<IEnumPins> enumPins;
|
||||
|
||||
HRESULT hr = baseFilter->EnumPins (enumPins.resetAndGetPointerAddress());
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = enumPins->Reset();
|
||||
|
||||
ComSmartPtr<IPin> pin;
|
||||
|
||||
while (SUCCEEDED (hr)
|
||||
&& enumPins->Next (1, pin.resetAndGetPointerAddress(), nullptr) == S_OK)
|
||||
{
|
||||
ComSmartPtr<IPin> otherPin;
|
||||
|
||||
hr = pin->ConnectedTo (otherPin.resetAndGetPointerAddress());
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
PIN_DIRECTION direction;
|
||||
hr = pin->QueryDirection (&direction);
|
||||
|
||||
if (SUCCEEDED (hr) && direction == PINDIR_INPUT)
|
||||
return true;
|
||||
}
|
||||
else if (hr == VFW_E_NOT_CONNECTED)
|
||||
{
|
||||
hr = S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct NativeWindowClass : private DeletedAtShutdown
|
||||
{
|
||||
bool isRegistered() const noexcept { return atom != 0; }
|
||||
LPCTSTR getWindowClassName() const noexcept { return (LPCTSTR) (pointer_sized_uint) MAKELONG (atom, 0); }
|
||||
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (NativeWindowClass)
|
||||
|
||||
private:
|
||||
NativeWindowClass()
|
||||
{
|
||||
String windowClassName ("JUCE_DIRECTSHOW_");
|
||||
windowClassName << (int) (Time::currentTimeMillis() & 0x7fffffff);
|
||||
|
||||
HINSTANCE moduleHandle = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
|
||||
|
||||
TCHAR moduleFile [1024] = {};
|
||||
GetModuleFileName (moduleHandle, moduleFile, 1024);
|
||||
|
||||
WNDCLASSEX wcex = {};
|
||||
wcex.cbSize = sizeof (wcex);
|
||||
wcex.style = CS_OWNDC;
|
||||
wcex.lpfnWndProc = (WNDPROC) wndProc;
|
||||
wcex.lpszClassName = windowClassName.toWideCharPointer();
|
||||
wcex.hInstance = moduleHandle;
|
||||
|
||||
atom = RegisterClassEx (&wcex);
|
||||
jassert (atom != 0);
|
||||
}
|
||||
|
||||
~NativeWindowClass()
|
||||
{
|
||||
if (atom != 0)
|
||||
UnregisterClass (getWindowClassName(), (HINSTANCE) Process::getCurrentModuleInstanceHandle());
|
||||
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK wndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (auto* c = (DirectShowContext*) GetWindowLongPtr (hwnd, GWLP_USERDATA))
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case WM_NCHITTEST: return HTTRANSPARENT;
|
||||
case WM_ERASEBKGND: return 1;
|
||||
case WM_DISPLAYCHANGE: c->displayResolutionChanged(); break;
|
||||
case graphEventID: c->graphEventProc(); return 0;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProc (hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
ATOM atom = {};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (NativeWindowClass)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct NativeWindow
|
||||
{
|
||||
NativeWindow (HWND parentToAddTo, void* userData)
|
||||
{
|
||||
auto* wc = NativeWindowClass::getInstance();
|
||||
|
||||
if (wc->isRegistered())
|
||||
{
|
||||
DWORD exstyle = 0;
|
||||
DWORD type = WS_CHILD;
|
||||
|
||||
hwnd = CreateWindowEx (exstyle, wc->getWindowClassName(),
|
||||
L"", type, 0, 0, 0, 0, parentToAddTo, nullptr,
|
||||
(HINSTANCE) Process::getCurrentModuleInstanceHandle(), nullptr);
|
||||
|
||||
if (hwnd != nullptr)
|
||||
{
|
||||
hdc = GetDC (hwnd);
|
||||
SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) userData);
|
||||
}
|
||||
}
|
||||
|
||||
jassert (hwnd != nullptr);
|
||||
}
|
||||
|
||||
~NativeWindow()
|
||||
{
|
||||
if (hwnd != nullptr)
|
||||
{
|
||||
SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) 0);
|
||||
DestroyWindow (hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
void setWindowPosition (Rectangle<int> newBounds)
|
||||
{
|
||||
SetWindowPos (hwnd, nullptr, newBounds.getX(), newBounds.getY(),
|
||||
newBounds.getWidth(), newBounds.getHeight(),
|
||||
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
|
||||
}
|
||||
|
||||
void showWindow (bool shouldBeVisible)
|
||||
{
|
||||
ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
|
||||
}
|
||||
|
||||
HWND hwnd = {};
|
||||
HDC hdc = {};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeWindow)
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeWindow> nativeWindow;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowContext)
|
||||
};
|
||||
|
||||
std::unique_ptr<DirectShowContext> context;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (VideoComponent::Pimpl::DirectShowContext::NativeWindowClass)
|
166
deps/juce/modules/juce_video/playback/juce_VideoComponent.cpp
vendored
Normal file
166
deps/juce/modules/juce_video/playback/juce_VideoComponent.cpp
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if ! (JUCE_LINUX || JUCE_BSD)
|
||||
|
||||
#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 (bool useNativeControlsIfAvailable)
|
||||
: pimpl (new Pimpl (*this, useNativeControlsIfAvailable))
|
||||
{
|
||||
addAndMakeVisible (pimpl.get());
|
||||
}
|
||||
|
||||
VideoComponent::~VideoComponent()
|
||||
{
|
||||
pimpl.reset();
|
||||
}
|
||||
|
||||
Result VideoComponent::load (const File& file)
|
||||
{
|
||||
return loadInternal (file, false);
|
||||
}
|
||||
|
||||
Result VideoComponent::load (const URL& url)
|
||||
{
|
||||
return loadInternal (url, false);
|
||||
}
|
||||
|
||||
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 = loadInternal (url, true);
|
||||
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(); }
|
||||
|
||||
File VideoComponent::getCurrentVideoFile() const { return pimpl->currentFile; }
|
||||
URL VideoComponent::getCurrentVideoURL() const { return pimpl->currentURL; }
|
||||
|
||||
double VideoComponent::getVideoDuration() const { return pimpl->getDuration(); }
|
||||
Rectangle<int> VideoComponent::getVideoNativeSize() const { return pimpl->getNativeSize(); }
|
||||
|
||||
void VideoComponent::play() { pimpl->play(); }
|
||||
void VideoComponent::stop() { pimpl->stop(); }
|
||||
|
||||
bool VideoComponent::isPlaying() const { return pimpl->isPlaying(); }
|
||||
|
||||
void VideoComponent::setPlayPosition (double newPos) { pimpl->setPosition (newPos); }
|
||||
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(); }
|
||||
|
||||
void VideoComponent::resized()
|
||||
{
|
||||
auto r = getLocalBounds();
|
||||
|
||||
if (isVideoOpen() && ! r.isEmpty())
|
||||
{
|
||||
auto nativeSize = getVideoNativeSize();
|
||||
|
||||
if (nativeSize.isEmpty())
|
||||
{
|
||||
// if we've just opened the file and are still waiting for it to
|
||||
// figure out the size, start our timer..
|
||||
if (! isTimerRunning())
|
||||
startTimer (50);
|
||||
}
|
||||
else
|
||||
{
|
||||
r = RectanglePlacement (RectanglePlacement::centred).appliedTo (nativeSize, r);
|
||||
stopTimer();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
pimpl->setBounds (r);
|
||||
}
|
||||
|
||||
void VideoComponent::timerCallback()
|
||||
{
|
||||
resized();
|
||||
}
|
||||
|
||||
template <class FileOrURL>
|
||||
Result VideoComponent::loadInternal (const FileOrURL& fileOrUrl, bool loadAsync)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
ignoreUnused (fileOrUrl, loadAsync);
|
||||
|
||||
// You need to use loadAsync on Android & iOS.
|
||||
jassertfalse;
|
||||
return Result::fail ("load() is not supported on this platform. Use loadAsync() instead.");
|
||||
#else
|
||||
auto result = pimpl->load (fileOrUrl);
|
||||
|
||||
if (loadAsync)
|
||||
startTimer (50);
|
||||
else
|
||||
resized();
|
||||
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
190
deps/juce/modules/juce_video/playback/juce_VideoComponent.h
vendored
Normal file
190
deps/juce/modules/juce_video/playback/juce_VideoComponent.h
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_VIDEOCOMPONENT_H_INCLUDED
|
||||
#define JUCE_VIDEOCOMPONENT_H_INCLUDED
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that can play a movie.
|
||||
|
||||
Use the load() method to open a video once you've added this component to
|
||||
a parent (or put it on the desktop).
|
||||
|
||||
@tags{Video}
|
||||
*/
|
||||
class JUCE_API VideoComponent : public Component,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty VideoComponent.
|
||||
|
||||
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 (bool useNativeControlsIfAvailable);
|
||||
|
||||
/** Destructor. */
|
||||
~VideoComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** 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();
|
||||
|
||||
/** Returns true if a video is currently open. */
|
||||
bool isVideoOpen() const;
|
||||
|
||||
/** Returns the last file that was loaded.
|
||||
If nothing is open, or if it was a URL rather than a file, this will return File().
|
||||
*/
|
||||
File getCurrentVideoFile() const;
|
||||
|
||||
/** Returns the last URL that was loaded.
|
||||
If nothing is open, or if it was a file rather than a URL, this will return URL().
|
||||
*/
|
||||
URL getCurrentVideoURL() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the length of the video, in seconds. */
|
||||
double getVideoDuration() const;
|
||||
|
||||
/** Returns the video's natural size, in pixels.
|
||||
If no video is loaded, an empty rectangle will be returned.
|
||||
*/
|
||||
Rectangle<int> getVideoNativeSize() const;
|
||||
|
||||
/** Starts the video playing. */
|
||||
void play();
|
||||
|
||||
/** Stops the video playing. */
|
||||
void stop();
|
||||
|
||||
/** Returns true if the video is currently playing. */
|
||||
bool isPlaying() const;
|
||||
|
||||
/** Sets the video's position to a given time. */
|
||||
void setPlayPosition (double newPositionSeconds);
|
||||
|
||||
/** Returns the current play position of the video. */
|
||||
double getPlayPosition() const;
|
||||
|
||||
/** Changes the video playback rate.
|
||||
A value of 1.0 is normal speed, greater values will play faster, smaller
|
||||
values play more slowly.
|
||||
*/
|
||||
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)
|
||||
*/
|
||||
void setAudioVolume (float newVolume);
|
||||
|
||||
/** Returns the video's playback volume.
|
||||
@returns the volume in the range 0 (silent) to 1.0 (full)
|
||||
*/
|
||||
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;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
void resized() override;
|
||||
void timerCallback() override;
|
||||
|
||||
template <class FileOrURL>
|
||||
Result loadInternal (const FileOrURL&, bool);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user