4b378fa17e6ef55dffedc58014d7f3feee625ca0
[WebKit-https.git] / Source / WebCore / platform / ios / PlatformSpeechSynthesizerIOS.mm
1 /*
2  * Copyright (C) 2013 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #import "config.h"
26 #import "PlatformSpeechSynthesizer.h"
27
28 #if ENABLE(SPEECH_SYNTHESIS) && PLATFORM(IOS_FAMILY)
29
30 #import "PlatformSpeechSynthesisUtterance.h"
31 #import "PlatformSpeechSynthesisVoice.h"
32 #import <AVFoundation/AVSpeechSynthesis.h>
33 #import <wtf/BlockObjCExceptions.h>
34 #import <wtf/RetainPtr.h>
35
36 #import <pal/cocoa/AVFoundationSoftLink.h>
37
38 static float getAVSpeechUtteranceDefaultSpeechRate()
39 {
40     static float value;
41     static void* symbol;
42     if (!symbol) {
43         void* symbol = dlsym(PAL::AVFoundationLibrary(), "AVSpeechUtteranceDefaultSpeechRate");
44         RELEASE_ASSERT_WITH_MESSAGE(symbol, "%s", dlerror());
45         value = *static_cast<float const *>(symbol);
46     }
47     return value;
48 }
49
50 static float getAVSpeechUtteranceMaximumSpeechRate()
51 {
52     static float value;
53     static void* symbol;
54     if (!symbol) {
55         void* symbol = dlsym(PAL::AVFoundationLibrary(), "AVSpeechUtteranceMaximumSpeechRate");
56         RELEASE_ASSERT_WITH_MESSAGE(symbol, "%s", dlerror());
57         value = *static_cast<float const *>(symbol);
58     }
59     return value;
60 }
61
62 #define AVSpeechUtteranceDefaultSpeechRate getAVSpeechUtteranceDefaultSpeechRate()
63 #define AVSpeechUtteranceMaximumSpeechRate getAVSpeechUtteranceMaximumSpeechRate()
64
65 @interface WebSpeechSynthesisWrapper : NSObject<AVSpeechSynthesizerDelegate>
66 {
67     WebCore::PlatformSpeechSynthesizer* m_synthesizerObject;
68     // Hold a Ref to the utterance so that it won't disappear until the synth is done with it.
69     RefPtr<WebCore::PlatformSpeechSynthesisUtterance> m_utterance;
70
71     RetainPtr<AVSpeechSynthesizer> m_synthesizer;
72 }
73
74 - (WebSpeechSynthesisWrapper *)initWithSpeechSynthesizer:(WebCore::PlatformSpeechSynthesizer*)synthesizer;
75 - (void)speakUtterance:(RefPtr<WebCore::PlatformSpeechSynthesisUtterance>&&)utterance;
76
77 @end
78
79 @implementation WebSpeechSynthesisWrapper
80
81 - (WebSpeechSynthesisWrapper *)initWithSpeechSynthesizer:(WebCore::PlatformSpeechSynthesizer*)synthesizer
82 {
83     if (!(self = [super init]))
84         return nil;
85
86     m_synthesizerObject = synthesizer;
87     return self;
88 }
89
90 - (float)mapSpeechRateToPlatformRate:(float)rate
91 {
92     // WebSpeech says to go from .1 -> 10 (default 1)
93     // AVSpeechSynthesizer asks for 0 -> 1 (default. 5)
94     if (rate < 1)
95         rate *= AVSpeechUtteranceDefaultSpeechRate;
96     else
97         rate = AVSpeechUtteranceDefaultSpeechRate + ((rate - 1) * (AVSpeechUtteranceMaximumSpeechRate - AVSpeechUtteranceDefaultSpeechRate));
98
99     return rate;
100 }
101
102 - (void)speakUtterance:(RefPtr<WebCore::PlatformSpeechSynthesisUtterance>&&)utterance
103 {
104     // When speak is called we should not have an existing speech utterance outstanding.
105     ASSERT(!m_utterance);
106     ASSERT(utterance);
107
108     if (!utterance)
109         return;
110
111     BEGIN_BLOCK_OBJC_EXCEPTIONS
112     if (!m_synthesizer) {
113         m_synthesizer = adoptNS([PAL::allocAVSpeechSynthesizerInstance() init]);
114         [m_synthesizer setDelegate:self];
115     }
116
117     // Choose the best voice, by first looking at the utterance voice, then the utterance language,
118     // then choose the default language.
119     WebCore::PlatformSpeechSynthesisVoice* utteranceVoice = utterance->voice();
120     NSString *voiceLanguage = nil;
121     if (!utteranceVoice) {
122         if (utterance->lang().isEmpty())
123             voiceLanguage = [PAL::getAVSpeechSynthesisVoiceClass() currentLanguageCode];
124         else
125             voiceLanguage = utterance->lang();
126     } else
127         voiceLanguage = utterance->voice()->lang();
128
129     AVSpeechSynthesisVoice *avVoice = nil;
130     if (voiceLanguage)
131         avVoice = [PAL::getAVSpeechSynthesisVoiceClass() voiceWithLanguage:voiceLanguage];
132
133     AVSpeechUtterance *avUtterance = [PAL::getAVSpeechUtteranceClass() speechUtteranceWithString:utterance->text()];
134
135     [avUtterance setRate:[self mapSpeechRateToPlatformRate:utterance->rate()]];
136     [avUtterance setVolume:utterance->volume()];
137     [avUtterance setPitchMultiplier:utterance->pitch()];
138     [avUtterance setVoice:avVoice];
139     m_utterance = WTFMove(utterance);
140
141     [m_synthesizer speakUtterance:avUtterance];
142     END_BLOCK_OBJC_EXCEPTIONS
143 }
144
145 - (void)pause
146 {
147     if (!m_utterance)
148         return;
149
150     BEGIN_BLOCK_OBJC_EXCEPTIONS
151     [m_synthesizer pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
152     END_BLOCK_OBJC_EXCEPTIONS
153 }
154
155 - (void)resume
156 {
157     if (!m_utterance)
158         return;
159
160     BEGIN_BLOCK_OBJC_EXCEPTIONS
161     [m_synthesizer continueSpeaking];
162     END_BLOCK_OBJC_EXCEPTIONS
163 }
164
165 - (void)cancel
166 {
167     if (!m_utterance)
168         return;
169
170     BEGIN_BLOCK_OBJC_EXCEPTIONS
171     [m_synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
172     END_BLOCK_OBJC_EXCEPTIONS
173 }
174
175 - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didStartSpeechUtterance:(AVSpeechUtterance *)utterance
176 {
177     UNUSED_PARAM(synthesizer);
178     UNUSED_PARAM(utterance);
179     if (!m_utterance)
180         return;
181
182     m_synthesizerObject->client()->didStartSpeaking(*m_utterance);
183 }
184
185 - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance
186 {
187     UNUSED_PARAM(synthesizer);
188     UNUSED_PARAM(utterance);
189     if (!m_utterance)
190         return;
191
192     // Clear the m_utterance variable in case finish speaking kicks off a new speaking job immediately.
193     RefPtr<WebCore::PlatformSpeechSynthesisUtterance> platformUtterance = m_utterance;
194     m_utterance = nullptr;
195
196     m_synthesizerObject->client()->didFinishSpeaking(*platformUtterance);
197 }
198
199 - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didPauseSpeechUtterance:(AVSpeechUtterance *)utterance
200 {
201     UNUSED_PARAM(synthesizer);
202     UNUSED_PARAM(utterance);
203     if (!m_utterance)
204         return;
205
206     m_synthesizerObject->client()->didPauseSpeaking(*m_utterance);
207 }
208
209 - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didContinueSpeechUtterance:(AVSpeechUtterance *)utterance
210 {
211     UNUSED_PARAM(synthesizer);
212     UNUSED_PARAM(utterance);
213     if (!m_utterance)
214         return;
215
216     m_synthesizerObject->client()->didResumeSpeaking(*m_utterance);
217 }
218
219 - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didCancelSpeechUtterance:(AVSpeechUtterance *)utterance
220 {
221     UNUSED_PARAM(synthesizer);
222     UNUSED_PARAM(utterance);
223     if (!m_utterance)
224         return;
225
226     // Clear the m_utterance variable in case finish speaking kicks off a new speaking job immediately.
227     RefPtr<WebCore::PlatformSpeechSynthesisUtterance> platformUtterance = m_utterance;
228     m_utterance = nullptr;
229
230     m_synthesizerObject->client()->didFinishSpeaking(*platformUtterance);
231 }
232
233 - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance
234 {
235     UNUSED_PARAM(synthesizer);
236     UNUSED_PARAM(utterance);
237
238     if (!m_utterance)
239         return;
240
241     // iOS only supports word boundaries.
242     m_synthesizerObject->client()->boundaryEventOccurred(*m_utterance, WebCore::SpeechBoundary::SpeechWordBoundary, characterRange.location);
243 }
244
245 @end
246
247 namespace WebCore {
248
249 PlatformSpeechSynthesizer::PlatformSpeechSynthesizer(PlatformSpeechSynthesizerClient* client)
250     : m_speechSynthesizerClient(client)
251 {
252 }
253
254 PlatformSpeechSynthesizer::~PlatformSpeechSynthesizer()
255 {
256 }
257
258 void PlatformSpeechSynthesizer::initializeVoiceList()
259 {
260     BEGIN_BLOCK_OBJC_EXCEPTIONS
261     for (AVSpeechSynthesisVoice *voice in [PAL::getAVSpeechSynthesisVoiceClass() speechVoices]) {
262         NSString *language = [voice language];
263         bool isDefault = true;
264         NSString *voiceURI = [voice identifier];
265         NSString *name = [voice name];
266         m_voiceList.append(PlatformSpeechSynthesisVoice::create(voiceURI, name, language, true, isDefault));
267     }
268     END_BLOCK_OBJC_EXCEPTIONS
269 }
270
271 void PlatformSpeechSynthesizer::pause()
272 {
273     [m_platformSpeechWrapper.get() pause];
274 }
275
276 void PlatformSpeechSynthesizer::resume()
277 {
278     [m_platformSpeechWrapper.get() resume];
279 }
280
281 void PlatformSpeechSynthesizer::speak(RefPtr<PlatformSpeechSynthesisUtterance>&& utterance)
282 {
283     if (!m_platformSpeechWrapper)
284         m_platformSpeechWrapper = adoptNS([[WebSpeechSynthesisWrapper alloc] initWithSpeechSynthesizer:this]);
285
286     [m_platformSpeechWrapper.get() speakUtterance:utterance.get()];
287 }
288
289 void PlatformSpeechSynthesizer::cancel()
290 {
291     [m_platformSpeechWrapper.get() cancel];
292 }
293
294 } // namespace WebCore
295
296 #endif // ENABLE(SPEECH_SYNTHESIS) && PLATFORM(IOS_FAMILY)