m3u8.go (5222B)
1 // Package m3u8 implements reading and writing of m3u8 playlists 2 // used in HTTP Live Streaming (HLS) as specified in RFC 8216. 3 package m3u8 4 5 import ( 6 "encoding/hex" 7 "fmt" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/untangledco/streaming/scte35" 13 ) 14 15 const MimeType string = "application/vnd.apple.mpegurl" 16 17 const rfc3339Milli string = "2006-01-02T15:04:05.999Z07:00" 18 19 type Playlist struct { 20 Version int 21 Segments []Segment 22 IndependentSegments bool 23 Start *StartPoint 24 25 // Media playlist 26 // RFC 8216, 4.4.3.1 27 TargetDuration time.Duration 28 Sequence int 29 DiscontinuitySequence int 30 End bool 31 Type PlaylistType 32 IFramesOnly bool 33 34 // Master playlist 35 Media []Rendition 36 Variants []Variant 37 SessionData []SessionData 38 SessionKey *Key 39 } 40 41 type Segment struct { 42 URI string 43 44 // Duration of this specific segment from the EXTINF tag. 45 Duration time.Duration 46 47 // Title is an optional human-readable name of the segment 48 // from the EXTINF tag. 49 Title string 50 51 // Indicates this segment holds a subset of the segment point to by URI. 52 // Range is the length of the subsegment from the EXT-X-BYTERANGE tag. 53 Range ByteRange 54 55 // If true, the preceding segment and the following segment 56 // are discontinuous. For example, this segment is part of a 57 // commercial break. 58 Discontinuity bool 59 60 // Holds information on how to decrypt this segment. 61 // If nil, the segment is not encrypted. 62 Key *Key 63 64 Map *Map 65 66 // Associates an absolute time with the start of the segment. 67 // The EXT-X-PROGRAM-DATE-TIME tag holds this value to 68 // millisecond accuracy. 69 DateTime time.Time 70 71 DateRange *DateRange 72 } 73 74 // Key represents the EXT-X-KEY tag specified in RFC 8216 seciton 4.3.2.3. 75 // A Key specifies how to decrypt encrypted playlist segments. 76 type Key struct { 77 Method EncryptMethod 78 // A URI pointing to instructions on how to obtain the key. 79 URI string 80 Format string 81 // An optional specification of the version of the key format 82 // set in Format. The first value of the slice is the major 83 // version; subsequent values are minor versions. 84 FormatVersions []uint32 85 // IV is a 128-bit unsigned integer holding the key's 86 // initialisation vector. 87 IV [16]byte 88 } 89 90 func (k Key) String() string { 91 var attrs []string 92 attrs = append(attrs, fmt.Sprintf("METHOD=%s", k.Method)) 93 attrs = append(attrs, fmt.Sprintf("URI=%q", k.URI)) 94 attrs = append(attrs, fmt.Sprintf("IV=0x%s", hex.EncodeToString(k.IV[:]))) 95 if k.Format != "" { 96 attrs = append(attrs, fmt.Sprintf("KEYFORMAT=%q", k.Format)) 97 } 98 if k.FormatVersions != nil { 99 ss := make([]string, len(k.FormatVersions)) 100 for i := range k.FormatVersions { 101 ss[i] = strconv.Itoa(int(k.FormatVersions[i])) 102 } 103 attrs = append(attrs, fmt.Sprintf("KEYFORMATVERSIONS=%q", strings.Join(ss, "/"))) 104 } 105 return tagKey + ":" + strings.Join(attrs, ",") 106 } 107 108 const defaultKeyFormat string = "identity" 109 110 type EncryptMethod uint8 111 112 const ( 113 EncryptMethodNone EncryptMethod = 0 + iota 114 EncryptMethodAES128 115 EncryptMethodSampleAES 116 encryptMethodInvalid EncryptMethod = 255 117 ) 118 119 func (m EncryptMethod) String() string { 120 switch m { 121 case EncryptMethodNone: 122 return "NONE" 123 case EncryptMethodAES128: 124 return "AES-128" 125 case EncryptMethodSampleAES: 126 return "SAMPLE-AES" 127 } 128 return "invalid" 129 } 130 131 func parseEncryptMethod(s string) EncryptMethod { 132 switch s { 133 case EncryptMethodNone.String(): 134 return EncryptMethodNone 135 case EncryptMethodAES128.String(): 136 return EncryptMethodAES128 137 case EncryptMethodSampleAES.String(): 138 return EncryptMethodSampleAES 139 } 140 return encryptMethodInvalid 141 } 142 143 // Map represents the EXT-X-MAP tag. 144 // A Map informs of any byte sequences to initialise readers of 145 // some media formats. 146 type Map struct { 147 URI string 148 ByteRange ByteRange 149 } 150 151 func (m Map) String() string { 152 if m.ByteRange != [2]int{0, 0} { 153 return fmt.Sprintf("%s:URI=%q,BYTERANGE=%s", tagMap, m.URI, m.ByteRange) 154 } 155 return fmt.Sprintf("%s:URI=%q", tagMap, m.URI) 156 } 157 158 // ByteRange represents... 159 // The first entry is an offset, the second...? 160 type ByteRange [2]int 161 162 func (r ByteRange) String() string { 163 if r[1] == 0 { 164 return strconv.Itoa(r[0]) 165 } 166 return fmt.Sprintf("%d@%d", r[0], r[1]) 167 } 168 169 // DateRange represents the EXT-X-DATERANGE tag. 170 // A DateRange associates third party-defined attribute/value pairs 171 // with a start and end time. 172 type DateRange struct { 173 ID string 174 Class string 175 Start time.Time 176 End time.Time 177 Duration time.Duration 178 Planned time.Duration 179 // value must be a string, float or hex sequence (int?) 180 Custom map[string]any 181 CueCommand *scte35.Splice 182 // Contains the first of the in/out cue pair. Command may be 183 // TimeSignal or Insert, with OutOfNetwork set to true. 184 CueOut *scte35.Splice 185 // Contains the second of the cue in/out pair. The Command's 186 // Type must match the "out" cue. 187 CueIn *scte35.Splice 188 EndOnNext bool 189 } 190 191 type PlaylistType uint8 192 193 const ( 194 PlaylistNone PlaylistType = iota 195 PlaylistEvent 196 PlaylistVOD 197 ) 198 199 func (t PlaylistType) String() string { 200 switch t { 201 case PlaylistEvent: 202 return "EVENT" 203 case PlaylistVOD: 204 return "VOD" 205 } 206 return "invalid" 207 } 208 209 type StartPoint struct { 210 Offset float32 211 Precise bool 212 }