mirror of
https://github.com/samsonjs/media.git
synced 2026-03-29 10:05:48 +00:00
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
This commit is contained in:
parent
c828d9b0bf
commit
f1e3d3f244
10 changed files with 286 additions and 55 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Map<TrackGroupArray, SelectionOverride>> 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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
|
|
|
|||
Loading…
Reference in a new issue