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)
}
Append general chunk to the tail of chunk slice for a media playlist.
This operation does reset playlist cache.
package m3u8
import (
	"bytes"
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"
)
var ErrPlaylistFull = errors.New("playlist is full")
func version(ver *uint8, newver uint8) {
	if *ver < newver {
		*ver = newver
	}
}
func strver(ver uint8) string {
	return strconv.FormatUint(uint64(ver), 10)
}
func NewMasterPlaylist() *MasterPlaylist {
	p := new(MasterPlaylist)
	p.ver = minver
	return p
}
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 {
		version(&p.ver, 4)  
	}
	p.buf.Reset()
}
func (p *MasterPlaylist) ResetCache() {
	p.buf.Reset()
}
func (p *MasterPlaylist) Encode() *bytes.Buffer {
	if p.buf.Len() > 0 {
		return &p.buf
	}
	p.buf.WriteString("#EXTM3U\n")
	if p.Twitch == "" {
		p.buf.WriteString("#EXT-X-VERSION:")
		p.buf.WriteString(strver(p.ver))
		p.buf.WriteRune('\n')
	}
	for _, c := range p.Comments {
		p.buf.WriteString(c)
		p.buf.WriteRune('\n')
	}
	if p.IndependentSegments() {
		p.buf.WriteString("#EXT-X-INDEPENDENT-SEGMENTS\n")
	}
	if p.Twitch != "" {
		p.buf.WriteString(string(p.Twitch))
		p.buf.WriteRune('\n')
	}
	if p.Custom != nil {
		for _, v := range p.Custom {
			if customBuf := v.Encode(); customBuf != nil {
				p.buf.WriteString(customBuf.String())
				p.buf.WriteRune('\n')
			}
		}
	}
	p.encodeAlternatives("VIDEO")
	p.encodeAlternatives("AUDIO")
	p.encodeAlternatives("SUBTITLES")
	p.encodeAlternatives("CLOSED-CAPTIONS")
	for _, pl := range p.Variants {
		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.AverageBandwidth != 0 {
				p.buf.WriteString(",AVERAGE-BANDWIDTH=")
				p.buf.WriteString(strconv.FormatUint(uint64(pl.AverageBandwidth), 10))
			}
			if pl.Codecs != "" {
				p.buf.WriteString(",CODECS=\"")
				p.buf.WriteString(pl.Codecs)
				p.buf.WriteRune('"')
			}
			if pl.Resolution != "" {
				p.buf.WriteString(",RESOLUTION=")  
				p.buf.WriteString(pl.Resolution)
			}
			if pl.Video != "" {
				p.buf.WriteString(",VIDEO=\"")
				p.buf.WriteString(pl.Video)
				p.buf.WriteRune('"')
			}
			if pl.VideoRange != "" {
				p.buf.WriteString(",VIDEO-RANGE=")
				p.buf.WriteString(pl.VideoRange)
			}
			if pl.HDCPLevel != "" {
				p.buf.WriteString(",HDCP-LEVEL=")
				p.buf.WriteString(pl.HDCPLevel)
			}
			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.AverageBandwidth != 0 {
				p.buf.WriteString(",AVERAGE-BANDWIDTH=")
				p.buf.WriteString(strconv.FormatUint(uint64(pl.AverageBandwidth), 10))
			}
			if pl.Codecs != "" {
				p.buf.WriteString(",CODECS=\"")
				p.buf.WriteString(pl.Codecs)
				p.buf.WriteRune('"')
			}
			if pl.Resolution != "" {
				p.buf.WriteString(",RESOLUTION=")  
				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)  
				} 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('"')
			}
			if pl.FrameRate != 0 {
				p.buf.WriteString(",FRAME-RATE=")
				p.buf.WriteString(strconv.FormatFloat(pl.FrameRate, 'f', 3, 64))
			}
			if pl.VideoRange != "" {
				p.buf.WriteString(",VIDEO-RANGE=")
				p.buf.WriteString(pl.VideoRange)
			}
			if pl.HDCPLevel != "" {
				p.buf.WriteString(",HDCP-LEVEL=")
				p.buf.WriteString(pl.HDCPLevel)
			}
			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
}
func (p *MasterPlaylist) encodeAlternatives(altType string) {
	var altsWritten = make(map[string]bool)
	for _, pl := range p.Variants {
		if pl.Alternatives != nil {
			for _, alt := range pl.Alternatives {
				altKey := fmt.Sprintf("%s-%s-%s-%s", alt.Type, alt.GroupId, alt.Name, alt.Language)
				if altsWritten[altKey] || alt.Type != altType {
					continue
				}
				altsWritten[altKey] = true
				p.buf.WriteString("#EXT-X-MEDIA:")
				if alt.Type != "" {
					p.buf.WriteString("TYPE=")  
					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.Channels != "" {
					p.buf.WriteString(",CHANNELS=\"")
					p.buf.WriteString(alt.Channels)
					p.buf.WriteRune('"')
				}
				if alt.URI != "" {
					p.buf.WriteString(",URI=\"")
					p.buf.WriteString(alt.URI)
					p.buf.WriteRune('"')
				}
				if alt.InstreamID != "" {
					p.buf.WriteString(",INSTREAM-ID=\"")
					p.buf.WriteString(alt.InstreamID)
					p.buf.WriteRune('"')
				}
				p.buf.WriteRune('\n')
			}
		}
	}
}
func (p *MasterPlaylist) SetCustomTag(tag CustomTag) {
	if p.Custom == nil {
		p.Custom = make(map[string]CustomTag)
	}
	p.Custom[tag.TagName()] = tag
}
func (p *MasterPlaylist) Version() uint8 {
	return p.ver
}
func (p *MasterPlaylist) SetVersion(ver uint8) {
	p.ver = ver
}
func (p *MasterPlaylist) IndependentSegments() bool {
	return p.independentSegments
}
func (p *MasterPlaylist) SetIndependentSegments(b bool) {
	p.independentSegments = b
}
func (p *MasterPlaylist) String() string {
	return p.Encode().String()
}
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
}
func (p *MediaPlaylist) last() uint {
	if p.tail == 0 {
		return p.capacity - 1
	}
	return p.tail - 1
}
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
}