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

Commit 839d484

Browse files
authored
Merge pull request #125 from spokestack/jz-mediaplayer-churn
fix: tighten task submission in TTS player
2 parents 816765b + 22b1f49 commit 839d484

File tree

3 files changed

+44
-84
lines changed

3 files changed

+44
-84
lines changed

src/main/java/io/spokestack/spokestack/tts/SpokestackTTSOutput.java

Lines changed: 29 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,12 @@
1212
import com.google.android.exoplayer2.C;
1313
import com.google.android.exoplayer2.ExoPlaybackException;
1414
import com.google.android.exoplayer2.ExoPlayer;
15-
import com.google.android.exoplayer2.PlaybackParameters;
1615
import com.google.android.exoplayer2.Player;
1716
import com.google.android.exoplayer2.SimpleExoPlayer;
18-
import com.google.android.exoplayer2.Timeline;
1917
import com.google.android.exoplayer2.audio.AudioAttributes;
2018
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
2119
import com.google.android.exoplayer2.source.MediaSource;
2220
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
23-
import com.google.android.exoplayer2.source.TrackGroupArray;
24-
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
2521
import com.google.android.exoplayer2.upstream.DataSource;
2622
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
2723
import com.google.android.exoplayer2.util.Util;
@@ -135,15 +131,18 @@ PlayerState getPlayerState() {
135131
* Establish the media player and allocate its internal resources.
136132
*/
137133
public void prepare() {
138-
this.taskHandler.run(() -> {
134+
this.taskHandler.run(this::inlinePrepare);
135+
}
136+
137+
void inlinePrepare() {
138+
if (this.mediaPlayer == null) {
139139
ExoPlayer player = this.playerFactory.createPlayer(
140140
this.usage,
141141
this.contentType,
142142
this.appContext);
143143
player.addListener(this);
144144
this.mediaPlayer = player;
145-
this.mediaPlayer.prepare(mediaSource);
146-
});
145+
}
147146
}
148147

149148
@Override
@@ -159,30 +158,24 @@ public void close() {
159158

160159
@Override
161160
public void audioReceived(AudioResponse response) {
162-
if (this.mediaPlayer == null) {
163-
prepare();
164-
}
165-
166161
this.taskHandler.run(() -> {
162+
inlinePrepare();
163+
167164
Uri audioUri = response.getAudioUri();
168165
MediaSource newTrack = createMediaSource(audioUri);
169166

170-
if (mediaPlayer.isPlaying()) {
171-
mediaSource.addMediaSource(newTrack);
172-
} else {
173-
mediaSource.clear();
174-
mediaSource.addMediaSource(newTrack);
175-
mediaPlayer.prepare(mediaSource);
176-
}
167+
this.mediaSource.addMediaSource(newTrack);
168+
this.mediaPlayer.prepare(this.mediaSource);
177169
this.playerState = new PlayerState(
178170
true,
179171
this.playerState.shouldPlay,
180172
this.playerState.curPosition,
181173
this.playerState.window
182174
);
175+
176+
inlinePlay();
183177
});
184178

185-
playContent();
186179
}
187180

188181
@Override
@@ -236,7 +229,12 @@ public void onIsPlayingChanged(boolean isPlaying) {
236229
}
237230

238231
private void resetPlayerState() {
232+
this.mediaSource.clear();
239233
this.playerState = new PlayerState(false, false, 0, 0);
234+
if (this.mediaPlayer != null) {
235+
this.mediaPlayer.release();
236+
this.mediaPlayer = null;
237+
}
240238
}
241239

242240
@Override
@@ -266,20 +264,20 @@ public void onAudioFocusChange(int focusChange) {
266264
* Start or resume playback of any TTS responses.
267265
*/
268266
public void playContent() {
269-
this.taskHandler.run(() -> {
270-
if (!playerState.hasContent) {
271-
return;
272-
}
267+
this.taskHandler.run(this::inlinePlay);
268+
}
273269

274-
if (mediaPlayer == null) {
275-
prepare();
276-
}
270+
void inlinePlay() {
271+
if (!playerState.hasContent) {
272+
return;
273+
}
277274

278-
// only play if focus is granted
279-
if (requestFocus() == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
280-
mediaPlayer.setPlayWhenReady(true);
281-
}
282-
});
275+
inlinePrepare();
276+
277+
// only play if focus is granted
278+
if (requestFocus() == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
279+
mediaPlayer.setPlayWhenReady(true);
280+
}
283281
}
284282

285283
int requestFocus() {
@@ -371,46 +369,4 @@ ExoPlayer createPlayer(int usage, int contentType,
371369
return player;
372370
}
373371
}
374-
375-
// similarly, implementing these listener methods maintains backwards
376-
// compatibility for ExoPlayer
377-
378-
@Override public void onTimelineChanged(@NotNull Timeline timeline,
379-
int reason) { }
380-
381-
@Override
382-
// it's deprecated, but it's still a default method, so we have to
383-
// implement it for older versions of Android
384-
@SuppressWarnings("deprecation")
385-
public void onTimelineChanged(@NotNull Timeline timeline,
386-
@Nullable Object manifest, int reason) { }
387-
388-
@Override
389-
public void onTracksChanged(@NotNull TrackGroupArray trackGroups,
390-
@NotNull TrackSelectionArray trackSelections) {
391-
392-
}
393-
394-
@Override public void onLoadingChanged(boolean isLoading) { }
395-
396-
@Override
397-
public void onPlaybackSuppressionReasonChanged(
398-
int playbackSuppressionReason) { }
399-
400-
@Override
401-
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
402-
}
403-
404-
@Override public void onRepeatModeChanged(int repeatMode) { }
405-
406-
@Override public void onShuffleModeEnabledChanged(
407-
boolean shuffleModeEnabled) { }
408-
409-
@Override public void onPositionDiscontinuity(int reason) { }
410-
411-
@Override
412-
public void onPlaybackParametersChanged(
413-
@NotNull PlaybackParameters playbackParameters) { }
414-
415-
@Override public void onSeekProcessed() { }
416372
}

src/main/java/io/spokestack/spokestack/util/TaskHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public final class TaskHandler {
1414
/**
1515
* Initialize a new task handler.
1616
*
17-
* @param runOnMainThread {@code true} if tasks submitted to this hanlder
17+
* @param runOnMainThread {@code true} if tasks submitted to this handler
1818
* should run on the application's main {@code
1919
* Looper}.
2020
*/

src/test/java/io/spokestack/spokestack/tts/SpokestackTTSOutputTest.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ public void testCallbacks() {
102102
// audioReceived ensures that the player gets set up
103103
mediaPlayer = ttsOutput.getMediaPlayer();
104104
assertNotNull(mediaPlayer);
105-
verify(ttsOutput, times(1)).playContent();
105+
verify(ttsOutput, times(1)).inlinePlay();
106106
verify(ttsOutput, times(1)).createMediaSource(Uri.EMPTY);
107107
verify(ttsOutput, times(1)).requestFocus();
108-
verify(mediaPlayer, times(2)).prepare(any());
108+
verify(mediaPlayer, times(1)).prepare(any());
109109
verify(mediaPlayer, times(1)).setPlayWhenReady(true);
110110
}
111111

@@ -119,22 +119,26 @@ public void testPlayerStateChange() {
119119

120120
TestExoPlayer mediaPlayer = (TestExoPlayer) ttsOutput.getMediaPlayer();
121121
assertNotNull(mediaPlayer);
122-
// this state change should do nothing
123-
ttsOutput.onPlayerStateChanged(false, Player.STATE_BUFFERING);
122+
// buffering doesn't send PLAYBACK_STOPPED events
123+
mediaPlayer.setPlaybackState(Player.STATE_BUFFERING);
124+
ttsOutput.onIsPlayingChanged(false);
124125
assertTrue(ttsOutput.getPlayerState().hasContent);
126+
assertTrue(listener.events.isEmpty());
125127

128+
// simulate playing audio from a non-Spokestack source
126129
ttsOutput.stopPlayback();
130+
ttsOutput.onIsPlayingChanged(true);
131+
ttsOutput.onIsPlayingChanged(false);
127132
assertFalse(ttsOutput.getPlayerState().shouldPlay);
128133
assertFalse(ttsOutput.getPlayerState().hasContent);
129-
130-
ttsOutput.onPlayerStateChanged(false, Player.STATE_ENDED);
131-
assertFalse(ttsOutput.getPlayerState().hasContent);
132-
// no playback_complete event if the player didn't have content from
133-
// the TTS system
134+
// no event for non-Spokestack audio
134135
assertTrue(listener.events.isEmpty());
135136

136-
// now give it audio to play and check again
137+
// give it Spokestack audio to play
137138
ttsOutput.audioReceived(new AudioResponse(Uri.EMPTY));
139+
// the player has to be recreated after `stopPlayback()` and receiving
140+
// new audio
141+
mediaPlayer = (TestExoPlayer) ttsOutput.getMediaPlayer();
138142
ttsOutput.onIsPlayingChanged(true);
139143
ttsOutput.onIsPlayingChanged(false);
140144
assertEquals(2, listener.events.size());

0 commit comments

Comments
 (0)