Commit 2f622b8b by Nate Roy

Allow passing of HlsPlaylistParser to HlsMediaSource.

Also create helper method in HlsMasterPlaylist to allow the copying of
the playlist to another, but with the variants reordered based on a
passed comparator. Also added an implementation of HlsPlaylistParser
which will reorder the variants returned.
parent a3be9376
...@@ -16,16 +16,20 @@ ...@@ -16,16 +16,20 @@
package com.google.android.exoplayer2.source.hls.playlist; package com.google.android.exoplayer2.source.hls.playlist;
import android.net.Uri; import android.net.Uri;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import junit.framework.TestCase;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import junit.framework.TestCase;
/** /**
* Test for {@link HlsMasterPlaylistParserTest} * Test for {@link HlsMasterPlaylistParserTest}
...@@ -147,6 +151,44 @@ public class HlsMasterPlaylistParserTest extends TestCase { ...@@ -147,6 +151,44 @@ public class HlsMasterPlaylistParserTest extends TestCase {
assertEquals(Collections.emptyList(), playlist.muxedCaptionFormats); assertEquals(Collections.emptyList(), playlist.muxedCaptionFormats);
} }
public void testReorderedVariantCopy() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST);
HlsMasterPlaylist nonReorderedPlaylist =
playlist.copyWithReorderedVariants(new Comparator<HlsMasterPlaylist.HlsUrl>() {
@Override
public int compare(HlsMasterPlaylist.HlsUrl url1, HlsMasterPlaylist.HlsUrl url2) {
return 0;
}
});
assertEquals(playlist.variants, nonReorderedPlaylist.variants);
HlsMasterPlaylist.HlsUrl preferred = null;
for (HlsMasterPlaylist.HlsUrl url : playlist.variants) {
if (preferred == null || url.format.bitrate > preferred.format.bitrate) {
preferred = url;
}
}
assertNotNull(preferred);
final Comparator comparator = Collections.reverseOrder(new Comparator<HlsMasterPlaylist.HlsUrl>() {
@Override
public int compare(HlsMasterPlaylist.HlsUrl url1, HlsMasterPlaylist.HlsUrl url2) {
if (url1.format.bitrate > url2.format.bitrate) {
return 1;
}
if (url2.format.bitrate > url1.format.bitrate) {
return -1;
}
return 0;
}
});
HlsMasterPlaylist reorderedPlaylist = playlist.copyWithReorderedVariants(comparator);
assertEquals(reorderedPlaylist.variants.get(0), preferred);
}
private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString) private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString)
throws IOException { throws IOException {
Uri playlistUri = Uri.parse(uri); Uri playlistUri = Uri.parse(uri);
......
package com.google.android.exoplayer2.source.hls.playlist;
import android.net.Uri;
import com.google.android.exoplayer2.C;
import junit.framework.TestCase;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Comparator;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
public class ReorderingHlsPlaylistParserTest extends TestCase {
private static final String MASTER_PLAYLIST = " #EXTM3U \n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=2560000,FRAME-RATE=25,RESOLUTION=384x160\n"
+ "http://example.com/mid.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=7680000,FRAME-RATE=29.997\n"
+ "http://example.com/hi.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n"
+ "http://example.com/audio-only.m3u8";
public void testReorderingWithNonMasterPlaylist() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString = "#EXTM3U\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXT-X-START:TIME-OFFSET=-25"
+ "#EXT-X-TARGETDURATION:8\n"
+ "#EXT-X-MEDIA-SEQUENCE:2679\n"
+ "#EXT-X-DISCONTINUITY-SEQUENCE:4\n"
+ "#EXT-X-ALLOW-CACHE:YES\n"
+ "\n"
+ "#EXTINF:7.975,\n"
+ "#EXT-X-BYTERANGE:51370@0\n"
+ "https://priv.example.com/fileSequence2679.ts\n"
+ "\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2680\",IV=0x1566B\n"
+ "#EXTINF:7.975,\n"
+ "#EXT-X-BYTERANGE:51501@2147483648\n"
+ "https://priv.example.com/fileSequence2680.ts\n"
+ "\n"
+ "#EXT-X-KEY:METHOD=NONE\n"
+ "#EXTINF:7.941,\n"
+ "#EXT-X-BYTERANGE:51501\n" // @2147535149
+ "https://priv.example.com/fileSequence2681.ts\n"
+ "\n"
+ "#EXT-X-DISCONTINUITY\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2682\"\n"
+ "#EXTINF:7.975,\n"
+ "#EXT-X-BYTERANGE:51740\n" // @2147586650
+ "https://priv.example.com/fileSequence2682.ts\n"
+ "\n"
+ "#EXTINF:7.975,\n"
+ "https://priv.example.com/fileSequence2683.ts\n"
+ "#EXT-X-ENDLIST";
InputStream inputStream = new ByteArrayInputStream(playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
Comparator<HlsMasterPlaylist.HlsUrl> comparator = mock(Comparator.class);
ReorderingHlsPlaylistParser playlistParser = new ReorderingHlsPlaylistParser(new HlsPlaylistParser(),
comparator);
final HlsMediaPlaylist playlist = (HlsMediaPlaylist) playlistParser.parse(playlistUri, inputStream);
assertNotNull(playlist);
// We should never compare the variants for a media level playlist.
verify(comparator, never()).compare(any(HlsMasterPlaylist.HlsUrl.class), any(HlsMasterPlaylist.HlsUrl.class));
}
public void testReorderingForMasterPlaylist() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
ByteArrayInputStream inputStream = new ByteArrayInputStream(
MASTER_PLAYLIST.getBytes(Charset.forName(C.UTF8_NAME)));
final Comparator comparator = Collections.reverseOrder(new Comparator<HlsMasterPlaylist.HlsUrl>() {
@Override
public int compare(HlsMasterPlaylist.HlsUrl url1, HlsMasterPlaylist.HlsUrl url2) {
if (url1.format.bitrate > url2.format.bitrate) {
return 1;
}
if (url2.format.bitrate > url1.format.bitrate) {
return -1;
}
return 0;
}
});
ReorderingHlsPlaylistParser playlistParser = new ReorderingHlsPlaylistParser(new HlsPlaylistParser(),
comparator);
final HlsMasterPlaylist reorderedPlaylist = (HlsMasterPlaylist) playlistParser.parse(playlistUri, inputStream);
assertNotNull(reorderedPlaylist);
inputStream.reset();
final HlsMasterPlaylist playlist = (HlsMasterPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
assertEquals(reorderedPlaylist.variants.get(0).format, playlist.variants.get(2).format);
}
}
\ No newline at end of file
...@@ -26,9 +26,12 @@ import com.google.android.exoplayer2.source.MediaPeriod; ...@@ -26,9 +26,12 @@ import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
...@@ -52,6 +55,7 @@ public final class HlsMediaSource implements MediaSource, ...@@ -52,6 +55,7 @@ public final class HlsMediaSource implements MediaSource,
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final ParsingLoadable.Parser<HlsPlaylist> playlistParser;
private HlsPlaylistTracker playlistTracker; private HlsPlaylistTracker playlistTracker;
private Listener sourceListener; private Listener sourceListener;
...@@ -72,9 +76,17 @@ public final class HlsMediaSource implements MediaSource, ...@@ -72,9 +76,17 @@ public final class HlsMediaSource implements MediaSource,
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory, public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler, int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) { AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, dataSourceFactory, minLoadableRetryCount, eventHandler, eventListener, new HlsPlaylistParser());
}
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener,
ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
this.manifestUri = manifestUri; this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.playlistParser = playlistParser;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
} }
...@@ -82,7 +94,7 @@ public final class HlsMediaSource implements MediaSource, ...@@ -82,7 +94,7 @@ public final class HlsMediaSource implements MediaSource,
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
Assertions.checkState(playlistTracker == null); Assertions.checkState(playlistTracker == null);
playlistTracker = new HlsPlaylistTracker(manifestUri, dataSourceFactory, eventDispatcher, playlistTracker = new HlsPlaylistTracker(manifestUri, dataSourceFactory, eventDispatcher,
minLoadableRetryCount, this); minLoadableRetryCount, this, playlistParser);
sourceListener = listener; sourceListener = listener;
playlistTracker.start(); playlistTracker.start();
} }
......
...@@ -19,6 +19,7 @@ import com.google.android.exoplayer2.Format; ...@@ -19,6 +19,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
/** /**
...@@ -124,6 +125,25 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -124,6 +125,25 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
} }
/** /**
* Returns a copy of this playlist which includes the variants sorted using the passed comparator. NOTE: the variants
* will be sorted in ascending order by default. If you wish to use descending order, you can wrap your comparator in
* {@link Collections#reverseOrder(Comparator)}.
*
* @param variantComparator the comparator to use to sort the variant list.
* @return a copy of this playlist which includes the variants sorted using the passed comparator.
*/
public HlsMasterPlaylist copyWithReorderedVariants(Comparator<HlsUrl> variantComparator) {
return new HlsMasterPlaylist(baseUri, tags, filterVariants(variants, variantComparator), audios,
subtitles, muxedAudioFormat, muxedCaptionFormats);
}
private List<HlsUrl> filterVariants(List<HlsUrl> variants, Comparator<HlsUrl> variantComparator) {
List<HlsUrl> reorderedList = new ArrayList<>(variants);
Collections.sort(reorderedList, variantComparator);
return reorderedList;
}
/**
* Creates a playlist with a single variant. * Creates a playlist with a single variant.
* *
* @param variantUrl The url of the single variant. * @param variantUrl The url of the single variant.
......
...@@ -115,7 +115,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -115,7 +115,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
private final Uri initialPlaylistUri; private final Uri initialPlaylistUri;
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final HlsPlaylistParser playlistParser; private final ParsingLoadable.Parser<HlsPlaylist> playlistParser;
private final int minRetryCount; private final int minRetryCount;
private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles; private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles;
private final Handler playlistRefreshHandler; private final Handler playlistRefreshHandler;
...@@ -140,7 +140,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -140,7 +140,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
*/ */
public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourceFactory, public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourceFactory,
EventDispatcher eventDispatcher, int minRetryCount, EventDispatcher eventDispatcher, int minRetryCount,
PrimaryPlaylistListener primaryPlaylistListener) { PrimaryPlaylistListener primaryPlaylistListener, ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
this.initialPlaylistUri = initialPlaylistUri; this.initialPlaylistUri = initialPlaylistUri;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
...@@ -148,7 +148,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -148,7 +148,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
this.primaryPlaylistListener = primaryPlaylistListener; this.primaryPlaylistListener = primaryPlaylistListener;
listeners = new ArrayList<>(); listeners = new ArrayList<>();
initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist"); initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist");
playlistParser = new HlsPlaylistParser(); this.playlistParser = playlistParser;
playlistBundles = new IdentityHashMap<>(); playlistBundles = new IdentityHashMap<>();
playlistRefreshHandler = new Handler(); playlistRefreshHandler = new Handler();
} }
......
package com.google.android.exoplayer2.source.hls.playlist;
import android.net.Uri;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Comparator;
/**
* Parser for {@link HlsPlaylist}s that reorders the variants based on the comparator passed.
*/
public class ReorderingHlsPlaylistParser implements ParsingLoadable.Parser<HlsPlaylist> {
private final ParsingLoadable.Parser<HlsPlaylist> playlistParser;
private final Comparator<HlsMasterPlaylist.HlsUrl> variantComparator;
/**
* @param playlistParser the {@link ParsingLoadable.Parser} to wrap.
* @param variantComparator the {@link Comparator} to use to reorder the variants.
* See {@link HlsMasterPlaylist#copyWithReorderedVariants(Comparator)} for more details.
*/
public ReorderingHlsPlaylistParser(ParsingLoadable.Parser<HlsPlaylist> playlistParser,
Comparator<HlsMasterPlaylist.HlsUrl> variantComparator) {
this.playlistParser = playlistParser;
this.variantComparator = variantComparator;
}
@Override
public HlsPlaylist parse(Uri uri, InputStream inputStream) throws IOException {
final HlsPlaylist playlist = playlistParser.parse(uri, inputStream);
if (playlist instanceof HlsMasterPlaylist) {
return ((HlsMasterPlaylist) playlist).copyWithReorderedVariants(variantComparator);
}
return playlist;
}
}
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