Skip to content
This repository has been archived by the owner on Dec 8, 2024. It is now read-only.

thought of how to address "alternatives" to fix inconsistency #192

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,15 +331,11 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st
alt.URI = v
}
}
state.alternatives = append(state.alternatives, &alt)
p.Alternatives = append(p.Alternatives, &alt)
case !state.tagStreamInf && strings.HasPrefix(line, "#EXT-X-STREAM-INF:"):
state.tagStreamInf = true
state.listType = MASTER
state.variant = new(Variant)
if len(state.alternatives) > 0 {
state.variant.Alternatives = state.alternatives
state.alternatives = nil
}
p.Variants = append(p.Variants, state.variant)
for k, v := range decodeParamsLine(line[18:]) {
switch k {
Expand Down Expand Up @@ -395,10 +391,6 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st
state.listType = MASTER
state.variant = new(Variant)
state.variant.Iframe = true
if len(state.alternatives) > 0 {
state.variant.Alternatives = state.alternatives
state.alternatives = nil
}
p.Variants = append(p.Variants, state.variant)
for k, v := range decodeParamsLine(line[26:]) {
switch k {
Expand Down
30 changes: 18 additions & 12 deletions reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,26 @@ func TestDecodeMasterPlaylistWithAlternatives(t *testing.T) {
t.Fatal("not all variants in master playlist parsed")
}
// TODO check other values
for i, v := range p.Variants {
if i == 0 && len(v.Alternatives) != 3 {
t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", len(v.Alternatives))
}
if i == 1 && len(v.Alternatives) != 3 {
t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", len(v.Alternatives))
}
if i == 2 && len(v.Alternatives) != 3 {
t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", len(v.Alternatives))
}
if i == 3 && len(v.Alternatives) > 0 {
t.Fatal("should not be alternatives for this variant")
var low_count, mid_count, hi_count int
for _, a := range p.Alternatives {
switch a.GroupId {
case "low":
low_count++
case "mid":
mid_count++
case "hi":
hi_count++
}
}
if low_count != 3 {
t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", low_count)
}
if mid_count != 3 {
t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", mid_count)
}
if hi_count != 3 {
t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", hi_count)
}
// fmt.Println(p.Encode().String())
}

Expand Down
9 changes: 4 additions & 5 deletions structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ type MediaPlaylist struct {
// http://example.com/audio-only.m3u8
type MasterPlaylist struct {
Variants []*Variant
Args string // optional arguments placed after URI (URI?Args)
CypherVersion string // non-standard tag for Widevine (see also WV struct)
Alternatives []*Alternative // EXT-X-MEDIA
Args string // optional arguments placed after URI (URI?Args)
CypherVersion string // non-standard tag for Widevine (see also WV struct)
buf bytes.Buffer
ver uint8
independentSegments bool
Expand Down Expand Up @@ -181,8 +182,7 @@ type VariantParams struct {
Iframe bool // EXT-X-I-FRAME-STREAM-INF
VideoRange string
HDCPLevel string
FrameRate float64 // EXT-X-STREAM-INF
Alternatives []*Alternative // EXT-X-MEDIA
FrameRate float64 // EXT-X-STREAM-INF
}

// Alternative structure represents EXT-X-MEDIA tag in variants.
Expand Down Expand Up @@ -327,7 +327,6 @@ type decodingState struct {
duration float64
title string
variant *Variant
alternatives []*Alternative
xkey *Key
xmap *Map
scte *SCTE
Expand Down
144 changes: 70 additions & 74 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ package m3u8
import (
"bytes"
"errors"
"fmt"
"math"
"strconv"
"strings"
Expand Down Expand Up @@ -43,6 +42,21 @@ func NewMasterPlaylist() *MasterPlaylist {
return p
}

func (p *MasterPlaylist) AppendAlternative(alt Alternative) {
a := new(Alternative)
*a = alt
p.Alternatives = append(p.Alternatives, a)
// 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()
}

// Append appends a variant to master playlist. This operation does
// reset playlist cache.
func (p *MasterPlaylist) Append(uri string, chunklist *MediaPlaylist, params VariantParams) {
Expand All @@ -51,16 +65,6 @@ func (p *MasterPlaylist) Append(uri string, chunklist *MediaPlaylist, params Var
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()
}

Expand Down Expand Up @@ -93,71 +97,63 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer {
}
}

var altsWritten = 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 p.Alternatives != nil {
for _, alt := range p.Alternatives {
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')
}
}

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))
Expand Down
3 changes: 2 additions & 1 deletion writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,8 @@ func TestNewMasterPlaylistWithAlternatives(t *testing.T) {
t.Errorf("Add segment #%d to a media playlist failed: %s", i, e)
}
}
m.Append("chunklist1.m3u8", p, VariantParams{Alternatives: []*Alternative{audioAlt}})
m.Append("chunklist1.m3u8", p, VariantParams{})
m.AppendAlternative(*audioAlt)

if m.ver != 4 {
t.Fatalf("Expected version 4, actual, %d", m.ver)
Expand Down