Bug 801693 - Plumb video recorder state-change (error, size/length limit) handling. r=jst
This commit is contained in:
@@ -26,6 +26,7 @@ CameraControlImpl::CameraControlImpl(uint32_t aCameraId, nsIThread* aCameraThrea
|
||||
, mStartRecordingOnErrorCb(nullptr)
|
||||
, mOnShutterCb(nullptr)
|
||||
, mOnClosedCb(nullptr)
|
||||
, mOnRecorderStateChangeCb(nullptr)
|
||||
{
|
||||
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
||||
}
|
||||
@@ -221,6 +222,20 @@ CameraControlImpl::Get(nsICameraClosedCallback** aOnClosed)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
CameraControlImpl::Set(nsICameraRecorderStateChange* aOnRecorderStateChange)
|
||||
{
|
||||
mOnRecorderStateChangeCb = aOnRecorderStateChange;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
CameraControlImpl::Get(nsICameraRecorderStateChange** aOnRecorderStateChange)
|
||||
{
|
||||
*aOnRecorderStateChange = mOnRecorderStateChangeCb;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<RecorderProfileManager>
|
||||
CameraControlImpl::GetRecorderProfileManager()
|
||||
{
|
||||
@@ -239,6 +254,7 @@ CameraControlImpl::Shutdown()
|
||||
mStartRecordingOnErrorCb = nullptr;
|
||||
mOnShutterCb = nullptr;
|
||||
mOnClosedCb = nullptr;
|
||||
mOnRecorderStateChangeCb = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -279,6 +295,18 @@ CameraControlImpl::OnClosed()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CameraControlImpl::OnRecorderStateChange(const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber)
|
||||
{
|
||||
DOM_CAMERA_LOGI("OnRecorderStateChange: '%s'\n", NS_ConvertUTF16toUTF8(aStateMsg).get());
|
||||
|
||||
nsCOMPtr<nsIRunnable> onRecorderStateChange = new CameraRecorderStateChange(mOnRecorderStateChangeCb, aStateMsg, aStatus, aTrackNumber, mWindowId);
|
||||
nsresult rv = NS_DispatchToMainThread(onRecorderStateChange);
|
||||
if (NS_FAILED(rv)) {
|
||||
DOM_CAMERA_LOGE("Failed to dispatch onRecorderStateChange event to main thread (%d)\n", rv);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
CameraControlImpl::GetPreviewStream(CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
|
||||
{
|
||||
|
||||
@@ -67,6 +67,8 @@ public:
|
||||
nsresult Get(nsICameraShutterCallback** aOnShutter);
|
||||
nsresult Set(nsICameraClosedCallback* aOnClosed);
|
||||
nsresult Get(nsICameraClosedCallback** aOnClosed);
|
||||
nsresult Set(nsICameraRecorderStateChange* aOnRecorderStateChange);
|
||||
nsresult Get(nsICameraRecorderStateChange** aOnRecorderStateChange);
|
||||
|
||||
nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue)
|
||||
{
|
||||
@@ -96,6 +98,7 @@ public:
|
||||
bool ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder);
|
||||
void OnShutter();
|
||||
void OnClosed();
|
||||
void OnRecorderStateChange(const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber);
|
||||
|
||||
uint64_t GetWindowId()
|
||||
{
|
||||
@@ -145,6 +148,7 @@ protected:
|
||||
nsCOMPtr<nsICameraErrorCallback> mStartRecordingOnErrorCb;
|
||||
nsCOMPtr<nsICameraShutterCallback> mOnShutterCb;
|
||||
nsCOMPtr<nsICameraClosedCallback> mOnClosedCb;
|
||||
nsCOMPtr<nsICameraRecorderStateChange> mOnRecorderStateChangeCb;
|
||||
|
||||
private:
|
||||
CameraControlImpl(const CameraControlImpl&) MOZ_DELETE;
|
||||
@@ -607,6 +611,37 @@ public:
|
||||
nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
|
||||
};
|
||||
|
||||
// Error result runnable
|
||||
class CameraRecorderStateChange : public nsRunnable
|
||||
{
|
||||
public:
|
||||
CameraRecorderStateChange(nsICameraRecorderStateChange* onStateChange, const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber, uint64_t aWindowId)
|
||||
: mOnStateChangeCb(onStateChange)
|
||||
, mStateMsg(aStateMsg)
|
||||
, mStatus(aStatus)
|
||||
, mTrackNumber(aTrackNumber)
|
||||
, mWindowId(aWindowId)
|
||||
{ }
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mOnStateChangeCb && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
|
||||
// For now, just pass the state message and swallow mStatus and mTrackNumber
|
||||
mOnStateChangeCb->HandleStateChange(mStateMsg);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
protected:
|
||||
nsCOMPtr<nsICameraRecorderStateChange> mOnStateChangeCb;
|
||||
const nsString mStateMsg;
|
||||
int32_t mStatus;
|
||||
int32_t mTrackNumber;
|
||||
uint64_t mWindowId;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // DOM_CAMERA_CAMERACONTROLIMPL_H
|
||||
|
||||
@@ -229,6 +229,18 @@ nsDOMCameraControl::SetOnClosed(nsICameraClosedCallback* aOnClosed)
|
||||
return mCameraControl->Set(aOnClosed);
|
||||
}
|
||||
|
||||
/* attribute nsICameraRecorderStateChange onRecorderStateChange; */
|
||||
NS_IMETHODIMP
|
||||
nsDOMCameraControl::GetOnRecorderStateChange(nsICameraRecorderStateChange** aOnRecorderStateChange)
|
||||
{
|
||||
return mCameraControl->Get(aOnRecorderStateChange);
|
||||
}
|
||||
NS_IMETHODIMP
|
||||
nsDOMCameraControl::SetOnRecorderStateChange(nsICameraRecorderStateChange* aOnRecorderStateChange)
|
||||
{
|
||||
return mCameraControl->Set(aOnRecorderStateChange);
|
||||
}
|
||||
|
||||
/* [implicit_jscontext] void startRecording (in jsval aOptions, in nsIDOMDeviceStorage storageArea, in DOMString filename, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
|
||||
NS_IMETHODIMP
|
||||
nsDOMCameraControl::StartRecording(const JS::Value& aOptions, nsIDOMDeviceStorage* storageArea, const nsAString& filename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
|
||||
@@ -237,6 +249,11 @@ nsDOMCameraControl::StartRecording(const JS::Value& aOptions, nsIDOMDeviceStorag
|
||||
NS_ENSURE_TRUE(storageArea, NS_ERROR_INVALID_ARG);
|
||||
|
||||
CameraStartRecordingOptions options;
|
||||
|
||||
// Default values, until the dictionary parser can handle them.
|
||||
options.rotation = 0;
|
||||
options.maxFileSizeBytes = 0;
|
||||
options.maxVideoLengthMs = 0;
|
||||
nsresult rv = options.Init(cx, &aOptions);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "nsThread.h"
|
||||
#include <media/MediaProfiles.h>
|
||||
#include "mozilla/FileUtils.h"
|
||||
#include <media/mediaplayer.h>
|
||||
#include "nsDirectoryServiceDefs.h" // for NS_GetSpecialDirectory
|
||||
#include "nsPrintfCString.h"
|
||||
#include "DOMCameraManager.h"
|
||||
@@ -741,7 +742,7 @@ nsGonkCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult rv = SetupRecording(fd);
|
||||
nsresult rv = SetupRecording(fd, aStartRecording->mOptions.maxFileSizeBytes, aStartRecording->mOptions.maxVideoLengthMs);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (mRecorder->start() != OK) {
|
||||
@@ -914,8 +915,154 @@ nsGonkCameraControl::SetupVideoMode(const nsAString& aProfile)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
class GonkRecorderListener : public IMediaRecorderClient
|
||||
{
|
||||
public:
|
||||
GonkRecorderListener(nsGonkCameraControl* aCameraControl)
|
||||
: mCameraControl(aCameraControl)
|
||||
{
|
||||
DOM_CAMERA_LOGT("%s:%d : this=%p, aCameraControl=%p\n", __func__, __LINE__, this, mCameraControl.get());
|
||||
}
|
||||
|
||||
void notify(int msg, int ext1, int ext2)
|
||||
{
|
||||
if (mCameraControl) {
|
||||
mCameraControl->HandleRecorderEvent(msg, ext1, ext2);
|
||||
}
|
||||
}
|
||||
|
||||
IBinder* onAsBinder()
|
||||
{
|
||||
DOM_CAMERA_LOGE("onAsBinder() called, should NEVER get called!\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
~GonkRecorderListener() { }
|
||||
nsRefPtr<nsGonkCameraControl> mCameraControl;
|
||||
};
|
||||
|
||||
void
|
||||
nsGonkCameraControl::HandleRecorderEvent(int msg, int ext1, int ext2)
|
||||
{
|
||||
/**
|
||||
* Refer to base/include/media/mediarecorder.h for a complete list
|
||||
* of error and info message codes. There are duplicate values
|
||||
* within the status/error code space, as determined by code inspection:
|
||||
*
|
||||
* +------- msg
|
||||
* | +----- ext1
|
||||
* | | +--- ext2
|
||||
* V V V
|
||||
* 1 MEDIA_RECORDER_EVENT_ERROR
|
||||
* 1 MEDIA_RECORDER_ERROR_UNKNOWN
|
||||
* [3] ERROR_MALFORMED
|
||||
* 100 mediaplayer.h::MEDIA_ERROR_SERVER_DIED
|
||||
* 0 <always zero>
|
||||
* 2 MEDIA_RECORDER_EVENT_INFO
|
||||
* 800 MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
|
||||
* 0 <always zero>
|
||||
* 801 MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED
|
||||
* 0 <always zero>
|
||||
* 1000 MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS[1b]
|
||||
* [3] UNKNOWN_ERROR, etc.
|
||||
* 100 MEDIA_ERROR[4]
|
||||
* 100 mediaplayer.h::MEDIA_ERROR_SERVER_DIED
|
||||
* 0 <always zero>
|
||||
* 100 MEDIA_RECORDER_TRACK_EVENT_ERROR
|
||||
* 100 MEDIA_RECORDER_TRACK_ERROR_GENERAL[1a]
|
||||
* [3] UNKNOWN_ERROR, etc.
|
||||
* 200 MEDIA_RECORDER_ERROR_VIDEO_NO_SYNC_FRAME[2]
|
||||
* ? <unknown>
|
||||
* 101 MEDIA_RECORDER_TRACK_EVENT_INFO
|
||||
* 1000 MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS[1a]
|
||||
* [3] UNKNOWN_ERROR, etc.
|
||||
* N see mediarecorder.h::media_recorder_info_type[5]
|
||||
*
|
||||
* 1. a) High 4 bits are the track number, the next 12 bits are reserved,
|
||||
* and the final 16 bits are the actual error code (above).
|
||||
* b) But not in this case.
|
||||
* 2. Never actually used in AOSP code?
|
||||
* 3. Specific error codes are from utils/Errors.h and/or
|
||||
* include/media/stagefright/MediaErrors.h.
|
||||
* 4. Only in frameworks/base/media/libmedia/mediaplayer.cpp.
|
||||
* 5. These are mostly informational and we can ignore them; note that
|
||||
* although the MEDIA_RECORDER_INFO_MAX_DURATION_REACHED and
|
||||
* MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED values are defined in this
|
||||
* enum, they are used with different ext1 codes. /o\
|
||||
*/
|
||||
int trackNum = -1; // no track
|
||||
|
||||
switch (msg) {
|
||||
// Recorder-related events
|
||||
case MEDIA_RECORDER_EVENT_INFO:
|
||||
switch (ext1) {
|
||||
case MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
|
||||
DOM_CAMERA_LOGI("recorder-event : info: maximum file size reached\n");
|
||||
OnRecorderStateChange(NS_LITERAL_STRING("FileSizeLimitReached"), ext2, trackNum);
|
||||
return;
|
||||
|
||||
case MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
|
||||
DOM_CAMERA_LOGI("recorder-event : info: maximum video duration reached\n");
|
||||
OnRecorderStateChange(NS_LITERAL_STRING("VideoLengthLimitReached"), ext2, trackNum);
|
||||
return;
|
||||
|
||||
case MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS:
|
||||
DOM_CAMERA_LOGI("recorder-event : info: track completed\n");
|
||||
OnRecorderStateChange(NS_LITERAL_STRING("TrackCompleted"), ext2, trackNum);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case MEDIA_RECORDER_EVENT_ERROR:
|
||||
switch (ext1) {
|
||||
case MEDIA_RECORDER_ERROR_UNKNOWN:
|
||||
DOM_CAMERA_LOGE("recorder-event : recorder-error: %d (0x%08x)\n", ext2, ext2);
|
||||
OnRecorderStateChange(NS_LITERAL_STRING("MediaRecorderFailed"), ext2, trackNum);
|
||||
return;
|
||||
|
||||
case MEDIA_ERROR_SERVER_DIED:
|
||||
DOM_CAMERA_LOGE("recorder-event : recorder-error: server died\n");
|
||||
OnRecorderStateChange(NS_LITERAL_STRING("MediaServerFailed"), ext2, trackNum);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
// Track-related events, see note 1(a) above.
|
||||
case MEDIA_RECORDER_TRACK_EVENT_INFO:
|
||||
trackNum = (ext1 & 0xF0000000) >> 28;
|
||||
ext1 &= 0xFFFF;
|
||||
switch (ext1) {
|
||||
case MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS:
|
||||
if (ext2 == OK) {
|
||||
DOM_CAMERA_LOGI("recorder-event : track-complete: track %d, %d (0x%08x)\n", trackNum, ext2, ext2);
|
||||
OnRecorderStateChange(NS_LITERAL_STRING("TrackCompleted"), ext2, trackNum);
|
||||
return;
|
||||
}
|
||||
DOM_CAMERA_LOGE("recorder-event : track-error: track %d, %d (0x%08x)\n", trackNum, ext2, ext2);
|
||||
OnRecorderStateChange(NS_LITERAL_STRING("TrackFailed"), ext2, trackNum);
|
||||
return;
|
||||
|
||||
case MEDIA_RECORDER_TRACK_INFO_PROGRESS_IN_TIME:
|
||||
DOM_CAMERA_LOGI("recorder-event : track-info: progress in time: %d ms\n", ext2);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case MEDIA_RECORDER_TRACK_EVENT_ERROR:
|
||||
trackNum = (ext1 & 0xF0000000) >> 28;
|
||||
ext1 &= 0xFFFF;
|
||||
DOM_CAMERA_LOGE("recorder-event : track-error: track %d, %d (0x%08x)\n", trackNum, ext2, ext2);
|
||||
OnRecorderStateChange(NS_LITERAL_STRING("TrackFailed"), ext2, trackNum);
|
||||
return;
|
||||
}
|
||||
|
||||
// All unhandled cases wind up here
|
||||
DOM_CAMERA_LOGW("recorder-event : unhandled: msg=%d, ext1=%d, ext2=%d\n", msg, ext1, ext2);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsGonkCameraControl::SetupRecording(int aFd, int aMaxFileSizeBytes, int aMaxVideoLengthMs)
|
||||
nsGonkCameraControl::SetupRecording(int aFd, int64_t aMaxFileSizeBytes, int64_t aMaxVideoLengthMs)
|
||||
{
|
||||
// choosing a size big enough to hold the params
|
||||
const size_t SIZE = 256;
|
||||
@@ -929,15 +1076,25 @@ nsGonkCameraControl::SetupRecording(int aFd, int aMaxFileSizeBytes, int aMaxVide
|
||||
|
||||
CHECK_SETARG(mRecorder->setCameraHandle((int32_t)mHwHandle));
|
||||
|
||||
snprintf(buffer, SIZE, "max-duration=%d", aMaxVideoLengthMs);
|
||||
DOM_CAMERA_LOGI("maxVideoLengthMs=%lld\n", aMaxVideoLengthMs);
|
||||
if (aMaxVideoLengthMs == 0) {
|
||||
aMaxVideoLengthMs = -1;
|
||||
}
|
||||
snprintf(buffer, SIZE, "max-duration=%lld", aMaxVideoLengthMs);
|
||||
CHECK_SETARG(mRecorder->setParameters(String8(buffer)));
|
||||
|
||||
snprintf(buffer, SIZE, "max-duration=%d", aMaxFileSizeBytes);
|
||||
DOM_CAMERA_LOGI("maxFileSizeBytes=%lld\n", aMaxFileSizeBytes);
|
||||
if (aMaxFileSizeBytes == 0) {
|
||||
aMaxFileSizeBytes = -1;
|
||||
}
|
||||
snprintf(buffer, SIZE, "max-filesize=%lld", aMaxFileSizeBytes);
|
||||
CHECK_SETARG(mRecorder->setParameters(String8(buffer)));
|
||||
|
||||
snprintf(buffer, SIZE, "video-param-rotation-angle-degrees=%d", mVideoRotation);
|
||||
CHECK_SETARG(mRecorder->setParameters(String8(buffer)));
|
||||
|
||||
CHECK_SETARG(mRecorder->setListener(new GonkRecorderListener(this)));
|
||||
|
||||
// recording API needs file descriptor of output file
|
||||
CHECK_SETARG(mRecorder->setOutputFile(aFd, 0, 0));
|
||||
CHECK_SETARG(mRecorder->prepare());
|
||||
|
||||
@@ -54,11 +54,12 @@ public:
|
||||
nsresult GetVideoSizes(nsTArray<CameraSize>& aVideoSizes);
|
||||
nsresult PushParameters();
|
||||
|
||||
nsresult SetupRecording(int aFd, int aMaxFileSizeBytes = -1, int aMaxVideoLengthMs = -1);
|
||||
nsresult SetupRecording(int aFd, int64_t aMaxFileSizeBytes = -1, int64_t aMaxVideoLengthMs = -1);
|
||||
nsresult SetupVideoMode(const nsAString& aProfile);
|
||||
|
||||
void AutoFocusComplete(bool aSuccess);
|
||||
void TakePictureComplete(uint8_t* aData, uint32_t aLength);
|
||||
void HandleRecorderEvent(int msg, int ext1, int ext2);
|
||||
|
||||
protected:
|
||||
~nsGonkCameraControl();
|
||||
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
virtual nsresult Get(nsICameraShutterCallback** aOnShutter) = 0;
|
||||
virtual nsresult Set(nsICameraClosedCallback* aOnClosed) = 0;
|
||||
virtual nsresult Get(nsICameraClosedCallback** aOnClosed) = 0;
|
||||
virtual nsresult Set(nsICameraRecorderStateChange* aOnRecorderStateChange) = 0;
|
||||
virtual nsresult Get(nsICameraRecorderStateChange** aOnRecorderStateChange) = 0;
|
||||
virtual nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue) = 0;
|
||||
virtual nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue) = 0;
|
||||
virtual nsresult GetVideoSizes(nsTArray<CameraSize>& aVideoSizes) = 0;
|
||||
|
||||
@@ -195,8 +195,8 @@ dictionary CameraRecorderOptions
|
||||
dictionary CameraStartRecordingOptions
|
||||
{
|
||||
long rotation;
|
||||
long maxFileSizeBytes;
|
||||
long maxVideoLengthMs;
|
||||
long long maxFileSizeBytes;
|
||||
long long maxVideoLengthMs;
|
||||
};
|
||||
|
||||
[scriptable, function, uuid(0444a687-4bc9-462c-8246-5423f0fe46a4)]
|
||||
@@ -235,6 +235,12 @@ interface nsICameraClosedCallback : nsISupports
|
||||
void handleEvent();
|
||||
};
|
||||
|
||||
[scriptable, function, uuid(550d675a-257d-4713-8b3d-0da53eba68fc)]
|
||||
interface nsICameraRecorderStateChange : nsISupports
|
||||
{
|
||||
void handleStateChange(in DOMString newState);
|
||||
};
|
||||
|
||||
[scriptable, function, uuid(a302c6c9-3776-4d1d-a395-f4105d47c3d3)]
|
||||
interface nsICameraErrorCallback : nsISupports
|
||||
{
|
||||
@@ -245,7 +251,7 @@ interface nsICameraErrorCallback : nsISupports
|
||||
attributes here affect the preview, any pictures taken, and/or
|
||||
any video recorded by the camera.
|
||||
*/
|
||||
[scriptable, uuid(0f206acd-196b-4bdf-8198-44c1a0cd1998)]
|
||||
[scriptable, uuid(70f45209-b69b-4937-bbac-57d82600e2af)]
|
||||
interface nsICameraControl : nsISupports
|
||||
{
|
||||
readonly attribute nsICameraCapabilities capabilities;
|
||||
@@ -341,6 +347,11 @@ interface nsICameraControl : nsISupports
|
||||
recent call to get the camera. */
|
||||
attribute nsICameraClosedCallback onClosed;
|
||||
|
||||
/* the function to call when the recorder changes state, either because
|
||||
the recording process encountered an error, or because one of the
|
||||
recording limits (see CameraStartRecordingOptions) was reached. */
|
||||
attribute nsICameraRecorderStateChange onRecorderStateChange;
|
||||
|
||||
/* tell the camera to attempt to focus the image */
|
||||
void autoFocus(in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user