mirror of
https://github.com/bluenviron/mediamtx
synced 2025-02-13 18:08:11 +00:00
* hls source: support fMP4s video streams * hls source: start reading live streams from (end of playlist - starting point) * hls client: wait processing of current fMP4 segment before downloading another one * hls client: support fmp4 trun boxes with default sample duration, flags and size * hls client: merge fmp4 init file reader and writer * hls client: merge fmp4 part reader and writer * hls client: improve precision of go <-> mp4 time conversion * hls client: fix esds generation in go-mp4 * hls client: support audio in separate playlist * hls client: support an arbitrary number of tracks in fmp4 init files * hls client: support EXT-X-BYTERANGE * hls client: support fmp4 segments with multiple parts at once * hls client: support an arbitrary number of mpeg-ts tracks * hls client: synchronize tracks around a primary track * update go-mp4 * hls: synchronize track reproduction around a leading one * hls client: reset stream if playback is too late * hls client: add limit on DTS-RTC difference * hls client: support again streams that don't provide codecs in master playlist
107 lines
2.3 KiB
Go
107 lines
2.3 KiB
Go
// Package m3u8 contains a M3U8 parser.
|
|
package m3u8
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
|
|
gm3u8 "github.com/grafov/m3u8"
|
|
)
|
|
|
|
var reKeyValue = regexp.MustCompile(`([a-zA-Z0-9_-]+)=("[^"]+"|[^",]+)`)
|
|
|
|
func decodeParamsLine(line string) map[string]string {
|
|
out := make(map[string]string)
|
|
for _, kv := range reKeyValue.FindAllStringSubmatch(line, -1) {
|
|
k, v := kv[1], kv[2]
|
|
out[k] = strings.Trim(v, ` "`)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// MasterPlaylist is a master playlist.
|
|
type MasterPlaylist struct {
|
|
gm3u8.MasterPlaylist
|
|
Alternatives []*gm3u8.Alternative
|
|
}
|
|
|
|
func (MasterPlaylist) isPlaylist() {}
|
|
|
|
func newMasterPlaylist(byts []byte, mpl *gm3u8.MasterPlaylist) (*MasterPlaylist, error) {
|
|
var alternatives []*gm3u8.Alternative
|
|
|
|
// https://github.com/grafov/m3u8/blob/036100c52a87e26c62be56df85450e9c703201a6/reader.go#L301
|
|
for _, line := range strings.Split(string(byts), "\n") {
|
|
if strings.HasPrefix(line, "#EXT-X-MEDIA:") {
|
|
var alt gm3u8.Alternative
|
|
for k, v := range decodeParamsLine(line[13:]) {
|
|
switch k {
|
|
case "TYPE":
|
|
alt.Type = v
|
|
case "GROUP-ID":
|
|
alt.GroupId = v
|
|
case "LANGUAGE":
|
|
alt.Language = v
|
|
case "NAME":
|
|
alt.Name = v
|
|
case "DEFAULT":
|
|
switch {
|
|
case strings.ToUpper(v) == "YES":
|
|
alt.Default = true
|
|
case strings.ToUpper(v) == "NO":
|
|
alt.Default = false
|
|
default:
|
|
return nil, errors.New("value must be YES or NO")
|
|
}
|
|
case "AUTOSELECT":
|
|
alt.Autoselect = v
|
|
case "FORCED":
|
|
alt.Forced = v
|
|
case "CHARACTERISTICS":
|
|
alt.Characteristics = v
|
|
case "SUBTITLES":
|
|
alt.Subtitles = v
|
|
case "URI":
|
|
alt.URI = v
|
|
}
|
|
}
|
|
alternatives = append(alternatives, &alt)
|
|
}
|
|
}
|
|
|
|
return &MasterPlaylist{
|
|
MasterPlaylist: *mpl,
|
|
Alternatives: alternatives,
|
|
}, nil
|
|
}
|
|
|
|
// MediaPlaylist is a media playlist.
|
|
type MediaPlaylist gm3u8.MediaPlaylist
|
|
|
|
func (MediaPlaylist) isPlaylist() {}
|
|
|
|
// Playlist is a M3U8 playlist.
|
|
type Playlist interface {
|
|
isPlaylist()
|
|
}
|
|
|
|
// Unmarshal decodes a M3U8 Playlist.
|
|
func Unmarshal(byts []byte) (Playlist, error) {
|
|
pl, _, err := gm3u8.Decode(*(bytes.NewBuffer(byts)), true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch tpl := pl.(type) {
|
|
case *gm3u8.MasterPlaylist:
|
|
return newMasterPlaylist(byts, tpl)
|
|
|
|
case *gm3u8.MediaPlaylist:
|
|
return (*MediaPlaylist)(tpl), nil
|
|
}
|
|
|
|
panic("unexpected playlist type")
|
|
}
|