diff --git a/.github/workflows/playback.yml b/.github/workflows/playback.yml new file mode 100644 index 0000000..24dd7d0 --- /dev/null +++ b/.github/workflows/playback.yml @@ -0,0 +1,43 @@ +name: Playback core regression + +on: + push: + branches: [main, master, 'fix/**'] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: playback-${{ github.ref }} + cancel-in-progress: true + +env: + GOPROXY: https://proxy.golang.org,direct + GOSUMDB: sum.golang.org + +jobs: + playback-core: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + all-sources: ['false', 'true'] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: native/go.mod + cache-dependency-path: native/go.sum + - name: 播放与下载核心回归 + working-directory: native + env: + ALL_SOURCES: ${{ matrix.all-sources }} + run: >- + go test -count=1 -race -timeout=5m + -ldflags="-X duanjuapp/native/core.buildAllSources=$ALL_SOURCES" + ./core + -run 'TestNative(Playback|Download|Downloaded|HLS|Danmaku)|TestDownloadMigration|TestLegacyPlayback' + diff --git a/native/core/app_stream.go b/native/core/app_stream.go index f58a21c..e55aa96 100644 --- a/native/core/app_stream.go +++ b/native/core/app_stream.go @@ -95,7 +95,7 @@ func (stream *nativeStreamServer) nativeOpen(media providerMedia) (string, strin stream.sessions[token] = session stream.mu.Unlock() entry := nativeStreamAsset{address: media.URL, contentType: "video/mp4"} - isHLS := media.Playlist != "" || len(media.HLSKey) > 0 || strings.Contains(strings.ToLower(media.URL), "m3u8") || strings.Contains(strings.ToLower(media.URL), "hls") + isHLS := media.Playlist != "" || len(media.HLSKey) > 0 if isHLS { entry.contentType = "application/vnd.apple.mpegurl" } @@ -301,7 +301,7 @@ func (stream *nativeStreamServer) nativeServe(writer http.ResponseWriter, reques if response.Request != nil && response.Request.URL != nil { finalURL = response.Request.URL } - playlist := strings.Contains(asset.contentType, "mpegurl") || strings.Contains(contentType, "mpegurl") || strings.HasSuffix(strings.ToLower(finalURL.Path), ".m3u8") || strings.Contains(strings.ToLower(finalURL.String()), "m3u8") || len(session.key) > 0 + playlist := strings.Contains(asset.contentType, "mpegurl") || strings.Contains(contentType, "mpegurl") || strings.HasSuffix(strings.ToLower(finalURL.Path), ".m3u8") reader := bufio.NewReader(response.Body) if !playlist && request.Method == http.MethodGet { peek, _ := reader.Peek(512) diff --git a/native/core/app_stream_format_test.go b/native/core/app_stream_format_test.go new file mode 100644 index 0000000..80ac5bf --- /dev/null +++ b/native/core/app_stream_format_test.go @@ -0,0 +1,49 @@ +package core + +import ( + "bytes" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestNativePlaybackMP4IgnoresPlaylistWordsInSignedURL(t *testing.T) { + body := append([]byte("\x00\x00\x00\x1cftypisom"), bytes.Repeat([]byte{0x42}, 4<<20)...) + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "video/mp4") + _, _ = w.Write(body) + })) + defer upstream.Close() + engine, err := newNativeEngine(t.TempDir()) + if err != nil { + t.Fatal(err) + } + stream, err := newNativeStreamServer(engine.downloader) + if err != nil { + t.Fatal(err) + } + defer stream.server.Close() + for _, suffix := range []string{"/video/?rc=signaturehLSvalue", "/video/?hint=m3u8", "/hls-cache/video.mp4"} { + t.Run(suffix, func(t *testing.T) { + address, token := stream.nativeOpen(providerMedia{URL: upstream.URL + suffix, CENCKey: bytes.Repeat([]byte{1}, 16)}) + defer stream.nativeRelease(token) + if !strings.HasSuffix(address, ".mp4") { + t.Errorf("MP4 classified as a playlist: %s", address) + } + response, err := http.Get(address) + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + actual, err := io.ReadAll(response.Body) + if err != nil { + t.Fatal(err) + } + if response.StatusCode != http.StatusOK || !bytes.Equal(actual, body) { + t.Fatalf("MP4 was not streamed intact: status=%d, bytes=%d", response.StatusCode, len(actual)) + } + }) + } +}