Fix audio session ID generation

- SimpleExoPlayer now always generates a session ID at
  construction time. This ID is used indefinitely, including
  for tunneling, unless a call to setAudioSessionId is made
  to change it.
- DefaultTrackSelector support for enabling tunneling has
  been changed to a boolean, since tunneling now uses the
  same session ID as non-tunneled mode.
- Since the session ID is now always set at the top level,
  internal propagation of generated session IDs is no longer
  necessary, and so is removed.

PiperOrigin-RevId: 351349687
This commit is contained in:
olly 2021-01-12 13:16:44 +00:00 committed by Oliver Woodman
parent 85641a38b1
commit a95b2ebb1d
16 changed files with 157 additions and 249 deletions

View file

@ -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:

View file

@ -89,7 +89,7 @@ public interface Player {
* default audio attributes will be used. They are suitable for general media playback.
*
* <p>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.
*
* <p>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.

View file

@ -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;
}
}

View file

@ -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<com.google.android.exoplayer2.video.VideoListener>
@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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) {

View file

@ -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) {

View file

@ -49,9 +49,9 @@ import java.nio.ByteBuffer;
*
* <p>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 &lt; 21.
*/
void enableTunnelingV21(int tunnelingAudioSessionId);
void enableTunnelingV21();
/**
* Disables tunneling. If tunneling was previously enabled then the sink is reset and any audio

View file

@ -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();

View file

@ -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} &lt; 21.
* <p>
* 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<WriteException> 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:

View file

@ -124,8 +124,8 @@ public class ForwardingAudioSink implements AudioSink {
}
@Override
public void enableTunnelingV21(int tunnelingAudioSessionId) {
sink.enableTunnelingV21(tunnelingAudioSessionId);
public void enableTunnelingV21() {
sink.enableTunnelingV21();
}
@Override

View file

@ -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();

View file

@ -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<Map<TrackGroupArray, @NullableType SelectionOverride>>
@ -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.
*
* <p>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<Map<TrackGroupArray, @NullableType SelectionOverride>> 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;
}

View file

@ -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();

View file

@ -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();

View file

@ -294,21 +294,6 @@ public class MediaCodecAudioRendererTest {
exceptionThrowingRenderer.render(/* positionUs= */ 750, SystemClock.elapsedRealtime() * 1000);
}
@Test
public void
render_callsAudioRendererEventListener_whenAudioSinkListenerOnAudioSessionIdIsCalled() {
final ArgumentCaptor<AudioSink.Listener> 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() {

View file

@ -1683,7 +1683,7 @@ public final class DefaultTrackSelectorTest {
/* forceLowestBitrate= */ false,
/* forceHighestSupportedBitrate= */ true,
/* exceedRendererCapabilitiesIfNecessary= */ false,
/* tunnelingAudioSessionId= */ 13,
/* tunnelingEnabled= */ true,
/* allowMultipleAdaptiveSelections= */ true,
// Overrides
selectionOverrides,

View file

@ -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;