diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/Modules/speech/SpeechSynthesis.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/Modules/speech/SpeechSynthesis.cpp')
-rw-r--r-- | Source/WebCore/Modules/speech/SpeechSynthesis.cpp | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/Source/WebCore/Modules/speech/SpeechSynthesis.cpp b/Source/WebCore/Modules/speech/SpeechSynthesis.cpp new file mode 100644 index 000000000..9b632b24f --- /dev/null +++ b/Source/WebCore/Modules/speech/SpeechSynthesis.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SpeechSynthesis.h" + +#if ENABLE(SPEECH_SYNTHESIS) + +#include "EventNames.h" +#include "PlatformSpeechSynthesisVoice.h" +#include "PlatformSpeechSynthesizer.h" +#include "ScriptController.h" +#include "SpeechSynthesisEvent.h" +#include "SpeechSynthesisUtterance.h" +#include <wtf/CurrentTime.h> +#include <wtf/NeverDestroyed.h> + +namespace WebCore { + +Ref<SpeechSynthesis> SpeechSynthesis::create() +{ + return adoptRef(*new SpeechSynthesis); +} + +SpeechSynthesis::SpeechSynthesis() + : m_currentSpeechUtterance(nullptr) + , m_isPaused(false) +#if PLATFORM(IOS) + , m_restrictions(RequireUserGestureForSpeechStartRestriction) +#endif +{ +} + +void SpeechSynthesis::setPlatformSynthesizer(std::unique_ptr<PlatformSpeechSynthesizer> synthesizer) +{ + m_platformSpeechSynthesizer = WTFMove(synthesizer); + m_voiceList.clear(); + m_currentSpeechUtterance = nullptr; + m_utteranceQueue.clear(); + m_isPaused = false; +} + +void SpeechSynthesis::voicesDidChange() +{ + m_voiceList.clear(); +} + +const Vector<Ref<SpeechSynthesisVoice>>& SpeechSynthesis::getVoices() +{ + if (m_voiceList.size()) + return m_voiceList; + + if (!m_platformSpeechSynthesizer) + m_platformSpeechSynthesizer = std::make_unique<PlatformSpeechSynthesizer>(this); + + // If the voiceList is empty, that's the cue to get the voices from the platform again. + for (auto& voice : m_platformSpeechSynthesizer->voiceList()) + m_voiceList.append(SpeechSynthesisVoice::create(*voice)); + + return m_voiceList; +} + +bool SpeechSynthesis::speaking() const +{ + // If we have a current speech utterance, then that means we're assumed to be in a speaking state. + // This state is independent of whether the utterance happens to be paused. + return m_currentSpeechUtterance; +} + +bool SpeechSynthesis::pending() const +{ + // This is true if there are any utterances that have not started. + // That means there will be more than one in the queue. + return m_utteranceQueue.size() > 1; +} + +bool SpeechSynthesis::paused() const +{ + return m_isPaused; +} + +void SpeechSynthesis::startSpeakingImmediately(SpeechSynthesisUtterance& utterance) +{ + ASSERT(!m_currentSpeechUtterance); + utterance.setStartTime(monotonicallyIncreasingTime()); + m_currentSpeechUtterance = &utterance; + m_isPaused = false; + + // Zero lengthed strings should immediately notify that the event is complete. + if (utterance.text().isEmpty()) { + handleSpeakingCompleted(utterance, false); + return; + } + + if (!m_platformSpeechSynthesizer) + m_platformSpeechSynthesizer = std::make_unique<PlatformSpeechSynthesizer>(this); + m_platformSpeechSynthesizer->speak(utterance.platformUtterance()); +} + +void SpeechSynthesis::speak(SpeechSynthesisUtterance& utterance) +{ + // Like Audio, we should require that the user interact to start a speech synthesis session. +#if PLATFORM(IOS) + if (ScriptController::processingUserGesture()) + removeBehaviorRestriction(RequireUserGestureForSpeechStartRestriction); + else if (userGestureRequiredForSpeechStart()) + return; +#endif + + m_utteranceQueue.append(utterance); + + // If the queue was empty, speak this immediately and add it to the queue. + if (m_utteranceQueue.size() == 1) + startSpeakingImmediately(m_utteranceQueue.first()); +} + +void SpeechSynthesis::cancel() +{ + // Remove all the items from the utterance queue. + // Hold on to the current utterance so the platform synthesizer can have a chance to clean up. + RefPtr<SpeechSynthesisUtterance> current = m_currentSpeechUtterance; + m_utteranceQueue.clear(); + if (m_platformSpeechSynthesizer) + m_platformSpeechSynthesizer->cancel(); + current = nullptr; + + // The platform should have called back immediately and cleared the current utterance. + ASSERT(!m_currentSpeechUtterance); +} + +void SpeechSynthesis::pause() +{ + if (!m_isPaused && m_platformSpeechSynthesizer) + m_platformSpeechSynthesizer->pause(); +} + +void SpeechSynthesis::resume() +{ + if (m_currentSpeechUtterance && m_platformSpeechSynthesizer) + m_platformSpeechSynthesizer->resume(); +} + +void SpeechSynthesis::fireEvent(const AtomicString& type, SpeechSynthesisUtterance& utterance, unsigned long charIndex, const String& name) +{ + utterance.dispatchEvent(SpeechSynthesisEvent::create(type, charIndex, (monotonicallyIncreasingTime() - utterance.startTime()), name)); +} + +void SpeechSynthesis::handleSpeakingCompleted(SpeechSynthesisUtterance& utterance, bool errorOccurred) +{ + ASSERT(m_currentSpeechUtterance); + Ref<SpeechSynthesisUtterance> protect(utterance); + + m_currentSpeechUtterance = nullptr; + + fireEvent(errorOccurred ? eventNames().errorEvent : eventNames().endEvent, utterance, 0, String()); + + if (m_utteranceQueue.size()) { + Ref<SpeechSynthesisUtterance> firstUtterance = m_utteranceQueue.takeFirst(); + ASSERT(&utterance == firstUtterance.ptr()); + + // Start the next job if there is one pending. + if (!m_utteranceQueue.isEmpty()) + startSpeakingImmediately(m_utteranceQueue.first()); + } +} + +void SpeechSynthesis::boundaryEventOccurred(PlatformSpeechSynthesisUtterance& utterance, SpeechBoundary boundary, unsigned charIndex) +{ + static NeverDestroyed<const String> wordBoundaryString(ASCIILiteral("word")); + static NeverDestroyed<const String> sentenceBoundaryString(ASCIILiteral("sentence")); + + ASSERT(utterance.client()); + + switch (boundary) { + case SpeechWordBoundary: + fireEvent(eventNames().boundaryEvent, static_cast<SpeechSynthesisUtterance&>(*utterance.client()), charIndex, wordBoundaryString); + break; + case SpeechSentenceBoundary: + fireEvent(eventNames().boundaryEvent, static_cast<SpeechSynthesisUtterance&>(*utterance.client()), charIndex, sentenceBoundaryString); + break; + default: + ASSERT_NOT_REACHED(); + } +} + +void SpeechSynthesis::didStartSpeaking(PlatformSpeechSynthesisUtterance& utterance) +{ + if (utterance.client()) + fireEvent(eventNames().startEvent, static_cast<SpeechSynthesisUtterance&>(*utterance.client()), 0, String()); +} + +void SpeechSynthesis::didPauseSpeaking(PlatformSpeechSynthesisUtterance& utterance) +{ + m_isPaused = true; + if (utterance.client()) + fireEvent(eventNames().pauseEvent, static_cast<SpeechSynthesisUtterance&>(*utterance.client()), 0, String()); +} + +void SpeechSynthesis::didResumeSpeaking(PlatformSpeechSynthesisUtterance& utterance) +{ + m_isPaused = false; + if (utterance.client()) + fireEvent(eventNames().resumeEvent, static_cast<SpeechSynthesisUtterance&>(*utterance.client()), 0, String()); +} + +void SpeechSynthesis::didFinishSpeaking(PlatformSpeechSynthesisUtterance& utterance) +{ + if (utterance.client()) + handleSpeakingCompleted(static_cast<SpeechSynthesisUtterance&>(*utterance.client()), false); +} + +void SpeechSynthesis::speakingErrorOccurred(PlatformSpeechSynthesisUtterance& utterance) +{ + if (utterance.client()) + handleSpeakingCompleted(static_cast<SpeechSynthesisUtterance&>(*utterance.client()), true); +} + +} // namespace WebCore + +#endif // ENABLE(SPEECH_SYNTHESIS) |