blob: 5805c630fa67d56cc6e2a7f50a4eaeb75dd750d0 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/mac/cocoa_protocols.h"
#include "base/memory/scoped_nsobject.h"
#include "base/memory/singleton.h"
#include "base/sys_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_function.h"
#include "chrome/browser/extensions/extension_tts_api_constants.h"
#include "chrome/browser/extensions/extension_tts_api_controller.h"
#include "chrome/browser/extensions/extension_tts_api_platform.h"
#import <Cocoa/Cocoa.h>
class ExtensionTtsPlatformImplMac;
@interface ChromeTtsDelegate : NSObject <NSSpeechSynthesizerDelegate> {
@private
ExtensionTtsPlatformImplMac* ttsImplMac_; // weak.
}
- (id)initWithPlatformImplMac:(ExtensionTtsPlatformImplMac*)ttsImplMac;
@end
class ExtensionTtsPlatformImplMac : public ExtensionTtsPlatformImpl {
public:
virtual bool PlatformImplAvailable() {
return true;
}
virtual bool Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const UtteranceContinuousParameters& params);
virtual bool StopSpeaking();
virtual bool IsSpeaking();
virtual bool SendsEvent(TtsEventType event_type);
// Called by ChromeTtsDelegate when we get a callback from the
// native speech engine.
void OnSpeechEvent(NSSpeechSynthesizer* sender,
TtsEventType event_type,
int char_index,
const std::string& error_message);
// Get the single instance of this class.
static ExtensionTtsPlatformImplMac* GetInstance();
private:
ExtensionTtsPlatformImplMac();
virtual ~ExtensionTtsPlatformImplMac();
scoped_nsobject<NSSpeechSynthesizer> speech_synthesizer_;
scoped_nsobject<ChromeTtsDelegate> delegate_;
int utterance_id_;
std::string utterance_;
bool sent_start_event_;
friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplMac>;
DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplMac);
};
// static
ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() {
return ExtensionTtsPlatformImplMac::GetInstance();
}
bool ExtensionTtsPlatformImplMac::Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const UtteranceContinuousParameters& params) {
// Deliberately construct a new speech synthesizer every time Speak is
// called, otherwise there's no way to know whether calls to the delegate
// apply to the current utterance or a previous utterance. In
// experimentation, the overhead of constructing and destructing a
// NSSpeechSynthesizer is minimal.
speech_synthesizer_.reset([[NSSpeechSynthesizer alloc] init]);
[speech_synthesizer_ setDelegate:delegate_];
utterance_id_ = utterance_id;
sent_start_event_ = false;
// TODO: convert SSML to SAPI xml. http://crbug.com/88072
utterance_ = utterance;
// TODO: support languages other than the default: crbug.com/88059
if (params.rate >= 0.0) {
// The TTS api defines rate via words per minute. Let 200 be the default.
[speech_synthesizer_
setObject:[NSNumber numberWithInt:params.rate * 200]
forProperty:NSSpeechRateProperty error:nil];
}
if (params.pitch >= 0.0) {
// The TTS api allows an approximate range of 30 to 65 for speech pitch.
[speech_synthesizer_
setObject: [NSNumber numberWithInt:(params.pitch * 17 + 30)]
forProperty:NSSpeechPitchBaseProperty error:nil];
}
if (params.volume >= 0.0) {
[speech_synthesizer_
setObject: [NSNumber numberWithFloat:params.volume]
forProperty:NSSpeechVolumeProperty error:nil];
}
return [speech_synthesizer_ startSpeakingString:
[NSString stringWithUTF8String: utterance.c_str()]];
}
bool ExtensionTtsPlatformImplMac::StopSpeaking() {
if (speech_synthesizer_.get()) {
[speech_synthesizer_ stopSpeaking];
speech_synthesizer_.reset(nil);
}
return true;
}
bool ExtensionTtsPlatformImplMac::IsSpeaking() {
// Note: this is OK even if speech_synthesizer_ is nil, it will return false.
return [speech_synthesizer_ isSpeaking];
}
bool ExtensionTtsPlatformImplMac::SendsEvent(TtsEventType event_type) {
return (event_type == TTS_EVENT_START ||
event_type == TTS_EVENT_END ||
event_type == TTS_EVENT_WORD ||
event_type == TTS_EVENT_ERROR);
}
void ExtensionTtsPlatformImplMac::OnSpeechEvent(
NSSpeechSynthesizer* sender,
TtsEventType event_type,
int char_index,
const std::string& error_message) {
// Don't send events from an utterance that's already completed.
// This depends on the fact that we construct a new NSSpeechSynthesizer
// each time we call Speak.
if (sender != speech_synthesizer_.get())
return;
if (event_type == TTS_EVENT_END)
char_index = utterance_.size();
ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
if (event_type == TTS_EVENT_WORD && !sent_start_event_) {
controller->OnTtsEvent(
utterance_id_, TTS_EVENT_START, 0, "");
sent_start_event_ = true;
}
controller->OnTtsEvent(
utterance_id_, event_type, char_index, error_message);
}
ExtensionTtsPlatformImplMac::ExtensionTtsPlatformImplMac() {
utterance_id_ = -1;
sent_start_event_ = true;
delegate_.reset([[ChromeTtsDelegate alloc] initWithPlatformImplMac:this]);
}
ExtensionTtsPlatformImplMac::~ExtensionTtsPlatformImplMac() {
}
// static
ExtensionTtsPlatformImplMac* ExtensionTtsPlatformImplMac::GetInstance() {
return Singleton<ExtensionTtsPlatformImplMac>::get();
}
@implementation ChromeTtsDelegate
- (id)initWithPlatformImplMac:(ExtensionTtsPlatformImplMac*)ttsImplMac {
if ((self = [super init])) {
ttsImplMac_ = ttsImplMac;
}
return self;
}
- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
didFinishSpeaking:(BOOL)finished_speaking {
ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_END, 0, "");
}
- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
willSpeakWord:(NSRange)character_range
ofString:(NSString*)string {
ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_WORD,
character_range.location, "");
}
- (void)speechSynthesizer:(NSSpeechSynthesizer*)sender
didEncounterErrorAtIndex:(NSUInteger)character_index
ofString:(NSString*)string
message:(NSString*)message {
std::string message_utf8 = base::SysNSStringToUTF8(message);
ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_ERROR, character_index,
message_utf8);
}
@end