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

Multiple keys are supported #205

Open
wants to merge 2 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
31 changes: 18 additions & 13 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 @@ -493,7 +493,7 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
}
case !strings.HasPrefix(line, "#"):
if state.tagInf {
err := p.Append(line, state.duration, state.title)
err := p.Append(line, state.duration, state.title, state.xkeys)
if err == ErrPlaylistFull {
// Extend playlist by doubling size, reset internal state, try again.
// If the second Append fails, the if err block will handle it.
Expand All @@ -502,7 +502,7 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
p.Segments = append(p.Segments, make([]*MediaSegment, p.Count())...)
p.capacity = uint(len(p.Segments))
p.tail = p.count
err = p.Append(line, state.duration, state.title)
err = p.Append(line, state.duration, state.title, state.xkeys)
}
// Check err for first or subsequent Append()
if err != nil {
Expand Down Expand Up @@ -536,11 +536,11 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
}
// If EXT-X-KEY appeared before reference to segment (EXTINF) then it linked to this segment
if state.tagKey {
p.Segments[p.last()].Key = &Key{state.xkey.Method, state.xkey.URI, state.xkey.IV, state.xkey.Keyformat, state.xkey.Keyformatversions}
p.Segments[p.last()].Keys = state.xkeys
// First EXT-X-KEY may appeared in the header of the playlist and linked to first segment
// but for convenient playlist generation it also linked as default playlist key
if p.Key == nil {
p.Key = state.xkey
if p.Keys == nil {
p.Keys = state.xkeys
}
state.tagKey = false
}
Expand Down Expand Up @@ -618,22 +618,27 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
}
}
case strings.HasPrefix(line, "#EXT-X-KEY:"):
xkey := new(Key)
state.listType = MEDIA
state.xkey = new(Key)
if !state.tagKey {
state.xkeys = []*Key{}
}

