Commit 846f8e1d by olly Committed by Oliver Woodman

Enable track blacklisting for DASH and SmoothStreaming

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=128721068
parent f66b90e3
/*
* Copyright (C) 2016 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.source.chunk;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
import android.util.Log;
/**
* Helper class for blacklisting tracks in a {@link TrackSelection} when 404 (Not Found) and 410
* (Gone) HTTP response codes are encountered.
*/
public final class ChunkedTrackBlacklistUtil {
/**
* The default duration for which a track is blacklisted in milliseconds.
*/
public static final long DEFAULT_TRACK_BLACKLIST_MS = 60000;
private static final String TAG = "ChunkedTrackBlacklist";
/**
* Blacklists {@code trackSelectionIndex} in {@code trackSelection} for
* {@link #DEFAULT_TRACK_BLACKLIST_MS} if {@code e} is an {@link InvalidResponseCodeException}
* with {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. Else does nothing.
* Note that blacklisting will fail if the track is the only non-blacklisted track in the
* selection.
*
* @param trackSelection The track selection.
* @param trackSelectionIndex The index in the selection to consider blacklisting.
* @param e The error to inspect.
* @return Whether the track was blacklisted in the selection.
*/
public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex,
Exception e) {
return maybeBlacklistTrack(trackSelection, trackSelectionIndex, e, DEFAULT_TRACK_BLACKLIST_MS);
}
/**
* Blacklists {@code trackSelectionIndex} in {@code trackSelection} for
* {@code blacklistDurationMs} if {@code e} is an {@link InvalidResponseCodeException} with
* {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. Else does nothing. Note
* that blacklisting will fail if the track is the only non-blacklisted track in the selection.
*
* @param trackSelection The track selection.
* @param trackSelectionIndex The index in the selection to consider blacklisting.
* @param e The error to inspect.
* @param blacklistDurationMs The duration to blacklist the track for, if it is blacklisted.
* @return Whether the track was blacklisted.
*/
public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex,
Exception e, long blacklistDurationMs) {
if (trackSelection.length() == 1) {
// Blacklisting won't ever work if there's only one track in the selection.
return false;
}
if (e instanceof InvalidResponseCodeException) {
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
int responseCode = responseCodeException.responseCode;
if (responseCode == 404 || responseCode == 410) {
boolean blacklisted = trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs);
if (blacklisted) {
Log.w(TAG, "Blacklisted: duration=" + blacklistDurationMs + ", responseCode="
+ responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex));
} else {
Log.w(TAG, "Blacklisting failed (cannot blacklist last enabled track): responseCode="
+ responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex));
}
return blacklisted;
}
}
return false;
}
private ChunkedTrackBlacklistUtil() {}
}
......@@ -27,6 +27,7 @@ import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk;
import com.google.android.exoplayer2.source.chunk.InitializationChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
......@@ -249,8 +250,11 @@ public class DefaultDashChunkSource implements DashChunkSource {
@Override
public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) {
if (!cancelable) {
return false;
}
// Workaround for missing segment at the end of the period
if (cancelable && !manifest.dynamic && chunk instanceof MediaChunk
if (!manifest.dynamic && chunk instanceof MediaChunk
&& e instanceof InvalidResponseCodeException
&& ((InvalidResponseCodeException) e).responseCode == 404) {
RepresentationHolder representationHolder =
......@@ -261,8 +265,9 @@ public class DefaultDashChunkSource implements DashChunkSource {
return true;
}
}
// TODO: Consider implementing representation blacklisting.
return false;
// Blacklist if appropriate.
return ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection,
trackSelection.indexOf(chunk.trackFormat), e);
}
// Private methods.
......
......@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
import com.google.android.exoplayer2.source.chunk.DataChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
......@@ -34,7 +35,6 @@ import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util;
......@@ -42,7 +42,6 @@ import com.google.android.exoplayer2.util.Util;
import android.net.Uri;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
......@@ -185,11 +184,16 @@ public class HlsChunkSource {
* @param out A holder to populate.
*/
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, ChunkHolder out) {
int previousChunkVariantIndex = previous != null ? trackGroup.indexOf(previous.trackFormat)
: -1;
updateSelectedTrack(previous, playbackPositionUs);
int oldVariantIndex = previous == null ? -1 : trackGroup.indexOf(previous.trackFormat);
// Use start time of the previous chunk rather than its end time because switching format will
// require downloading overlapping segments.
long bufferedDurationUs = previous == null ? 0
: Math.max(0, previous.getAdjustedStartTimeUs() - playbackPositionUs);
trackSelection.updateSelectedTrack(bufferedDurationUs);
int newVariantIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingVariant = previousChunkVariantIndex != newVariantIndex;
boolean switchingVariant = oldVariantIndex != newVariantIndex;
HlsMediaPlaylist mediaPlaylist = variantPlaylists[newVariantIndex];
if (mediaPlaylist == null) {
// We don't have the media playlist for the next variant. Request it now.
......@@ -204,8 +208,8 @@ public class HlsChunkSource {
chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, playbackPositionUs,
true, true) + mediaPlaylist.mediaSequence;
} else {
chunkMediaSequence = getLiveNextChunkSequenceNumber(previous.chunkIndex,
previousChunkVariantIndex, newVariantIndex);
chunkMediaSequence = getLiveNextChunkSequenceNumber(previous.chunkIndex, oldVariantIndex,
newVariantIndex);
if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
fatalError = new BehindLiveWindowException();
return;
......@@ -403,44 +407,12 @@ public class HlsChunkSource {
* @return Whether the load should be canceled.
*/
public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException e) {
if (cancelable && e instanceof InvalidResponseCodeException) {
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
int responseCode = responseCodeException.responseCode;
if (responseCode == 404 || responseCode == 410) {
int trackSelectionIndex = trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat));
if (trackSelectionIndex != -1) {
boolean blacklisted = trackSelection.blacklist(trackSelectionIndex,
DEFAULT_PLAYLIST_BLACKLIST_MS);
if (blacklisted) {
// We've handled the 404/410 by blacklisting the variant.
Log.w(TAG, "Blacklisted variant (" + responseCode + "): " + chunk.dataSpec.uri);
return true;
} else {
// This was the last non-blacklisted playlist. Don't blacklist it.
Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): "
+ chunk.dataSpec.uri);
return false;
}
}
}
}
return false;
return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection,
trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), e);
}
// Private methods.
private void updateSelectedTrack(HlsMediaChunk previous, long playbackPositionUs) {
long bufferedDurationUs;
if (previous != null) {
// Use start time of the previous chunk rather than its end time because switching format
// will require downloading overlapping segments.
bufferedDurationUs = Math.max(0, previous.getAdjustedStartTimeUs() - playbackPositionUs);
} else {
bufferedDurationUs = 0;
}
trackSelection.updateSelectedTrack(bufferedDurationUs);
}
private boolean shouldRerequestLiveMediaPlaylist(int variantIndex) {
HlsMediaPlaylist mediaPlaylist = variantPlaylists[variantIndex];
long timeSinceLastMediaPlaylistLoadMs =
......
......@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
......@@ -205,8 +206,8 @@ public class DefaultSsChunkSource implements SsChunkSource {
@Override
public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) {
// TODO: Consider implementing stream element blacklisting.
return false;
return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection,
trackSelection.indexOf(chunk.trackFormat), e);
}
// Private methods.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment