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
HlsPlaylistParser
does not support#EXTINF:-1
. So I copied the source code ofHlsPlaylistParser
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 calledMyPlaylistParser
; Implement aHlsPlaylistParserFactory
based on the default classDefaultHlsPlaylistParserFactory
, let's call itMyPlaylistParserFactory
;(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 :)
Any update?
@rohitjoins ???