master.go (8350B)
1 package m3u8 2 3 import ( 4 "fmt" 5 "strings" 6 ) 7 8 // Rendition represents a unique rendition as described by a single 9 // EXT-X-MEDIA tag specified in RFC 8216 section 4.3.4.1. 10 // 11 // Each Rendition must belong to a named Group specified by the Group field. 12 // Each member must have a unique Name set. 13 // Only one member may have Default set. 14 // Members with AutoSelect set must have unique values for Language, 15 // AssocLanguage, Forced, and Characteristics in their Group. 16 type Rendition struct { 17 // Type is a required field identifying the type of media in the rendition. 18 Type MediaType 19 // URI is a required field pointing to a media playlist carrying the rendition. 20 URI string 21 // Group names the group in which this Rendition belongs. 22 // It corresponds to the "GROUP-ID" attribute. 23 Group string 24 // Language contains a RFC 5646-formatted tag identifying 25 // the primary language used in the rendition. 26 Language string 27 // AssocLanguage contains a RFC 5646-formatted tag identifying 28 // an associated language used in a different role than the one set in Language. 29 AssocLanguage string 30 // Name describes the rendition. 31 Name string 32 // Default specifies whether players should play this 33 // Rendition when a user has not indicated otherwise. 34 Default bool 35 // AutoSelect indicates that this Rendition may be 36 // automatically played based on the playback environment, such 37 // as a matching system language. 38 AutoSelect bool 39 // Forced indicates that the rendition contains essential content for playing. 40 Forced bool 41 // InstreamID points to closed-caption information. 42 InstreamID *CCInfo 43 // Characteristics contains Uniform Type Identifiers. 44 // For example []string{CharacteristicTranscribesDialog, ChractersticEasyToRead} 45 Characteristics []string 46 // Channels contains the different counts of audio channels available in the Rendition. 47 // For example []string{"2,6"} represents stereo (2) and 5.1 surround-sound (5 + 1) channels. 48 Channels []string 49 } 50 51 func (r Rendition) String() string { 52 var attrs []string 53 attrs = append(attrs, fmt.Sprintf("NAME=%q", r.Name)) 54 attrs = append(attrs, fmt.Sprintf("TYPE=%s", r.Type)) 55 if r.URI != "" { 56 attrs = append(attrs, fmt.Sprintf("URI=%q", r.URI)) 57 } 58 attrs = append(attrs, fmt.Sprintf("GROUP-ID=%q", r.Group)) 59 if r.Language != "" { 60 attrs = append(attrs, fmt.Sprintf("LANGUAGE=%q", r.Language)) 61 } 62 if r.AssocLanguage != "" { 63 attrs = append(attrs, fmt.Sprintf("ASSOC-LANGUAGE=%q", r.AssocLanguage)) 64 } 65 if r.Default { 66 attrs = append(attrs, "DEFAULT=YES") 67 } 68 if r.AutoSelect { 69 attrs = append(attrs, "AUTOSELECT=YES") 70 } 71 if r.Forced { 72 attrs = append(attrs, "FORCED=YES") 73 } 74 if r.Type == MediaClosedCaptions && r.InstreamID != nil { 75 attrs = append(attrs, fmt.Sprintf("INSTREAM-ID=%q", r.InstreamID)) 76 } 77 if len(r.Characteristics) > 0 { 78 chars := strings.Join(r.Characteristics, ",") 79 attrs = append(attrs, fmt.Sprintf("CHARACTERISTICS=%q", chars)) 80 } 81 if len(r.Channels) > 0 { 82 channels := strings.Join(r.Channels, "/") 83 attrs = append(attrs, fmt.Sprintf("CHANNELS=%q", channels)) 84 } 85 return tagRendition + ":" + strings.Join(attrs, ",") 86 } 87 88 type MediaType uint8 89 90 const ( 91 MediaAudio MediaType = 0 + iota 92 MediaVideo 93 MediaSubtitles 94 MediaClosedCaptions 95 ) 96 97 func (t MediaType) String() string { 98 switch t { 99 case MediaAudio: 100 return "AUDIO" 101 case MediaVideo: 102 return "VIDEO" 103 case MediaSubtitles: 104 return "SUBTITLES" 105 case MediaClosedCaptions: 106 return "CLOSED-CAPTIONS" 107 } 108 return "invalid" 109 } 110 111 type CCInfo struct { 112 ID int 113 Service bool 114 } 115 116 func (info *CCInfo) String() string { 117 if info.Service { 118 return fmt.Sprintf("%s%d", "SERVICE", info.ID) 119 } 120 return fmt.Sprintf("%s%d", "CC", info.ID) 121 } 122 123 const ( 124 CharacteristicTranscribesDialog = "public.accessibility.transcribes-spoken-dialog" 125 CharacteristicDescribesMusicAndSound = "public.accessibility.transcribes-spoken-dialog" 126 CharactersticEasyToRead = "public.easy-to-read" 127 CharacteristicDescribesVideo = "public.accessibility.describes-video" 128 ) 129 130 // Variant represents the EXT-X-STREAM-INF tag specified in RFC 8216 section 4.3.4.2. 131 // The Audio, Video, Subtitles and ClosedCaptions fields each name a 132 // [Rendition] which can be combined to play the presentation. 133 type Variant struct { 134 // Points to a media playlist carrying a rendition of this stream. 135 URI string 136 // Peak segment bitrate in bits per second. 137 // This must be set. 138 Bandwidth int 139 // Average segment bitrate in bits per second. 140 AverageBandwidth int 141 142 // Codecs present in the stream in the format defined in ISO 143 // Base Media File Format Name Space RFC6381 section 3.3. 144 // For example: []string{"mp4a.40.2", "avc1.64001f"} 145 Codecs []string 146 147 // The width and height of the video in number of pixels. 148 Resolution [2]int 149 150 // Maximum video frame rate, in frames per second. Values are 151 // rounded to 3 decimal places. 152 FrameRate float32 153 154 // Indicates if the stream is protected by High-bandwidth 155 // Digital Content Protection (HDCP). The zero value 156 // (HDCPLevelNone) indicates no output copy protection is 157 // required for playing. 158 HDCP HDCPLevel 159 160 // Each remaining field identifies a matching rendition of 161 // the same type in the playlist. The match has its Group set to 162 // the same value, and Type set to corresponding MediaType. 163 // The empty string indicates there is no matching rendition in the playlist. 164 // 165 // For example, if Audio is set to "music", then a matching rendition 166 // would be: 167 // 168 // Rendition{ 169 // Type: MediaAudio, 170 // Group: "music", 171 // ... 172 // } 173 // 174 // Similarly a matching rendition with Subtitles set to "sub1" would be: 175 // 176 // Rendition{ 177 // Type: MediaSubtitles, 178 // Group: "sub1", 179 // ... 180 // } 181 Audio string 182 Video string 183 Subtitles string 184 ClosedCaptions string // May be NoClosedCaptions to explicitly signal no rendition. 185 } 186 187 func (v Variant) String() string { 188 var attrs []string 189 attrs = append(attrs, fmt.Sprintf("BANDWIDTH=%d", v.Bandwidth)) 190 if v.AverageBandwidth > 0 { 191 attrs = append(attrs, fmt.Sprintf("AVERAGE-BANDWIDTH=%d,", v.AverageBandwidth)) 192 } 193 if len(v.Codecs) > 0 { 194 attrs = append(attrs, fmt.Sprintf("CODECS=%q", strings.Join(v.Codecs, ","))) 195 } 196 if v.Resolution != [2]int{0, 0} { 197 attrs = append(attrs, fmt.Sprintf("RESOLUTION=%dx%d", v.Resolution[0], v.Resolution[1])) 198 } 199 if v.FrameRate > 0 { 200 attrs = append(attrs, fmt.Sprintf("FRAME-RATE=%.03f", v.FrameRate)) 201 } 202 if v.HDCP != HDCPNone { 203 attrs = append(attrs, fmt.Sprintf("HDCP-LEVEL=%s", v.HDCP)) 204 } 205 if v.Audio != "" { 206 attrs = append(attrs, fmt.Sprintf("AUDIO=%q", v.Audio)) 207 } 208 if v.Video != "" { 209 attrs = append(attrs, fmt.Sprintf("VIDEO=%q", v.Video)) 210 } 211 if v.Subtitles != "" { 212 attrs = append(attrs, fmt.Sprintf("SUBTITLES=%q", v.Subtitles)) 213 } 214 if v.ClosedCaptions != "" && v.ClosedCaptions != NoClosedCaptions { 215 attrs = append(attrs, fmt.Sprintf("CLOSED-CAPTIONS=%q", v.ClosedCaptions)) 216 } 217 return fmt.Sprintf("%s:%s\n%s", tagVariant, strings.Join(attrs, ","), v.URI) 218 } 219 220 // NoClosedCaptions may be the value for Variant.ClosedCaptions to 221 // explicitly indicate that no closed captions are available for the 222 // Variant. 223 const NoClosedCaptions string = "NONE" 224 225 type HDCPLevel uint8 226 227 const ( 228 HDCPNone HDCPLevel = 0 + iota 229 HDCPType0 230 HDCPType1 231 ) 232 233 func (l HDCPLevel) String() string { 234 switch l { 235 case HDCPNone: 236 return "NONE" 237 case HDCPType0: 238 return "TYPE-0" 239 case HDCPType1: 240 return "TYPE-1" 241 } 242 return "unknown" 243 } 244 245 // IFrameInfo represents the EXT-X-I-FRAME-STREAM-INF tag. 246 // It has the same structure as Variant, but the following fields should be unset: 247 // - FrameRate 248 // - Audio 249 // - Subtitles 250 // - ClosedCaptions 251 type IFrameInfo Variant 252 253 // SessionData represents the EXT-X-SESSION-DATA tag. 254 type SessionData struct { 255 ID string // This attribute is REQUIRED 256 Value string // MUST contain either a VALUE or URI attribute, but not both 257 URI string 258 Language string // This attribute is OPTIONAL. 259 } 260 261 func (sd SessionData) String() string { 262 var attrs []string 263 attrs = append(attrs, fmt.Sprintf("DATA-ID=%q", sd.ID)) 264 if sd.Value != "" { 265 attrs = append(attrs, fmt.Sprintf("VALUE=%q", sd.Value)) 266 } 267 if sd.URI != "" { 268 attrs = append(attrs, fmt.Sprintf("URI=%q", sd.URI)) 269 } 270 if sd.Language != "" { 271 attrs = append(attrs, fmt.Sprintf("LANGUAGE=%q", sd.Language)) 272 } 273 return tagSessionData + ":" + strings.Join(attrs, ",") 274 }