Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+281
View File
@@ -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())
}
}