ExoPlayer icon indicating copy to clipboard operation
ExoPlayer copied to clipboard

Support m3u playlists

Open hassanhe opened this issue 6 years ago • 10 comments

👋 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 ❤

hassanhe avatar Apr 01 '18 03:04 hassanhe

We do not support m3u playlists (which are not the same as m3u8 HLS playlists). Marking as an enhancement.

ojw28 avatar Apr 02 '18 15:04 ojw28

@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.

ojw28 avatar Apr 18 '18 16:04 ojw28

@tonihei - This probably fits in with nicely DefaultMediaSource, as per my comments above.

ojw28 avatar May 06 '19 01:05 ojw28

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 :)

ChenglongMa avatar Mar 09 '21 13:03 ChenglongMa

@google ?

VuzzyM avatar Dec 16 '21 20:12 VuzzyM

@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?

Waleedasim avatar Feb 02 '22 12:02 Waleedasim

???

VuzzyM avatar Aug 21 '22 22:08 VuzzyM

@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.

ChenglongMa avatar Aug 22 '22 01:08 ChenglongMa

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 :)

Any update?

VuzzyM avatar Sep 10 '22 23:09 VuzzyM

@rohitjoins ???

VuzzyM avatar Sep 14 '22 16:09 VuzzyM