for k, v := range decodeParamsLine(line[11:]) {
switch k {
case "METHOD":
state.xkey.Method = v
xkey.Method = v
case "URI":
state.xkey.URI = v
xkey.URI = v
case "IV":
state.xkey.IV = v
xkey.IV = v
case "KEYFORMAT":
state.xkey.Keyformat = v
xkey.Keyformat = v
case "KEYFORMATVERSIONS":
state.xkey.Keyformatversions = v
xkey.Keyformatversions = v
}
}
state.xkeys = append(state.xkeys, xkey)
state.tagKey = true
case strings.HasPrefix(line, "#EXT-X-MAP:"):
state.listType = MEDIA
Expand Down
49 changes: 34 additions & 15 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 @@ -305,7 +305,7 @@ func TestDecodeMediaPlaylist(t *testing.T) {
if err != nil {
t.Fatal(err)
}
//fmt.Printf("Playlist object: %+v\n", p)

// check parsed values
if p.ver != 3 {
t.Errorf("Version of parsed playlist = %d (must = 3)", p.ver)
Expand Down Expand Up @@ -402,17 +402,41 @@ func TestDecodeMediaPlaylistWithWidevine(t *testing.T) {
if err != nil {
t.Fatal(err)
}
//fmt.Printf("Playlist object: %+v\n", p)
// check parsed values
if p.ver != 2 {
t.Errorf("Version of parsed playlist = %d (must = 2)", p.ver)
}
if p.TargetDuration != 9 {
t.Errorf("TargetDuration of parsed playlist = %f (must = 9.0)", p.TargetDuration)
}
// TODO check other values…
//fmt.Printf("%+v\n", p.Key)
//fmt.Println(p.Encode().String())

}

// https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.2.4
func TestDecodeMediaPlaylistWithMutipleKeys(t *testing.T) {
f, err := os.Open("sample-playlists/multiple-keys.m3u8")
if err != nil {
t.Fatal(err)
}
p, err := NewMediaPlaylist(5, 798)
if err != nil {
t.Fatalf("Create media playlist failed: %s", err)
}
err = p.DecodeFrom(bufio.NewReader(f), true)
if err != nil {
t.Fatal(err)
}
// check parsed values
if p.ver != 2 {
t.Errorf("Version of parsed playlist = %d (must = 2)", p.ver)
}
if p.TargetDuration != 9 {
t.Errorf("TargetDuration of parsed playlist = %f (must = 9.0)", p.TargetDuration)
}

if len(p.Keys) != 2 {
t.Errorf("Number of keys = %d (must = 2)", len(p.Keys))
}
}

func TestDecodeMasterPlaylistWithAutodetection(t *testing.T) {
Expand All @@ -428,11 +452,6 @@ func TestDecodeMasterPlaylistWithAutodetection(t *testing.T) {
t.Error("Sample not recognized as master playlist.")
}
mp := m.(*MasterPlaylist)
// fmt.Printf(">%+v\n", mp)
// for _, v := range mp.Variants {
// fmt.Printf(">>%+v +v\n", v)
// }
//fmt.Println("Type below must be MasterPlaylist:")
CheckType(t, mp)
}

Expand Down
40 changes: 40 additions & 0 deletions sample-playlists/multiple-keys.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#EXTM3U
#EXT-X-VERSION:2
#EXT-X-ALLOW-CACHE:NO
#EXT-X-KEY:METHOD=AES-128,URI="http://localhost:20001/key?ecm=AAAAAQAAOpgCAAHFYAaVFH6QrFv2wYU1lEaO2L3fGQB1%2FR3oaD9auWtXNAmcVLxgRTvRlHpqHgXX1YY00%2FpdUiOlgONVbViqou2%2FItyDOWc%3D",IV=0X00000000000000000000000000000000
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="http://localhost:20001/key?ecm=AAAAAQAAOpgCAAHFYAaVFH6QrFv2wYU1lEaO2L3fGQB1%2FR3oaD9auWtXNAmcVLxgRTvRlHpqHgXX1YY00%2FpdUiOlgONVbViqou2%2FItyDOWc%3D",IV=0X00000000000000000000000000000000
#EXT-X-MEDIA-SEQUENCE:3080
#EXT-X-TARGETDURATION:9
#WV-AUDIO-CHANNELS 2
#WV-AUDIO-FORMAT 1
#WV-AUDIO-PROFILE-IDC 2
#WV-AUDIO-SAMPLE-SIZE 16
#WV-AUDIO-SAMPLING-FREQUENCY 48000
#WV-CYPHER-VERSION Version 5.0.0.4972 AES SVN_500 (Debug) 20111020-03:01:33
#WV-ECM AAAAAQAAOpgCAAHFYAaVFH6QrFv2wYU1lEaO2L3fGQB1/R3oaD9auWtXNAmcVLxgRTvRlHpqHgXX1YY00/pdUiOlgONVbViqou2/ItyDOWc=
#WV-VIDEO-FORMAT 1
#WV-VIDEO-FRAME-RATE 25
#WV-VIDEO-LEVEL-IDC 12
#WV-VIDEO-PROFILE-IDC 66
#WV-VIDEO-RESOLUTION 320 x 192
#WV-VIDEO-SAR 1:1
#EXTINF:6,
01-3079.ts
#EXTINF:6,
01-3080.ts
#EXTINF:6,
01-3081.ts
#EXTINF:8,
01-3082.ts
#EXTINF:6,
01-3083.ts
#EXTINF:8,
01-3084.ts
#EXTINF:6,
01-3085.ts
#EXTINF:7,
01-3086.ts
#EXTINF:9,
01-3087.ts
#EXTINF:7,
01-3088.ts
61 changes: 31 additions & 30 deletions structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,26 +85,26 @@ const (
//
// Simple Media Playlist file sample:
//
// #EXTM3U
// #EXT-X-VERSION:3
// #EXT-X-TARGETDURATION:5220
// #EXTINF:5219.2,
// http://media.example.com/entire.ts
// #EXT-X-ENDLIST
// #EXTM3U
// #EXT-X-VERSION:3
// #EXT-X-TARGETDURATION:5220
// #EXTINF:5219.2,
// http://media.example.com/entire.ts
// #EXT-X-ENDLIST
//
// Sample of Sliding Window Media Playlist, using HTTPS:
//
// #EXTM3U
// #EXT-X-VERSION:3
// #EXT-X-TARGETDURATION:8
// #EXT-X-MEDIA-SEQUENCE:2680
// #EXTM3U
// #EXT-X-VERSION:3
// #EXT-X-TARGETDURATION:8
// #EXT-X-MEDIA-SEQUENCE:2680
//
// #EXTINF:7.975,
// https://priv.example.com/fileSequence2680.ts
// #EXTINF:7.941,
// https://priv.example.com/fileSequence2681.ts
// #EXTINF:7.975,
// https://priv.example.com/fileSequence2682.ts
// #EXTINF:7.975,
// https://priv.example.com/fileSequence2680.ts
// #EXTINF:7.941,
// https://priv.example.com/fileSequence2681.ts
// #EXTINF:7.975,
// https://priv.example.com/fileSequence2682.ts
type MediaPlaylist struct {
TargetDuration float64
SeqNo uint64 // EXT-X-MEDIA-SEQUENCE
Expand All @@ -125,9 +125,9 @@ type MediaPlaylist struct {
count uint // number of segments added to the playlist
buf bytes.Buffer
ver uint8
Key *Key // EXT-X-KEY is optional encryption key displayed before any segments (default key for the playlist)
Map *Map // EXT-X-MAP is optional tag specifies how to obtain the Media Initialization Section (default map for the playlist)
WV *WV // Widevine related tags outside of M3U8 specs
Keys []*Key // EXT-X-KEY is optional encryption key displayed before any segments (default key for the playlist)
Map *Map // EXT-X-MAP is optional tag specifies how to obtain the Media Initialization Section (default map for the playlist)
WV *WV // Widevine related tags outside of M3U8 specs
Custom map[string]CustomTag
customDecoders []CustomDecoder
}
Expand All @@ -136,15 +136,15 @@ type MediaPlaylist struct {
// combines media playlists for multiple bitrates. URI lines in the
// playlist identify media playlists. Sample of Master Playlist file:
//
// #EXTM3U
// #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000
// http://example.com/low.m3u8
// #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2560000
// http://example.com/mid.m3u8
// #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000
// http://example.com/hi.m3u8
// #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS="mp4a.40.5"
// http://example.com/audio-only.m3u8
// #EXTM3U
// #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000
// http://example.com/low.m3u8
// #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2560000
// http://example.com/mid.m3u8
// #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000
// http://example.com/hi.m3u8
// #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS="mp4a.40.5"
// http://example.com/audio-only.m3u8
type MasterPlaylist struct {
Variants []*Variant
Args string // optional arguments placed after URI (URI?Args)
Expand Down Expand Up @@ -209,7 +209,7 @@ type MediaSegment struct {
Duration float64 // first parameter for EXTINF tag; duration must be integers if protocol version is less than 3 but we are always keep them float
Limit int64 // EXT-X-BYTERANGE <n> is length in bytes for the file under URI
Offset int64 // EXT-X-BYTERANGE [@o] is offset from the start of the file under URI
Key *Key // EXT-X-KEY displayed before the segment and means changing of encryption key (in theory each segment may have own key)
Keys []*Key // EXT-X-KEY displayed before the segment and means changing of encryption key (in theory each segment may have own key)
Map *Map // EXT-X-MAP displayed before the segment
Discontinuity bool // 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)
SCTE *SCTE // SCTE-35 used for Ad signaling in HLS
Expand All @@ -236,6 +236,7 @@ type Key struct {
IV string
Keyformat string
Keyformatversions string
VideoFormat string
}

// Map structure represents specifies how to obtain the Media
Expand Down Expand Up @@ -328,7 +329,7 @@ type decodingState struct {
title string
variant *Variant
alternatives []*Alternative
xkey *Key
xkeys []*Key
xmap *Map
scte *SCTE
custom map[string]CustomTag
Expand Down
Loading