| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768 |
- package m3u8
- /*
- Part of M3U8 parser & generator library.
- This file defines functions related to playlist generation.
- Copyright 2013-2017 The Project Developers.
- See the AUTHORS and LICENSE files at the top-level directory of this distribution
- and at https://github.com/grafov/m3u8/
- ॐ तारे तुत्तारे तुरे स्व
- */
- import (
- "bytes"
- "errors"
- "fmt"
- "math"
- "strconv"
- "strings"
- "time"
- )
- var (
- ErrPlaylistFull = errors.New("playlist is full")
- )
- // Set version of the playlist accordingly with section 7
- func version(ver *uint8, newver uint8) {
- if *ver < newver {
- *ver = newver
- }
- }
- func strver(ver uint8) string {
- return strconv.FormatUint(uint64(ver), 10)
- }
- // Create new empty master playlist.
- // Master playlist consists of variants.
- func NewMasterPlaylist() *MasterPlaylist {
- p := new(MasterPlaylist)
- p.ver = minver
- return p
- }
- // Append variant to master playlist.
- // This operation does reset playlist cache.
- func (p *MasterPlaylist) Append(uri string, chunklist *MediaPlaylist, params VariantParams) {
- v := new(Variant)
- v.URI = uri
- v.Chunklist = chunklist
- v.VariantParams = params
- p.Variants = append(p.Variants, v)
- if len(v.Alternatives) > 0 {
- // From section 7:
- // 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(&p.ver, 4) // so it is optional and in theory may be set to ver.1
- // but more tests required
- }
- p.buf.Reset()
- }
- func (p *MasterPlaylist) ResetCache() {
- p.buf.Reset()
- }
- // Generate output in M3U8 format.
- func (p *MasterPlaylist) Encode() *bytes.Buffer {
- if p.buf.Len() > 0 {
- return &p.buf
- }
- p.buf.WriteString("#EXTM3U\n#EXT-X-VERSION:")
- p.buf.WriteString(strver(p.ver))
- p.buf.WriteRune('\n')
- var altsWritten map[string]bool = make(map[string]bool)
- for _, pl := range p.Variants {
- if pl.Alternatives != nil {
- for _, alt := range pl.Alternatives {
- // Make sure that we only write out an alternative once
- altKey := fmt.Sprintf("%s-%s-%s-%s", alt.Type, alt.GroupId, alt.Name, alt.Language)
- if altsWritten[altKey] {
- continue
- }
- altsWritten[altKey] = true
- p.buf.WriteString("#EXT-X-MEDIA:")
- if alt.Type != "" {
- p.buf.WriteString("TYPE=") // Type should not be quoted
- p.buf.WriteString(alt.Type)
- }
- if alt.GroupId != "" {
- p.buf.WriteString(",GROUP-ID=\"")
- p.buf.WriteString(alt.GroupId)
- p.buf.WriteRune('"')
- }
- if alt.Name != "" {
- p.buf.WriteString(",NAME=\"")
- p.buf.WriteString(alt.Name)
- p.buf.WriteRune('"')
- }
- p.buf.WriteString(",DEFAULT=")
- if alt.Default {
- p.buf.WriteString("YES")
- } else {
- p.buf.WriteString("NO")
- }
- if alt.Autoselect != "" {
- p.buf.WriteString(",AUTOSELECT=")
- p.buf.WriteString(alt.Autoselect)
- }
- if alt.Language != "" {
- p.buf.WriteString(",LANGUAGE=\"")
- p.buf.WriteString(alt.Language)
- p.buf.WriteRune('"')
- }
- if alt.Forced != "" {
- p.buf.WriteString(",FORCED=\"")
- p.buf.WriteString(alt.Forced)
- p.buf.WriteRune('"')
- }
- if alt.Characteristics != "" {
- p.buf.WriteString(",CHARACTERISTICS=\"")
- p.buf.WriteString(alt.Characteristics)
- p.buf.WriteRune('"')
- }
- if alt.Subtitles != "" {
- p.buf.WriteString(",SUBTITLES=\"")
- p.buf.WriteString(alt.Subtitles)
- p.buf.WriteRune('"')
- }
- if alt.URI != "" {
- p.buf.WriteString(",URI=\"")
- p.buf.WriteString(alt.URI)
- p.buf.WriteRune('"')
- }
- p.buf.WriteRune('\n')
- }
- }
- if pl.Iframe {
- p.buf.WriteString("#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=")
- p.buf.WriteString(strconv.FormatUint(uint64(pl.ProgramId), 10))
- p.buf.WriteString(",BANDWIDTH=")
- p.buf.WriteString(strconv.FormatUint(uint64(pl.Bandwidth), 10))
- if pl.Codecs != "" {
- p.buf.WriteString(",CODECS=\"")
- p.buf.WriteString(pl.Codecs)
- p.buf.WriteRune('"')
- }
- if pl.Resolution != "" {
- p.buf.WriteString(",RESOLUTION=") // Resolution should not be quoted
- p.buf.WriteString(pl.Resolution)
- }
- if pl.Video != "" {
- p.buf.WriteString(",VIDEO=\"")
- p.buf.WriteString(pl.Video)
- p.buf.WriteRune('"')
- }
- if pl.URI != "" {
- p.buf.WriteString(",URI=\"")
- p.buf.WriteString(pl.URI)
- p.buf.WriteRune('"')
- }
- p.buf.WriteRune('\n')
- } else {
- p.buf.WriteString("#EXT-X-STREAM-INF:PROGRAM-ID=")
- p.buf.WriteString(strconv.FormatUint(uint64(pl.ProgramId), 10))
- p.buf.WriteString(",BANDWIDTH=")
- p.buf.WriteString(strconv.FormatUint(uint64(pl.Bandwidth), 10))
- if pl.Codecs != "" {
- p.buf.WriteString(",CODECS=\"")
- p.buf.WriteString(pl.Codecs)
- p.buf.WriteRune('"')
- }
- if pl.Resolution != "" {
- p.buf.WriteString(",RESOLUTION=") // Resolution should not be quoted
- p.buf.WriteString(pl.Resolution)
- }
- if pl.Audio != "" {
- p.buf.WriteString(",AUDIO=\"")
- p.buf.WriteString(pl.Audio)
- p.buf.WriteRune('"')
- }
- if pl.Video != "" {
- p.buf.WriteString(",VIDEO=\"")
- p.buf.WriteString(pl.Video)
- p.buf.WriteRune('"')
- }
- if pl.Captions != "" {
- p.buf.WriteString(",CLOSED-CAPTIONS=")
- if pl.Captions == "NONE" {
- p.buf.WriteString(pl.Captions) // CC should not be quoted when eq NONE
- } else {
- p.buf.WriteRune('"')
- p.buf.WriteString(pl.Captions)
- p.buf.WriteRune('"')
- }
- }
- if pl.Subtitles != "" {
- p.buf.WriteString(",SUBTITLES=\"")
- p.buf.WriteString(pl.Subtitles)
- p.buf.WriteRune('"')
- }
- if pl.Name != "" {
- p.buf.WriteString(",NAME=\"")
- p.buf.WriteString(pl.Name)
- p.buf.WriteRune('"')
- }
- p.buf.WriteRune('\n')
- p.buf.WriteString(pl.URI)
- if p.Args != "" {
- if strings.Contains(pl.URI, "?") {
- p.buf.WriteRune('&')
- } else {
- p.buf.WriteRune('?')
- }
- p.buf.WriteString(p.Args)
- }
- p.buf.WriteRune('\n')
- }
- }
- return &p.buf
- }
- // Version returns the current playlist version number
- func (p *MasterPlaylist) Version() uint8 {
- return p.ver
- }
- // SetVersion sets the playlist version number, note the version maybe changed
- // automatically by other Set methods.
- func (p *MasterPlaylist) SetVersion(ver uint8) {
- p.ver = ver
- }
- // For compatibility with Stringer interface
- // For example fmt.Printf("%s", sampleMediaList) will encode
- // playist and print its string representation.
- func (p *MasterPlaylist) String() string {
- return p.Encode().String()
- }
- // Creates new media playlist structure.
- // Winsize defines how much items will displayed on playlist generation.
- // Capacity is total size of a playlist.
- func NewMediaPlaylist(winsize uint, capacity uint) (*MediaPlaylist, error) {
- p := new(MediaPlaylist)
- p.ver = minver
- p.capacity = capacity
- if err := p.SetWinSize(winsize); err != nil {
- return nil, err
- }
- p.Segments = make([]*MediaSegment, capacity)
- return p, nil
- }
- // last returns the previously written segment's index
- func (p *MediaPlaylist) last() uint {
- if p.tail == 0 {
- return p.capacity - 1
- }
- return p.tail - 1
- }
- // Remove current segment from the head of chunk slice form a media playlist. Useful for sliding playlists.
- // This operation does reset playlist cache.
- func (p *MediaPlaylist) Remove() (err error) {
- if p.count == 0 {
- return errors.New("playlist is empty")
- }
- p.head = (p.head + 1) % p.capacity
- p.count--
- if !p.Closed {
- p.SeqNo++
- }
- p.buf.Reset()
- return nil
- }
- // Append general chunk to the tail of chunk slice for a media playlist.
- // This operation does reset playlist cache.
- func (p *MediaPlaylist) Append(uri string, duration float64, title string) error {
- seg := new(MediaSegment)
- seg.URI = uri
- seg.Duration = duration
- seg.Title = title
- return p.AppendSegment(seg)
- }
- // AppendSegment appends a MediaSegment to the tail of chunk slice for a media playlist.
- // This operation does reset playlist cache.
- func (p *MediaPlaylist) AppendSegment(seg *MediaSegment) error {
- if p.head == p.tail && p.count > 0 {
- return ErrPlaylistFull
- }
- p.Segments[p.tail] = seg
- p.tail = (p.tail + 1) % p.capacity
- p.count++
- if p.TargetDuration < seg.Duration {
- p.TargetDuration = math.Ceil(seg.Duration)
- }
- p.buf.Reset()
- return nil
- }
- // Combines two operations: firstly it removes one chunk from the head of chunk slice and move pointer to
- // next chunk. Secondly it appends one chunk to the tail of chunk slice. Useful for sliding playlists.
- // This operation does reset cache.
- func (p *MediaPlaylist) Slide(uri string, duration float64, title string) {
- if !p.Closed && p.count >= p.winsize {
- p.Remove()
- } else if !p.Closed {
- p.SeqNo++
- }
- p.Append(uri, duration, title)
- }
- // Reset playlist cache. Next called Encode() will regenerate playlist from the chunk slice.
- func (p *MediaPlaylist) ResetCache() {
- p.buf.Reset()
- }
- // Generate output in M3U8 format. Marshal `winsize` elements from bottom of the `segments` queue.
- func (p *MediaPlaylist) Encode() *bytes.Buffer {
- if p.buf.Len() > 0 {
- return &p.buf
- }
- p.buf.WriteString("#EXTM3U\n#EXT-X-VERSION:")
- p.buf.WriteString(strver(p.ver))
- p.buf.WriteRune('\n')
- // default key (workaround for Widevine)
- if p.Key != nil {
- p.buf.WriteString("#EXT-X-KEY:")
- p.buf.WriteString("METHOD=")
- p.buf.WriteString(p.Key.Method)
- if p.Key.Method != "NONE" {
- p.buf.WriteString(",URI=\"")
- p.buf.WriteString(p.Key.URI)
- p.buf.WriteRune('"')
- if p.Key.IV != "" {
- p.buf.WriteString(",IV=")
- p.buf.WriteString(p.Key.IV)
- }
- if p.Key.Keyformat != "" {
- p.buf.WriteString(",KEYFORMAT=\"")
- p.buf.WriteString(p.Key.Keyformat)
- p.buf.WriteRune('"')
- }
- if p.Key.Keyformatversions != "" {
- p.buf.WriteString(",KEYFORMATVERSIONS=\"")
- p.buf.WriteString(p.Key.Keyformatversions)
- p.buf.WriteRune('"')
- }
- }
- p.buf.WriteRune('\n')
- }
- if p.Map != nil {
- p.buf.WriteString("#EXT-X-MAP:")
- p.buf.WriteString("URI=\"")
- p.buf.WriteString(p.Map.URI)
- p.buf.WriteRune('"')
- if p.Map.Limit > 0 {
- p.buf.WriteString(",BYTERANGE=")
- p.buf.WriteString(strconv.FormatInt(p.Map.Limit, 10))
- p.buf.WriteRune('@')
- p.buf.WriteString(strconv.FormatInt(p.Map.Offset, 10))
- }
- p.buf.WriteRune('\n')
- }
- if p.MediaType > 0 {
- p.buf.WriteString("#EXT-X-PLAYLIST-TYPE:")
- switch p.MediaType {
- case EVENT:
- p.buf.WriteString("EVENT\n")
- p.buf.WriteString("#EXT-X-ALLOW-CACHE:NO\n")
- case VOD:
- p.buf.WriteString("VOD\n")
- }
- }
- p.buf.WriteString("#EXT-X-MEDIA-SEQUENCE:")
- p.buf.WriteString(strconv.FormatUint(p.SeqNo, 10))
- p.buf.WriteRune('\n')
- p.buf.WriteString("#EXT-X-TARGETDURATION:")
- p.buf.WriteString(strconv.FormatInt(int64(math.Ceil(p.TargetDuration)), 10)) // due section 3.4.2 of M3U8 specs EXT-X-TARGETDURATION must be integer
- p.buf.WriteRune('\n')
- if p.Iframe {
- p.buf.WriteString("#EXT-X-I-FRAMES-ONLY\n")
- }
- // Widevine tags
- if p.WV != nil {
- if p.WV.AudioChannels != 0 {
- p.buf.WriteString("#WV-AUDIO-CHANNELS ")
- p.buf.WriteString(strconv.FormatUint(uint64(p.WV.AudioChannels), 10))
- p.buf.WriteRune('\n')
- }
- if p.WV.AudioFormat != 0 {
- p.buf.WriteString("#WV-AUDIO-FORMAT ")
- p.buf.WriteString(strconv.FormatUint(uint64(p.WV.AudioFormat), 10))
- p.buf.WriteRune('\n')
- }
- if p.WV.AudioProfileIDC != 0 {
- p.buf.WriteString("#WV-AUDIO-PROFILE-IDC ")
- p.buf.WriteString(strconv.FormatUint(uint64(p.WV.AudioProfileIDC), 10))
- p.buf.WriteRune('\n')
- }
- if p.WV.AudioSampleSize != 0 {
- p.buf.WriteString("#WV-AUDIO-SAMPLE-SIZE ")
- p.buf.WriteString(strconv.FormatUint(uint64(p.WV.AudioSampleSize), 10))
- p.buf.WriteRune('\n')
- }
- if p.WV.AudioSamplingFrequency != 0 {
- p.buf.WriteString("#WV-AUDIO-SAMPLING-FREQUENCY ")
- p.buf.WriteString(strconv.FormatUint(uint64(p.WV.AudioSamplingFrequency), 10))
- p.buf.WriteRune('\n')
- }
- if p.WV.CypherVersion != "" {
- p.buf.WriteString("#WV-CYPHER-VERSION ")
- p.buf.WriteString(p.WV.CypherVersion)
- p.buf.WriteRune('\n')
- }
- if p.WV.ECM != "" {
- p.buf.WriteString("#WV-ECM ")
- p.buf.WriteString(p.WV.ECM)
- p.buf.WriteRune('\n')
- }
- if p.WV.VideoFormat != 0 {
- p.buf.WriteString("#WV-VIDEO-FORMAT ")
- p.buf.WriteString(strconv.FormatUint(uint64(p.WV.VideoFormat), 10))
- p.buf.WriteRune('\n')
- }
- if p.WV.VideoFrameRate != 0 {
- p.buf.WriteString("#WV-VIDEO-FRAME-RATE ")
- p.buf.WriteString(strconv.FormatUint(uint64(p.WV.VideoFrameRate), 10))
- p.buf.WriteRune('\n')
- }
- if p.WV.VideoLevelIDC != 0 {
- p.buf.WriteString("#WV-VIDEO-LEVEL-IDC")
- p.buf.WriteString(strconv.FormatUint(uint64(p.WV.VideoLevelIDC), 10))
- p.buf.WriteRune('\n')
- }
- if p.WV.VideoProfileIDC != 0 {
- p.buf.WriteString("#WV-VIDEO-PROFILE-IDC ")
- p.buf.WriteString(strconv.FormatUint(uint64(p.WV.VideoProfileIDC), 10))
- p.buf.WriteRune('\n')
- }
- if p.WV.VideoResolution != "" {
- p.buf.WriteString("#WV-VIDEO-RESOLUTION ")
- p.buf.WriteString(p.WV.VideoResolution)
- p.buf.WriteRune('\n')
- }
- if p.WV.VideoSAR != "" {
- p.buf.WriteString("#WV-VIDEO-SAR ")
- p.buf.WriteString(p.WV.VideoSAR)
- p.buf.WriteRune('\n')
- }
- }
- var (
- seg *MediaSegment
- durationCache = make(map[float64]string)
- )
- head := p.head
- count := p.count
- for i := uint(0); (i < p.winsize || p.winsize == 0) && count > 0; count-- {
- seg = p.Segments[head]
- head = (head + 1) % p.capacity
- if seg == nil { // protection from badly filled chunklists
- continue
- }
- if p.winsize > 0 { // skip for VOD playlists, where winsize = 0
- i++
- }
- if seg.SCTE != nil {
- switch seg.SCTE.Syntax {
- case SCTE35_67_2014:
- p.buf.WriteString("#EXT-SCTE35:")
- p.buf.WriteString("CUE=\"")
- p.buf.WriteString(seg.SCTE.Cue)
- p.buf.WriteRune('"')
- if seg.SCTE.ID != "" {
- p.buf.WriteString(",ID=\"")
- p.buf.WriteString(seg.SCTE.ID)
- p.buf.WriteRune('"')
- }
- if seg.SCTE.Time != 0 {
- p.buf.WriteString(",TIME=")
- p.buf.WriteString(strconv.FormatFloat(seg.SCTE.Time, 'f', -1, 64))
- }
- p.buf.WriteRune('\n')
- case SCTE35_OATCLS:
- switch seg.SCTE.CueType {
- case SCTE35Cue_Start:
- p.buf.WriteString("#EXT-OATCLS-SCTE35:")
- p.buf.WriteString(seg.SCTE.Cue)
- p.buf.WriteRune('\n')
- p.buf.WriteString("#EXT-X-CUE-OUT:")
- p.buf.WriteString(strconv.FormatFloat(seg.SCTE.Time, 'f', -1, 64))
- p.buf.WriteRune('\n')
- case SCTE35Cue_Mid:
- p.buf.WriteString("#EXT-X-CUE-OUT-CONT:")
- p.buf.WriteString("ElapsedTime=")
- p.buf.WriteString(strconv.FormatFloat(seg.SCTE.Elapsed, 'f', -1, 64))
- p.buf.WriteString(",Duration=")
- p.buf.WriteString(strconv.FormatFloat(seg.SCTE.Time, 'f', -1, 64))
- p.buf.WriteString(",SCTE35=")
- p.buf.WriteString(seg.SCTE.Cue)
- p.buf.WriteRune('\n')
- case SCTE35Cue_End:
- p.buf.WriteString("#EXT-X-CUE-IN")
- p.buf.WriteRune('\n')
- }
- }
- }
- // check for key change
- if seg.Key != nil && p.Key != seg.Key {
- p.buf.WriteString("#EXT-X-KEY:")
- p.buf.WriteString("METHOD=")
- p.buf.WriteString(seg.Key.Method)
- if seg.Key.Method != "NONE" {
- p.buf.WriteString(",URI=\"")
- p.buf.WriteString(seg.Key.URI)
- p.buf.WriteRune('"')
- if seg.Key.IV != "" {
- p.buf.WriteString(",IV=")
- p.buf.WriteString(seg.Key.IV)
- }
- if seg.Key.Keyformat != "" {
- p.buf.WriteString(",KEYFORMAT=\"")
- p.buf.WriteString(seg.Key.Keyformat)
- p.buf.WriteRune('"')
- }
- if seg.Key.Keyformatversions != "" {
- p.buf.WriteString(",KEYFORMATVERSIONS=\"")
- p.buf.WriteString(seg.Key.Keyformatversions)
- p.buf.WriteRune('"')
- }
- }
- p.buf.WriteRune('\n')
- }
- if seg.Discontinuity {
- p.buf.WriteString("#EXT-X-DISCONTINUITY\n")
- }
- // ignore segment Map if default playlist Map is present
- if p.Map == nil && seg.Map != nil {
- p.buf.WriteString("#EXT-X-MAP:")
- p.buf.WriteString("URI=\"")
- p.buf.WriteString(seg.Map.URI)
- p.buf.WriteRune('"')
- if seg.Map.Limit > 0 {
- p.buf.WriteString(",BYTERANGE=")
- p.buf.WriteString(strconv.FormatInt(seg.Map.Limit, 10))
- p.buf.WriteRune('@')
- p.buf.WriteString(strconv.FormatInt(seg.Map.Offset, 10))
- }
- p.buf.WriteRune('\n')
- }
- if !seg.ProgramDateTime.IsZero() {
- p.buf.WriteString("#EXT-X-PROGRAM-DATE-TIME:")
- p.buf.WriteString(seg.ProgramDateTime.Format(DATETIME))
- p.buf.WriteRune('\n')
- }
- if seg.Limit > 0 {
- p.buf.WriteString("#EXT-X-BYTERANGE:")
- p.buf.WriteString(strconv.FormatInt(seg.Limit, 10))
- p.buf.WriteRune('@')
- p.buf.WriteString(strconv.FormatInt(seg.Offset, 10))
- p.buf.WriteRune('\n')
- }
- p.buf.WriteString("#EXTINF:")
- if str, ok := durationCache[seg.Duration]; ok {
- p.buf.WriteString(str)
- } else {
- if p.durationAsInt {
- // Old Android players has problems with non integer Duration.
- durationCache[seg.Duration] = strconv.FormatInt(int64(math.Ceil(seg.Duration)), 10)
- } else {
- // Wowza Mediaserver and some others prefer floats.
- durationCache[seg.Duration] = strconv.FormatFloat(seg.Duration, 'f', 3, 32)
- }
- p.buf.WriteString(durationCache[seg.Duration])
- }
- p.buf.WriteRune(',')
- p.buf.WriteString(seg.Title)
- p.buf.WriteRune('\n')
- p.buf.WriteString(seg.URI)
- if p.Args != "" {
- p.buf.WriteRune('?')
- p.buf.WriteString(p.Args)
- }
- p.buf.WriteRune('\n')
- }
- if p.Closed {
- p.buf.WriteString("#EXT-X-ENDLIST\n")
- }
- return &p.buf
- }
- // For compatibility with Stringer interface
- // For example fmt.Printf("%s", sampleMediaList) will encode
- // playist and print its string representation.
- func (p *MediaPlaylist) String() string {
- return p.Encode().String()
- }
- // TargetDuration will be int on Encode
- func (p *MediaPlaylist) DurationAsInt(yes bool) {
- if yes {
- // duration must be integers if protocol version is less than 3
- version(&p.ver, 3)
- }
- p.durationAsInt = yes
- }
- // Count tells us the number of items that are currently in the media playlist
- func (p *MediaPlaylist) Count() uint {
- return p.count
- }
- // Close sliding playlist and make them fixed.
- func (p *MediaPlaylist) Close() {
- if p.buf.Len() > 0 {
- p.buf.WriteString("#EXT-X-ENDLIST\n")
- }
- p.Closed = true
- }
- // Set encryption key appeared once in header of the playlist (pointer to MediaPlaylist.Key).
- // It useful when keys not changed during playback.
- // Set tag for the whole list.
- func (p *MediaPlaylist) SetDefaultKey(method, uri, iv, keyformat, keyformatversions string) error {
- // A Media Playlist MUST indicate a EXT-X-VERSION of 5 or higher if it
- // contains:
- // - The KEYFORMAT and KEYFORMATVERSIONS attributes of the EXT-X-KEY tag.
- if keyformat != "" || keyformatversions != "" {
- version(&p.ver, 5)
- }
- p.Key = &Key{method, uri, iv, keyformat, keyformatversions}
- return nil
- }
- // Set default Media Initialization Section values for playlist (pointer to MediaPlaylist.Map).
- // Set EXT-X-MAP tag for the whole playlist.
- func (p *MediaPlaylist) SetDefaultMap(uri string, limit, offset int64) {
- version(&p.ver, 5) // due section 4
- p.Map = &Map{uri, limit, offset}
- }
- // Mark medialist as consists of only I-frames (Intra frames).
- // Set tag for the whole list.
- func (p *MediaPlaylist) SetIframeOnly() {
- version(&p.ver, 4) // due section 4.3.3
- p.Iframe = true
- }
- // Set encryption key for the current segment of media playlist (pointer to Segment.Key)
- func (p *MediaPlaylist) SetKey(method, uri, iv, keyformat, keyformatversions string) error {
- if p.count == 0 {
- return errors.New("playlist is empty")
- }
- // A Media Playlist MUST indicate a EXT-X-VERSION of 5 or higher if it
- // contains:
- // - The KEYFORMAT and KEYFORMATVERSIONS attributes of the EXT-X-KEY tag.
- if keyformat != "" || keyformatversions != "" {
- version(&p.ver, 5)
- }
- p.Segments[p.last()].Key = &Key{method, uri, iv, keyformat, keyformatversions}
- return nil
- }
- // Set map for the current segment of media playlist (pointer to Segment.Map)
- func (p *MediaPlaylist) SetMap(uri string, limit, offset int64) error {
- if p.count == 0 {
- return errors.New("playlist is empty")
- }
- version(&p.ver, 5) // due section 4
- p.Segments[p.last()].Map = &Map{uri, limit, offset}
- return nil
- }
- // Set limit and offset for the current media segment (EXT-X-BYTERANGE support for protocol version 4).
- func (p *MediaPlaylist) SetRange(limit, offset int64) error {
- if p.count == 0 {
- return errors.New("playlist is empty")
- }
- version(&p.ver, 4) // due section 3.4.1
- p.Segments[p.last()].Limit = limit
- p.Segments[p.last()].Offset = offset
- return nil
- }
- // SetSCTE sets the SCTE cue format for the current media segment.
- //
- // Deprecated: Use SetSCTE35 instead.
- func (p *MediaPlaylist) SetSCTE(cue string, id string, time float64) error {
- return p.SetSCTE35(&SCTE{Syntax: SCTE35_67_2014, Cue: cue, ID: id, Time: time})
- }
- // SetSCTE35 sets the SCTE cue format for the current media segment
- func (p *MediaPlaylist) SetSCTE35(scte35 *SCTE) error {
- if p.count == 0 {
- return errors.New("playlist is empty")
- }
- p.Segments[p.last()].SCTE = scte35
- return nil
- }
- // Set discontinuity flag for the current media segment.
- // EXT-X-DISCONTINUITY indicates an encoding discontinuity between the media segment
- // that follows it and the one that preceded it (i.e. file format, number and type of tracks,
- // encoding parameters, encoding sequence, timestamp sequence).
- func (p *MediaPlaylist) SetDiscontinuity() error {
- if p.count == 0 {
- return errors.New("playlist is empty")
- }
- p.Segments[p.last()].Discontinuity = true
- return nil
- }
- // Set program date and time for the current media segment.
- // EXT-X-PROGRAM-DATE-TIME tag associates the first sample of a
- // media segment with an absolute date and/or time. It applies only
- // to the current media segment.
- // Date/time format is YYYY-MM-DDThh:mm:ssZ (ISO8601) and includes time zone.
- func (p *MediaPlaylist) SetProgramDateTime(value time.Time) error {
- if p.count == 0 {
- return errors.New("playlist is empty")
- }
- p.Segments[p.last()].ProgramDateTime = value
- return nil
- }
- // Version returns the current playlist version number
- func (p *MediaPlaylist) Version() uint8 {
- return p.ver
- }
- // SetVersion sets the playlist version number, note the version maybe changed
- // automatically by other Set methods.
- func (p *MediaPlaylist) SetVersion(ver uint8) {
- p.ver = ver
- }
- // WinSize returns the playlist's window size.
- func (p *MediaPlaylist) WinSize() uint {
- return p.winsize
- }
- // SetWinSize overwrites the playlist's window size.
- func (p *MediaPlaylist) SetWinSize(winsize uint) error {
- if winsize > p.capacity {
- return errors.New("capacity must be greater than winsize or equal")
- }
- p.winsize = winsize
- return nil
- }
|