Skip to content

Commit

Permalink
Merge pull request #1 from jwplayer/feature/SSAI-158-jwplayer-m3u8
Browse files Browse the repository at this point in the history
Feature/ssai 158 jwplayer m3u8
  • Loading branch information
rogerpales authored Feb 14, 2023
2 parents b0c4e14 + 336fe69 commit 47edf19
Show file tree
Hide file tree
Showing 14 changed files with 508 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .deepsource.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ name = "go"
enabled = true

[analyzers.meta]
import_path = "github.com/grafov/m3u8"
import_path = "github.com/jwplayer/m3u8"
2 changes: 1 addition & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: default

workspace:
base: /go
path: src/github.com/grafov/m3u8
path: src/github.com/jwplayer/m3u8

steps:
- name: test
Expand Down
6 changes: 3 additions & 3 deletions example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"os"
"path"

"github.com/grafov/m3u8"
"github.com/grafov/m3u8/example/template"
"github.com/jwplayer/m3u8"
"github.com/jwplayer/m3u8/example/template"
)

func main() {
Expand All @@ -16,7 +16,7 @@ func main() {
panic("$GOPATH is empty")
}

m3u8File := "github.com/grafov/m3u8/sample-playlists/media-playlist-with-custom-tags.m3u8"
m3u8File := "github.com/jwplayer/m3u8/sample-playlists/media-playlist-with-custom-tags.m3u8"
f, err := os.Open(path.Join(GOPATH, "src", m3u8File))
if err != nil {
panic(err)
Expand Down
2 changes: 1 addition & 1 deletion example/template/custom-playlist-tag-template.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"
"strconv"

"github.com/grafov/m3u8"
"github.com/jwplayer/m3u8"
)

// #CUSTOM-PLAYLIST-TAG:<number>
Expand Down
2 changes: 1 addition & 1 deletion example/template/custom-segment-tag-template.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bytes"
"errors"

"github.com/grafov/m3u8"
"github.com/jwplayer/m3u8"
)

// #CUSTOM-SEGMENT-TAG:<attribute-list>
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/grafov/m3u8
module github.com/jwplayer/m3u8

go 1.12
54 changes: 52 additions & 2 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ var reKeyValue = regexp.MustCompile(`([a-zA-Z0-9_-]+)=("[^"]+"|[^",]+)`)

// TimeParse allows globally apply and/or override Time Parser function.
// Available variants:
// * FullTimeParse - implements full featured ISO/IEC 8601:2004
// * StrictTimeParse - implements only RFC3339 Nanoseconds format
// - FullTimeParse - implements full featured ISO/IEC 8601:2004
// - StrictTimeParse - implements only RFC3339 Nanoseconds format
var TimeParse func(value string) (time.Time, error) = FullTimeParse

// Decode parses a master playlist passed from the buffer. If `strict`
Expand Down Expand Up @@ -329,6 +329,8 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st
alt.Subtitles = v
case "URI":
alt.URI = v
case "INSTREAM-ID":
alt.InstreamId = v
}
}
state.alternatives = append(state.alternatives, &alt)
Expand Down Expand Up @@ -561,6 +563,10 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
state.custom = make(map[string]CustomTag)
state.tagCustom = false
}
if len(state.daterange) > 0 {
p.SetDateRange(state.daterange)
state.daterange = []*DateRange{}
}
// start tag first
case line == "#EXTM3U":
state.m3u = true
Expand Down Expand Up @@ -655,6 +661,41 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
if state.programDateTime, err = TimeParse(line[25:]); strict && err != nil {
return err
}
case strings.HasPrefix(line, "#EXT-X-DATERANGE:"):
dr := new(DateRange)
for k, v := range decodeParamsLine(line[17:]) {
switch k {
case "ID":
dr.ID = v
case "CLASS":
dr.Class = v
case "START-DATE":
dr.StartDate, _ = time.Parse(DATETIME, v)
case "END-DATE":
dr.EndDate, _ = time.Parse(DATETIME, v)
case "DURATION":
dr.Duration, _ = strconv.ParseFloat(v, 64)
case "PLANNED-DURATION":
dr.PlannedDuration, _ = strconv.ParseFloat(v, 64)
case "SCTE35-CMD":
dr.SCTE35Cmd = v
case "SCTE35-OUT":
dr.SCTE35Out = v
case "SCTE35-IN":
dr.SCTE35In = v
case "END-ON-NEXT":
dr.EndOnNext = v
default:
if strings.HasPrefix(k, "X-") {
dr.X[k] = v
} else {
if strict {
return fmt.Errorf("unrecognized EXT-X-DATERANGE attribte: %s", k)
}
}
}
}
state.daterange = append(state.daterange, dr)
case !state.tagRange && strings.HasPrefix(line, "#EXT-X-BYTERANGE:"):
state.tagRange = true
state.listType = MEDIA
Expand Down Expand Up @@ -708,6 +749,15 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
state.scte.Elapsed, _ = strconv.ParseFloat(value, 64)
}
}
case !state.tagSCTE35 && strings.HasPrefix(line, "#EXT-X-CUE-OUT"):
state.tagSCTE35 = true
state.scte = new(SCTE)
state.scte.Syntax = SCTE35_OATCLS
state.scte.CueType = SCTE35Cue_Start
lenLine := len(line)
if lenLine > 14 {
state.scte.Time, _ = strconv.ParseFloat(line[15:], 64)
}
case !state.tagSCTE35 && line == "#EXT-X-CUE-IN":
state.tagSCTE35 = true
state.scte = new(SCTE)
Expand Down
96 changes: 91 additions & 5 deletions reader_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
Playlist parsing tests.
Playlist parsing tests.
Copyright 2013-2019 The Project Developers.
See the AUTHORS and LICENSE files at the top-level directory of this distribution
and at https://github.com/grafov/m3u8/
Copyright 2013-2019 The Project Developers.
See the AUTHORS and LICENSE files at the top-level directory of this distribution
and at https://github.com/grafov/m3u8/
ॐ तारे तुत्तारे तुरे स्व
ॐ तारे तुत्तारे तुरे स्व
*/
package m3u8

