Implement DeviceComponent of SimpleExoPlayer

PiperOrigin-RevId: 305460260
This commit is contained in:
gyumin 2020-04-08 13:55:03 +01:00 committed by Oliver Woodman
parent 7504ce763e
commit dbfb6b183c
4 changed files with 514 additions and 3 deletions

View file

@ -63,6 +63,8 @@
* Update `CachedContentIndex` to use `SecureRandom` for generating the
initialization vector used to encrypt the cache contents.
* Remove deprecated members in `DefaultTrackSelector`.
* Add `Player.DeviceComponent` and implement it for `SimpleExoPlayer` so
that the device volume can be controlled by player.
* Text:
* Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
later).

View file

@ -0,0 +1,270 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import com.google.android.exoplayer2.testutil.DummyMainThread;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link StreamVolumeManager}. */
@RunWith(AndroidJUnit4.class)
public class StreamVolumeManagerTest {
private static final long TIMEOUT_MS = 1_000;
private AudioManager audioManager;
private TestListener testListener;
private DummyMainThread testThread;
private StreamVolumeManager streamVolumeManager;
@Before
public void setUp() {
Context context = ApplicationProvider.getApplicationContext();
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
testListener = new TestListener();
testThread = new DummyMainThread();
testThread.runOnMainThread(
() ->
streamVolumeManager =
new StreamVolumeManager(context, new Handler(Looper.myLooper()), testListener));
}
@After
public void tearDown() {
testThread.runOnMainThread(() -> streamVolumeManager.release());
testThread.release();
}
@Test
@SdkSuppress(minSdkVersion = 28)
public void getMinVolume_returnsStreamMinVolume() {
testThread.runOnMainThread(
() -> {
int streamMinVolume = audioManager.getStreamMinVolume(C.STREAM_TYPE_DEFAULT);
assertThat(streamVolumeManager.getMinVolume()).isEqualTo(streamMinVolume);
});
}
@Test
public void getMaxVolume_returnsStreamMaxVolume() {
testThread.runOnMainThread(
() -> {
int streamMaxVolume = audioManager.getStreamMaxVolume(C.STREAM_TYPE_DEFAULT);
assertThat(streamVolumeManager.getMaxVolume()).isEqualTo(streamMaxVolume);
});
}
@Test
public void getVolume_returnsStreamVolume() {
testThread.runOnMainThread(
() -> {
int streamVolume = audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT);
assertThat(streamVolumeManager.getVolume()).isEqualTo(streamVolume);
});
}
@Test
public void setVolume_changesStreamVolume() {
testThread.runOnMainThread(
() -> {
int minVolume = streamVolumeManager.getMinVolume();
int maxVolume = streamVolumeManager.getMaxVolume();
if (minVolume == maxVolume) {
return;
}
int oldVolume = streamVolumeManager.getVolume();
int targetVolume = oldVolume == maxVolume ? minVolume : maxVolume;
streamVolumeManager.setVolume(targetVolume);
assertThat(streamVolumeManager.getVolume()).isEqualTo(targetVolume);
assertThat(testListener.lastStreamVolume).isEqualTo(targetVolume);
assertThat(audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT)).isEqualTo(targetVolume);
});
}
@Test
public void setVolume_withOutOfRange_isIgnored() {
testThread.runOnMainThread(
() -> {
int maxVolume = streamVolumeManager.getMaxVolume();
int minVolume = streamVolumeManager.getMinVolume();
int oldVolume = streamVolumeManager.getVolume();
streamVolumeManager.setVolume(maxVolume + 1);
assertThat(streamVolumeManager.getVolume()).isEqualTo(oldVolume);
streamVolumeManager.setVolume(minVolume - 1);
assertThat(streamVolumeManager.getVolume()).isEqualTo(oldVolume);
});
}
@Test
public void increaseVolume_increasesStreamVolumeByOne() {
testThread.runOnMainThread(
() -> {
int minVolume = streamVolumeManager.getMinVolume();
int maxVolume = streamVolumeManager.getMaxVolume();
if (minVolume == maxVolume) {
return;
}
streamVolumeManager.setVolume(minVolume);
int targetVolume = minVolume + 1;
streamVolumeManager.increaseVolume();
assertThat(streamVolumeManager.getVolume()).isEqualTo(targetVolume);
assertThat(testListener.lastStreamVolume).isEqualTo(targetVolume);
assertThat(audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT)).isEqualTo(targetVolume);
});
}
@Test
public void increaseVolume_onMaxVolume_isIgnored() {
testThread.runOnMainThread(
() -> {
int maxVolume = streamVolumeManager.getMaxVolume();
streamVolumeManager.setVolume(maxVolume);
streamVolumeManager.increaseVolume();
assertThat(streamVolumeManager.getVolume()).isEqualTo(maxVolume);
});
}
@Test
public void decreaseVolume_decreasesStreamVolumeByOne() {
testThread.runOnMainThread(
() -> {
int minVolume = streamVolumeManager.getMinVolume();
int maxVolume = streamVolumeManager.getMaxVolume();
if (minVolume == maxVolume) {
return;
}
streamVolumeManager.setVolume(maxVolume);
int targetVolume = maxVolume - 1;
streamVolumeManager.decreaseVolume();
assertThat(streamVolumeManager.getVolume()).isEqualTo(targetVolume);
assertThat(testListener.lastStreamVolume).isEqualTo(targetVolume);
assertThat(audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT)).isEqualTo(targetVolume);
});
}
@Test
public void decreaseVolume_onMinVolume_isIgnored() {
testThread.runOnMainThread(
() -> {
int minVolume = streamVolumeManager.getMinVolume();
streamVolumeManager.setVolume(minVolume);
streamVolumeManager.decreaseVolume();
assertThat(streamVolumeManager.getVolume()).isEqualTo(minVolume);
});
}
@Test
public void setStreamType_notifiesStreamTypeAndVolume() {
testThread.runOnMainThread(
() -> {
int minVolume = streamVolumeManager.getMinVolume();
int maxVolume = streamVolumeManager.getMaxVolume();
if (minVolume == maxVolume) {
return;
}
int testStreamType = C.STREAM_TYPE_ALARM;
int testStreamVolume = audioManager.getStreamVolume(testStreamType);
int oldVolume = streamVolumeManager.getVolume();
if (oldVolume == testStreamVolume) {
int differentVolume = oldVolume == minVolume ? maxVolume : minVolume;
streamVolumeManager.setVolume(differentVolume);
}
streamVolumeManager.setStreamType(testStreamType);
assertThat(testListener.lastStreamType).isEqualTo(testStreamType);
assertThat(testListener.lastStreamVolume).isEqualTo(testStreamVolume);
assertThat(streamVolumeManager.getVolume()).isEqualTo(testStreamVolume);
});
}
@Test
public void onStreamVolumeChanged_isCalled_whenAudioManagerChangesIt() throws Exception {
AtomicInteger targetVolumeRef = new AtomicInteger();
testThread.runOnMainThread(
() -> {
int minVolume = streamVolumeManager.getMinVolume();
int maxVolume = streamVolumeManager.getMaxVolume();
if (minVolume == maxVolume) {
return;
}
int oldVolume = streamVolumeManager.getVolume();
int targetVolume = oldVolume == maxVolume ? minVolume : maxVolume;
targetVolumeRef.set(targetVolume);
audioManager.setStreamVolume(C.STREAM_TYPE_DEFAULT, targetVolume, /* flags= */ 0);
});
testListener.onStreamVolumeChangedLatch.await(TIMEOUT_MS, MILLISECONDS);
assertThat(testListener.lastStreamVolume).isEqualTo(targetVolumeRef.get());
}
private static class TestListener implements StreamVolumeManager.Listener {
@C.StreamType private int lastStreamType;
private int lastStreamVolume;
public final CountDownLatch onStreamVolumeChangedLatch;
public TestListener() {
onStreamVolumeChangedLatch = new CountDownLatch(1);
}
@Override
public void onStreamTypeChanged(@C.StreamType int streamType) {
lastStreamType = streamType;
}
@Override
public void onStreamVolumeChanged(int streamVolume) {
lastStreamVolume = streamVolume;
onStreamVolumeChangedLatch.countDown();
}
}
}

