зеркало из https://github.com/mozilla/gecko-dev.git
Bug 863852 - Part 2 - Handle reentrance in the state machine. r=smaug
This commit is contained in:
Родитель
7f1573cedd
Коммит
597f33c342
|
@ -38,10 +38,6 @@ static const uint32_t kSPEECH_DETECTION_TIMEOUT_MS = 10000;
|
||||||
// kSAMPLE_RATE frames = 1s, kESTIMATION_FRAMES frames = 300ms
|
// kSAMPLE_RATE frames = 1s, kESTIMATION_FRAMES frames = 300ms
|
||||||
static const uint32_t kESTIMATION_SAMPLES = 300 * kSAMPLE_RATE / 1000;
|
static const uint32_t kESTIMATION_SAMPLES = 300 * kSAMPLE_RATE / 1000;
|
||||||
|
|
||||||
#define STATE_EQUALS(state) (mCurrentState == state)
|
|
||||||
#define STATE_BETWEEN(state1, state2) \
|
|
||||||
(mCurrentState >= (state1) && mCurrentState <= (state2))
|
|
||||||
|
|
||||||
#ifdef PR_LOGGING
|
#ifdef PR_LOGGING
|
||||||
PRLogModuleInfo*
|
PRLogModuleInfo*
|
||||||
GetSpeechRecognitionLog()
|
GetSpeechRecognitionLog()
|
||||||
|
@ -68,8 +64,7 @@ NS_IMPL_RELEASE_INHERITED(SpeechRecognition, nsDOMEventTargetHelper)
|
||||||
struct SpeechRecognition::TestConfig SpeechRecognition::mTestConfig;
|
struct SpeechRecognition::TestConfig SpeechRecognition::mTestConfig;
|
||||||
|
|
||||||
SpeechRecognition::SpeechRecognition()
|
SpeechRecognition::SpeechRecognition()
|
||||||
: mProcessingEvent(false)
|
: mEndpointer(kSAMPLE_RATE)
|
||||||
, mEndpointer(kSAMPLE_RATE)
|
|
||||||
, mAudioSamplesPerChunk(mEndpointer.FrameSize())
|
, mAudioSamplesPerChunk(mEndpointer.FrameSize())
|
||||||
, mSpeechDetectionTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
|
, mSpeechDetectionTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
|
||||||
{
|
{
|
||||||
|
@ -89,7 +84,21 @@ SpeechRecognition::SpeechRecognition()
|
||||||
Preferences::GetInt(PREFERENCE_ENDPOINTER_LONG_SILENCE_LENGTH, 1000000));
|
Preferences::GetInt(PREFERENCE_ENDPOINTER_LONG_SILENCE_LENGTH, 1000000));
|
||||||
mEndpointer.set_long_speech_length(
|
mEndpointer.set_long_speech_length(
|
||||||
Preferences::GetInt(PREFERENCE_ENDPOINTER_SILENCE_LENGTH, 3 * 1000000));
|
Preferences::GetInt(PREFERENCE_ENDPOINTER_SILENCE_LENGTH, 3 * 1000000));
|
||||||
mCurrentState = Reset();
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
SpeechRecognition::StateBetween(FSMState begin, FSMState end)
|
||||||
|
{
|
||||||
|
return mCurrentState >= begin && mCurrentState <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SpeechRecognition::SetState(FSMState state)
|
||||||
|
{
|
||||||
|
mCurrentState = state;
|
||||||
|
SR_LOG("Transitioned to state %s", GetName(mCurrentState));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSObject*
|
JSObject*
|
||||||
|
@ -125,17 +134,22 @@ SpeechRecognition::ProcessEvent(SpeechEvent* aEvent)
|
||||||
GetName(aEvent),
|
GetName(aEvent),
|
||||||
GetName(mCurrentState));
|
GetName(mCurrentState));
|
||||||
|
|
||||||
MOZ_ASSERT(!mProcessingEvent, "Event dispatch should be sequential!");
|
// Run priority events first
|
||||||
mProcessingEvent = true;
|
for (uint32_t i = 0; i < mPriorityEvents.Length(); ++i) {
|
||||||
|
nsRefPtr<SpeechEvent> event = mPriorityEvents[i];
|
||||||
|
|
||||||
mCurrentState = TransitionAndGetNextState(aEvent);
|
SR_LOG("Processing priority %s", GetName(event));
|
||||||
SR_LOG("Transitioned to state: %s", GetName(mCurrentState));
|
Transition(event);
|
||||||
|
}
|
||||||
|
|
||||||
mProcessingEvent = false;
|
mPriorityEvents.Clear();
|
||||||
|
|
||||||
|
SR_LOG("Processing %s received as argument", GetName(aEvent));
|
||||||
|
Transition(aEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent)
|
SpeechRecognition::Transition(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
switch (mCurrentState) {
|
switch (mCurrentState) {
|
||||||
case STATE_IDLE:
|
case STATE_IDLE:
|
||||||
|
@ -143,112 +157,145 @@ SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent)
|
||||||
case EVENT_START:
|
case EVENT_START:
|
||||||
// TODO: may want to time out if we wait too long
|
// TODO: may want to time out if we wait too long
|
||||||
// for user to approve
|
// for user to approve
|
||||||
return STATE_STARTING;
|
WaitForAudioData(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_STOP:
|
case EVENT_STOP:
|
||||||
case EVENT_ABORT:
|
case EVENT_ABORT:
|
||||||
case EVENT_AUDIO_DATA:
|
case EVENT_AUDIO_DATA:
|
||||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||||
return DoNothing(aEvent);
|
DoNothing(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_AUDIO_ERROR:
|
case EVENT_AUDIO_ERROR:
|
||||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||||
return AbortError(aEvent);
|
AbortError(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_COUNT:
|
case EVENT_COUNT:
|
||||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
case STATE_STARTING:
|
case STATE_STARTING:
|
||||||
switch (aEvent->mType) {
|
switch (aEvent->mType) {
|
||||||
case EVENT_AUDIO_DATA:
|
case EVENT_AUDIO_DATA:
|
||||||
return StartedAudioCapture(aEvent);
|
StartedAudioCapture(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_AUDIO_ERROR:
|
case EVENT_AUDIO_ERROR:
|
||||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||||
return AbortError(aEvent);
|
AbortError(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_ABORT:
|
case EVENT_ABORT:
|
||||||
return AbortSilently(aEvent);
|
AbortSilently(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_STOP:
|
case EVENT_STOP:
|
||||||
return Reset();
|
Reset();
|
||||||
|
break;
|
||||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||||
return DoNothing(aEvent);
|
DoNothing(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_START:
|
case EVENT_START:
|
||||||
SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
|
SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
|
||||||
MOZ_NOT_REACHED("");
|
MOZ_NOT_REACHED("");
|
||||||
case EVENT_COUNT:
|
case EVENT_COUNT:
|
||||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
case STATE_ESTIMATING:
|
case STATE_ESTIMATING:
|
||||||
switch (aEvent->mType) {
|
switch (aEvent->mType) {
|
||||||
case EVENT_AUDIO_DATA:
|
case EVENT_AUDIO_DATA:
|
||||||
return WaitForEstimation(aEvent);
|
WaitForEstimation(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_STOP:
|
case EVENT_STOP:
|
||||||
return StopRecordingAndRecognize(aEvent);
|
StopRecordingAndRecognize(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_ABORT:
|
case EVENT_ABORT:
|
||||||
return AbortSilently(aEvent);
|
AbortSilently(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||||
return DoNothing(aEvent);
|
DoNothing(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_AUDIO_ERROR:
|
case EVENT_AUDIO_ERROR:
|
||||||
return AbortError(aEvent);
|
AbortError(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_START:
|
case EVENT_START:
|
||||||
SR_LOG("STATE_ESTIMATING: Unhandled event %d", aEvent->mType);
|
SR_LOG("STATE_ESTIMATING: Unhandled event %d", aEvent->mType);
|
||||||
MOZ_NOT_REACHED("");
|
MOZ_NOT_REACHED("");
|
||||||
case EVENT_COUNT:
|
case EVENT_COUNT:
|
||||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
case STATE_WAITING_FOR_SPEECH:
|
case STATE_WAITING_FOR_SPEECH:
|
||||||
switch (aEvent->mType) {
|
switch (aEvent->mType) {
|
||||||
case EVENT_AUDIO_DATA:
|
case EVENT_AUDIO_DATA:
|
||||||
return DetectSpeech(aEvent);
|
DetectSpeech(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_STOP:
|
case EVENT_STOP:
|
||||||
return StopRecordingAndRecognize(aEvent);
|
StopRecordingAndRecognize(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_ABORT:
|
case EVENT_ABORT:
|
||||||
return AbortSilently(aEvent);
|
AbortSilently(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_AUDIO_ERROR:
|
case EVENT_AUDIO_ERROR:
|
||||||
return AbortError(aEvent);
|
AbortError(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||||
return DoNothing(aEvent);
|
DoNothing(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_START:
|
case EVENT_START:
|
||||||
SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
|
SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
|
||||||
MOZ_NOT_REACHED("");
|
MOZ_NOT_REACHED("");
|
||||||
case EVENT_COUNT:
|
case EVENT_COUNT:
|
||||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
case STATE_RECOGNIZING:
|
case STATE_RECOGNIZING:
|
||||||
switch (aEvent->mType) {
|
switch (aEvent->mType) {
|
||||||
case EVENT_AUDIO_DATA:
|
case EVENT_AUDIO_DATA:
|
||||||
return WaitForSpeechEnd(aEvent);
|
WaitForSpeechEnd(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_STOP:
|
case EVENT_STOP:
|
||||||
return StopRecordingAndRecognize(aEvent);
|
StopRecordingAndRecognize(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_AUDIO_ERROR:
|
case EVENT_AUDIO_ERROR:
|
||||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||||
return AbortError(aEvent);
|
AbortError(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_ABORT:
|
case EVENT_ABORT:
|
||||||
return AbortSilently(aEvent);
|
AbortSilently(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||||
return DoNothing(aEvent);
|
DoNothing(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_START:
|
case EVENT_START:
|
||||||
SR_LOG("STATE_RECOGNIZING: Unhandled aEvent %s", GetName(aEvent));
|
SR_LOG("STATE_RECOGNIZING: Unhandled aEvent %s", GetName(aEvent));
|
||||||
MOZ_NOT_REACHED("");
|
MOZ_NOT_REACHED("");
|
||||||
case EVENT_COUNT:
|
case EVENT_COUNT:
|
||||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
case STATE_WAITING_FOR_RESULT:
|
case STATE_WAITING_FOR_RESULT:
|
||||||
switch (aEvent->mType) {
|
switch (aEvent->mType) {
|
||||||
case EVENT_STOP:
|
case EVENT_STOP:
|
||||||
return DoNothing(aEvent);
|
DoNothing(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_AUDIO_ERROR:
|
case EVENT_AUDIO_ERROR:
|
||||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||||
return AbortError(aEvent);
|
AbortError(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||||
return NotifyFinalResult(aEvent);
|
NotifyFinalResult(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_AUDIO_DATA:
|
case EVENT_AUDIO_DATA:
|
||||||
return DoNothing(aEvent);
|
DoNothing(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_ABORT:
|
case EVENT_ABORT:
|
||||||
return AbortSilently(aEvent);
|
AbortSilently(aEvent);
|
||||||
|
break;
|
||||||
case EVENT_START:
|
case EVENT_START:
|
||||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||||
SR_LOG("STATE_WAITING_FOR_RESULT: Unhandled aEvent %s", GetName(aEvent));
|
SR_LOG("STATE_WAITING_FOR_RESULT: Unhandled aEvent %s", GetName(aEvent));
|
||||||
|
@ -256,12 +303,15 @@ SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent)
|
||||||
case EVENT_COUNT:
|
case EVENT_COUNT:
|
||||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case STATE_ABORTING:
|
||||||
|
DoNothing(aEvent);
|
||||||
|
break;
|
||||||
case STATE_COUNT:
|
case STATE_COUNT:
|
||||||
MOZ_NOT_REACHED("Invalid state STATE_COUNT");
|
MOZ_NOT_REACHED("Invalid state STATE_COUNT");
|
||||||
}
|
}
|
||||||
SR_LOG("Unhandled state %s", GetName(mCurrentState));
|
|
||||||
MOZ_NOT_REACHED("");
|
return;
|
||||||
return mCurrentState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -311,101 +361,116 @@ SpeechRecognition::GetRecognitionServiceCID(nsACString& aResultCID)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************
|
/****************************************************************************
|
||||||
* FSM Transition functions *
|
* FSM Transition functions
|
||||||
****************************/
|
*
|
||||||
|
* If a transition function may cause a DOM event to be fired,
|
||||||
|
* it may also be re-entered, since the event handler may cause the
|
||||||
|
* event loop to spin and new SpeechEvents to be processed.
|
||||||
|
*
|
||||||
|
* Rules:
|
||||||
|
* 1) These methods should call SetState as soon as possible.
|
||||||
|
* 2) If these methods dispatch DOM events, or call methods that dispatch
|
||||||
|
* DOM events, that should be done as late as possible.
|
||||||
|
* 3) If anything must happen after dispatching a DOM event, make sure
|
||||||
|
* the state is still what the method expected it to be.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::Reset()
|
SpeechRecognition::Reset()
|
||||||
{
|
{
|
||||||
|
SetState(STATE_IDLE);
|
||||||
mRecognitionService = nullptr;
|
mRecognitionService = nullptr;
|
||||||
mEstimationSamples = 0;
|
mEstimationSamples = 0;
|
||||||
mBufferedSamples = 0;
|
mBufferedSamples = 0;
|
||||||
mSpeechDetectionTimer->Cancel();
|
mSpeechDetectionTimer->Cancel();
|
||||||
|
|
||||||
return STATE_IDLE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
void
|
||||||
* Since the handler for "end" may call
|
|
||||||
* start(), we want to fully reset before dispatching
|
|
||||||
* the event.
|
|
||||||
*/
|
|
||||||
SpeechRecognition::FSMState
|
|
||||||
SpeechRecognition::ResetAndEnd()
|
SpeechRecognition::ResetAndEnd()
|
||||||
{
|
{
|
||||||
mCurrentState = Reset();
|
Reset();
|
||||||
DispatchTrustedEvent(NS_LITERAL_STRING("end"));
|
DispatchTrustedEvent(NS_LITERAL_STRING("end"));
|
||||||
return mCurrentState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
|
SpeechRecognition::WaitForAudioData(SpeechEvent* aEvent)
|
||||||
|
{
|
||||||
|
SetState(STATE_STARTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
SpeechRecognition::StartedAudioCapture(SpeechEvent* aEvent)
|
SpeechRecognition::StartedAudioCapture(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
|
SetState(STATE_ESTIMATING);
|
||||||
|
|
||||||
mEndpointer.SetEnvironmentEstimationMode();
|
mEndpointer.SetEnvironmentEstimationMode();
|
||||||
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
|
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
|
||||||
|
|
||||||
DispatchTrustedEvent(NS_LITERAL_STRING("start"));
|
|
||||||
DispatchTrustedEvent(NS_LITERAL_STRING("audiostart"));
|
DispatchTrustedEvent(NS_LITERAL_STRING("audiostart"));
|
||||||
|
if (mCurrentState == STATE_ESTIMATING) {
|
||||||
return STATE_ESTIMATING;
|
DispatchTrustedEvent(NS_LITERAL_STRING("start"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::StopRecordingAndRecognize(SpeechEvent* aEvent)
|
SpeechRecognition::StopRecordingAndRecognize(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
StopRecording();
|
SetState(STATE_WAITING_FOR_RESULT);
|
||||||
|
|
||||||
MOZ_ASSERT(mRecognitionService, "Service deleted before recording done");
|
MOZ_ASSERT(mRecognitionService, "Service deleted before recording done");
|
||||||
mRecognitionService->SoundEnd();
|
mRecognitionService->SoundEnd();
|
||||||
|
|
||||||
return STATE_WAITING_FOR_RESULT;
|
StopRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::WaitForEstimation(SpeechEvent* aEvent)
|
SpeechRecognition::WaitForEstimation(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
|
SetState(STATE_ESTIMATING);
|
||||||
|
|
||||||
|
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
|
||||||
if (mEstimationSamples > kESTIMATION_SAMPLES) {
|
if (mEstimationSamples > kESTIMATION_SAMPLES) {
|
||||||
mEndpointer.SetUserInputMode();
|
mEndpointer.SetUserInputMode();
|
||||||
return STATE_WAITING_FOR_SPEECH;
|
SetState(STATE_WAITING_FOR_SPEECH);
|
||||||
}
|
}
|
||||||
|
|
||||||
return STATE_ESTIMATING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::DetectSpeech(SpeechEvent* aEvent)
|
SpeechRecognition::DetectSpeech(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
ProcessAudioSegment(aEvent->mAudioSegment);
|
SetState(STATE_WAITING_FOR_SPEECH);
|
||||||
|
|
||||||
|
ProcessAudioSegment(aEvent->mAudioSegment);
|
||||||
if (mEndpointer.DidStartReceivingSpeech()) {
|
if (mEndpointer.DidStartReceivingSpeech()) {
|
||||||
mSpeechDetectionTimer->Cancel();
|
mSpeechDetectionTimer->Cancel();
|
||||||
|
SetState(STATE_RECOGNIZING);
|
||||||
DispatchTrustedEvent(NS_LITERAL_STRING("speechstart"));
|
DispatchTrustedEvent(NS_LITERAL_STRING("speechstart"));
|
||||||
return STATE_RECOGNIZING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return STATE_WAITING_FOR_SPEECH;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::WaitForSpeechEnd(SpeechEvent* aEvent)
|
SpeechRecognition::WaitForSpeechEnd(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
|
SetState(STATE_RECOGNIZING);
|
||||||
|
|
||||||
ProcessAudioSegment(aEvent->mAudioSegment);
|
ProcessAudioSegment(aEvent->mAudioSegment);
|
||||||
|
|
||||||
if (mEndpointer.speech_input_complete()) {
|
if (mEndpointer.speech_input_complete()) {
|
||||||
// FIXME: StopRecordingAndRecognize should only be called for single
|
|
||||||
// shot services for continous we should just inform the service
|
|
||||||
DispatchTrustedEvent(NS_LITERAL_STRING("speechend"));
|
DispatchTrustedEvent(NS_LITERAL_STRING("speechend"));
|
||||||
return StopRecordingAndRecognize(aEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return STATE_RECOGNIZING;
|
if (mCurrentState == STATE_RECOGNIZING) {
|
||||||
|
// FIXME: StopRecordingAndRecognize should only be called for single
|
||||||
|
// shot services for continuous we should just inform the service
|
||||||
|
StopRecordingAndRecognize(aEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::NotifyFinalResult(SpeechEvent* aEvent)
|
SpeechRecognition::NotifyFinalResult(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
|
ResetAndEnd();
|
||||||
|
|
||||||
nsCOMPtr<nsIDOMEvent> domEvent;
|
nsCOMPtr<nsIDOMEvent> domEvent;
|
||||||
NS_NewDOMSpeechRecognitionEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr);
|
NS_NewDOMSpeechRecognitionEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr);
|
||||||
|
|
||||||
|
@ -420,35 +485,37 @@ SpeechRecognition::NotifyFinalResult(SpeechEvent* aEvent)
|
||||||
|
|
||||||
bool defaultActionEnabled;
|
bool defaultActionEnabled;
|
||||||
this->DispatchEvent(domEvent, &defaultActionEnabled);
|
this->DispatchEvent(domEvent, &defaultActionEnabled);
|
||||||
return ResetAndEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::DoNothing(SpeechEvent* aEvent)
|
SpeechRecognition::DoNothing(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
return mCurrentState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::AbortSilently(SpeechEvent* aEvent)
|
SpeechRecognition::AbortSilently(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
|
bool stopRecording = StateBetween(STATE_ESTIMATING, STATE_RECOGNIZING);
|
||||||
|
|
||||||
|
// prevent reentrancy from DOM events
|
||||||
|
SetState(STATE_ABORTING);
|
||||||
|
|
||||||
if (mRecognitionService) {
|
if (mRecognitionService) {
|
||||||
mRecognitionService->Abort();
|
mRecognitionService->Abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (STATE_BETWEEN(STATE_ESTIMATING, STATE_RECOGNIZING)) {
|
if (stopRecording) {
|
||||||
StopRecording();
|
StopRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResetAndEnd();
|
ResetAndEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeechRecognition::FSMState
|
void
|
||||||
SpeechRecognition::AbortError(SpeechEvent* aEvent)
|
SpeechRecognition::AbortError(SpeechEvent* aEvent)
|
||||||
{
|
{
|
||||||
FSMState nextState = AbortSilently(aEvent);
|
AbortSilently(aEvent);
|
||||||
NotifyError(aEvent);
|
NotifyError(aEvent);
|
||||||
return nextState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -506,7 +573,7 @@ SpeechRecognition::Observe(nsISupports* aSubject, const char* aTopic,
|
||||||
MOZ_ASSERT(NS_IsMainThread(), "Observer invoked off the main thread");
|
MOZ_ASSERT(NS_IsMainThread(), "Observer invoked off the main thread");
|
||||||
|
|
||||||
if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) &&
|
if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) &&
|
||||||
STATE_BETWEEN(STATE_IDLE, STATE_WAITING_FOR_SPEECH)) {
|
StateBetween(STATE_IDLE, STATE_WAITING_FOR_SPEECH)) {
|
||||||
|
|
||||||
DispatchError(SpeechRecognition::EVENT_AUDIO_ERROR,
|
DispatchError(SpeechRecognition::EVENT_AUDIO_ERROR,
|
||||||
nsIDOMSpeechRecognitionError::NO_SPEECH,
|
nsIDOMSpeechRecognitionError::NO_SPEECH,
|
||||||
|
@ -638,7 +705,7 @@ SpeechRecognition::SetServiceURI(const nsAString& aArg, ErrorResult& aRv)
|
||||||
void
|
void
|
||||||
SpeechRecognition::Start(ErrorResult& aRv)
|
SpeechRecognition::Start(ErrorResult& aRv)
|
||||||
{
|
{
|
||||||
if (!STATE_EQUALS(STATE_IDLE)) {
|
if (!mCurrentState == STATE_IDLE) {
|
||||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -830,7 +897,8 @@ SpeechRecognition::GetName(FSMState aId)
|
||||||
"STATE_ESTIMATING",
|
"STATE_ESTIMATING",
|
||||||
"STATE_WAITING_FOR_SPEECH",
|
"STATE_WAITING_FOR_SPEECH",
|
||||||
"STATE_RECOGNIZING",
|
"STATE_RECOGNIZING",
|
||||||
"STATE_WAITING_FOR_RESULT"
|
"STATE_WAITING_FOR_RESULT",
|
||||||
|
"STATE_ABORTING"
|
||||||
};
|
};
|
||||||
|
|
||||||
MOZ_ASSERT(aId < STATE_COUNT);
|
MOZ_ASSERT(aId < STATE_COUNT);
|
||||||
|
|
|
@ -167,9 +167,13 @@ private:
|
||||||
STATE_WAITING_FOR_SPEECH,
|
STATE_WAITING_FOR_SPEECH,
|
||||||
STATE_RECOGNIZING,
|
STATE_RECOGNIZING,
|
||||||
STATE_WAITING_FOR_RESULT,
|
STATE_WAITING_FOR_RESULT,
|
||||||
|
STATE_ABORTING,
|
||||||
STATE_COUNT
|
STATE_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void SetState(FSMState state);
|
||||||
|
bool StateBetween(FSMState begin, FSMState end);
|
||||||
|
|
||||||
class GetUserMediaStreamOptions : public nsIMediaStreamOptions
|
class GetUserMediaStreamOptions : public nsIMediaStreamOptions
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -219,19 +223,20 @@ private:
|
||||||
void NotifyError(SpeechEvent* aEvent);
|
void NotifyError(SpeechEvent* aEvent);
|
||||||
|
|
||||||
void ProcessEvent(SpeechEvent* aEvent);
|
void ProcessEvent(SpeechEvent* aEvent);
|
||||||
FSMState TransitionAndGetNextState(SpeechEvent* aEvent);
|
void Transition(SpeechEvent* aEvent);
|
||||||
|
|
||||||
FSMState Reset();
|
void Reset();
|
||||||
FSMState ResetAndEnd();
|
void ResetAndEnd();
|
||||||
FSMState StartedAudioCapture(SpeechEvent* aEvent);
|
void WaitForAudioData(SpeechEvent* aEvent);
|
||||||
FSMState StopRecordingAndRecognize(SpeechEvent* aEvent);
|
void StartedAudioCapture(SpeechEvent* aEvent);
|
||||||
FSMState WaitForEstimation(SpeechEvent* aEvent);
|
void StopRecordingAndRecognize(SpeechEvent* aEvent);
|
||||||
FSMState DetectSpeech(SpeechEvent* aEvent);
|
void WaitForEstimation(SpeechEvent* aEvent);
|
||||||
FSMState WaitForSpeechEnd(SpeechEvent* aEvent);
|
void DetectSpeech(SpeechEvent* aEvent);
|
||||||
FSMState NotifyFinalResult(SpeechEvent* aEvent);
|
void WaitForSpeechEnd(SpeechEvent* aEvent);
|
||||||
FSMState DoNothing(SpeechEvent* aEvent);
|
void NotifyFinalResult(SpeechEvent* aEvent);
|
||||||
FSMState AbortSilently(SpeechEvent* aEvent);
|
void DoNothing(SpeechEvent* aEvent);
|
||||||
FSMState AbortError(SpeechEvent* aEvent);
|
void AbortSilently(SpeechEvent* aEvent);
|
||||||
|
void AbortError(SpeechEvent* aEvent);
|
||||||
|
|
||||||
nsRefPtr<DOMMediaStream> mDOMStream;
|
nsRefPtr<DOMMediaStream> mDOMStream;
|
||||||
nsRefPtr<SpeechStreamListener> mSpeechListener;
|
nsRefPtr<SpeechStreamListener> mSpeechListener;
|
||||||
|
@ -240,7 +245,7 @@ private:
|
||||||
void GetRecognitionServiceCID(nsACString& aResultCID);
|
void GetRecognitionServiceCID(nsACString& aResultCID);
|
||||||
|
|
||||||
FSMState mCurrentState;
|
FSMState mCurrentState;
|
||||||
bool mProcessingEvent;
|
nsTArray<nsRefPtr<SpeechEvent> > mPriorityEvents;
|
||||||
|
|
||||||
Endpointer mEndpointer;
|
Endpointer mEndpointer;
|
||||||
uint32_t mEstimationSamples;
|
uint32_t mEstimationSamples;
|
||||||
|
|
|
@ -23,6 +23,7 @@ MOCHITEST_FILES := \
|
||||||
test_abort.html \
|
test_abort.html \
|
||||||
test_call_start_from_end_handler.html \
|
test_call_start_from_end_handler.html \
|
||||||
test_preference_enable.html \
|
test_preference_enable.html \
|
||||||
|
test_nested_eventloop.html \
|
||||||
hello.ogg \
|
hello.ogg \
|
||||||
silence.ogg \
|
silence.ogg \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
|
@ -159,7 +159,9 @@ function performTest(options) {
|
||||||
|
|
||||||
em.doneFunc = function() {
|
em.doneFunc = function() {
|
||||||
em.requestTestEnd();
|
em.requestTestEnd();
|
||||||
options.doneFunc();
|
if (options.doneFunc) {
|
||||||
|
options.doneFunc();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
em.audioSampleFile = DEFAULT_AUDIO_SAMPLE_FILE;
|
em.audioSampleFile = DEFAULT_AUDIO_SAMPLE_FILE;
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=650295
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug 650295 -- Spin the event loop from inside a callback</title>
|
||||||
|
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||||
|
<script type="application/javascript" src="head.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650295">Mozilla Bug 650295</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
<script type="text/javascript">
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* window.showModalDialog() can be used to spin the event loop, causing
|
||||||
|
* queued SpeechEvents (such as those created by calls to start(), stop()
|
||||||
|
* or abort()) to be processed immediately.
|
||||||
|
* When this is done from inside DOM event handlers, it is possible to
|
||||||
|
* cause reentrancy in our C++ code, which we should be able to withstand.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Garbage collecting the windows created in this test can
|
||||||
|
// cause assertions (Bug 600703).
|
||||||
|
if (!navigator.platform.startsWith("Win")) {
|
||||||
|
SimpleTest.expectAssertions(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function abortAndSpinEventLoop(evt, sr) {
|
||||||
|
sr.abort();
|
||||||
|
window.showModalDialog("javascript:window.close()");
|
||||||
|
}
|
||||||
|
|
||||||
|
function doneFunc() {
|
||||||
|
// Trigger gc now and wait some time to make sure this test gets the blame
|
||||||
|
// for any assertions caused by showModalDialog
|
||||||
|
var count = 0, GC_COUNT = 4;
|
||||||
|
|
||||||
|
function triggerGCOrFinish() {
|
||||||
|
SpecialPowers.gc();
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count == GC_COUNT) {
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < GC_COUNT; i++) {
|
||||||
|
setTimeout(triggerGCOrFinish, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We start by performing a normal start, then abort from the audiostart
|
||||||
|
* callback and force the EVENT_ABORT to be processed while still inside
|
||||||
|
* the event handler. This causes the recording to stop, which raises
|
||||||
|
* the audioend and (later on) end events.
|
||||||
|
* Then, we abort (once again spinning the event loop) from the audioend
|
||||||
|
* handler, attempting to cause a re-entry into the abort code. This second
|
||||||
|
* call should be ignored, and we get the end callback and finish.
|
||||||
|
*/
|
||||||
|
|
||||||
|
performTest({
|
||||||
|
eventsToRequest: [
|
||||||
|
"EVENT_START",
|
||||||
|
"EVENT_AUDIO_DATA",
|
||||||
|
],
|
||||||
|
expectedEvents: {
|
||||||
|
"audiostart": abortAndSpinEventLoop,
|
||||||
|
"audioend": abortAndSpinEventLoop,
|
||||||
|
"end": null
|
||||||
|
},
|
||||||
|
doneFunc: doneFunc,
|
||||||
|
prefs: [["media.webspeech.test.fake_fsm_events", true],
|
||||||
|
["media.webspeech.test.fake_recognition_service", true]]
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -119,6 +119,7 @@
|
||||||
"content/media/test/test_wave_data_s16.html": "TIMED_OUT",
|
"content/media/test/test_wave_data_s16.html": "TIMED_OUT",
|
||||||
"content/media/test/test_wave_data_u8.html": "TIMED_OUT",
|
"content/media/test/test_wave_data_u8.html": "TIMED_OUT",
|
||||||
"content/media/webspeech/synth/ipc/test/test_ipc.html": "bug 857673",
|
"content/media/webspeech/synth/ipc/test/test_ipc.html": "bug 857673",
|
||||||
|
"content/media/webspeech/recognition/test/test_nested_eventloop.html": "",
|
||||||
"content/smil/test/test_smilRepeatTiming.xhtml": "TIMED_OUT",
|
"content/smil/test/test_smilRepeatTiming.xhtml": "TIMED_OUT",
|
||||||
"content/smil/test/test_smilExtDoc.xhtml": "",
|
"content/smil/test/test_smilExtDoc.xhtml": "",
|
||||||
"content/xul/content/test/test_bug486990.xul": "TIMED_OUT",
|
"content/xul/content/test/test_bug486990.xul": "TIMED_OUT",
|
||||||
|
|
Загрузка…
Ссылка в новой задаче