Bug 1187364 - Part 1. Add ability for camera to pause/resume recording. r=dhylands,bz

This commit is contained in:
Andrew Osmond 2015-08-17 15:20:28 -04:00
parent 16a553cad8
commit b0bb78b2ec
13 changed files with 252 additions and 8 deletions

View File

@ -298,6 +298,8 @@ CameraControlImpl::OnUserError(CameraControlListener::UserContext aContext,
"TakePicture",
"StartRecording",
"StopRecording",
"PauseRecording",
"ResumeRecording",
"SetConfiguration",
"StartPreview",
"StopPreview",
@ -602,6 +604,48 @@ CameraControlImpl::StopRecording()
return Dispatch(new Message(this, CameraControlListener::kInStopRecording));
}
nsresult
CameraControlImpl::PauseRecording()
{
class Message : public ControlMessage
{
public:
Message(CameraControlImpl* aCameraControl,
CameraControlListener::UserContext aContext)
: ControlMessage(aCameraControl, aContext)
{ }
nsresult
RunImpl() override
{
return mCameraControl->PauseRecordingImpl();
}
};
return Dispatch(new Message(this, CameraControlListener::kInPauseRecording));
}
nsresult
CameraControlImpl::ResumeRecording()
{
class Message : public ControlMessage
{
public:
Message(CameraControlImpl* aCameraControl,
CameraControlListener::UserContext aContext)
: ControlMessage(aCameraControl, aContext)
{ }
nsresult
RunImpl() override
{
return mCameraControl->ResumeRecordingImpl();
}
};
return Dispatch(new Message(this, CameraControlListener::kInResumeRecording));
}
nsresult
CameraControlImpl::StartPreview()
{

View File

@ -48,6 +48,8 @@ public:
virtual nsresult StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
const StartRecordingOptions* aOptions) override;
virtual nsresult StopRecording() override;
virtual nsresult PauseRecording() override;
virtual nsresult ResumeRecording() override;
virtual nsresult ResumeContinuousFocus() override;
// Event handlers called directly from outside this class.
@ -121,6 +123,8 @@ protected:
virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
const StartRecordingOptions* aOptions) = 0;
virtual nsresult StopRecordingImpl() = 0;
virtual nsresult PauseRecordingImpl() = 0;
virtual nsresult ResumeRecordingImpl() = 0;
virtual nsresult ResumeContinuousFocusImpl() = 0;
virtual nsresult PushParametersImpl() = 0;
virtual nsresult PullParametersImpl() = 0;

View File

@ -64,6 +64,8 @@ public:
{
kRecorderStopped,
kRecorderStarted,
kRecorderPaused,
kRecorderResumed,
kPosterCreated,
kPosterFailed,
#ifdef MOZ_B2G_CAMERA
@ -109,6 +111,8 @@ public:
kInTakePicture,
kInStartRecording,
kInStopRecording,
kInPauseRecording,
kInResumeRecording,
kInSetConfiguration,
kInStartPreview,
kInStopPreview,

View File

@ -890,6 +890,24 @@ nsDOMCameraControl::StopRecording(ErrorResult& aRv)
aRv = mCameraControl->StopRecording();
}
void
nsDOMCameraControl::PauseRecording(ErrorResult& aRv)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
THROW_IF_NO_CAMERACONTROL();
aRv = mCameraControl->PauseRecording();
}
void
nsDOMCameraControl::ResumeRecording(ErrorResult& aRv)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
THROW_IF_NO_CAMERACONTROL();
aRv = mCameraControl->ResumeRecording();
}
void
nsDOMCameraControl::ResumePreview(ErrorResult& aRv)
{
@ -1400,6 +1418,14 @@ nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState a
mOptions.mPosterStorageArea = nullptr;
break;
case CameraControlListener::kRecorderPaused:
state = NS_LITERAL_STRING("Paused");
break;
case CameraControlListener::kRecorderResumed:
state = NS_LITERAL_STRING("Resumed");
break;
#ifdef MOZ_B2G_CAMERA
case CameraControlListener::kFileSizeLimitReached:
state = NS_LITERAL_STRING("FileSizeLimitReached");
@ -1656,6 +1682,18 @@ nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsr
NS_WARNING("Failed to stop recording");
return;
case CameraControlListener::kInPauseRecording:
// This method doesn't have any callbacks, so all we can do is log the
// failure. This only happens after the hardware has been released.
NS_WARNING("Failed to pause recording");
return;
case CameraControlListener::kInResumeRecording:
// This method doesn't have any callbacks, so all we can do is log the
// failure. This only happens after the hardware has been released.
NS_WARNING("Failed to resume recording");
return;
case CameraControlListener::kInStartPreview:
// This method doesn't have any callbacks, so all we can do is log the
// failure. This only happens after the hardware has been released.

View File

@ -120,6 +120,8 @@ public:
const nsAString& filename,
ErrorResult& aRv);
void StopRecording(ErrorResult& aRv);
void PauseRecording(ErrorResult& aRv);
void ResumeRecording(ErrorResult& aRv);
void ResumePreview(ErrorResult& aRv);
already_AddRefed<dom::Promise> ReleaseHardware(ErrorResult& aRv);
void ResumeContinuousFocus(ErrorResult& aRv);