Expand Down Expand Up @@ -564,6 +564,57 @@ func TestMediaPlaylistWithOATCLSSCTE35Tag(t *testing.T) {
}
}

func TestMediaPlaylistWithDATERANAGETags(t *testing.T) {
f, err := os.Open("sample-playlists/media-playlist-with-daterange.m3u8")
if err != nil {
t.Fatal(err)
}
p, _, err := DecodeFrom(bufio.NewReader(f), true)
if err != nil {
t.Fatal(err)
}
pp := p.(*MediaPlaylist)

d1, _ := time.Parse(time.RFC3339, "2022-04-26T12:00:00.000000Z")
d2, _ := time.Parse(time.RFC3339, "2022-04-26T12:00:26.000000Z")
expect := map[int][]*DateRange{
1: {
{ID: "1", StartDate: d1, Duration: 26.0, SCTE35Cmd: "0xFC304A000000000BB800FFF00506FFCD4CC4900034023243554549FFFFFFFF7FFF0002631D50011E30303034304D4130303030303030303030383554303432373232303530302101003B1B1B19"},
{ID: "2", StartDate: d1, PlannedDuration: 26.0, SCTE35Out: "0xFC304A000000000BB80000000506FFCD4CC4900034023243554549FFFFFFFF7FFF0000E969E8011E30303034304D413030303030303030303038355430343237323230343330220103AF904DEA"},
},
3: {
{ID: "3", StartDate: d1, Duration: 26.0, SCTE35Cmd: "0xFC304A000000000BB80000000506FFCD5A80300034023243554549FFFFFFFF7FFF0000E969E8011E30303034304D41303030303030303030303835543034323732323034333001010349323975"},
},
5: {
{ID: "4", StartDate: d1, EndDate: d2, Duration: 26.0, SCTE35In: "0xFC304A000000000BB80000000506FFCE363A300034023243554549FFFFFFFF7FFF0000E969E8011E30303034304D4130303030303030303030383554303432373232303433302301031D34C4FF"},
{ID: "5", StartDate: d2, Duration: 416.766666, SCTE35Cmd: "0xFC304A000000000BB800FFF00506FFCE363A300034023243554549FFFFFFFF7FFF00023C5788011E30303034304D4130303030303030303030383554303432373232303433302001002A4FFFBD"},
},
}

for i, v := range expect {
for j, dr := range pp.Segments[i].DateRange {
if v[j].ID != dr.ID {
t.Errorf("daterange comparison error ID %s != %s", v[j].ID, dr.ID)
}
if v[j].Duration != dr.Duration {
t.Errorf("daterange comparison error Duration %f != %f", v[j].Duration, dr.Duration)
}
if v[j].PlannedDuration != dr.PlannedDuration {
t.Errorf("daterange comparison error PlannedDuration %f != %f", v[j].PlannedDuration, dr.PlannedDuration)
}
if v[j].SCTE35Cmd != dr.SCTE35Cmd {
t.Errorf("daterange comparison error SCTE35Cmd %s != %s", v[j].SCTE35Cmd, dr.SCTE35Cmd)
}
if v[j].SCTE35Out != dr.SCTE35Out {
t.Errorf("daterange comparison error SCTE35Out %s != %s", v[j].SCTE35Out, dr.SCTE35Out)
}
if v[j].SCTE35In != dr.SCTE35In {
t.Errorf("daterange comparison error SCTE35In %s != %s", v[j].SCTE35In, dr.SCTE35In)
}
}
}
}

func TestDecodeMediaPlaylistWithDiscontinuitySeq(t *testing.T) {
f, err := os.Open("sample-playlists/media-playlist-with-discontinuity-seq.m3u8")
if err != nil {
Expand Down Expand Up @@ -972,6 +1023,41 @@ func TestDecodeMediaPlaylistStartTime(t *testing.T) {
}
}

func TestDecodeMediaPlaylistWithCueOutCueIn(t *testing.T) {
f, err := os.Open("sample-playlists/media-playlist-with-cue-out-in-without-oatcls.m3u8")
if err != nil {
t.Fatal(err)
}
p, listType, err := DecodeFrom(bufio.NewReader(f), true)
if err != nil {
t.Fatal(err)
}
pp := p.(*MediaPlaylist)
CheckType(t, pp)
if listType != MEDIA {
t.Error("Sample not recognized as media playlist.")
}

if pp.Segments[5].SCTE.CueType != SCTE35Cue_Start {
t.Errorf("EXT-CUE-OUT must result in SCTE35Cue_Start")
}
if pp.Segments[5].SCTE.Time != 0 {
t.Errorf("EXT-CUE-OUT without duration must not have Time set")
}
if pp.Segments[9].SCTE.CueType != SCTE35Cue_End {
t.Errorf("EXT-CUE-IN must result in SCTE35Cue_End")
}
if pp.Segments[30].SCTE.CueType != SCTE35Cue_Start {
t.Errorf("EXT-CUE-OUT must result in SCTE35Cue_Start")
}
if pp.Segments[30].SCTE.Time != 180 {
t.Errorf("EXT-CUE-OUT:180.0 must have time set to 180")
}
if pp.Segments[60].SCTE.CueType != SCTE35Cue_End {
t.Errorf("EXT-CUE-IN must result in SCTE35Cue_End")
}
}

/****************
* Benchmarks *
****************/
Expand Down
Loading

0 comments on commit 47edf19

Please sign in to comment.