ExoPlayer
ExoPlayer copied to clipboard
Support m3u playlists
👋 hi, i have the following .m3u playlist file
#EXTM3U
#EXTINF:-1,livestream1
http://foo.com/live/1.ts
when i try to stream it with the following
Uri uri = Uri.parse("http://foo.com/playlist.m3u");
DefaultHttpDataSourceFactory httpDataSourceFactory = new DefaultHttpDataSourceFactory(Util.getUserAgent(this, "test"));
DefaultHlsDataSourceFactory hlsDataSourceFactory = new DefaultHlsDataSourceFactory(httpDataSourceFactory);
HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(hlsDataSourceFactory).createMediaSource(uri);
player.prepare(hlsMediaSource);
i get the following error which mean the failing happened on the streams with #EXTINF:-1 (aka. the live stream ones which don't specify a duration time but -1 instead)
E/ExoPlayerImplInternal: Source error.
com.google.android.exoplayer2.ParserException: Couldn't match #EXTINF:([\d\.]+)\b in #EXTINF:-1,livestream1
at com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser.parseStringAttr(HlsPlaylistParser.java:538)
at com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser.parseDoubleAttr(HlsPlaylistParser.java:525)
at com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser.parseMediaPlaylist(HlsPlaylistParser.java:407)
at com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser.parse(HlsPlaylistParser.java:167)
at com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser.parse(HlsPlaylistParser.java:48)
at com.google.android.exoplayer2.upstream.ParsingLoadable.load(ParsingLoadable.java:130)
at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:308)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
how can i support an m3u live streams list if HlsPlaylistParser dont allow -1 as duration then
am using version 2.7.2
PS : i replaced the uri's in the example above but it's the same.
thanx in advance ❤
We do not support m3u playlists (which are not the same as m3u8 HLS playlists). Marking as an enhancement.
@botaydotcom - It's probably fairly trivial to make an m3u MediaSource (I think in all cases I've actually seen, the file has only contained a single URL, in which case it's a bit like a glorified redirect). Although it's usefulness would probably only be realized once we solve the "sniff all media types" problem, so they kinda belong together.
@tonihei - This probably fits in with nicely DefaultMediaSource, as per my comments above.
The issue here is that the regex used in HlsPlaylistParser does not support #EXTINF:-1. So I copied the source code of HlsPlaylistParser and changed its regex pattern from :([\d\.]+)\b to :(-?[\d\.]+)\b (simply add -? after :(;
More trivially, my changelog:
Line [166] (may also [168]):
private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION
+ ":(-?[\\d\\.]+)\\b"); // prev version: :([\d\.]+)\b
Line [742]:
segmentDurationUs =
(long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND);
add a new line to make sure segmentDurationUs is greater than 0 (IMPORTANT!!):
segmentDurationUs = max(segmentDurationUs, 1)
How to use it:
Suppose your new variation of HlsPlaylistParser is called MyPlaylistParser;
Implement a HlsPlaylistParserFactory based on the default class DefaultHlsPlaylistParserFactory, let's call it MyPlaylistParserFactory;
(Mine is kotlin version):
class MyPlaylistParserFactory : HlsPlaylistParserFactory {
override fun createPlaylistParser(): ParsingLoadable.Parser<HlsPlaylist> {
return MyPlaylistParser()
}
override fun createPlaylistParser(
masterPlaylist: HlsMasterPlaylist,
previousMediaPlaylist: HlsMediaPlaylist?
): ParsingLoadable.Parser<HlsPlaylist> {
return MyPlaylistParser(masterPlaylist, previousMediaPlaylist)
}
}
Then create a HlsMediaSource like:
val item = MediaItem.Builder()
.setMimeType(MimeTypes.APPLICATION_M3U8) // optional
.setUri(uri)
val hlsMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory())
.setPlaylistParserFactory(MyPlaylistParserFactory()) // This is your implementation
.createMediaSource(item.build())
exoPlayer.setMediaSource(hlsMediaSource)
exoPlayer.prepare()
exoPlayer.playWhenReady = true
Done. Hope this helps :)
@google ?
@botaydotcom - It's probably fairly trivial to make an m3u MediaSource (I think in all cases I've actually seen, the file has only contained a single URL, in which case it's a bit like a glorified redirect). Although it's usefulness would probably only be realized once we solve the "sniff all media types" problem, so they kinda belong together.
We do not support m3u playlists (which are not the same as m3u8 HLS playlists). Marking as an enhancement.
Still not supported?
???
@botaydotcom - It's probably fairly trivial to make an m3u MediaSource (I think in all cases I've actually seen, the file has only contained a single URL, in which case it's a bit like a glorified redirect). Although it's usefulness would probably only be realized once we solve the "sniff all media types" problem, so they kinda belong together.
We do not support m3u playlists (which are not the same as m3u8 HLS playlists). Marking as an enhancement.
Still not supported?
Hi @Waleedasim, may I know why I got a thumbs down? Thanks.
The issue here is that the regex used in
HlsPlaylistParserdoes not support#EXTINF:-1. So I copied the source code ofHlsPlaylistParserand changed its regex pattern from:([\d\.]+)\bto:(-?[\d\.]+)\b(simply add-?after:(;More trivially, my changelog:
Line [166] (may also [168]):
private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION + ":(-?[\\d\\.]+)\\b"); // prev version: :([\d\.]+)\bLine [742]:
segmentDurationUs = (long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND);add a new line to make sure
segmentDurationUsis greater than 0 (IMPORTANT!!):segmentDurationUs = max(segmentDurationUs, 1)How to use it:
Suppose your new variation of
HlsPlaylistParseris calledMyPlaylistParser; Implement aHlsPlaylistParserFactorybased on the default classDefaultHlsPlaylistParserFactory, let's call itMyPlaylistParserFactory;(Mine is
kotlinversion):class MyPlaylistParserFactory : HlsPlaylistParserFactory { override fun createPlaylistParser(): ParsingLoadable.Parser<HlsPlaylist> { return MyPlaylistParser() } override fun createPlaylistParser( masterPlaylist: HlsMasterPlaylist, previousMediaPlaylist: HlsMediaPlaylist? ): ParsingLoadable.Parser<HlsPlaylist> { return MyPlaylistParser(masterPlaylist, previousMediaPlaylist) } }Then create a
HlsMediaSourcelike:val item = MediaItem.Builder() .setMimeType(MimeTypes.APPLICATION_M3U8) // optional .setUri(uri) val hlsMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory()) .setPlaylistParserFactory(MyPlaylistParserFactory()) // This is your implementation .createMediaSource(item.build()) exoPlayer.setMediaSource(hlsMediaSource) exoPlayer.prepare() exoPlayer.playWhenReady = trueDone. Hope this helps :)
Any update?
@rohitjoins ???