streaming

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

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 }