streaming

Media streaming and broadcast systems in Go
Log | Files | Refs | README | LICENSE

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 }