From 20a40f5d113cfdfe84aadc111395a6dfcb28f216 Mon Sep 17 00:00:00 2001 From: "J. Oliva" Date: Mon, 23 Feb 2015 15:14:09 +0100 Subject: [PATCH 1/4] Better management of blacklisted playlists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added an expiration time field to playlists blacklisted to allow Exoplayer to continue playback when playlists that failed were recovered from a bad state. In live environments, some times occur that primary encoder stop working for a while. In that cases, HLS failover mechanism in the player should detect the situation and “switch” to playlists served by the backup encoder (in case a backup encoder exists). This was well managed before these changes. However, and to ensure a playback experience that can recover itself from temporary issues, we cannot blacklist a playlist forever. When streaming live events using HLS, it is quite typical that the player needs to switch from primary to backup playlists, and from backup to primary ones, from time to time to have playback working when temporary issues in the network/encoder are happening. Most of the issues are recoverable, so what I have implemented is a mechanism that makes blacklisted playlist to be available again after a while (60 seconds). Evaluation of this algorithm should happen just when something fails. If player is working with a backup playlist, it shouldn’t switch to the primary one at least something fail. --- .../android/exoplayer/hls/HlsChunkSource.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index dc70320f0d..7ffbda4497 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -106,6 +106,12 @@ public class HlsChunkSource { */ public static final long DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS = 20000; + /** + * The default maximum time a media playlist is blacklisted without + * rechecking if it is alive again (because an encoder reset, for example) + */ + public static final long DEFAULT_MAX_TIME_MEDIA_PLAYLIST_BLACKLISTED_MS = 60000; + private static final String TAG = "HlsChunkSource"; private static final String AAC_FILE_EXTENSION = ".aac"; private static final float BANDWIDTH_FRACTION = 0.8f; @@ -127,6 +133,7 @@ public class HlsChunkSource { /* package */ byte[] scratchSpace; /* package */ final HlsMediaPlaylist[] mediaPlaylists; /* package */ final boolean[] mediaPlaylistBlacklistFlags; + /* package */ final long[] mediaPlaylistBlacklistedTimeMs; /* package */ final long[] lastMediaPlaylistLoadTimesMs; /* package */ boolean live; /* package */ long durationUs; @@ -182,6 +189,7 @@ public class HlsChunkSource { enabledVariants = new Variant[] {new Variant(0, playlistUrl, 0, null, -1, -1)}; mediaPlaylists = new HlsMediaPlaylist[1]; mediaPlaylistBlacklistFlags = new boolean[1]; + mediaPlaylistBlacklistedTimeMs = new long[1]; lastMediaPlaylistLoadTimesMs = new long[1]; setMediaPlaylist(0, (HlsMediaPlaylist) playlist); } else { @@ -189,6 +197,7 @@ public class HlsChunkSource { enabledVariants = filterVariants((HlsMasterPlaylist) playlist, variantIndices); mediaPlaylists = new HlsMediaPlaylist[enabledVariants.length]; mediaPlaylistBlacklistFlags = new boolean[enabledVariants.length]; + mediaPlaylistBlacklistedTimeMs = new long[enabledVariants.length]; lastMediaPlaylistLoadTimesMs = new long[enabledVariants.length]; } @@ -362,6 +371,8 @@ public class HlsChunkSource { if (responseCode == 404 || responseCode == 410) { MediaPlaylistChunk playlistChunk = (MediaPlaylistChunk) chunk; mediaPlaylistBlacklistFlags[playlistChunk.variantIndex] = true; + mediaPlaylistBlacklistedTimeMs[playlistChunk.variantIndex] = SystemClock.elapsedRealtime(); + evaluatePlaylistBlacklistedTimestamps(); if (!allPlaylistsBlacklisted()) { // We've handled the 404/410 by blacklisting the playlist. Log.w(TAG, "Blacklisted playlist (" + responseCode + "): " @@ -372,6 +383,7 @@ public class HlsChunkSource { Log.w(TAG, "Final playlist not blacklisted (" + responseCode + "): " + playlistChunk.dataSpec.uri); mediaPlaylistBlacklistFlags[playlistChunk.variantIndex] = false; + mediaPlaylistBlacklistedTimeMs[playlistChunk.variantIndex] = 0; return false; } } @@ -541,6 +553,17 @@ public class HlsChunkSource { return true; } + private void evaluatePlaylistBlacklistedTimestamps() + { + long currentTime = SystemClock.elapsedRealtime(); + for (int i = 0; i < mediaPlaylistBlacklistFlags.length; i++) { + if (mediaPlaylistBlacklistFlags[i] && currentTime - mediaPlaylistBlacklistedTimeMs[i] > DEFAULT_MAX_TIME_MEDIA_PLAYLIST_BLACKLISTED_MS) { + mediaPlaylistBlacklistFlags[i] = false; + mediaPlaylistBlacklistedTimeMs[i] = 0; + } + } + } + private class MediaPlaylistChunk extends DataChunk { @SuppressWarnings("hiding") From f8a9da90e81dde23fb8ee97c7b435d2da0f9eee0 Mon Sep 17 00:00:00 2001 From: "J. Oliva" Date: Mon, 23 Feb 2015 20:06:49 +0100 Subject: [PATCH 2/4] Renaming methods and code formating - Method evaluatePlayListBlackListedTimestamps renamed to clearStaleBlacklistedPlaylists - Code formatted to be consistent with style elsewhere. --- .../com/google/android/exoplayer/hls/HlsChunkSource.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 7ffbda4497..3a412d6956 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -372,7 +372,7 @@ public class HlsChunkSource { MediaPlaylistChunk playlistChunk = (MediaPlaylistChunk) chunk; mediaPlaylistBlacklistFlags[playlistChunk.variantIndex] = true; mediaPlaylistBlacklistedTimeMs[playlistChunk.variantIndex] = SystemClock.elapsedRealtime(); - evaluatePlaylistBlacklistedTimestamps(); + clearStaleBlacklistedPlaylists(); if (!allPlaylistsBlacklisted()) { // We've handled the 404/410 by blacklisting the playlist. Log.w(TAG, "Blacklisted playlist (" + responseCode + "): " @@ -553,11 +553,11 @@ public class HlsChunkSource { return true; } - private void evaluatePlaylistBlacklistedTimestamps() - { + private void clearStaleBlacklistedPlaylists() { long currentTime = SystemClock.elapsedRealtime(); for (int i = 0; i < mediaPlaylistBlacklistFlags.length; i++) { - if (mediaPlaylistBlacklistFlags[i] && currentTime - mediaPlaylistBlacklistedTimeMs[i] > DEFAULT_MAX_TIME_MEDIA_PLAYLIST_BLACKLISTED_MS) { + if (mediaPlaylistBlacklistFlags[i] && + currentTime - mediaPlaylistBlacklistedTimeMs[i] > DEFAULT_MAX_TIME_MEDIA_PLAYLIST_BLACKLISTED_MS) { mediaPlaylistBlacklistFlags[i] = false; mediaPlaylistBlacklistedTimeMs[i] = 0; } From 4fe62b9b0a31f95c3b5ca43e277544caa0b440e9 Mon Sep 17 00:00:00 2001 From: "J. Oliva" Date: Mon, 23 Feb 2015 20:40:37 +0100 Subject: [PATCH 3/4] Clear stale blacklist in getChunkOperation Clear stale blacklist in getChunkOperation before getting next variant. This ensures: 1.- Player resilience to failures, always trying to look for a working playlist that allows player to non stop playback. 2.- High quality blacklisted playlists can be reused in case they go up after a failure. Player always trying to provide the best user experience. --- .../java/com/google/android/exoplayer/hls/HlsChunkSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 3a412d6956..a38d87c8c9 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -258,6 +258,7 @@ public class HlsChunkSource { if (adaptiveMode == ADAPTIVE_MODE_NONE) { // Do nothing. } else { + clearStaleBlacklistedPlaylists(); nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs); switchingVariant = nextVariantIndex != variantIndex; switchingVariantSpliced = switchingVariant && adaptiveMode == ADAPTIVE_MODE_SPLICE; @@ -372,7 +373,6 @@ public class HlsChunkSource { MediaPlaylistChunk playlistChunk = (MediaPlaylistChunk) chunk; mediaPlaylistBlacklistFlags[playlistChunk.variantIndex] = true; mediaPlaylistBlacklistedTimeMs[playlistChunk.variantIndex] = SystemClock.elapsedRealtime(); - clearStaleBlacklistedPlaylists(); if (!allPlaylistsBlacklisted()) { // We've handled the 404/410 by blacklisting the playlist. Log.w(TAG, "Blacklisted playlist (" + responseCode + "): " From 2ac7046ffcba342f5f176563b5a1287eb217995f Mon Sep 17 00:00:00 2001 From: "J. Oliva" Date: Mon, 23 Feb 2015 22:42:53 +0100 Subject: [PATCH 4/4] Clear stale blacklisted playlist when getting next variant index Calling clearStaleBlacklistedPlaylist within getNextVariantIndex method. --- .../java/com/google/android/exoplayer/hls/HlsChunkSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index a38d87c8c9..c05f73a890 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -258,7 +258,6 @@ public class HlsChunkSource { if (adaptiveMode == ADAPTIVE_MODE_NONE) { // Do nothing. } else { - clearStaleBlacklistedPlaylists(); nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs); switchingVariant = nextVariantIndex != variantIndex; switchingVariantSpliced = switchingVariant && adaptiveMode == ADAPTIVE_MODE_SPLICE; @@ -392,6 +391,7 @@ public class HlsChunkSource { } private int getNextVariantIndex(TsChunk previousTsChunk, long playbackPositionUs) { + clearStaleBlacklistedPlaylists(); int idealVariantIndex = getVariantIndexForBandwdith( (int) (bandwidthMeter.getBitrateEstimate() * BANDWIDTH_FRACTION)); if (idealVariantIndex == variantIndex) {