Bug 1152500 - Fix how stop recording may be handled out-of-order. r=dhylands

This commit is contained in:
Andrew Osmond 2015-06-12 18:51:38 -04:00
Родитель a7226ca634
Коммит dca0017a8d
3 изменённых файлов: 150 добавлений и 32 удалений

Просмотреть файл

@ -201,6 +201,7 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
, mGetCameraPromise(aPromise)
, mWindow(aWindow)
, mPreviewState(CameraControlListener::kPreviewStopped)
, mRecording(false)
, mSetInitialConfig(false)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
@ -742,31 +743,22 @@ nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
return nullptr;
}
if (mStartRecordingPromise) {
if (mStartRecordingPromise || mRecording) {
promise->MaybeReject(NS_ERROR_IN_PROGRESS);
return promise.forget();
}
NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
#ifdef MOZ_B2G
if (!mAudioChannelAgent) {
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
if (mAudioChannelAgent) {
// Camera app will stop recording when it falls to the background, so no callback is necessary.
mAudioChannelAgent->Init(mWindow, (int32_t)AudioChannel::Content, nullptr);
// Video recording doesn't output any sound, so it's not necessary to check canPlay.
int32_t canPlay;
mAudioChannelAgent->StartPlaying(&canPlay);
}
aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
if (aRv.Failed()) {
return nullptr;
}
#endif
mDSFileDescriptor = new DeviceStorageFileDescriptor();
nsRefPtr<DOMRequest> request = aStorageArea.CreateFileDescriptor(aFilename,
mDSFileDescriptor.get(),
aRv);
if (aRv.Failed()) {
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
return nullptr;
}
@ -775,10 +767,12 @@ nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
EventListenerManager* elm = request->GetOrCreateListenerManager();
if (!elm) {
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
mRecording = true;
nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
elm->AddEventListener(NS_LITERAL_STRING("success"), listener, false, false);
elm->AddEventListener(NS_LITERAL_STRING("error"), listener, false, false);
@ -792,6 +786,10 @@ nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
if (!mCameraControl) {
rv = NS_ERROR_NOT_AVAILABLE;
} else if (!mRecording) {
// Race condition where StopRecording comes in before we issue
// the start recording request to Gonk
rv = NS_ERROR_ABORT;
} else if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) {
ICameraControl::StartRecordingOptions o;
@ -823,13 +821,8 @@ nsDOMCameraControl::StopRecording(ErrorResult& aRv)
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
THROW_IF_NO_CAMERACONTROL();
#ifdef MOZ_B2G
if (mAudioChannelAgent) {
mAudioChannelAgent->StopPlaying();
mAudioChannelAgent = nullptr;
}
#endif
ReleaseAudioChannelAgent();
mRecording = false;
aRv = mCameraControl->StopRecording();
}
@ -1039,15 +1032,50 @@ nsDOMCameraControl::Shutdown()
}
}
void
nsDOMCameraControl::ReleaseAudioChannelAgent()
{
#ifdef MOZ_B2G
if (mAudioChannelAgent) {
mAudioChannelAgent->StopPlaying();
mAudioChannelAgent = nullptr;
}
#endif
}
nsresult
nsDOMCameraControl::NotifyRecordingStatusChange(const nsString& aMsg)
{
NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
return MediaManager::NotifyRecordingStatusChange(mWindow,
aMsg,
true /* aIsAudio */,
true /* aIsVideo */);
if (aMsg.EqualsLiteral("shutdown")) {
ReleaseAudioChannelAgent();
}
nsresult rv = MediaManager::NotifyRecordingStatusChange(mWindow,
aMsg,
true /* aIsAudio */,
true /* aIsVideo */);
if (NS_FAILED(rv)) {
return rv;
}
#ifdef MOZ_B2G
if (aMsg.EqualsLiteral("starting") && !mAudioChannelAgent) {
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
if (!mAudioChannelAgent) {
return NS_ERROR_UNEXPECTED;
}
// Camera app will stop recording when it falls to the background, so no callback is necessary.
mAudioChannelAgent->Init(mWindow, (int32_t)AudioChannel::Content, nullptr);
// Video recording doesn't output any sound, so it's not necessary to check canPlay.
int32_t canPlay;
mAudioChannelAgent->StartPlaying(&canPlay);
}
#endif
return rv;
}
already_AddRefed<Promise>
@ -1231,7 +1259,7 @@ nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState a
int32_t aArg, int32_t aTrackNum)
{
// For now, we do nothing with 'aStatus' and 'aTrackNum'.
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
DOM_CAMERA_LOGT("%s:%d : this=%p, state=%u\n", __func__, __LINE__, this, aState);
MOZ_ASSERT(NS_IsMainThread());
ErrorResult ignored;
@ -1424,7 +1452,7 @@ nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture)
void
nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsresult aError)
{
DOM_CAMERA_LOGI("DOM OnUserError : this=%paContext=%u, aError=0x%x\n",
DOM_CAMERA_LOGI("DOM OnUserError : this=%p, aContext=%u, aError=0x%x\n",
this, aContext, aError);
MOZ_ASSERT(NS_IsMainThread());
@ -1477,6 +1505,8 @@ nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsr
case CameraControlListener::kInStartRecording:
promise = mStartRecordingPromise.forget();
mRecording = false;
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
break;
case CameraControlListener::kInStartFaceDetection:

Просмотреть файл

@ -182,6 +182,7 @@ protected:
bool IsWindowStillActive();
nsresult SelectPreviewSize(const dom::CameraSize& aRequestedPreviewSize, ICameraControl::Size& aSelectedPreviewSize);
void ReleaseAudioChannelAgent();
nsresult NotifyRecordingStatusChange(const nsString& aMsg);
already_AddRefed<dom::Promise> CreatePromise(ErrorResult& aRv);
@ -223,7 +224,7 @@ protected:
dom::CameraStartRecordingOptions mOptions;
nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
DOMCameraControlListener::PreviewState mPreviewState;
bool mRecording;
bool mSetInitialConfig;
#ifdef MOZ_WIDGET_GONK

Просмотреть файл

@ -19,11 +19,18 @@ var baseConfig = {
mode: 'video',
};
var testFilePath = 'test.3gp';
var storage = navigator.getDeviceStorage("videos");
suite.test('recording', function() {
storage.delete("test.3gp");
function cleanup()
{
return storage.delete(testFilePath).then(function(p) {
}, function(e) {
Promise.resolve();
});
}
suite.test('recording', function() {
function startRecording(p) {
var eventPromise = new Promise(function(resolve, reject) {
function onEvent(evt) {
@ -34,7 +41,7 @@ suite.test('recording', function() {
suite.camera.addEventListener('recorderstatechange', onEvent);
});
var domPromise = suite.camera.startRecording({}, storage, "test.3gp");
var domPromise = suite.camera.startRecording({}, storage, testFilePath);
return Promise.all([domPromise, eventPromise]);
}
@ -61,11 +68,91 @@ suite.test('recording', function() {
}
return suite.getCamera(undefined, baseConfig)
.then(startRecording, suite.rejectGetCamera)
.then(cleanup, suite.rejectGetCamera)
.then(startRecording)
.then(stopRecording, suite.rejectStartRecording)
.catch(suite.rejectStopRecording);
});
// bug 1152500
suite.test('interrupt-record', function() {
function startRecording(p) {
var startPromise = suite.camera.startRecording({}, storage, testFilePath);
suite.camera.stopRecording();
return startPromise;
}
function rejectStartRecording(e) {
ok(e.name === 'NS_ERROR_ABORT', 'onError called correctly on startRecording interrupted: ' + e);
}
return suite.getCamera(undefined, baseConfig)
.then(cleanup, suite.rejectGetCamera)
.then(startRecording)
.then(suite.expectedRejectStartRecording, rejectStartRecording);
});
// bug 1152500
suite.test('already-initiated-recording', function() {
function startRecording(p) {
return new Promise(function(resolve, reject) {
var firstCall = false;
var secondCall = false;
function end() {
if (firstCall && secondCall) {
resolve();
}
}
suite.camera.startRecording({}, storage, testFilePath).then(function(p) {
ok(true, "First call to startRecording() succeeded");
firstCall = true;
end();
}, function(e) {
ok(false, "First call to startRecording() failed unexpectedly with: " + e);
firstCall = true;
end();
});
suite.camera.startRecording({}, storage, testFilePath).then(function(p) {
ok(false, "Second call to startRecording() succeeded unexpectedly");
secondCall = true;
end();
}, function(e) {
ok(e.name === 'NS_ERROR_IN_PROGRESS', "Second call to startRecording() failed expectedly with: " + e);
secondCall = true;
end();
});
});
}
return suite.getCamera(undefined, baseConfig)
.then(cleanup, suite.rejectGetCamera)
.then(startRecording);
});
// bug 1152500
suite.test('already-started-recording', function() {
function startRecording(p) {
return suite.camera.startRecording({}, storage, testFilePath);
}
function startRecordingAgain(p) {
return suite.camera.startRecording({}, storage, testFilePath);
}
function rejectStartRecordingAgain(e) {
ok(e.name === 'NS_ERROR_IN_PROGRESS', "Second call to startRecording() failed expectedly with: " + e);
}
return suite.getCamera(undefined, baseConfig)
.then(cleanup, suite.rejectGetCamera)
.then(startRecording)
.then(startRecordingAgain, suite.rejectStartRecording)
.then(suite.expectedRejectStartRecording, rejectStartRecordingAgain)
});
suite.setup()
.then(suite.run);