mediamtx/internal/hls/m3u8/m3u8.go
Alessandro Ros e5ab731d14
Improve HLS client (#1179)
* 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
2022-10-23 14:04:33 +02:00

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")
}