View File

@ -1323,6 +1323,48 @@ nsGonkCameraControl::StopRecordingImpl()
return NS_DispatchToMainThread(new RecordingComplete(mVideoFile.forget()));
}
nsresult
nsGonkCameraControl::PauseRecordingImpl()
{
ReentrantMonitorAutoEnter mon(mRecorderMonitor);
#ifdef MOZ_WIDGET_GONK
if (!mRecorder) {
return NS_ERROR_NOT_AVAILABLE;
}
int err = mRecorder->pause();
switch (err) {
case OK:
break;
case INVALID_OPERATION:
return NS_ERROR_NOT_IMPLEMENTED;
default:
return NS_ERROR_FAILURE;
}
#endif
OnRecorderStateChange(CameraControlListener::kRecorderPaused);
return NS_OK;
}
nsresult
nsGonkCameraControl::ResumeRecordingImpl()
{
ReentrantMonitorAutoEnter mon(mRecorderMonitor);
#ifdef MOZ_WIDGET_GONK
if (!mRecorder) {
return NS_ERROR_NOT_AVAILABLE;
}
if (mRecorder->resume() != OK) {
return NS_ERROR_FAILURE;
}
#endif
OnRecorderStateChange(CameraControlListener::kRecorderResumed);
return NS_OK;
}
nsresult
nsGonkCameraControl::ResumeContinuousFocusImpl()
{

View File

@ -138,6 +138,8 @@ protected:
virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
const StartRecordingOptions* aOptions = nullptr) override;
virtual nsresult StopRecordingImpl() override;
virtual nsresult PauseRecordingImpl() override;
virtual nsresult ResumeRecordingImpl() override;
virtual nsresult ResumeContinuousFocusImpl() override;
virtual nsresult PushParametersImpl() override;
virtual nsresult PullParametersImpl() override;

View File

