@@ -0,0 +1,281 @@
|
||||
package m3u8
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"91porn-server/common/constant"
|
||||
"91porn-server/common/constant/redisconst"
|
||||
|
||||
grafovm3u8 "github.com/grafov/m3u8"
|
||||
)
|
||||
|
||||
func TestM3u8PureCacheKeyUsesFullSource(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sourceA string
|
||||
sourceB string
|
||||
}{
|
||||
{
|
||||
name: "same basename in different directories",
|
||||
sourceA: "/sp/movie-a/index.m3u8",
|
||||
sourceB: "/sp/movie-b/index.m3u8",
|
||||
},
|
||||
{
|
||||
name: "filenames previously collapsed by strings.Trim",
|
||||
sourceA: "/sp/movie/movie.m3u8",
|
||||
sourceB: "/sp/movie/ovie.m3u8",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
keyA := m3u8PureCacheKey(tt.sourceA, constant.MediaSourceSP)
|
||||
keyB := m3u8PureCacheKey(tt.sourceB, constant.MediaSourceSP)
|
||||
if keyA == keyB {
|
||||
t.Fatalf("different playlist sources share cache key %q", keyA)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeLaosijiM3u8Source(t *testing.T) {
|
||||
for _, source := range []string{
|
||||
"laosiji/m3m/movie/index.m3u8",
|
||||
"/laosiji/m3m/movie/index.m3u8",
|
||||
} {
|
||||
if got := normalizeLaosijiM3u8Source(source); got != "m3m/movie/index.m3u8" {
|
||||
t.Fatalf("normalizeLaosijiM3u8Source(%q) = %q", source, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestM3u8PureCacheKeyNormalizesSourceAndIncludesMediaSource(t *testing.T) {
|
||||
const source = "/sp/movie/season/../index.m3u8?b=2&a=1"
|
||||
normalizedVariant := " /sp/movie/index.m3u8?a=1&b=2#ignored "
|
||||
|
||||
spKey := m3u8PureCacheKey(source, constant.MediaSourceSP)
|
||||
if got := m3u8PureCacheKey(normalizedVariant, constant.MediaSourceSP); got != spKey {
|
||||
t.Fatalf("equivalent sources should share a cache key: %q != %q", got, spKey)
|
||||
}
|
||||
if got := m3u8PureCacheKey(source, constant.MediaSourcePMS); got == spKey {
|
||||
t.Fatalf("different media sources should not share a cache key: %q", got)
|
||||
}
|
||||
if !strings.HasPrefix(spKey, redisconst.M3u8PureCacheFmt(constant.MediaSourceSP+":")) {
|
||||
t.Fatalf("cache key should include media source prefix, got %q", spKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeFromReaderKeepsHevcMap(t *testing.T) {
|
||||
data := []byte(`#EXTM3U
|
||||
#EXT-X-TARGETDURATION:6
|
||||
#EXT-X-VERSION:6
|
||||
#EXT-X-MEDIA-SEQUENCE:1
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-MAP:URI="https://cdn.g3ejjm8m.com/rk130/m3u8-v5/f98/f98495114eca4ae66714b0d0249d8980/683a_0.m4s"
|
||||
#EXTINF:6.006,
|
||||
683a_1.m4s
|
||||
#EXTINF:6.006,
|
||||
683a_2.m4s
|
||||
#EXT-X-ENDLIST
|
||||
`)
|
||||
buf := DecodeFromReader(
|
||||
data,
|
||||
"https://rs.hyxrp.cn",
|
||||
"/api/app/vid/lsjsec",
|
||||
"",
|
||||
constant.MediaSourceLaoSiJi,
|
||||
constant.MediaSourceAuthKey,
|
||||
constant.DefaultTsAuthKeyVersion,
|
||||
"m3m/demo.m3u8",
|
||||
)
|
||||
if buf == nil {
|
||||
t.Fatal("DecodeFromReader returned nil")
|
||||
}
|
||||
|
||||
got := buf.String()
|
||||
for _, want := range []string{
|
||||
`#EXT-X-MAP:URI="https://rs.hyxrp.cn/laosiji/rk130/m3u8-v5/f98/f98495114eca4ae66714b0d0249d8980/683a_0.m4s?md=`,
|
||||
`https://rs.hyxrp.cn/laosiji/rk130/m3u8-v5/f98/f98495114eca4ae66714b0d0249d8980/683a_1.m4s?md=`,
|
||||
`https://rs.hyxrp.cn/laosiji/rk130/m3u8-v5/f98/f98495114eca4ae66714b0d0249d8980/683a_2.m4s?md=`,
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("rewritten playlist missing %q:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
if strings.Contains(got, "#EXT-X-KEY") {
|
||||
t.Fatalf("unencrypted HEVC playlist should not add EXT-X-KEY:\n%s", got)
|
||||
}
|
||||
if strings.Contains(got, "/laosiji/683a_") {
|
||||
t.Fatalf("relative HEVC segments were not resolved against EXT-X-MAP:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeFromReaderKeepsEncryptedH264Key(t *testing.T) {
|
||||
data := []byte(`#EXTM3U
|
||||
#EXT-X-TARGETDURATION:6
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-KEY:METHOD=AES-128,URI="source.key",IV=0x00000000000000000000000000000001
|
||||
#EXTINF:6,
|
||||
segment.ts
|
||||
#EXT-X-ENDLIST
|
||||
`)
|
||||
buf := DecodeFromReader(
|
||||
data,
|
||||
"https://cdn.example.com/video",
|
||||
"/api/app/vid/sec",
|
||||
"",
|
||||
constant.MediaSourceSP,
|
||||
"",
|
||||
constant.DefaultTsAuthKeyVersion,
|
||||
"sp/demo.m3u8",
|
||||
)
|
||||
if buf == nil {
|
||||
t.Fatal("DecodeFromReader returned nil")
|
||||
}
|
||||
|
||||
got := buf.String()
|
||||
for _, want := range []string{
|
||||
`#EXT-X-KEY:METHOD=AES-128,URI="/api/app/vid/sec",IV=0x00000000000000000000000000000001`,
|
||||
"https://cdn.example.com/video/segment.ts",
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("rewritten playlist missing %q:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateResolvesFirstSegmentAgainstSegmentMap(t *testing.T) {
|
||||
src, err := grafovm3u8.NewMediaPlaylist(1, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("create source playlist: %v", err)
|
||||
}
|
||||
if err = src.Append("first.m4s", 6, ""); err != nil {
|
||||
t.Fatalf("append source segment: %v", err)
|
||||
}
|
||||
mapURI := "https://cdn.g3ejjm8m.com/hevc/movie/init.m4s"
|
||||
if err = src.SetMap(mapURI, 0, 0); err != nil {
|
||||
t.Fatalf("set source segment map: %v", err)
|
||||
}
|
||||
if src.Map != nil {
|
||||
t.Fatal("test setup must use a segment-level map")
|
||||
}
|
||||
|
||||
buf := Create(
|
||||
src,
|
||||
"https://rs.hyxrp.cn",
|
||||
"/api/app/vid/lsjsec",
|
||||
"",
|
||||
constant.MediaSourceLaoSiJi,
|
||||
constant.MediaSourceAuthKey,
|
||||
constant.DefaultTsAuthKeyVersion,
|
||||
"m3m/demo.m3u8",
|
||||
)
|
||||
if buf == nil {
|
||||
t.Fatal("Create returned nil")
|
||||
}
|
||||
|
||||
got := buf.String()
|
||||
for _, want := range []string{
|
||||
`#EXT-X-MAP:URI="https://rs.hyxrp.cn/laosiji/hevc/movie/init.m4s?md=`,
|
||||
"https://rs.hyxrp.cn/laosiji/hevc/movie/first.m4s?md=",
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("rewritten playlist missing %q:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
if strings.Contains(got, "/laosiji/first.m4s") {
|
||||
t.Fatalf("first relative segment was not resolved against its map:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeFromReaderPreservesCMAFSegmentMetadata(t *testing.T) {
|
||||
data := []byte(`#EXTM3U
|
||||
#EXT-X-VERSION:6
|
||||
#EXT-X-TARGETDURATION:6
|
||||
#EXT-X-KEY:METHOD=AES-128,URI="first.key",IV=0x00000000000000000000000000000001
|
||||
#EXT-X-MAP:URI="https://cdn.example.com/cmaf/init.mp4"
|
||||
#EXT-X-BYTERANGE:100@0
|
||||
#EXTINF:6,
|
||||
chunk.mp4
|
||||
#EXT-X-DISCONTINUITY
|
||||
#EXT-X-KEY:METHOD=AES-128,URI="second.key",IV=0x00000000000000000000000000000002
|
||||
#EXT-X-BYTERANGE:120@100
|
||||
#EXTINF:6,
|
||||
chunk.mp4
|
||||
#EXT-X-ENDLIST
|
||||
`)
|
||||
buf := DecodeFromReader(
|
||||
data,
|
||||
"https://play.example.com",
|
||||
"/api/app/vid/sec",
|
||||
"",
|
||||
constant.MediaSourceSP,
|
||||
"",
|
||||
constant.DefaultTsAuthKeyVersion,
|
||||
"sp/cmaf/index.m3u8",
|
||||
)
|
||||
if buf == nil {
|
||||
t.Fatal("DecodeFromReader returned nil")
|
||||
}
|
||||
|
||||
got := buf.String()
|
||||
for _, want := range []string{
|
||||
"#EXT-X-BYTERANGE:100@0",
|
||||
"#EXT-X-BYTERANGE:120@100",
|
||||
"#EXT-X-DISCONTINUITY",
|
||||
"IV=0x00000000000000000000000000000001",
|
||||
"IV=0x00000000000000000000000000000002",
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("rewritten playlist missing %q:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
if count := strings.Count(got, "#EXT-X-KEY:"); count != 2 {
|
||||
t.Fatalf("rewritten playlist key count = %d, want 2:\n%s", count, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteMasterPlaylist(t *testing.T) {
|
||||
input := []byte(`#EXTM3U
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="main",DEFAULT=YES,AUTOSELECT=YES,URI="audio/index.m3u8"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1500000,RESOLUTION=1280x720,AUDIO="audio"
|
||||
video/720/index.m3u8
|
||||
`)
|
||||
output, err := RewriteMasterPlaylist(input, func(rawURI string) (string, error) {
|
||||
return "/signed/" + rawURI, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("RewriteMasterPlaylist failed: %v", err)
|
||||
}
|
||||
playlist, listType, err := grafovm3u8.DecodeFrom(bytes.NewReader(output.Bytes()), false)
|
||||
if err != nil {
|
||||
t.Fatalf("decode rewritten master: %v", err)
|
||||
}
|
||||
if listType != grafovm3u8.MASTER {
|
||||
t.Fatalf("list type = %v, want master", listType)
|
||||
}
|
||||
master := playlist.(*grafovm3u8.MasterPlaylist)
|
||||
if len(master.Variants) != 1 || master.Variants[0].URI != "/signed/video/720/index.m3u8" {
|
||||
t.Fatalf("variant was not rewritten: %+v", master.Variants)
|
||||
}
|
||||
if len(master.Variants[0].Alternatives) != 1 ||
|
||||
master.Variants[0].Alternatives[0].URI != "/signed/audio/index.m3u8" {
|
||||
t.Fatalf("alternative was not rewritten: %+v", master.Variants[0].Alternatives)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteMasterPlaylistLeavesMediaPlaylistUnchanged(t *testing.T) {
|
||||
input := []byte("#EXTM3U\n#EXT-X-TARGETDURATION:4\n#EXTINF:4,\nsegment.ts\n#EXT-X-ENDLIST\n")
|
||||
output, err := RewriteMasterPlaylist(input, func(string) (string, error) {
|
||||
t.Fatal("media playlist URI rewriter must not be called")
|
||||
return "", nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("RewriteMasterPlaylist failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(output.Bytes(), input) {
|
||||
t.Fatalf("media playlist changed:\n%s", output.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user