View file

@ -36,6 +36,8 @@ import com.google.android.exoplayer2.audio.AudioListener;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AuxEffectInfo;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.device.DeviceInfo;
import com.google.android.exoplayer2.device.DeviceListener;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
@ -73,7 +75,8 @@ public class SimpleExoPlayer extends BasePlayer
Player.AudioComponent,
Player.VideoComponent,
Player.TextComponent,
Player.MetadataComponent {
Player.MetadataComponent,
Player.DeviceComponent {
/** @deprecated Use {@link com.google.android.exoplayer2.video.VideoListener}. */
@Deprecated
@ -330,6 +333,7 @@ public class SimpleExoPlayer extends BasePlayer
private final CopyOnWriteArraySet<AudioListener> audioListeners;
private final CopyOnWriteArraySet<TextOutput> textOutputs;
private final CopyOnWriteArraySet<MetadataOutput> metadataOutputs;
private final CopyOnWriteArraySet<DeviceListener> deviceListeners;
private final CopyOnWriteArraySet<VideoRendererEventListener> videoDebugListeners;
private final CopyOnWriteArraySet<AudioRendererEventListener> audioDebugListeners;
private final BandwidthMeter bandwidthMeter;
@ -337,6 +341,7 @@ public class SimpleExoPlayer extends BasePlayer
private final AudioBecomingNoisyManager audioBecomingNoisyManager;
private final AudioFocusManager audioFocusManager;
private final StreamVolumeManager streamVolumeManager;
private final WakeLockManager wakeLockManager;
private final WifiLockManager wifiLockManager;
@ -364,6 +369,7 @@ public class SimpleExoPlayer extends BasePlayer
@Nullable private PriorityTaskManager priorityTaskManager;
private boolean isPriorityTaskManagerRegistered;
private boolean playerReleased;
private DeviceInfo deviceInfo;
/** @param builder The {@link Builder} to obtain all construction parameters. */
protected SimpleExoPlayer(Builder builder) {
@ -414,6 +420,7 @@ public class SimpleExoPlayer extends BasePlayer
audioListeners = new CopyOnWriteArraySet<>();
textOutputs = new CopyOnWriteArraySet<>();
metadataOutputs = new CopyOnWriteArraySet<>();
deviceListeners = new CopyOnWriteArraySet<>();
videoDebugListeners = new CopyOnWriteArraySet<>();
audioDebugListeners = new CopyOnWriteArraySet<>();
eventHandler = new Handler(looper);
@ -456,8 +463,10 @@ public class SimpleExoPlayer extends BasePlayer
audioBecomingNoisyManager =
new AudioBecomingNoisyManager(context, eventHandler, componentListener);
audioFocusManager = new AudioFocusManager(context, eventHandler, componentListener);
streamVolumeManager = new StreamVolumeManager(context, eventHandler, componentListener);
wakeLockManager = new WakeLockManager(context);
wifiLockManager = new WifiLockManager(context);
deviceInfo = createDeviceInfo(streamVolumeManager);
}
@Override
@ -487,8 +496,7 @@ public class SimpleExoPlayer extends BasePlayer
@Override
@Nullable
public DeviceComponent getDeviceComponent() {
// TODO(b/145595776): Return this after implementing DeviceComponent.
return null;
return this;
}
/**
@ -684,6 +692,7 @@ public class SimpleExoPlayer extends BasePlayer
.send();
}
}
streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage));
for (AudioListener audioListener : audioListeners) {
audioListener.onAudioAttributesChanged(audioAttributes);
}
@ -1565,6 +1574,7 @@ public class SimpleExoPlayer extends BasePlayer
public void release() {
verifyApplicationThread();
audioBecomingNoisyManager.setEnabled(false);
streamVolumeManager.release();
wakeLockManager.setStayAwake(false);
wifiLockManager.setStayAwake(false);
audioFocusManager.release();
@ -1743,6 +1753,46 @@ public class SimpleExoPlayer extends BasePlayer
}
}
@Override
public void addDeviceListener(DeviceListener listener) {
deviceListeners.add(listener);
}
@Override
public void removeDeviceListener(DeviceListener listener) {
deviceListeners.remove(listener);
}
@Override
public DeviceInfo getDeviceInfo() {
verifyApplicationThread();
return deviceInfo;
}
@Override
public int getDeviceVolume() {
verifyApplicationThread();
return streamVolumeManager.getVolume();
}
@Override
public void setDeviceVolume(int volume) {
verifyApplicationThread();
streamVolumeManager.setVolume(volume);
}
@Override
public void increaseDeviceVolume() {
verifyApplicationThread();
streamVolumeManager.increaseVolume();
}
@Override
public void decreaseDeviceVolume() {
verifyApplicationThread();
streamVolumeManager.decreaseVolume();
}
// Internal methods.
private void removeSurfaceCallbacks() {
@ -1898,6 +1948,13 @@ public class SimpleExoPlayer extends BasePlayer
}
}
private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) {
return new DeviceInfo(
DeviceInfo.PLAYBACK_TYPE_LOCAL,
streamVolumeManager.getMinVolume(),
streamVolumeManager.getMaxVolume());
}
private static int getPlayWhenReadyChangeReason(boolean playWhenReady, int playerCommand) {
return playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS
@ -1913,6 +1970,7 @@ public class SimpleExoPlayer extends BasePlayer
TextureView.SurfaceTextureListener,
AudioFocusManager.PlayerControl,
AudioBecomingNoisyManager.EventListener,
StreamVolumeManager.Listener,
Player.EventListener {
// VideoRendererEventListener implementation
@ -2145,6 +2203,26 @@ public class SimpleExoPlayer extends BasePlayer
Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY);
}
// StreamVolumeManager.Listener implementation.
@Override
public void onStreamTypeChanged(@C.StreamType int streamType) {
DeviceInfo deviceInfo = createDeviceInfo(streamVolumeManager);
if (!deviceInfo.equals(SimpleExoPlayer.this.deviceInfo)) {
SimpleExoPlayer.this.deviceInfo = deviceInfo;
for (DeviceListener deviceListener : deviceListeners) {
deviceListener.onDeviceInfoChanged(deviceInfo);
}
}
}
@Override
public void onStreamVolumeChanged(int streamVolume) {
for (DeviceListener deviceListener : deviceListeners) {
deviceListener.onDeviceVolumeChanged(streamVolume);
}
}
// Player.EventListener implementation.
@Override

View file

@ -0,0 +1,161 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Handler;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
/** A manager that wraps {@link AudioManager} to control/listen audio stream volume. */
/* package */ final class StreamVolumeManager {
/** A listener for changes in the manager. */
public interface Listener {
/** Called when the audio stream type is changed. */
void onStreamTypeChanged(@C.StreamType int streamType);
/** Called when the audio stream volume is changed. */
void onStreamVolumeChanged(int streamVolume);
}
// TODO(b/151280453): Replace the hidden intent action with an official one.
// Copied from AudioManager#VOLUME_CHANGED_ACTION
private static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
// TODO(b/153317944): Allow users to override these flags.
private static final int VOLUME_FLAGS = AudioManager.FLAG_SHOW_UI;
private final Context applicationContext;
private final Handler eventHandler;
private final Listener listener;
private final AudioManager audioManager;
private final VolumeChangeReceiver receiver;
@C.StreamType private int streamType;
private int volume;
/** Creates a manager. */
public StreamVolumeManager(Context context, Handler eventHandler, Listener listener) {
applicationContext = context.getApplicationContext();
this.eventHandler = eventHandler;
this.listener = listener;
audioManager =
Assertions.checkStateNotNull(
(AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE));
streamType = C.STREAM_TYPE_DEFAULT;
volume = audioManager.getStreamVolume(streamType);
receiver = new VolumeChangeReceiver();
IntentFilter filter = new IntentFilter(VOLUME_CHANGED_ACTION);
applicationContext.registerReceiver(receiver, filter);
}
/** Sets the audio stream type. */
public void setStreamType(@C.StreamType int streamType) {
if (this.streamType == streamType) {
return;
}
this.streamType = streamType;
updateVolumeAndNotifyIfChanged();
listener.onStreamTypeChanged(streamType);
}
/**
* Gets the minimum volume for the current audio stream. It can be changed if {@link
* #setStreamType(int)} is called.
*/
public int getMinVolume() {
return Util.SDK_INT >= 28 ? audioManager.getStreamMinVolume(streamType) : 0;
}
/**
* Gets the maximum volume for the current audio stream. It can be changed if {@link
* #setStreamType(int)} is called.
*/
public int getMaxVolume() {
return audioManager.getStreamMaxVolume(streamType);
}
/** Gets the current volume for the current audio stream. */
public int getVolume() {
return volume;
}
/**
* Sets the volume with the given value for the current audio stream. The value should be between
* {@link #getMinVolume()} and {@link #getMaxVolume()}, otherwise it will be ignored.
*/
public void setVolume(int volume) {
if (volume < getMinVolume() || volume > getMaxVolume()) {
return;
}
audioManager.setStreamVolume(streamType, volume, VOLUME_FLAGS);
updateVolumeAndNotifyIfChanged();
}
/**
* Increases the volume by one for the current audio stream. It will be ignored if the current
* volume is equal to {@link #getMaxVolume()}.
*/
public void increaseVolume() {
if (volume >= getMaxVolume()) {
return;
}
audioManager.adjustStreamVolume(streamType, AudioManager.ADJUST_RAISE, VOLUME_FLAGS);
updateVolumeAndNotifyIfChanged();
}
/**
* Decreases the volume by one for the current audio stream. It will be ignored if the current
* volume is equal to {@link #getMinVolume()}.
*/
public void decreaseVolume() {
if (volume <= getMinVolume()) {
return;
}
audioManager.adjustStreamVolume(streamType, AudioManager.ADJUST_LOWER, VOLUME_FLAGS);
updateVolumeAndNotifyIfChanged();
}
/** Releases the manager. It must be called when the manager is no longer required. */
public void release() {
applicationContext.unregisterReceiver(receiver);
}
private void updateVolumeAndNotifyIfChanged() {
int newVolume = audioManager.getStreamVolume(streamType);
if (volume != newVolume) {
volume = newVolume;
listener.onStreamVolumeChanged(newVolume);
}
}
private final class VolumeChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
eventHandler.post(StreamVolumeManager.this::updateVolumeAndNotifyIfChanged);
}
}
}