Skip to content
This repository has been archived by the owner on May 6, 2022. It is now read-only.

Commit

Permalink
Store Android Context directly in TTSManager
Browse files Browse the repository at this point in the history
  • Loading branch information
space-pope committed Jan 13, 2020
1 parent 1446c54 commit 9b6017a
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 42 deletions.
6 changes: 3 additions & 3 deletions src/main/java/io/spokestack/spokestack/SpeechOutput.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ public void eventReceived(TTSEvent event) {
public abstract void audioReceived(AudioResponse response);

/**
* Sets the output's application context.
* Sets the output's Android context.
*
* @param appContext The application context.
* @param androidContext The Android context.
*/
public abstract void setAppContext(Context appContext);
public abstract void setAndroidContext(Context androidContext);
}
35 changes: 19 additions & 16 deletions src/main/java/io/spokestack/spokestack/tts/SpokestackTTSOutput.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
Expand All @@ -29,9 +27,9 @@
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import io.spokestack.spokestack.util.TaskHandler;
import io.spokestack.spokestack.SpeechConfig;
import io.spokestack.spokestack.SpeechOutput;
import io.spokestack.spokestack.util.TaskHandler;
import org.jetbrains.annotations.NotNull;

/**
Expand All @@ -52,6 +50,14 @@
* playback is desired, consider adding a {@link TTSListener} to the {@code
* TTSManager} and managing audio via its methods.
* </p>
*
* <p>
* Additionally, this component requires an Android {@code Context} to be
* attached to the manager that has created it. If the manager is meant to
* persist across different {@code Activity}s, the {@code Context} used must
* either be the <em>application</em> context, or it must be re-set on the
* manager when the Activity context changes.
* </p>
*/
public class SpokestackTTSOutput extends SpeechOutput
implements Player.EventListener,
Expand All @@ -60,7 +66,6 @@ public class SpokestackTTSOutput extends SpeechOutput
private final int contentType;
private final int usage;
private TaskHandler taskHandler;
private Handler playerHandler;
private PlayerFactory playerFactory;
private ExoPlayer mediaPlayer;
private ConcatenatingMediaSource mediaSource;
Expand All @@ -74,9 +79,11 @@ public class SpokestackTTSOutput extends SpeechOutput
* configuration properties, but this constructor is required
* for participation in the TTS subsystem.
*/
public SpokestackTTSOutput(@Nullable SpeechConfig config) {
// the constant value here is the same in ExoPlayer as in
// AudioAttributesCompat in versions 2.11.0 and v1.1.0, respectively
@SuppressWarnings("unused")
public SpokestackTTSOutput(SpeechConfig config) {
// this constant's value (1) is the same in ExoPlayer as in
// AudioAttributesCompat in versions 2.11.0 and v1.1.0, respectively,
// which is why it's used as a stand-in for both in this class
this.contentType = C.CONTENT_TYPE_SPEECH;

this.playerState = new PlayerState();
Expand All @@ -93,13 +100,6 @@ public SpokestackTTSOutput(@Nullable SpeechConfig config) {
this.mediaSource = new ConcatenatingMediaSource();
}

private void runOnPlayerThread(Runnable task) {
if (playerHandler == null) {
playerHandler = new Handler(Looper.getMainLooper());
}
playerHandler.post(task);
}

/**
* Creates a new instance of the audio player with a custom player factory
* and task handler. Used for testing.
Expand All @@ -115,8 +115,8 @@ private void runOnPlayerThread(Runnable task) {
}

@Override
public void setAppContext(@NonNull Context context) {
this.appContext = context;
public void setAndroidContext(@NonNull Context androidContext) {
this.appContext = androidContext;
}

/**
Expand Down Expand Up @@ -379,6 +379,9 @@ ExoPlayer createPlayer(int usage, int contentType,
@Override public void onTimelineChanged(Timeline timeline, int reason) { }

@Override
// it's deprecated, but it's still a default method, so we have to
// implement it for older versions of Android
@SuppressWarnings("deprecation")
public void onTimelineChanged(Timeline timeline,
@Nullable Object manifest, int reason) { }

Expand Down
55 changes: 41 additions & 14 deletions src/main/java/io/spokestack/spokestack/tts/TTSManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
* TTSManager ttsManager = new TTSManager.Builder()
* .setTTSServiceClass("io.spokestack.spokestack.tts.SpokestackTTSService")
* .setOutputClass("io.spokestack.spokestack.tts.SpokestackTTSOutput")
* .setProperty("spokestack-key", "f854fbf30a5f40c189ecb1b38bc78059")
* .setProperty("spokestack-key", "f0bc990c-e9db-4a0c-a2b1-6a6395a3d97e")
* .setProperty(
* "spokestack-secret",
* "5BD5483F573D691A15CFA493C1782F451D4BD666E39A9E7B2EBE287E6A72C6B6")
* .setAndroidContext(getApplicationContext())
* .setLifecycle(getLifecycle())
* .build();
* }
Expand All @@ -58,8 +62,8 @@ public final class TTSManager implements AutoCloseable {
private final List<TTSListener> listeners = new ArrayList<>();
private TTSService ttsService;
private SpeechOutput output;
private Context context;
private Lifecycle lifecycle;
private Context appContext;

/**
* Get the current TTS service.
Expand Down Expand Up @@ -89,9 +93,9 @@ private TTSManager(Builder builder) throws Exception {
this.ttsServiceClass = builder.ttsServiceClass;
this.outputClass = builder.outputClass;
this.config = builder.config;
this.context = builder.context.getApplicationContext();
this.lifecycle = builder.lifecycle;
this.listeners.addAll(builder.listeners);
this.appContext = builder.appContext;
prepare();
}

Expand Down Expand Up @@ -123,6 +127,22 @@ public void registerLifecycle(@NonNull Lifecycle newLifecycle) {
}
}

/**
* Sets the android context for the TTS Subsystem. Some components may
* require an application context instead of an activity context;
* see individual component documentation for details.
*
* @param androidContext the android context for the TTS subsystem.
*
* @see io.spokestack.spokestack.tts.SpokestackTTSOutput
*/
public void setAndroidContext(Context androidContext) {
this.appContext = androidContext;
if (this.output != null) {
this.output.setAndroidContext(androidContext);
}
}

@Override
public void close() {
release();
Expand All @@ -149,8 +169,8 @@ public void prepare() throws Exception {
}
if (this.outputClass != null && this.output == null) {
this.output = createComponent(this.outputClass, SpeechOutput.class);
this.output.setAndroidContext(appContext);
this.ttsService.addListener(this.output);
this.output.setAppContext(this.context);
if (this.lifecycle != null) {
this.registerLifecycle(this.lifecycle);
}
Expand Down Expand Up @@ -205,22 +225,15 @@ private void raiseError(Throwable e) {
public static final class Builder {
private String ttsServiceClass;
private String outputClass;
private Context context;
private Context appContext;
private Lifecycle lifecycle;
private SpeechConfig config = new SpeechConfig();
private List<TTSListener> listeners = new ArrayList<>();

/**
* Initializes a new builder with no default configuration.
*
* @param appContext The context for the TTS manager. Since the manager
* is meant to cross activity boundaries, this should
* be the application context rather than an activity
* context.
*/
public Builder(Context appContext) {
this.context = appContext;
}
public Builder() { }

/**
* Sets the class name of the external TTS service component.
Expand Down Expand Up @@ -267,11 +280,25 @@ public Builder setProperty(String key, Object value) {
return this;
}

/**
* Sets the Android context for the pipeline. Some components may
* require an Application context instead of an Activity context;
* see individual component documentation for details.
*
* @param androidContext The Android context for the pipeline.
* @return this
* @see io.spokestack.spokestack.tts.SpokestackTTSOutput
*/
public Builder setAndroidContext(Context androidContext) {
this.appContext = androidContext;
return this;
}

/**
* Sets the manager's current lifecycle.
*
* @param curLifecycle The lifecycle dispatching events to this TTS
* manager.
* manager.
* @return this
*/
public Builder setLifecycle(Lifecycle curLifecycle) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public void testConstruction() {
public void testResourceManagement() {
SpokestackTTSOutput ttsOutput =
new SpokestackTTSOutput(null, factory);
ttsOutput.setAppContext(mockContext);
ttsOutput.setAndroidContext(mockContext);
lifecycleRegistry.addObserver(ttsOutput);
ttsOutput.prepare();
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
Expand Down Expand Up @@ -197,7 +197,6 @@ private SpokestackTTSOutput spiedOutput() {
.when(ttsOutput).createMediaSource(any());
doReturn(AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
.when(ttsOutput).requestFocus();
ttsOutput.setAppContext(mockContext);
return ttsOutput;
}

Expand Down
14 changes: 7 additions & 7 deletions src/test/java/io/spokestack/spokestack/tts/TTSManagerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ public void before() {
public void testBuilder() throws Exception {
// invalid TTS service
assertThrows(ClassNotFoundException.class,
() -> new TTSManager.Builder(context)
() -> new TTSManager.Builder()
.setTTSServiceClass("invalid")
.build());

// invalid output component
assertThrows(ClassNotFoundException.class,
() -> new TTSManager.Builder(context)
() -> new TTSManager.Builder()
.setTTSServiceClass("io.spokestack.spokestack.tts.SpokestackTTSService")
.setProperty("spokestack-key", "test")
.setProperty("spokestack-secret", "test")
Expand All @@ -60,11 +60,12 @@ public void testBuilder() throws Exception {
LifecycleRegistry lifecycleRegistry =
new LifecycleRegistry(mock(LifecycleOwner.class));

TTSManager manager = new TTSManager.Builder(context)
TTSManager manager = new TTSManager.Builder()
.setTTSServiceClass("io.spokestack.spokestack.tts.TTSManagerTest$Input")
.setOutputClass("io.spokestack.spokestack.tts.TTSManagerTest$Output")
.setProperty("spokestack-key", "test")
.setConfig(new SpeechConfig())
.setAndroidContext(context)
.setLifecycle(lifecycleRegistry)
.addTTSListener(this)
.build();
Expand Down Expand Up @@ -138,8 +139,8 @@ public void close() {
public static class Output extends SpeechOutput
implements DefaultLifecycleObserver {

public Output(SpeechConfig config) {
}
@SuppressWarnings("unused")
public Output(SpeechConfig config) { }

@Override
public void onResume(@NotNull LifecycleOwner owner) {
Expand All @@ -155,8 +156,7 @@ public void audioReceived(AudioResponse response) {
}

@Override
public void setAppContext(Context appContext) {
}
public void setAndroidContext(Context appContext) { }

@Override
public void close() {
Expand Down

0 comments on commit 9b6017a

Please sign in to comment.