From f1e3d3f244ff90db6ea53a168c99fb710860e722 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 17 Jan 2017 17:21:10 +0000 Subject: [PATCH] Enable tunneling end-to-end - Tunneling can be enabled by calling: trackSelector.setTunnelingAudioSessionId( C.generateAudioSessionIdV21(this)); - If enabled, tunneling is automatically used when the renderers and track selection combination support it. Tunneling is automatically turned on and off through playlists if the support changes. Issue: #1688 --- .../android/exoplayer2/BaseRenderer.java | 16 ++- .../exoplayer2/ExoPlayerImplInternal.java | 38 +++--- .../com/google/android/exoplayer2/Format.java | 37 +++--- .../google/android/exoplayer2/Renderer.java | 5 +- .../exoplayer2/RendererConfiguration.java | 60 +++++++++ .../audio/MediaCodecAudioRenderer.java | 3 +- .../audio/SimpleDecoderAudioRenderer.java | 3 +- .../trackselection/MappingTrackSelector.java | 123 +++++++++++++++++- .../trackselection/TrackSelectorResult.java | 44 ++++++- .../video/MediaCodecVideoRenderer.java | 12 +- 10 files changed, 286 insertions(+), 55 deletions(-) create mode 100644 library/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java diff --git a/library/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index 99ed1b8a74..9973a50cff 100644 --- a/library/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -28,6 +28,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { private final int trackType; + private RendererConfiguration configuration; private int index; private int state; private SampleStream stream; @@ -70,9 +71,11 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { } @Override - public final void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining, - long offsetUs) throws ExoPlaybackException { + public final void enable(RendererConfiguration configuration, Format[] formats, + SampleStream stream, long positionUs, boolean joining, long offsetUs) + throws ExoPlaybackException { Assertions.checkState(state == STATE_DISABLED); + this.configuration = configuration; state = STATE_ENABLED; onEnabled(joining); replaceStream(formats, stream, offsetUs); @@ -237,10 +240,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { // Methods to be called by subclasses. + /** + * Returns the configuration set when the renderer was most recently enabled. + */ + protected final RendererConfiguration getConfiguration() { + return configuration; + } + /** * Returns the index of the renderer within the player. - * - * @return The index of the renderer within the player. */ protected final int getIndex() { return index; diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index f0f37f963a..266a1e0da2 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -35,7 +35,6 @@ import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.PriorityHandlerThread; import com.google.android.exoplayer2.util.StandaloneMediaClock; import com.google.android.exoplayer2.util.TraceUtil; -import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** @@ -1149,16 +1148,15 @@ import java.io.IOException; } if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) { - TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelectorResult.selections; + TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.trackSelectorResult; readingPeriodHolder = readingPeriodHolder.next; - TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelectorResult.selections; + TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.trackSelectorResult; boolean initialDiscontinuity = readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET; for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; - TrackSelection oldSelection = oldTrackSelections.get(i); - TrackSelection newSelection = newTrackSelections.get(i); + TrackSelection oldSelection = oldTrackSelectorResult.selections.get(i); if (oldSelection == null) { // The renderer has no current stream and will be enabled when we play the next period. } else if (initialDiscontinuity) { @@ -1166,9 +1164,12 @@ import java.io.IOException; // be disabled and re-enabled when it starts playing the next period. renderer.setCurrentStreamFinal(); } else if (!renderer.isCurrentStreamFinal()) { - if (newSelection != null) { - // Replace the renderer's SampleStream so the transition to playing the next period - // can be seamless. + TrackSelection newSelection = newTrackSelectorResult.selections.get(i); + RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[i]; + RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[i]; + if (newSelection != null && newConfig.equals(oldConfig)) { + // Replace the renderer's SampleStream so the transition to playing the next period can + // be seamless. Format[] formats = new Format[newSelection.length()]; for (int j = 0; j < formats.length; j++) { formats[j] = newSelection.getFormat(j); @@ -1176,8 +1177,9 @@ import java.io.IOException; renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i], readingPeriodHolder.getRendererOffset()); } else { - // The renderer will be disabled when transitioning to playing the next period. Mark the - // SampleStream as final to play out any remaining data. + // The renderer will be disabled when transitioning to playing the next period, either + // because there's no new selection or because a configuration change is required. Mark + // the SampleStream as final to play out any remaining data. renderer.setCurrentStreamFinal(); } } @@ -1354,6 +1356,8 @@ import java.io.IOException; if (newSelection != null) { enabledRenderers[enabledRendererCount++] = renderer; if (renderer.getState() == Renderer.STATE_DISABLED) { + RendererConfiguration rendererConfiguration = + playingPeriodHolder.trackSelectorResult.rendererConfigurations[i]; // The renderer needs enabling with its new track selection. boolean playing = playWhenReady && state == ExoPlayer.STATE_READY; // Consider as joining only if the renderer was previously disabled. @@ -1364,8 +1368,8 @@ import java.io.IOException; formats[j] = newSelection.getFormat(j); } // Enable the renderer. - renderer.enable(formats, playingPeriodHolder.sampleStreams[i], rendererPositionUs, - joining, playingPeriodHolder.getRendererOffset()); + renderer.enable(rendererConfiguration, formats, playingPeriodHolder.sampleStreams[i], + rendererPositionUs, joining, playingPeriodHolder.getRendererOffset()); MediaClock mediaClock = renderer.getMediaClock(); if (mediaClock != null) { if (rendererMediaClock != null) { @@ -1410,7 +1414,7 @@ import java.io.IOException; private final LoadControl loadControl; private final MediaSource mediaSource; - private TrackSelectionArray periodTrackSelections; + private TrackSelectorResult periodTrackSelectorResult; public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl, @@ -1463,8 +1467,7 @@ import java.io.IOException; public boolean selectTracks() throws ExoPlaybackException { TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities, mediaPeriod.getTrackGroups()); - TrackSelectionArray newTrackSelections = selectorResult.selections; - if (newTrackSelections.equals(periodTrackSelections)) { + if (selectorResult.isEquivalent(periodTrackSelectorResult)) { return false; } trackSelectorResult = selectorResult; @@ -1481,14 +1484,13 @@ import java.io.IOException; TrackSelectionArray trackSelections = trackSelectorResult.selections; for (int i = 0; i < trackSelections.length; i++) { mayRetainStreamFlags[i] = !forceRecreateStreams - && Util.areEqual(periodTrackSelections == null ? null : periodTrackSelections.get(i), - trackSelections.get(i)); + && trackSelectorResult.isEquivalent(periodTrackSelectorResult, i); } // Disable streams on the period and get new streams for updated/newly-enabled tracks. positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags, sampleStreams, streamResetFlags, positionUs); - periodTrackSelections = trackSelections; + periodTrackSelectorResult = trackSelectorResult; // Update whether we have enabled tracks and sanity check the expected streams are non-null. hasEnabledTracks = false; diff --git a/library/src/main/java/com/google/android/exoplayer2/Format.java b/library/src/main/java/com/google/android/exoplayer2/Format.java index f3b058621a..0b558153fd 100644 --- a/library/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/src/main/java/com/google/android/exoplayer2/Format.java @@ -183,10 +183,8 @@ public final class Format implements Parcelable { */ public final int accessibilityChannel; - // Lazily initialized hashcode and framework media format. - + // Lazily initialized hashcode. private int hashCode; - private MediaFormat frameworkMediaFormat; // Video. @@ -495,25 +493,22 @@ public final class Format implements Parcelable { @SuppressLint("InlinedApi") @TargetApi(16) public final MediaFormat getFrameworkMediaFormatV16() { - if (frameworkMediaFormat == null) { - MediaFormat format = new MediaFormat(); - format.setString(MediaFormat.KEY_MIME, sampleMimeType); - maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language); - maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); - maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width); - maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height); - maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate); - maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees); - maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount); - maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate); - maybeSetIntegerV16(format, "encoder-delay", encoderDelay); - maybeSetIntegerV16(format, "encoder-padding", encoderPadding); - for (int i = 0; i < initializationData.size(); i++) { - format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); - } - frameworkMediaFormat = format; + MediaFormat format = new MediaFormat(); + format.setString(MediaFormat.KEY_MIME, sampleMimeType); + maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language); + maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); + maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width); + maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height); + maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate); + maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees); + maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount); + maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate); + maybeSetIntegerV16(format, "encoder-delay", encoderDelay); + maybeSetIntegerV16(format, "encoder-padding", encoderPadding); + for (int i = 0; i < initializationData.size(); i++) { + format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); } - return frameworkMediaFormat; + return format; } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/src/main/java/com/google/android/exoplayer2/Renderer.java index b610a64bea..e16caec980 100644 --- a/library/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -92,6 +92,7 @@ public interface Renderer extends ExoPlayerComponent { * This method may be called when the renderer is in the following states: * {@link #STATE_DISABLED}. * + * @param configuration The renderer configuration. * @param formats The enabled formats. * @param stream The {@link SampleStream} from which the renderer should consume. * @param positionUs The player's current position. @@ -100,8 +101,8 @@ public interface Renderer extends ExoPlayerComponent { * before they are rendered. * @throws ExoPlaybackException If an error occurs. */ - void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining, - long offsetUs) throws ExoPlaybackException; + void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream, + long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException; /** * Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be diff --git a/library/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java b/library/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java new file mode 100644 index 0000000000..93bbd1e4b6 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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; + +/** + * The configuration of a {@link Renderer}. + */ +public final class RendererConfiguration { + + /** + * The default configuration. + */ + public static final RendererConfiguration DEFAULT = + new RendererConfiguration(C.AUDIO_SESSION_ID_UNSET); + + /** + * 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; + + /** + * @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; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + RendererConfiguration other = (RendererConfiguration) obj; + return tunnelingAudioSessionId == other.tunnelingAudioSessionId; + } + + @Override + public int hashCode() { + return tunnelingAudioSessionId; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index eef89bce3a..b4813d90a2 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -259,8 +259,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media protected void onEnabled(boolean joining) throws ExoPlaybackException { super.onEnabled(joining); eventDispatcher.enabled(decoderCounters); - // TODO: Allow this to be set. - int tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; + int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { audioTrack.enableTunnelingV21(tunnelingAudioSessionId); } else { diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index 1b6a5a183f..d23ee769dd 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -407,8 +407,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements protected void onEnabled(boolean joining) throws ExoPlaybackException { decoderCounters = new DecoderCounters(); eventDispatcher.enabled(decoderCounters); - // TODO: Allow this to be set. - int tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; + int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { audioTrack.enableTunnelingV21(tunnelingAudioSessionId); } else { diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 3eb44d1d05..690723cf15 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -15,11 +15,13 @@ */ package com.google.android.exoplayer2.trackselection; +import android.content.Context; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.RendererCapabilities; +import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.util.Util; @@ -81,12 +83,14 @@ public abstract class MappingTrackSelector extends TrackSelector { private final SparseArray> selectionOverrides; private final SparseBooleanArray rendererDisabledFlags; + private int tunnelingAudioSessionId; private MappedTrackInfo currentMappedTrackInfo; public MappingTrackSelector() { selectionOverrides = new SparseArray<>(); rendererDisabledFlags = new SparseBooleanArray(); + tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; } /** @@ -223,6 +227,23 @@ public abstract class MappingTrackSelector extends TrackSelector { invalidate(); } + /** + * 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 + * 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. + */ + public void setTunnelingAudioSessionId(int tunnelingAudioSessionId) { + if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { + this.tunnelingAudioSessionId = tunnelingAudioSessionId; + invalidate(); + } + } + // TrackSelector implementation. @Override @@ -295,8 +316,20 @@ public abstract class MappingTrackSelector extends TrackSelector { MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes, rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports, unassociatedTrackGroupArray); + + // Initialize the renderer configurations to the default configuration for all renderers with + // selections, and null otherwise. + RendererConfiguration[] rendererConfigurations = + new RendererConfiguration[rendererCapabilities.length]; + for (int i = 0; i < rendererCapabilities.length; i++) { + rendererConfigurations[i] = trackSelections[i] != null ? RendererConfiguration.DEFAULT : null; + } + // Configure audio and video renderers to use tunneling if appropriate. + maybeConfigureRenderersForTunneling(rendererCapabilities, rendererTrackGroupArrays, + rendererFormatSupports, rendererConfigurations, trackSelections, tunnelingAudioSessionId); + return new TrackSelectorResult(trackGroups, new TrackSelectionArray(trackSelections), - mappedTrackInfo); + mappedTrackInfo, rendererConfigurations); } @Override @@ -399,6 +432,94 @@ public abstract class MappingTrackSelector extends TrackSelector { return mixedMimeTypeAdaptationSupport; } + /** + * Determines whether tunneling should be enabled, replacing {@link RendererConfiguration}s in + * {@code rendererConfigurations} with configurations that enable tunneling on the appropriate + * renderers if so. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which + * {@link TrackSelection}s are to be generated. + * @param rendererTrackGroupArrays An array of {@link TrackGroupArray}s where each entry + * corresponds to the renderer of equal index in {@code renderers}. + * @param rendererFormatSupports Maps every available track to a specific level of support as + * defined by the renderer {@code FORMAT_*} constants. + * @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( + RendererCapabilities[] rendererCapabilities, TrackGroupArray[] rendererTrackGroupArrays, + int[][][] rendererFormatSupports, RendererConfiguration[] rendererConfigurations, + TrackSelection[] trackSelections, int tunnelingAudioSessionId) { + if (tunnelingAudioSessionId == C.AUDIO_SESSION_ID_UNSET) { + return; + } + // 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; + int tunnelingVideoRendererIndex = -1; + boolean enableTunneling = true; + for (int i = 0; i < rendererCapabilities.length; i++) { + int rendererType = rendererCapabilities[i].getTrackType(); + TrackSelection trackSelection = trackSelections[i]; + if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO) + && trackSelection != null) { + if (rendererSupportsTunneling(rendererFormatSupports[i], rendererTrackGroupArrays[i], + trackSelection)) { + if (rendererType == C.TRACK_TYPE_AUDIO) { + if (tunnelingAudioRendererIndex != -1) { + enableTunneling = false; + break; + } else { + tunnelingAudioRendererIndex = i; + } + } else { + if (tunnelingVideoRendererIndex != -1) { + enableTunneling = false; + break; + } else { + tunnelingVideoRendererIndex = i; + } + } + } + } + } + enableTunneling &= tunnelingAudioRendererIndex != -1 && tunnelingVideoRendererIndex != -1; + if (enableTunneling) { + RendererConfiguration tunnelingRendererConfiguration = + new RendererConfiguration(tunnelingAudioSessionId); + rendererConfigurations[tunnelingAudioRendererIndex] = tunnelingRendererConfiguration; + rendererConfigurations[tunnelingVideoRendererIndex] = tunnelingRendererConfiguration; + } + } + + /** + * Returns whether a renderer supports tunneling for a {@link TrackSelection}. + * + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each + * track, indexed by group index and track index (in that order). + * @param trackGroups The {@link TrackGroupArray}s for the renderer. + * @param selection The track selection. + * @return Whether the renderer supports tunneling for the {@link TrackSelection}. + */ + private static boolean rendererSupportsTunneling(int[][] formatSupport, + TrackGroupArray trackGroups, TrackSelection selection) { + if (selection == null) { + return false; + } + int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup()); + for (int i = 0; i < selection.length(); i++) { + int trackFormatSupport = formatSupport[trackGroupIndex][selection.getIndexInTrackGroup(i)]; + if ((trackFormatSupport & RendererCapabilities.TUNNELING_SUPPORT_MASK) + != RendererCapabilities.TUNNELING_SUPPORTED) { + return false; + } + } + return true; + } + /** * Provides track information for each renderer. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java index 84e4aad86a..390b77391c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java @@ -15,7 +15,9 @@ */ package com.google.android.exoplayer2.trackselection; +import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.util.Util; /** * The result of a {@link TrackSelector} operation. @@ -35,17 +37,57 @@ public final class TrackSelectorResult { * should the selections be activated. */ public final Object info; + /** + * A {@link RendererConfiguration} for each renderer, to be used with the selections. + */ + public final RendererConfiguration[] rendererConfigurations; /** * @param groups The groups provided to the {@link TrackSelector}. * @param selections A {@link TrackSelectionArray} containing the selection for each renderer. * @param info An opaque object that will be returned to * {@link TrackSelector#onSelectionActivated(Object)} should the selections be activated. + * @param rendererConfigurations A {@link RendererConfiguration} for each renderer, to be used + * with the selections. */ - public TrackSelectorResult(TrackGroupArray groups, TrackSelectionArray selections, Object info) { + public TrackSelectorResult(TrackGroupArray groups, TrackSelectionArray selections, Object info, + RendererConfiguration[] rendererConfigurations) { this.groups = groups; this.selections = selections; this.info = info; + this.rendererConfigurations = rendererConfigurations; + } + + /** + * Returns whether this result is equivalent to {@code other} for all renderers. + * + * @param other The other {@link TrackSelectorResult}. May be null. + * @return Whether this result is equivalent to {@code other} for all renderers. + */ + public boolean isEquivalent(TrackSelectorResult other) { + for (int i = 0; i < selections.length; i++) { + if (!isEquivalent(other, i)) { + return false; + } + } + return true; + } + + /** + * Returns whether this result is equivalent to {@code other} for the renderer at the given index. + * The results are equivalent if they have equal track selections and configurations for the + * renderer. + * + * @param other The other {@link TrackSelectorResult}. May be null. + * @param index The renderer index to check for equivalence. + * @return Whether this result is equivalent to {@code other} for all renderers. + */ + public boolean isEquivalent(TrackSelectorResult other, int index) { + if (other == null) { + return selections.get(index) == null && rendererConfigurations[index] == null; + } + return Util.areEqual(selections.get(index), other.selections.get(index)) + && Util.areEqual(rendererConfigurations[index], other.rendererConfigurations[index]); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index a6fb772110..62224a64d6 100644 --- a/library/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -210,8 +210,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override protected void onEnabled(boolean joining) throws ExoPlaybackException { super.onEnabled(joining); - // TODO: Allow this to be set. - tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; + tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET; eventDispatcher.enabled(decoderCounters); frameReleaseTimeHelper.enable(); @@ -583,12 +582,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } // Configure tunneling if enabled. if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { - frameworkMediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true); - frameworkMediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId); + configureTunnelingV21(frameworkMediaFormat, tunnelingAudioSessionId); } return frameworkMediaFormat; } + @TargetApi(21) + private static void configureTunnelingV21(MediaFormat mediaFormat, int tunnelingAudioSessionId) { + mediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true); + mediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId); + } + /** * Returns {@link CodecMaxValues} suitable for configuring a codec for {@code format} in a way * that will allow possible adaptation to other compatible formats in {@code streamFormats}.