diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c1b7e9d6aa..b029419701 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -55,6 +55,10 @@ * Track selection: * Allow parallel adaptation for video and audio ([#5111](https://github.com/google/ExoPlayer/issues/5111)). + * Simplified enabling tunneling with `DefaultTrackSelector`. + `ParametersBuilder.setTunnelingAudioSessionId` has been replaced with + `ParametersBuilder.setTunnelingEnabled`. The player's audio session ID + will be used, and so a tunneling specified ID is no longer needed. * Add option to specify multiple preferred audio or text languages. * Add option to specify preferred MIME type(s) for video and audio ([#8320](https://github.com/google/ExoPlayer/issues/8320)). @@ -87,6 +91,18 @@ `DecoderReuseEvaluation` indicates whether it was possible to re-use an existing decoder instance for the new format, and if not then the reasons why. +* Audio: + * Fix handling of audio session IDs + ([#8190](https://github.com/google/ExoPlayer/issues/8190)). + `SimpleExoPlayer` now generates an audio session ID on construction, + which can be immediately queried by calling + `SimpleExoPlayer.getAudioSessionId`. The audio session ID will only + change if application code calls `SimpleExoPlayer.setAudioSessionId`. +* Text: + * Gracefully handle null-terminated subtitle content in Matroska + containers. + * Fix CEA-708 anchor positioning + ([#1807](https://github.com/google/ExoPlayer/issues/1807)). * Metadata retriever: * Parse Google Photos HEIC motion photos metadata. * Data sources: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 1abe7d4e5d..961d930f02 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -89,7 +89,7 @@ public interface Player { * default audio attributes will be used. They are suitable for general media playback. * *

Setting the audio attributes during playback may introduce a short gap in audio output as - * the audio track is recreated. A new audio session id will also be generated. + * the audio track is recreated. * *

If tunneling is enabled by the track selector, the specified audio attributes will be * ignored, but they will take effect if audio is later played without tunneling. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java b/library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java index bc8c6ff633..e36cb0c71d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java @@ -22,24 +22,16 @@ import androidx.annotation.Nullable; */ public final class RendererConfiguration { - /** - * The default configuration. - */ + /** The default configuration. */ public static final RendererConfiguration DEFAULT = - new RendererConfiguration(C.AUDIO_SESSION_ID_UNSET); + new RendererConfiguration(/* tunneling= */ false); - /** - * The audio session id to use for tunneling, or {@link C#AUDIO_SESSION_ID_UNSET} if tunneling - * should not be enabled. - */ - public final int tunnelingAudioSessionId; + /** Whether to enable tunneling. */ + public final boolean tunneling; - /** - * @param tunnelingAudioSessionId The audio session id to use for tunneling, or - * {@link C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. - */ - public RendererConfiguration(int tunnelingAudioSessionId) { - this.tunnelingAudioSessionId = tunnelingAudioSessionId; + /** @param tunneling Whether to enable tunneling. */ + public RendererConfiguration(boolean tunneling) { + this.tunneling = tunneling; } @Override @@ -51,12 +43,11 @@ public final class RendererConfiguration { return false; } RendererConfiguration other = (RendererConfiguration) obj; - return tunnelingAudioSessionId == other.tunnelingAudioSessionId; + return tunneling == other.tunneling; } @Override public int hashCode() { - return tunnelingAudioSessionId; + return tunneling ? 0 : 1; } - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 53babe2709..2516d8f488 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -18,6 +18,8 @@ package com.google.android.exoplayer2; import android.content.Context; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.media.AudioFormat; +import android.media.AudioTrack; import android.media.MediaCodec; import android.media.PlaybackParams; import android.os.Handler; @@ -568,6 +570,7 @@ public class SimpleExoPlayer extends BasePlayer protected final Renderer[] renderers; + private final Context applicationContext; private final ExoPlayerImpl player; private final ComponentListener componentListener; private final CopyOnWriteArraySet @@ -588,7 +591,7 @@ public class SimpleExoPlayer extends BasePlayer @Nullable private Format videoFormat; @Nullable private Format audioFormat; - + @Nullable private AudioTrack keepSessionIdAudioTrack; @Nullable private VideoDecoderOutputBufferRenderer videoDecoderOutputBufferRenderer; @Nullable private Surface surface; private boolean ownsSurface; @@ -640,6 +643,7 @@ public class SimpleExoPlayer extends BasePlayer /** @param builder The {@link Builder} to obtain all construction parameters. */ protected SimpleExoPlayer(Builder builder) { + applicationContext = builder.context.getApplicationContext(); analyticsCollector = builder.analyticsCollector; priorityTaskManager = builder.priorityTaskManager; audioAttributes = builder.audioAttributes; @@ -665,7 +669,11 @@ public class SimpleExoPlayer extends BasePlayer // Set initial values. audioVolume = 1; - audioSessionId = C.AUDIO_SESSION_ID_UNSET; + if (Util.SDK_INT < 21) { + audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); + } else { + audioSessionId = C.generateAudioSessionIdV21(applicationContext); + } currentCues = Collections.emptyList(); throwsWhenUsingWrongThread = true; @@ -706,6 +714,8 @@ public class SimpleExoPlayer extends BasePlayer wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK); deviceInfo = createDeviceInfo(streamVolumeManager); + sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_AUDIO_SESSION_ID, audioSessionId); + sendRendererMessage(C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); sendRendererMessage(C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_SCALING_MODE, videoScalingMode); sendRendererMessage( @@ -960,11 +970,22 @@ public class SimpleExoPlayer extends BasePlayer if (this.audioSessionId == audioSessionId) { return; } + if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { + if (Util.SDK_INT < 21) { + audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); + } else { + audioSessionId = C.generateAudioSessionIdV21(applicationContext); + } + } else if (Util.SDK_INT < 21) { + // We need to re-initialize keepSessionIdAudioTrack to make sure the session is kept alive for + // as long as the player is using it. + initializeKeepSessionIdAudioTrack(audioSessionId); + } this.audioSessionId = audioSessionId; sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_AUDIO_SESSION_ID, audioSessionId); - if (audioSessionId != C.AUDIO_SESSION_ID_UNSET) { - notifyAudioSessionIdSet(); + for (AudioListener audioListener : audioListeners) { + audioListener.onAudioSessionId(audioSessionId); } } @@ -1024,7 +1045,7 @@ public class SimpleExoPlayer extends BasePlayer * Sets the stream type for audio playback, used by the underlying audio track. * *

Setting the stream type during playback may introduce a short gap in audio output as the - * audio track is recreated. A new audio session id will also be generated. + * audio track is recreated. * *

Calling this method overwrites any attributes set previously by calling {@link * #setAudioAttributes(AudioAttributes)}. @@ -1760,6 +1781,10 @@ public class SimpleExoPlayer extends BasePlayer @Override public void release() { verifyApplicationThread(); + if (Util.SDK_INT < 21 && keepSessionIdAudioTrack != null) { + keepSessionIdAudioTrack.release(); + keepSessionIdAudioTrack = null; + } audioBecomingNoisyManager.setEnabled(false); streamVolumeManager.release(); wakeLockManager.setStayAwake(false); @@ -2100,19 +2125,6 @@ public class SimpleExoPlayer extends BasePlayer sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_VOLUME, scaledVolume); } - private void notifyAudioSessionIdSet() { - for (AudioListener audioListener : audioListeners) { - // Prevent duplicate notification if a listener is both a AudioRendererEventListener and - // a AudioListener, as they have the same method signature. - if (!audioDebugListeners.contains(audioListener)) { - audioListener.onAudioSessionId(audioSessionId); - } - } - for (AudioRendererEventListener audioDebugListener : audioDebugListeners) { - audioDebugListener.onAudioSessionId(audioSessionId); - } - } - @SuppressWarnings("SuspiciousMethodCalls") private void notifySkipSilenceEnabledChanged() { for (AudioListener listener : audioListeners) { @@ -2181,6 +2193,40 @@ public class SimpleExoPlayer extends BasePlayer } } + /** + * Initializes {@link #keepSessionIdAudioTrack} to keep an audio session ID alive. If the audio + * session ID is {@link C#AUDIO_SESSION_ID_UNSET} then a new audio session ID is generated. + * + *

Use of this method is only required on API level 21 and earlier. + * + * @param audioSessionId The audio session ID, or {@link C#AUDIO_SESSION_ID_UNSET} to generate a + * new one. + * @return The audio session ID. + */ + private int initializeKeepSessionIdAudioTrack(int audioSessionId) { + if (keepSessionIdAudioTrack != null + && keepSessionIdAudioTrack.getAudioSessionId() != audioSessionId) { + keepSessionIdAudioTrack.release(); + keepSessionIdAudioTrack = null; + } + if (keepSessionIdAudioTrack == null) { + int sampleRate = 4000; // Minimum sample rate supported by the platform. + int channelConfig = AudioFormat.CHANNEL_OUT_MONO; + @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; + int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. + keepSessionIdAudioTrack = + new AudioTrack( + C.STREAM_TYPE_DEFAULT, + sampleRate, + channelConfig, + encoding, + bufferSize, + AudioTrack.MODE_STATIC, + audioSessionId); + } + return keepSessionIdAudioTrack.getAudioSessionId(); + } + private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) { return new DeviceInfo( DeviceInfo.PLAYBACK_TYPE_LOCAL, @@ -2303,15 +2349,6 @@ public class SimpleExoPlayer extends BasePlayer } } - @Override - public void onAudioSessionId(int sessionId) { - if (audioSessionId == sessionId) { - return; - } - audioSessionId = sessionId; - notifyAudioSessionIdSet(); - } - @Override public void onAudioDecoderInitialized( String decoderName, long initializedTimestampMs, long initializationDurationMs) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java index 48d0e03144..82ca6f4b9a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java @@ -44,13 +44,6 @@ public interface AudioRendererEventListener { */ default void onAudioEnabled(DecoderCounters counters) {} - /** - * Called when the audio session is set. - * - * @param audioSessionId The audio session id. - */ - default void onAudioSessionId(int audioSessionId) {} - /** * Called when a decoder is created. * @@ -224,13 +217,6 @@ public interface AudioRendererEventListener { } } - /** Invokes {@link AudioRendererEventListener#onAudioSessionId(int)}. */ - public void audioSessionId(int audioSessionId) { - if (handler != null) { - handler.post(() -> castNonNull(listener).onAudioSessionId(audioSessionId)); - } - } - /** Invokes {@link AudioRendererEventListener#onSkipSilenceEnabledChanged(boolean)}. */ public void skipSilenceEnabledChanged(boolean skipSilenceEnabled) { if (handler != null) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java index 1cbfde0bca..463461916f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java @@ -49,9 +49,9 @@ import java.nio.ByteBuffer; * *

The implementation may be backed by a platform {@link AudioTrack}. In this case, {@link * #setAudioSessionId(int)}, {@link #setAudioAttributes(AudioAttributes)}, {@link - * #enableTunnelingV21(int)} and/or {@link #disableTunneling()} may be called before writing data to - * the sink. These methods may also be called after writing data to the sink, in which case it will - * be reinitialized as required. For implementations that are not based on platform {@link + * #enableTunnelingV21()} and {@link #disableTunneling()} may be called before writing data to the + * sink. These methods may also be called after writing data to the sink, in which case it will be + * reinitialized as required. For implementations that are not based on platform {@link * AudioTrack}s, calling methods relating to audio sessions, audio attributes, and tunneling may * have no effect. */ @@ -62,13 +62,6 @@ public interface AudioSink { */ interface Listener { - /** - * Called if the audio sink has started rendering audio to a new platform audio session. - * - * @param audioSessionId The newly generated audio session's identifier. - */ - void onAudioSessionId(int audioSessionId); - /** * Called when the audio sink handles a buffer whose timestamp is discontinuous with the last * buffer handled since it was reset. @@ -392,14 +385,13 @@ public interface AudioSink { void setAuxEffectInfo(AuxEffectInfo auxEffectInfo); /** - * Enables tunneling, if possible. The sink is reset if tunneling was previously disabled or if - * the audio session id has changed. Enabling tunneling is only possible if the sink is based on a - * platform {@link AudioTrack}, and requires platform API version 21 onwards. + * Enables tunneling, if possible. The sink is reset if tunneling was previously disabled. + * Enabling tunneling is only possible if the sink is based on a platform {@link AudioTrack}, and + * requires platform API version 21 onwards. * - * @param tunnelingAudioSessionId The audio session id to use. * @throws IllegalStateException Thrown if enabling tunneling on platform API version < 21. */ - void enableTunnelingV21(int tunnelingAudioSessionId); + void enableTunnelingV21(); /** * Disables tunneling. If tunneling was previously enabled then the sink is reset and any audio diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java index ac5ff75d08..12962a786a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java @@ -512,9 +512,8 @@ public abstract class DecoderAudioRenderer< throws ExoPlaybackException { decoderCounters = new DecoderCounters(); eventDispatcher.enabled(decoderCounters); - int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; - if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { - audioSink.enableTunnelingV21(tunnelingAudioSessionId); + if (getConfiguration().tunneling) { + audioSink.enableTunnelingV21(); } else { audioSink.disableTunneling(); } @@ -714,11 +713,6 @@ public abstract class DecoderAudioRenderer< private final class AudioSinkListener implements AudioSink.Listener { - @Override - public void onAudioSessionId(int audioSessionId) { - eventDispatcher.audioSessionId(audioSessionId); - } - @Override public void onPositionDiscontinuity() { DecoderAudioRenderer.this.onPositionDiscontinuity(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 2acaa42e5b..7bd7d1a7eb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -261,15 +261,6 @@ public final class DefaultAudioSink implements AudioSink { private static final String TAG = "DefaultAudioSink"; - /** - * Whether to enable a workaround for an issue where an audio effect does not keep its session - * active across releasing/initializing a new audio track, on platform builds where - * {@link Util#SDK_INT} < 21. - *

- * The flag must be set before creating a player. - */ - public static boolean enablePreV21AudioSessionWorkaround = false; - /** * Whether to throw an {@link InvalidAudioTrackTimestampException} when a spurious timestamp is * reported from {@link AudioTrack#getTimestamp}. @@ -297,11 +288,6 @@ public final class DefaultAudioSink implements AudioSink { private final PendingExceptionHolder writeExceptionPendingExceptionHolder; @Nullable private Listener listener; - /** - * Used to keep the audio session active on pre-V21 builds (see {@link #initializeAudioTrack()}). - */ - @Nullable private AudioTrack keepSessionIdAudioTrack; - @Nullable private Configuration pendingConfiguration; @MonotonicNonNull private Configuration configuration; @Nullable private AudioTrack audioTrack; @@ -336,6 +322,7 @@ public final class DefaultAudioSink implements AudioSink { private boolean stoppedAudioTrack; private boolean playing; + private boolean externalAudioSessionIdProvided; private int audioSessionId; private AuxEffectInfo auxEffectInfo; private boolean tunneling; @@ -646,27 +633,7 @@ public final class DefaultAudioSink implements AudioSink { audioTrack.setOffloadDelayPadding( configuration.inputFormat.encoderDelay, configuration.inputFormat.encoderPadding); } - int audioSessionId = audioTrack.getAudioSessionId(); - if (enablePreV21AudioSessionWorkaround) { - if (Util.SDK_INT < 21) { - // The workaround creates an audio track with a two byte buffer on the same session, and - // does not release it until this object is released, which keeps the session active. - if (keepSessionIdAudioTrack != null - && audioSessionId != keepSessionIdAudioTrack.getAudioSessionId()) { - releaseKeepSessionIdAudioTrack(); - } - if (keepSessionIdAudioTrack == null) { - keepSessionIdAudioTrack = initializeKeepSessionIdAudioTrack(audioSessionId); - } - } - } - if (this.audioSessionId != audioSessionId) { - this.audioSessionId = audioSessionId; - if (listener != null) { - listener.onAudioSessionId(audioSessionId); - } - } - + audioSessionId = audioTrack.getAudioSessionId(); audioTrackPositionTracker.setAudioTrack( audioTrack, /* isPassthrough= */ configuration.outputMode == OUTPUT_MODE_PASSTHROUGH, @@ -1115,13 +1082,13 @@ public final class DefaultAudioSink implements AudioSink { return; } flush(); - audioSessionId = C.AUDIO_SESSION_ID_UNSET; } @Override public void setAudioSessionId(int audioSessionId) { if (this.audioSessionId != audioSessionId) { this.audioSessionId = audioSessionId; + externalAudioSessionIdProvided = audioSessionId != C.AUDIO_SESSION_ID_UNSET; flush(); } } @@ -1145,11 +1112,11 @@ public final class DefaultAudioSink implements AudioSink { } @Override - public void enableTunnelingV21(int tunnelingAudioSessionId) { + public void enableTunnelingV21() { Assertions.checkState(Util.SDK_INT >= 21); - if (!tunneling || audioSessionId != tunnelingAudioSessionId) { + Assertions.checkState(externalAudioSessionIdProvided); + if (!tunneling) { tunneling = true; - audioSessionId = tunnelingAudioSessionId; flush(); } } @@ -1158,7 +1125,6 @@ public final class DefaultAudioSink implements AudioSink { public void disableTunneling() { if (tunneling) { tunneling = false; - audioSessionId = C.AUDIO_SESSION_ID_UNSET; flush(); } } @@ -1203,6 +1169,14 @@ public final class DefaultAudioSink implements AudioSink { // AudioTrack.release can take some time, so we call it on a background thread. final AudioTrack toRelease = audioTrack; audioTrack = null; + if (Util.SDK_INT < 21 && !externalAudioSessionIdProvided) { + // Prior to API level 21, audio sessions are not kept alive once there are no components + // associated with them. If we generated the session ID internally, the only component + // associated with the session is the audio track that's being released, and therefore + // the session will not be kept alive. As a result, we need to generate a new session when + // we next create an audio track. + audioSessionId = C.AUDIO_SESSION_ID_UNSET; + } if (pendingConfiguration != null) { configuration = pendingConfiguration; pendingConfiguration = null; @@ -1261,14 +1235,12 @@ public final class DefaultAudioSink implements AudioSink { @Override public void reset() { flush(); - releaseKeepSessionIdAudioTrack(); for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) { audioProcessor.reset(); } for (AudioProcessor audioProcessor : toFloatPcmAvailableAudioProcessors) { audioProcessor.reset(); } - audioSessionId = C.AUDIO_SESSION_ID_UNSET; playing = false; offloadDisabledUntilNextConfiguration = false; } @@ -1303,23 +1275,6 @@ public final class DefaultAudioSink implements AudioSink { flushAudioProcessors(); } - /** Releases {@link #keepSessionIdAudioTrack} asynchronously, if it is non-{@code null}. */ - private void releaseKeepSessionIdAudioTrack() { - if (keepSessionIdAudioTrack == null) { - return; - } - - // AudioTrack.release can take some time, so we call it on a background thread. - final AudioTrack toRelease = keepSessionIdAudioTrack; - keepSessionIdAudioTrack = null; - new Thread() { - @Override - public void run() { - toRelease.release(); - } - }.start(); - } - @RequiresApi(23) private void setAudioTrackPlaybackParametersV23(PlaybackParameters audioTrackPlaybackParameters) { if (isAudioTrackInitialized()) { @@ -1587,21 +1542,6 @@ public final class DefaultAudioSink implements AudioSink { return Util.SDK_INT >= 30 && Util.MODEL.startsWith("Pixel"); } - private static AudioTrack initializeKeepSessionIdAudioTrack(int audioSessionId) { - int sampleRate = 4000; // Equal to private AudioTrack.MIN_SAMPLE_RATE. - int channelConfig = AudioFormat.CHANNEL_OUT_MONO; - @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; - int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. - return new AudioTrack( - C.STREAM_TYPE_DEFAULT, - sampleRate, - channelConfig, - encoding, - bufferSize, - AudioTrack.MODE_STATIC, - audioSessionId); - } - private static int getMaximumEncodedRateBytesPerSecond(@C.Encoding int encoding) { switch (encoding) { case C.ENCODING_MP3: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java index 7460d12457..1e60dc3ed1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java @@ -124,8 +124,8 @@ public class ForwardingAudioSink implements AudioSink { } @Override - public void enableTunnelingV21(int tunnelingAudioSessionId) { - sink.enableTunnelingV21(tunnelingAudioSessionId); + public void enableTunnelingV21() { + sink.enableTunnelingV21(); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 990369c783..5786af503d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -486,9 +486,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media throws ExoPlaybackException { super.onEnabled(joining, mayRenderStartOfStream); eventDispatcher.enabled(decoderCounters); - int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; - if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { - audioSink.enableTunnelingV21(tunnelingAudioSessionId); + if (getConfiguration().tunneling) { + audioSink.enableTunnelingV21(); } else { audioSink.disableTunneling(); } @@ -813,11 +812,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media private final class AudioSinkListener implements AudioSink.Listener { - @Override - public void onAudioSessionId(int audioSessionId) { - eventDispatcher.audioSessionId(audioSessionId); - } - @Override public void onPositionDiscontinuity() { MediaCodecAudioRenderer.this.onPositionDiscontinuity(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index d477bc1fed..85be24aafa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -159,7 +159,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; * * Tunneled playback can be enabled in cases where the combination of renderers and selected tracks * support it. Tunneled playback is enabled by passing an audio session ID to {@link - * ParametersBuilder#setTunnelingAudioSessionId(int)}. + * ParametersBuilder#setTunnelingEnabled(boolean)}. */ public class DefaultTrackSelector extends MappingTrackSelector { @@ -197,7 +197,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { private boolean forceLowestBitrate; private boolean forceHighestSupportedBitrate; private boolean exceedRendererCapabilitiesIfNecessary; - private int tunnelingAudioSessionId; + private boolean tunnelingEnabled; private boolean allowMultipleAdaptiveSelections; private final SparseArray> @@ -266,7 +266,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { forceLowestBitrate = initialValues.forceLowestBitrate; forceHighestSupportedBitrate = initialValues.forceHighestSupportedBitrate; exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary; - tunnelingAudioSessionId = initialValues.tunnelingAudioSessionId; + tunnelingEnabled = initialValues.tunnelingEnabled; allowMultipleAdaptiveSelections = initialValues.allowMultipleAdaptiveSelections; // Overrides selectionOverrides = cloneSelectionOverrides(initialValues.selectionOverrides); @@ -681,20 +681,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { } /** - * Sets the audio session id to use when tunneling. - * - *

Enables or disables tunneling. To enable tunneling, pass an audio session id to use when - * in tunneling mode. Session ids can be generated using {@link - * C#generateAudioSessionIdV21(Context)}. To disable tunneling pass {@link - * C#AUDIO_SESSION_ID_UNSET}. Tunneling will only be activated if it's both enabled and + * Sets whether to enable tunneling if possible. Tunneling will only be enabled if it's * supported by the audio and video renderers for the selected tracks. * - * @param tunnelingAudioSessionId The audio session id to use when tunneling, or {@link - * C#AUDIO_SESSION_ID_UNSET} to disable tunneling. + * @param tunnelingEnabled Whether to enable tunneling if possible. * @return This builder. */ - public ParametersBuilder setTunnelingAudioSessionId(int tunnelingAudioSessionId) { - this.tunnelingAudioSessionId = tunnelingAudioSessionId; + public ParametersBuilder setTunnelingEnabled(boolean tunnelingEnabled) { + this.tunnelingEnabled = tunnelingEnabled; return this; } @@ -865,7 +859,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { forceLowestBitrate, forceHighestSupportedBitrate, exceedRendererCapabilitiesIfNecessary, - tunnelingAudioSessionId, + tunnelingEnabled, allowMultipleAdaptiveSelections, selectionOverrides, rendererDisabledFlags); @@ -897,7 +891,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { forceLowestBitrate = false; forceHighestSupportedBitrate = false; exceedRendererCapabilitiesIfNecessary = true; - tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; + tunnelingEnabled = false; allowMultipleAdaptiveSelections = true; } @@ -1082,12 +1076,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { * {@code true}. */ public final boolean exceedRendererCapabilitiesIfNecessary; - /** - * The audio session id to use when tunneling, or {@link C#AUDIO_SESSION_ID_UNSET} if tunneling - * is disabled. The default value is {@link C#AUDIO_SESSION_ID_UNSET} (i.e. tunneling is - * disabled). - */ - public final int tunnelingAudioSessionId; + /** Whether to enable tunneling if possible. */ + public final boolean tunnelingEnabled; /** * Whether multiple adaptive selections with more than one track are allowed. The default value * is {@code true}. @@ -1138,7 +1128,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { boolean forceLowestBitrate, boolean forceHighestSupportedBitrate, boolean exceedRendererCapabilitiesIfNecessary, - int tunnelingAudioSessionId, + boolean tunnelingEnabled, boolean allowMultipleAdaptiveSelections, // Overrides SparseArray> selectionOverrides, @@ -1177,7 +1167,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.forceLowestBitrate = forceLowestBitrate; this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; - this.tunnelingAudioSessionId = tunnelingAudioSessionId; + this.tunnelingEnabled = tunnelingEnabled; this.allowMultipleAdaptiveSelections = allowMultipleAdaptiveSelections; // Overrides this.selectionOverrides = selectionOverrides; @@ -1218,7 +1208,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.forceLowestBitrate = Util.readBoolean(in); this.forceHighestSupportedBitrate = Util.readBoolean(in); this.exceedRendererCapabilitiesIfNecessary = Util.readBoolean(in); - this.tunnelingAudioSessionId = in.readInt(); + this.tunnelingEnabled = Util.readBoolean(in); this.allowMultipleAdaptiveSelections = Util.readBoolean(in); // Overrides this.selectionOverrides = readSelectionOverrides(in); @@ -1307,7 +1297,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { && forceLowestBitrate == other.forceLowestBitrate && forceHighestSupportedBitrate == other.forceHighestSupportedBitrate && exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary - && tunnelingAudioSessionId == other.tunnelingAudioSessionId + && tunnelingEnabled == other.tunnelingEnabled && allowMultipleAdaptiveSelections == other.allowMultipleAdaptiveSelections // Overrides && areRendererDisabledFlagsEqual(rendererDisabledFlags, other.rendererDisabledFlags) @@ -1345,7 +1335,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { result = 31 * result + (forceLowestBitrate ? 1 : 0); result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0); result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0); - result = 31 * result + tunnelingAudioSessionId; + result = 31 * result + (tunnelingEnabled ? 1 : 0); result = 31 * result + (allowMultipleAdaptiveSelections ? 1 : 0); // Overrides (omitted from hashCode). return result; @@ -1389,7 +1379,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { Util.writeBoolean(dest, forceLowestBitrate); Util.writeBoolean(dest, forceHighestSupportedBitrate); Util.writeBoolean(dest, exceedRendererCapabilitiesIfNecessary); - dest.writeInt(tunnelingAudioSessionId); + Util.writeBoolean(dest, tunnelingEnabled); Util.writeBoolean(dest, allowMultipleAdaptiveSelections); // Overrides writeSelectionOverridesToParcel(dest, selectionOverrides); @@ -1757,12 +1747,10 @@ public class DefaultTrackSelector extends MappingTrackSelector { } // Configure audio and video renderers to use tunneling if appropriate. - maybeConfigureRenderersForTunneling( - mappedTrackInfo, - rendererFormatSupports, - rendererConfigurations, - rendererTrackSelections, - params.tunnelingAudioSessionId); + if (params.tunnelingEnabled) { + maybeConfigureRenderersForTunneling( + mappedTrackInfo, rendererFormatSupports, rendererConfigurations, rendererTrackSelections); + } return Pair.create(rendererConfigurations, rendererTrackSelections); } @@ -2418,9 +2406,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Utility methods. /** - * Determines whether tunneling should be enabled, replacing {@link RendererConfiguration}s in - * {@code rendererConfigurations} with configurations that enable tunneling on the appropriate - * renderers if so. + * Determines whether tunneling can be enabled, replacing {@link RendererConfiguration}s in {@code + * rendererConfigurations} with configurations that enable tunneling on the appropriate renderers + * if so. * * @param mappedTrackInfo Mapped track information. * @param renderererFormatSupports The {@link Capabilities} for each mapped track, indexed by @@ -2428,18 +2416,12 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @param rendererConfigurations The renderer configurations. Configurations may be replaced with * ones that enable tunneling as a result of this call. * @param trackSelections The renderer track selections. - * @param tunnelingAudioSessionId The audio session id to use when tunneling, or {@link - * C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. */ private static void maybeConfigureRenderersForTunneling( MappedTrackInfo mappedTrackInfo, @Capabilities int[][][] renderererFormatSupports, @NullableType RendererConfiguration[] rendererConfigurations, - @NullableType TrackSelection[] trackSelections, - int tunnelingAudioSessionId) { - if (tunnelingAudioSessionId == C.AUDIO_SESSION_ID_UNSET) { - return; - } + @NullableType TrackSelection[] trackSelections) { // Check whether we can enable tunneling. To enable tunneling we require exactly one audio and // one video renderer to support tunneling and have a selection. int tunnelingAudioRendererIndex = -1; @@ -2473,7 +2455,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { enableTunneling &= tunnelingAudioRendererIndex != -1 && tunnelingVideoRendererIndex != -1; if (enableTunneling) { RendererConfiguration tunnelingRendererConfiguration = - new RendererConfiguration(tunnelingAudioSessionId); + new RendererConfiguration(/* tunneling= */ true); rendererConfigurations[tunnelingAudioRendererIndex] = tunnelingRendererConfiguration; rendererConfigurations[tunnelingVideoRendererIndex] = tunnelingRendererConfiguration; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 9e3ccbf570..5908c6ff8f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -285,6 +285,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { currentHeight = Format.NO_VALUE; currentPixelWidthHeightRatio = Format.NO_VALUE; scalingMode = C.VIDEO_SCALING_MODE_DEFAULT; + tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; clearReportedVideoSize(); } @@ -400,10 +401,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) throws ExoPlaybackException { super.onEnabled(joining, mayRenderStartOfStream); - int oldTunnelingAudioSessionId = tunnelingAudioSessionId; - tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; - tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET; - if (tunnelingAudioSessionId != oldTunnelingAudioSessionId) { + boolean tunneling = getConfiguration().tunneling; + Assertions.checkState(!tunneling || tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET); + if (this.tunneling != tunneling) { + this.tunneling = tunneling; releaseCodec(); } eventDispatcher.enabled(decoderCounters); @@ -516,7 +517,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { frameMetadataListener = (VideoFrameMetadataListener) message; break; case MSG_SET_AUDIO_SESSION_ID: - // TODO: Set tunnelingAudioSessionId. + int tunnelingAudioSessionId = (int) message; + if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { + this.tunnelingAudioSessionId = tunnelingAudioSessionId; + if (tunneling) { + releaseCodec(); + } + } break; default: super.handleMessage(messageType, message); @@ -600,7 +607,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { codecMaxValues, codecOperatingRate, deviceNeedsNoPostProcessWorkaround, - tunnelingAudioSessionId); + tunneling ? tunnelingAudioSessionId : C.AUDIO_SESSION_ID_UNSET); if (surface == null) { if (!shouldUseDummySurface(codecInfo)) { throw new IllegalStateException(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index 53f8e98bd9..56b948ab3d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -20,7 +20,6 @@ import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AU import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_ENABLED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_INPUT_FORMAT_CHANGED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_POSITION_ADVANCING; -import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_AUDIO_SESSION_ID; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DOWNSTREAM_FORMAT_CHANGED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_KEYS_LOADED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_SESSION_ACQUIRED; @@ -243,7 +242,6 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_AUDIO_ENABLED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INITIALIZED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period0); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0); assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)).containsExactly(period0); @@ -323,7 +321,6 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)) .containsExactly(period0, period1) .inOrder(); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0); assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)) @@ -399,7 +396,6 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_AUDIO_ENABLED)).containsExactly(period1); assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INITIALIZED)).containsExactly(period1); assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period1); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1); assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period1); assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)).containsExactly(period0); @@ -492,9 +488,6 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)) .containsExactly(period0, period1) .inOrder(); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)) - .containsExactly(period0, period1) - .inOrder(); assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)) .containsExactly(period0, period1) .inOrder(); @@ -595,9 +588,6 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)) .containsExactly(period1Seq1, period1Seq2) .inOrder(); - assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)) - .containsExactly(period1Seq1, period1Seq2) - .inOrder(); assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)) .containsExactly(period1Seq1, period1Seq2) .inOrder(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java index 02cc40f528..e39769f2c3 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java @@ -294,21 +294,6 @@ public class MediaCodecAudioRendererTest { exceptionThrowingRenderer.render(/* positionUs= */ 750, SystemClock.elapsedRealtime() * 1000); } - @Test - public void - render_callsAudioRendererEventListener_whenAudioSinkListenerOnAudioSessionIdIsCalled() { - final ArgumentCaptor listenerCaptor = - ArgumentCaptor.forClass(AudioSink.Listener.class); - verify(audioSink, atLeastOnce()).setListener(listenerCaptor.capture()); - AudioSink.Listener audioSinkListener = listenerCaptor.getValue(); - - int audioSessionId = 2; - audioSinkListener.onAudioSessionId(audioSessionId); - - shadowOf(Looper.getMainLooper()).idle(); - verify(audioRendererEventListener).onAudioSessionId(audioSessionId); - } - @Test public void render_callsAudioRendererEventListener_whenAudioSinkListenerOnAudioSinkErrorIsCalled() { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index d30fa384e9..0762eee9fb 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -1683,7 +1683,7 @@ public final class DefaultTrackSelectorTest { /* forceLowestBitrate= */ false, /* forceHighestSupportedBitrate= */ true, /* exceedRendererCapabilitiesIfNecessary= */ false, - /* tunnelingAudioSessionId= */ 13, + /* tunnelingEnabled= */ true, /* allowMultipleAdaptiveSelections= */ true, // Overrides selectionOverrides, diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java index f26d414db4..eb02f675c2 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java @@ -29,7 +29,6 @@ public class FakeAudioRenderer extends FakeRenderer { private final AudioRendererEventListener.EventDispatcher eventDispatcher; private final DecoderCounters decoderCounters; - private boolean notifiedAudioSessionId; private boolean notifiedPositionAdvancing; public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) { @@ -43,7 +42,6 @@ public class FakeAudioRenderer extends FakeRenderer { throws ExoPlaybackException { super.onEnabled(joining, mayRenderStartOfStream); eventDispatcher.enabled(decoderCounters); - notifiedAudioSessionId = false; notifiedPositionAdvancing = false; } @@ -65,10 +63,6 @@ public class FakeAudioRenderer extends FakeRenderer { @Override protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) { boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs); - if (shouldProcess && !notifiedAudioSessionId) { - eventDispatcher.audioSessionId(/* audioSessionId= */ 1); - notifiedAudioSessionId = true; - } if (shouldProcess && !notifiedPositionAdvancing) { eventDispatcher.positionAdvancing(System.currentTimeMillis()); notifiedPositionAdvancing = true;