Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
.vscode
.DS_Store
16 changes: 10 additions & 6 deletions TextToSpeech.podspec
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
s.name = "TextToSpeech"
s.version = "4.1.1"
s.summary = "React Native Text-To-Speech library for Android and iOS"
s.version = package["version"]
s.summary = package["description"]

s.homepage = "https://github.com/ak1394/react-native-tts"
s.homepage = package["homepage"]

s.license = "MIT"
s.authors = "Anton Krasovsky"
s.license = package["license"]
s.authors = package["author"]
s.platform = :ios, "9.0"

s.source = { :git => "https://github.com/ak1394/react-native-tts.git" }
s.source = { :git => "https://github.com/Iternio-Planning-AB/react-native-tts.git" }

s.source_files = "ios/TextToSpeech/*.{h,m}"

Expand Down
16 changes: 11 additions & 5 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ def safeExtGet(prop, fallback) {
}

buildscript {
repositories {
jcenter()
}
// The Android Gradle plugin is only required when opening the android folder stand-alone.
// This avoids unnecessary downloads and potential conflicts when the library is included as a
// module dependency in an application project.
// ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
if (project == rootProject) {
repositories {
google()
}

dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
}
}
}

Expand Down
70 changes: 56 additions & 14 deletions android/src/main/java/net/no_mad/tts/TextToSpeechModule.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
package net.no_mad.tts;

import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.content.Intent;
import android.content.ActivityNotFoundException;
import android.app.Activity;
import android.net.Uri;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.speech.tts.Voice;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import com.facebook.react.bridge.*;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import java.util.ArrayList;
Expand All @@ -29,14 +38,21 @@ public class TextToSpeechModule extends ReactContextBaseJavaModule {

private boolean ducking = false;
private AudioManager audioManager;
private AudioManager.OnAudioFocusChangeListener afChangeListener;
private AudioManager.OnAudioFocusChangeListener afChangeListener = i -> {};

private Map<String, Locale> localeCountryMap;
private Map<String, Locale> localeLanguageMap;

private AudioAttributes audioAttributes;

public TextToSpeechModule(ReactApplicationContext reactContext) {
super(reactContext);
audioManager = (AudioManager) reactContext.getApplicationContext().getSystemService(reactContext.AUDIO_SERVICE);
audioAttributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.build();

initStatusPromises = new ArrayList<Promise>();
//initialize ISO3, ISO2 languague country code mapping.
initCountryLanguageCodeMapping();
Expand Down Expand Up @@ -97,6 +113,7 @@ public void onRangeStart (String utteranceId, int start, int end, int frame) {
params.putInt("start", start);
params.putInt("end", end);
params.putInt("frame", frame);
params.putInt("length", end - start);
sendEvent("tts-progress", params);
}
});
Expand Down Expand Up @@ -210,12 +227,23 @@ public void speak(String utterance, ReadableMap params, Promise promise) {
if(notReady(promise)) return;

if(ducking) {
int amResult;
// Request audio focus for playback
int amResult = audioManager.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(audioAttributes)
.setAcceptsDelayedFocusGain(false)
.setOnAudioFocusChangeListener(afChangeListener)
.build();

amResult = audioManager.requestAudioFocus(audioFocusRequest);
} else {
amResult = audioManager.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
}

if(amResult != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
promise.reject("Android AudioManager error, failed to request audio focus");
Expand Down Expand Up @@ -311,6 +339,18 @@ public void setDefaultVoice(String voiceId, Promise promise) {
}
}

@ReactMethod
public void getDefaultVoiceIdentifier(String language, Promise promise) {
if(notReady(promise)) return;

Voice currentVoice = tts.getVoice();
if (currentVoice == null) {
promise.reject("not_found", "Language not found");
return;
}
promise.resolve(currentVoice.getName());
}

@ReactMethod
public void voices(Promise promise) {
if(notReady(promise)) return;
Expand Down Expand Up @@ -498,6 +538,8 @@ private int speak(String utterance, String utteranceId, ReadableMap inputParams)
audioStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
}

tts.setAudioAttributes(audioAttributes);

if (Build.VERSION.SDK_INT >= 21) {
Bundle params = new Bundle();
params.putInt(TextToSpeech.Engine.KEY_PARAM_STREAM, audioStreamType);
Expand Down
15 changes: 8 additions & 7 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export type Engine = {

export type AndroidOptions = {
/** Parameter key to specify the audio stream type to be used when speaking text or playing back a file */
KEY_PARAM_STREAM:
KEY_PARAM_STREAM?:
| "STREAM_VOICE_CALL"
| "STREAM_SYSTEM"
| "STREAM_RING"
Expand All @@ -72,17 +72,17 @@ export type AndroidOptions = {
| "STREAM_DTMF"
| "STREAM_ACCESSIBILITY";
/** Parameter key to specify the speech volume relative to the current stream type volume used when speaking text. Volume is specified as a float ranging from 0 to 1 where 0 is silence, and 1 is the maximum volume (the default behavior). */
KEY_PARAM_VOLUME: number;
KEY_PARAM_VOLUME?: number;
/** Parameter key to specify how the speech is panned from left to right when speaking text. Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan, 0 to center (the default behavior), and +1 to hard-right. */
KEY_PARAM_PAN: number;
KEY_PARAM_PAN?: number;
};

export type Options =
| string
| {
iosVoiceId: string;
rate: number;
androidParams: AndroidOptions;
iosVoiceId?: string;
rate?: number;
androidParams?: AndroidOptions;
};

export class ReactNativeTts extends RN.NativeEventEmitter {
Expand All @@ -97,9 +97,10 @@ export class ReactNativeTts extends RN.NativeEventEmitter {
setDefaultLanguage: (language: string) => Promise<"success">;
setIgnoreSilentSwitch: (ignoreSilentSwitch: IOSSilentSwitchBehavior) => Promise<boolean>;
voices: () => Promise<Voice[]>;
getDefaultVoiceIdentifier: (language: String) => Promise<String>;
engines: () => Promise<Engine[]>;
/** Read the sentence and return an id for the task. */
speak: (utterance: string, options?: Options) => string | number;
speak: (utterance: string, options?: Options) => Promise<string | number>;
stop: (onWordBoundary?: boolean) => Promise<boolean>;
pause: (onWordBoundary?: boolean) => Promise<boolean>;
resume: () => Promise<boolean>;
Expand Down
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ class Tts extends NativeEventEmitter {
return TextToSpeech.voices();
}

getDefaultVoiceIdentifier(language) {
return TextToSpeech.getDefaultVoiceIdentifier(language);
}

engines() {
if (Platform.OS === 'ios' || Platform.OS === 'windows') {
return Promise.resolve([]);
Expand Down
3 changes: 3 additions & 0 deletions ios/TextToSpeech/TextToSpeech.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@
@property (nonatomic) float defaultRate;
@property (nonatomic) float defaultPitch;
@property (nonatomic) bool ducking;

// Singleton accessor for New Architecture compatibility
+ (instancetype)sharedInstance;
@end
Loading