-
Notifications
You must be signed in to change notification settings - Fork 0
/
master.go
155 lines (133 loc) · 3.37 KB
/
master.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package m3u8
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
const (
variantTag = "#EXT-X-STREAM-INF"
mediaTag = "#EXT-X-MEDIA"
)
// Attributes is a list of key:value pairs.
type Attributes string
// XStream is a Variant Stream.
type XStream struct {
URI string
Info Attributes // EXT-X-STREAM-INF
// X-Media are alternative Renditions.
// They are not necessarily binded to a certain XStream/variant,
// but put inside a XStream for keeping relative output position
XMedia []Attributes
// optional
Directives string
}
// Marshal returns the string form of the XStream.
func (s XStream) Marshal() string {
ret := s.Directives
for _, m := range s.XMedia {
ret += fmt.Sprintf("%s:%s\n", mediaTag, m)
}
if len(s.XMedia) > 0 {
ret += "\n" // extra black line
}
ret += fmt.Sprintf("%s:%s\n", variantTag, s.Info)
ret += s.URI + "\n"
return ret
}
// Master is a master playlist.
type Master struct {
// A Master Playlist MUST indicate a EXT-X-VERSION of 7 or higher if it
// contains:
// - "SERVICE" values for the INSTREAM-ID attribute of the EXT-X-MEDIA
// tag.
// The EXT-X-MEDIA tag and the AUDIO, VIDEO and SUBTITLES attributes of
// the EXT-X-STREAM-INF tag are backward compatible to protocol version
// 1, but playback on older clients may not be desirable. A server MAY
// consider indicating a EXT-X-VERSION of 4 or higher in the Master
// Playlist but is not required to do so.
Version int
Streams []XStream
}
// Marshal returns the string form of the Master playlist.
func (m *Master) Marshal() string {
ret := starter + "\n"
if m.Version > 0 {
ret += fmt.Sprintf("%s:%d\n", verTag, m.Version)
}
for _, s := range m.Streams {
ret += s.Marshal() + "\n" // extra blank line for style
}
return ret
}
// IsMaster tells if a playlist is a master playlist.
func IsMaster(r io.Reader) (bool, error) {
s := bufio.NewScanner(r)
for s.Scan() {
l := s.Text()
if strings.HasPrefix(l, infTag) {
return false, nil
}
if strings.HasPrefix(l, variantTag) {
return true, nil
}
}
err := s.Err()
if err == nil {
err = ErrInvalidPlaylist
}
return false, err
}
// DecodeMaster decodes a master playlist.
func DecodeMaster(r io.Reader) (m *Master, err error) {
m = &Master{
Streams: make([]XStream, 0),
}
var cur XStream
s := bufio.NewScanner(r)
for s.Scan() {
l := strings.TrimSpace(s.Text())
switch {
case l == starter || len(l) < 1:
// ignore blank lines
case !strings.HasPrefix(l, "#"):
if cur.Info == "" {
return nil, ErrMissingStream
}
cur.URI = l
m.Streams = append(m.Streams, cur)
cur = XStream{}
case strings.HasPrefix(l, "#EXT"):
switch {
case strings.HasPrefix(l, variantTag):
cur.Info = Attributes(l[18:]) // 18 = len("#EXT-X-STREAM-INF:")
case strings.HasPrefix(l, mediaTag):
cur.XMedia = append(cur.XMedia, Attributes(l[13:])) // 13 = len("#EXT-X-MEDIA:")
default:
cur.Directives += l + "\n"
}
default:
// ignore comments
}
}
err = s.Err()
return
}
// TryDecodeMaster tests if the read content is a master playlist
// and try to decode it; otherwise return ErrNotMaster.
func TryDecodeMaster(r io.Reader) (m *Master, err error) {
bs, err := io.ReadAll(r)
if err != nil {
return
}
isMaster, err := IsMaster(bytes.NewBuffer(bs))
if err != nil {
return
}
if isMaster {
return DecodeMaster(bytes.NewBuffer(bs))
}
err = ErrNotMaster
return
}