mediamtx/internal/hls/m3u8/m3u8.go

107 lines
2.3 KiB
Go
Raw Normal View History

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