@ -1616,14 +1616,40 @@ status_t GonkRecorder::pause() {
if (mWriter == NULL) {
return UNKNOWN_ERROR;
}
mWriter->pause();
if (mStarted) {
if (!mStarted) {
return OK;
}
// Pause is not properly supported by all writers although
// for B2G we only currently use 3GPP/MPEG4
int err = INVALID_OPERATION;
switch (mOutputFormat) {
case OUTPUT_FORMAT_DEFAULT:
case OUTPUT_FORMAT_THREE_GPP:
case OUTPUT_FORMAT_MPEG_4:
err = mWriter->pause();
break;
default:
break;
}
if (err == OK) {
mStarted = false;
}
return err;
}
return OK;
status_t GonkRecorder::resume() {
RE_LOGV("resume");
if (mWriter == NULL) {
return UNKNOWN_ERROR;
}
if (mStarted) {
return OK;
}
int err = mWriter->start(NULL);
if (err == OK) {
mStarted = true;
}
return err;
}
status_t GonkRecorder::stop() {

View File

@ -63,6 +63,7 @@ struct GonkRecorder {
virtual status_t prepare();
virtual status_t start();
virtual status_t pause();
virtual status_t resume();
virtual status_t stop();
virtual status_t close();
virtual status_t reset();

View File

@ -256,6 +256,8 @@ public:
virtual nsresult StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
const StartRecordingOptions* aOptions = nullptr) = 0;
virtual nsresult StopRecording() = 0;
virtual nsresult PauseRecording() = 0;
virtual nsresult ResumeRecording() = 0;
virtual nsresult StartFaceDetection() = 0;
virtual nsresult StopFaceDetection() = 0;
virtual nsresult ResumeContinuousFocus() = 0;

View File

@ -80,6 +80,8 @@ function CameraTestSuite() {
this.rejectTakePicture = this._rejectTakePicture.bind(this);
this.rejectStartRecording = this._rejectStartRecording.bind(this);
this.rejectStopRecording = this._rejectStopRecording.bind(this);
this.rejectPauseRecording = this._rejectPauseRecording.bind(this);
this.rejectResumeRecording = this._rejectResumeRecording.bind(this);
this.rejectPreviewStarted = this._rejectPreviewStarted.bind(this);
var self = this;
@ -409,6 +411,14 @@ CameraTestSuite.prototype = {
return this.logError('stop recording failed', e);
},
_rejectPauseRecording: function(e) {
return this.logError('pause recording failed', e);
},
_rejectResumeRecording: function(e) {
return this.logError('resume recording failed', e);
},
_rejectPreviewStarted: function(e) {
return this.logError('preview start failed', e);
},

View File

@ -45,6 +45,50 @@ suite.test('recording', function() {
return Promise.all([domPromise, eventPromise]);
}
function pauseRecording(p) {
var eventPromise = new Promise(function(resolve, reject) {
function onEvent(evt) {
ok(evt.newState === 'Paused', 'recorder state change event = ' + evt.newState);
suite.camera.removeEventListener('recorderstatechange', onEvent);
resolve();
}
suite.camera.addEventListener('recorderstatechange', onEvent);
});
var domPromise = new Promise(function(resolve, reject) {
try {
suite.camera.pauseRecording();
resolve();
} catch(e) {
reject(e);
}
});
return Promise.all([domPromise, eventPromise]);
}
function resumeRecording(p) {
var eventPromise = new Promise(function(resolve, reject) {
function onEvent(evt) {
ok(evt.newState === 'Resumed', 'recorder state change event = ' + evt.newState);
suite.camera.removeEventListener('recorderstatechange', onEvent);
resolve();
}
suite.camera.addEventListener('recorderstatechange', onEvent);
});
var domPromise = new Promise(function(resolve, reject) {
try {
suite.camera.resumeRecording();
resolve();
} catch(e) {
reject(e);
}
});
return Promise.all([domPromise, eventPromise]);
}
function stopRecording(p) {
var eventPromise = new Promise(function(resolve, reject) {
function onEvent(evt) {
@ -70,7 +114,9 @@ suite.test('recording', function() {
return suite.getCamera(undefined, baseConfig)
.then(cleanup, suite.rejectGetCamera)
.then(startRecording)
.then(stopRecording, suite.rejectStartRecording)
.then(pauseRecording, suite.rejectStartRecording)
.then(resumeRecording, suite.rejectPauseRecording)
.then(stopRecording, suite.rejectResumeRecording)
.catch(suite.rejectStopRecording);
});

View File

@ -249,7 +249,19 @@ interface CameraControl : MediaStream
recording limits (see CameraStartRecordingOptions) was reached.
event type is CameraStateChangeEvent where:
'newState' is the new recorder state */
'newState' is one of the following states:
'Started' if the recording has begun capturing data
'Stopped' when the recording has completed (success and failure)
'Paused' if the recording is paused
'Resumed' if the recording is resumed after pausing
'PosterCreated' if a poster was requested and created
'PosterFailed' if a poster was requested and failed to create
'FileSizeLimitReached' if stopped due to file size limit
'VideoLengthLimitReached' if stopped due to a time limit
'TrackCompleted' if audio or video track complete when stopping
'TrackFailed' if audio or video track incomplete when stopping
'MediaRecorderFailed' if failed due to local error
'MediaServerFailed' if failed due to media server
attribute EventHandler onrecorderstatechange;
/* the event dispatched when the viewfinder stops or starts,
@ -346,10 +358,21 @@ interface CameraControl : MediaStream
DeviceStorage storageArea,
DOMString filename);
/* stop precording video. */
/* stop recording video. */
[Throws]
void stopRecording();
/* pause recording video. The camera remains active but audio and video
frames are no longer saved in the output file. If called when not
recording or already paused, it fails silently. */
[Throws]
void pauseRecording();
/* resume recording video while paused. If called when not recording or
not paused, it fails silently. */
[Throws]
void resumeRecording();
/* call in or after the takePicture() onSuccess callback to
resume the camera preview stream. */
[Throws]