From 8387829ad1345e9db081c237dba732c6c7517a01 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Thu, 9 May 2024 10:07:04 +0200 Subject: [PATCH] remove gin-gonic/gin dependency (#155) --- client_test.go | 977 ++++++++++++++++++++++++------------------------- go.mod | 26 -- go.sum | 74 ---- 3 files changed, 487 insertions(+), 590 deletions(-) diff --git a/client_test.go b/client_test.go index da31907..30025d0 100644 --- a/client_test.go +++ b/client_test.go @@ -6,6 +6,7 @@ import ( "io" "net" "net/http" + "net/url" "os" "testing" "time" @@ -13,7 +14,6 @@ import ( "github.com/bluenviron/gohlslib/pkg/codecs" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" - "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "github.com/bluenviron/mediacommon/pkg/formats/fmp4" @@ -115,232 +115,238 @@ func TestClient(t *testing.T) { for _, mode := range []string{"plain", "tls"} { for _, format := range []string{"mpegts", "fmp4"} { t.Run(mode+"_"+format, func(t *testing.T) { - gin.SetMode(gin.ReleaseMode) - router := gin.New() - - if format == "mpegts" { - router.GET("/stream.m3u8", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:3\n" + - "#EXT-X-ALLOW-CACHE:NO\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MEDIA-SEQUENCE:0\n" + - "#EXT-X-PLAYLIST-TYPE:VOD\n" + - "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + - "#EXTINF:1,\n" + - "segment1.ts?key=val\n" + - "#EXTINF:1,\n" + - "segment2.ts\n" + - "#EXT-X-ENDLIST\n")) - }) - - router.GET("/segment1.ts", func(ctx *gin.Context) { - require.Equal(t, "val", ctx.Query("key")) - ctx.Writer.Header().Set("Content-Type", `video/MP2T`) - - h264Track := &mpegts.Track{ - Codec: &mpegts.CodecH264{}, - } - mpeg4audioTrack := &mpegts.Track{ - Codec: &mpegts.CodecMPEG4Audio{ - Config: mpeg4audio.AudioSpecificConfig{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, - }, - }, - } - mw := mpegts.NewWriter(ctx.Writer, []*mpegts.Track{h264Track, mpeg4audioTrack}) - - err := mw.WriteH26x( - h264Track, - 90000, // +1 sec - 8589844592, // -1 sec - true, - [][]byte{ - {7, 1, 2, 3}, // SPS - {8}, // PPS - {5}, // IDR - }, - ) - require.NoError(t, err) - - err = mw.WriteH26x( - h264Track, - 90000+90000/30, - 8589844592+90000/30, - false, - [][]byte{ - {1, 4, 5, 6}, - }, - ) - require.NoError(t, err) - - err = mw.WriteMPEG4Audio( - mpeg4audioTrack, - 8589844592, - [][]byte{{1, 2, 3, 4}}, - ) - require.NoError(t, err) - - err = mw.WriteMPEG4Audio( - mpeg4audioTrack, - 8589844592+90000/30, - [][]byte{{5, 6, 7, 8}}, - ) - require.NoError(t, err) - }) - - router.GET("/segment2.ts", func(ctx *gin.Context) { - require.Equal(t, "", ctx.Query("key")) - ctx.Writer.Header().Set("Content-Type", `video/MP2T`) - - h264Track := &mpegts.Track{ - Codec: &mpegts.CodecH264{}, - } - mpeg4audioTrack := &mpegts.Track{ - Codec: &mpegts.CodecMPEG4Audio{ - Config: mpeg4audio.AudioSpecificConfig{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, - }, - }, - } - mw := mpegts.NewWriter(ctx.Writer, []*mpegts.Track{h264Track, mpeg4audioTrack}) - - err := mw.WriteH26x( - h264Track, - 8589844592+2*90000/30, - 8589844592+2*90000/30, - true, - [][]byte{ - {4}, - }, - ) - require.NoError(t, err) - }) - } else { - router.GET("/stream.m3u8", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:7\n" + - "#EXT-X-MEDIA-SEQUENCE:20\n" + - "#EXT-X-PLAYLIST-TYPE:VOD\n" + - "#EXT-X-INDEPENDENT-SEGMENTS\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MAP:URI=\"init.mp4?key=val\"\n" + - "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + - "#EXTINF:2,\n" + - "segment1.mp4?key=val\n" + - "#EXTINF:2,\n" + - "segment2.mp4\n" + - "#EXT-X-ENDLIST\n")) - }) - - router.GET("/init.mp4", func(ctx *gin.Context) { - require.Equal(t, "val", ctx.Query("key")) - ctx.Writer.Header().Set("Content-Type", `video/mp4`) - err := mp4ToWriter(&fmp4.Init{ - Tracks: []*fmp4.InitTrack{ - { - ID: 99, - TimeScale: 90000, - Codec: &fmp4.CodecH264{ - SPS: testSPS, - PPS: testPPS, + httpServ := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if format == "mpegts" { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/stream.m3u8": + w.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-ALLOW-CACHE:NO\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MEDIA-SEQUENCE:0\n" + + "#EXT-X-PLAYLIST-TYPE:VOD\n" + + "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + + "#EXTINF:1,\n" + + "segment1.ts?key=val\n" + + "#EXTINF:1,\n" + + "segment2.ts\n" + + "#EXT-X-ENDLIST\n")) + + case r.Method == http.MethodGet && r.URL.Path == "/segment1.ts": + q, err := url.ParseQuery(r.URL.RawQuery) + require.NoError(t, err) + require.Equal(t, "val", q.Get("key")) + w.Header().Set("Content-Type", `video/MP2T`) + + h264Track := &mpegts.Track{ + Codec: &mpegts.CodecH264{}, + } + mpeg4audioTrack := &mpegts.Track{ + Codec: &mpegts.CodecMPEG4Audio{ + Config: mpeg4audio.AudioSpecificConfig{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }, }, - }, - { - ID: 98, - TimeScale: 44100, - Codec: &fmp4.CodecMPEG4Audio{ - Config: testConfig, + } + mw := mpegts.NewWriter(w, []*mpegts.Track{h264Track, mpeg4audioTrack}) + + err = mw.WriteH26x( + h264Track, + 90000, // +1 sec + 8589844592, // -1 sec + true, + [][]byte{ + {7, 1, 2, 3}, // SPS + {8}, // PPS + {5}, // IDR }, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) - - router.GET("/segment1.mp4", func(ctx *gin.Context) { - require.Equal(t, "val", ctx.Query("key")) - ctx.Writer.Header().Set("Content-Type", `video/mp4`) - - err := mp4ToWriter(&fmp4.Part{ - Tracks: []*fmp4.PartTrack{ - { - ID: 98, - BaseTime: 44100 * 6, - Samples: []*fmp4.PartSample{ + ) + require.NoError(t, err) + + err = mw.WriteH26x( + h264Track, + 90000+90000/30, + 8589844592+90000/30, + false, + [][]byte{ + {1, 4, 5, 6}, + }, + ) + require.NoError(t, err) + + err = mw.WriteMPEG4Audio( + mpeg4audioTrack, + 8589844592, + [][]byte{{1, 2, 3, 4}}, + ) + require.NoError(t, err) + + err = mw.WriteMPEG4Audio( + mpeg4audioTrack, + 8589844592+90000/30, + [][]byte{{5, 6, 7, 8}}, + ) + require.NoError(t, err) + + case r.Method == http.MethodGet && r.URL.Path == "/segment2.ts": + q, err := url.ParseQuery(r.URL.RawQuery) + require.NoError(t, err) + require.Equal(t, "", q.Get("key")) + w.Header().Set("Content-Type", `video/MP2T`) + + h264Track := &mpegts.Track{ + Codec: &mpegts.CodecH264{}, + } + mpeg4audioTrack := &mpegts.Track{ + Codec: &mpegts.CodecMPEG4Audio{ + Config: mpeg4audio.AudioSpecificConfig{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }, + }, + } + mw := mpegts.NewWriter(w, []*mpegts.Track{h264Track, mpeg4audioTrack}) + + err = mw.WriteH26x( + h264Track, + 8589844592+2*90000/30, + 8589844592+2*90000/30, + true, + [][]byte{ + {4}, + }, + ) + require.NoError(t, err) + } + } else { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/stream.m3u8": + w.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:7\n" + + "#EXT-X-MEDIA-SEQUENCE:20\n" + + "#EXT-X-PLAYLIST-TYPE:VOD\n" + + "#EXT-X-INDEPENDENT-SEGMENTS\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MAP:URI=\"init.mp4?key=val\"\n" + + "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + + "#EXTINF:2,\n" + + "segment1.mp4?key=val\n" + + "#EXTINF:2,\n" + + "segment2.mp4\n" + + "#EXT-X-ENDLIST\n")) + + case r.Method == http.MethodGet && r.URL.Path == "/init.mp4": + q, err := url.ParseQuery(r.URL.RawQuery) + require.NoError(t, err) + require.Equal(t, "val", q.Get("key")) + w.Header().Set("Content-Type", `video/mp4`) + err = mp4ToWriter(&fmp4.Init{ + Tracks: []*fmp4.InitTrack{ { - Duration: 44100 / 30, - Payload: []byte{1, 2, 3, 4}, + ID: 99, + TimeScale: 90000, + Codec: &fmp4.CodecH264{ + SPS: testSPS, + PPS: testPPS, + }, }, { - Duration: 44100 / 30, - Payload: []byte{5, 6, 7, 8}, + ID: 98, + TimeScale: 44100, + Codec: &fmp4.CodecMPEG4Audio{ + Config: testConfig, + }, }, }, - }, - { - ID: 99, - BaseTime: 90000 * 6, - Samples: []*fmp4.PartSample{ + }, w) + require.NoError(t, err) + + case r.Method == http.MethodGet && r.URL.Path == "/segment1.mp4": + q, err := url.ParseQuery(r.URL.RawQuery) + require.NoError(t, err) + require.Equal(t, "val", q.Get("key")) + w.Header().Set("Content-Type", `video/mp4`) + + err = mp4ToWriter(&fmp4.Part{ + Tracks: []*fmp4.PartTrack{ { - Duration: 90000 / 30, - PTSOffset: 90000 * 2, - Payload: mustMarshalAVCC([][]byte{ - {7, 1, 2, 3}, // SPS - {8}, // PPS - {5}, // IDR - }), + ID: 98, + BaseTime: 44100 * 6, + Samples: []*fmp4.PartSample{ + { + Duration: 44100 / 30, + Payload: []byte{1, 2, 3, 4}, + }, + { + Duration: 44100 / 30, + Payload: []byte{5, 6, 7, 8}, + }, + }, }, { - Duration: 90000 / 30, - PTSOffset: 90000 * 2, - Payload: mustMarshalAVCC([][]byte{ - {1, 4, 5, 6}, - }), + ID: 99, + BaseTime: 90000 * 6, + Samples: []*fmp4.PartSample{ + { + Duration: 90000 / 30, + PTSOffset: 90000 * 2, + Payload: mustMarshalAVCC([][]byte{ + {7, 1, 2, 3}, // SPS + {8}, // PPS + {5}, // IDR + }), + }, + { + Duration: 90000 / 30, + PTSOffset: 90000 * 2, + Payload: mustMarshalAVCC([][]byte{ + {1, 4, 5, 6}, + }), + }, + }, }, }, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) + }, w) + require.NoError(t, err) - router.GET("/segment2.mp4", func(ctx *gin.Context) { - require.Equal(t, "", ctx.Query("key")) - ctx.Writer.Header().Set("Content-Type", `video/mp4`) + case r.Method == http.MethodGet && r.URL.Path == "/segment2.mp4": + q, err := url.ParseQuery(r.URL.RawQuery) + require.NoError(t, err) + require.Equal(t, "", q.Get("key")) + w.Header().Set("Content-Type", `video/mp4`) - err := mp4ToWriter(&fmp4.Part{ - Tracks: []*fmp4.PartTrack{ - { - ID: 99, - BaseTime: 90000*6 + 2*90000/30, - Samples: []*fmp4.PartSample{ + err = mp4ToWriter(&fmp4.Part{ + Tracks: []*fmp4.PartTrack{ { - Duration: 90000 / 30, - PTSOffset: 0, - Payload: mustMarshalAVCC([][]byte{ - {4}, - }), + ID: 99, + BaseTime: 90000*6 + 2*90000/30, + Samples: []*fmp4.PartSample{ + { + Duration: 90000 / 30, + PTSOffset: 0, + Payload: mustMarshalAVCC([][]byte{ + {4}, + }), + }, + }, }, }, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) + }, w) + require.NoError(t, err) + } + } + }), } ln, err := net.Listen("tcp", "localhost:5780") require.NoError(t, err) - s := &http.Server{Handler: router} - if mode == "tls" { go func() { serverCertFpath, err2 := writeTempFile(serverCert) @@ -355,13 +361,13 @@ func TestClient(t *testing.T) { } defer os.Remove(serverKeyFpath) - s.ServeTLS(ln, serverCertFpath, serverKeyFpath) + httpServ.ServeTLS(ln, serverCertFpath, serverKeyFpath) }() } else { - go s.Serve(ln) + go httpServ.Serve(ln) } - defer s.Shutdown(context.Background()) + defer httpServ.Shutdown(context.Background()) videoRecv := make(chan struct{}) audioRecv := make(chan struct{}) @@ -483,126 +489,117 @@ func TestClient(t *testing.T) { } func TestClientFMP4MultiRenditions(t *testing.T) { - gin.SetMode(gin.ReleaseMode) - router := gin.New() - - router.GET("/index.m3u8", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"English\"," + - "DEFAULT=YES,AUTOSELECT=YES,LANGUAGE=\"en\",URI=\"audio.m3u8\"\n" + - "#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS=\"avc1.640015,mp4a.40.5\",AUDIO=\"aac\"\n" + - "video.m3u8\n")) - }) - - router.GET("/video.m3u8", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:7\n" + - "#EXT-X-MEDIA-SEQUENCE:20\n" + - "#EXT-X-PLAYLIST-TYPE:VOD\n" + - "#EXT-X-INDEPENDENT-SEGMENTS\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MAP:URI=\"init_video.mp4\"\n" + - "#EXTINF:2,\n" + - "segment_video.mp4\n" + - "#EXT-X-ENDLIST\n")) - }) - - router.GET("/audio.m3u8", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:7\n" + - "#EXT-X-MEDIA-SEQUENCE:20\n" + - "#EXT-X-PLAYLIST-TYPE:VOD\n" + - "#EXT-X-INDEPENDENT-SEGMENTS\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MAP:URI=\"init_audio.mp4\"\n" + - "#EXTINF:2,\n" + - "segment_audio.mp4\n" + - "#EXT-X-ENDLIST")) - }) - - router.GET("/init_video.mp4", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `video/mp4`) - - err := mp4ToWriter(&fmp4.Init{ - Tracks: []*fmp4.InitTrack{ - { - ID: 1, - TimeScale: 90000, - Codec: &fmp4.CodecH264{ - SPS: testSPS, - PPS: testPPS, + httpServ := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/index.m3u8": + w.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"English\"," + + "DEFAULT=YES,AUTOSELECT=YES,LANGUAGE=\"en\",URI=\"audio.m3u8\"\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS=\"avc1.640015,mp4a.40.5\",AUDIO=\"aac\"\n" + + "video.m3u8\n")) + + case r.Method == http.MethodGet && r.URL.Path == "/video.m3u8": + w.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:7\n" + + "#EXT-X-MEDIA-SEQUENCE:20\n" + + "#EXT-X-PLAYLIST-TYPE:VOD\n" + + "#EXT-X-INDEPENDENT-SEGMENTS\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MAP:URI=\"init_video.mp4\"\n" + + "#EXTINF:2,\n" + + "segment_video.mp4\n" + + "#EXT-X-ENDLIST\n")) + + case r.Method == http.MethodGet && r.URL.Path == "/audio.m3u8": + w.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:7\n" + + "#EXT-X-MEDIA-SEQUENCE:20\n" + + "#EXT-X-PLAYLIST-TYPE:VOD\n" + + "#EXT-X-INDEPENDENT-SEGMENTS\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MAP:URI=\"init_audio.mp4\"\n" + + "#EXTINF:2,\n" + + "segment_audio.mp4\n" + + "#EXT-X-ENDLIST")) + + case r.Method == http.MethodGet && r.URL.Path == "/init_video.mp4": + w.Header().Set("Content-Type", `video/mp4`) + err := mp4ToWriter(&fmp4.Init{ + Tracks: []*fmp4.InitTrack{ + { + ID: 1, + TimeScale: 90000, + Codec: &fmp4.CodecH264{ + SPS: testSPS, + PPS: testPPS, + }, + }, }, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) - - router.GET("/init_audio.mp4", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `video/mp4`) + }, w) + require.NoError(t, err) - err := mp4ToWriter(&fmp4.Init{ - Tracks: []*fmp4.InitTrack{ - { - ID: 1, - TimeScale: 44100, - Codec: &fmp4.CodecMPEG4Audio{ - Config: testConfig, + case r.Method == http.MethodGet && r.URL.Path == "/init_audio.mp4": + w.Header().Set("Content-Type", `video/mp4`) + err := mp4ToWriter(&fmp4.Init{ + Tracks: []*fmp4.InitTrack{ + { + ID: 1, + TimeScale: 44100, + Codec: &fmp4.CodecMPEG4Audio{ + Config: testConfig, + }, + }, }, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) - - router.GET("/segment_video.mp4", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `video/mp4`) - - err := mp4ToWriter(&fmp4.Part{ - Tracks: []*fmp4.PartTrack{ - { - ID: 1, - Samples: []*fmp4.PartSample{{ - Duration: 90000, - PTSOffset: 90000 * 3, - Payload: mustMarshalAVCC([][]byte{ - {7, 1, 2, 3}, // SPS - {8}, // PPS - {5}, // IDR - }), - }}, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) + }, w) + require.NoError(t, err) - router.GET("/segment_audio.mp4", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `video/mp4`) + case r.Method == http.MethodGet && r.URL.Path == "/segment_video.mp4": + w.Header().Set("Content-Type", `video/mp4`) + err := mp4ToWriter(&fmp4.Part{ + Tracks: []*fmp4.PartTrack{ + { + ID: 1, + Samples: []*fmp4.PartSample{{ + Duration: 90000, + PTSOffset: 90000 * 3, + Payload: mustMarshalAVCC([][]byte{ + {7, 1, 2, 3}, // SPS + {8}, // PPS + {5}, // IDR + }), + }}, + }, + }, + }, w) + require.NoError(t, err) - err := mp4ToWriter(&fmp4.Part{ - Tracks: []*fmp4.PartTrack{ - { - ID: 1, - Samples: []*fmp4.PartSample{{ - Duration: 44100, - Payload: []byte{1, 2, 3, 4}, - }}, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) + case r.Method == http.MethodGet && r.URL.Path == "/segment_audio.mp4": + w.Header().Set("Content-Type", `video/mp4`) + err := mp4ToWriter(&fmp4.Part{ + Tracks: []*fmp4.PartTrack{ + { + ID: 1, + Samples: []*fmp4.PartSample{{ + Duration: 44100, + Payload: []byte{1, 2, 3, 4}, + }}, + }, + }, + }, w) + require.NoError(t, err) + } + }), + } ln, err := net.Listen("tcp", "localhost:5780") require.NoError(t, err) - s := &http.Server{Handler: router} - go s.Serve(ln) - defer s.Shutdown(context.Background()) + go httpServ.Serve(ln) + defer httpServ.Shutdown(context.Background()) packetRecv := make(chan struct{}, 2) tracksRecv := make(chan struct{}, 1) @@ -666,137 +663,138 @@ func TestClientFMP4MultiRenditions(t *testing.T) { } func TestClientFMP4LowLatency(t *testing.T) { - gin.SetMode(gin.ReleaseMode) - router := gin.New() - count := 0 + closeRequest := make(chan struct{}) - router.GET("/stream.m3u8", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) - - switch count { - case 0: - require.Equal(t, "", ctx.Query("_HLS_skip")) - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:9\n" + - "#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=5.00000,CAN-SKIP-UNTIL=24.00000\n" + - "#EXT-X-MEDIA-SEQUENCE:20\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MAP:URI=\"init.mp4\"\n" + - "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + - "#EXTINF:2,\n" + - "segment.mp4\n" + - "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=part1.mp4\n")) - - case 1: - require.Equal(t, "YES", ctx.Query("_HLS_skip")) - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:9\n" + - "#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=5.00000,CAN-SKIP-UNTIL=24.00000\n" + - "#EXT-X-MEDIA-SEQUENCE:20\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MAP:URI=\"init.mp4\"\n" + - "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + - "#EXTINF:2,\n" + - "segment.mp4\n" + - "#EXT-X-PART:DURATION=0.066666666,URI=\"part1.mp4\",INDEPENDENT=YES\n" + - "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=part2.mp4\n")) - - case 2: - require.Equal(t, "YES", ctx.Query("_HLS_skip")) - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:9\n" + - "#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=5.00000,CAN-SKIP-UNTIL=24.00000\n" + - "#EXT-X-MEDIA-SEQUENCE:20\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MAP:URI=\"init.mp4\"\n" + - "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + - "#EXTINF:2,\n" + - "segment.mp4\n" + - "#EXT-X-PART:DURATION=0.066666666,URI=\"part1.mp4\",INDEPENDENT=YES\n" + - "#EXT-X-PART:DURATION=0.033333333,URI=\"part2.mp4\"\n" + - "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=part3.mp4\n")) - } - count++ - }) + httpServ := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/stream.m3u8": + w.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) - router.GET("/init.mp4", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `video/mp4`) + switch count { + case 0: + q, err := url.ParseQuery(r.URL.RawQuery) + require.NoError(t, err) + require.Equal(t, "", q.Get("_HLS_skip")) + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:9\n" + + "#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=5.00000,CAN-SKIP-UNTIL=24.00000\n" + + "#EXT-X-MEDIA-SEQUENCE:20\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MAP:URI=\"init.mp4\"\n" + + "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + + "#EXTINF:2,\n" + + "segment.mp4\n" + + "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=part1.mp4\n")) - err := mp4ToWriter(&fmp4.Init{ - Tracks: []*fmp4.InitTrack{ - { - ID: 1, - TimeScale: 90000, - Codec: &fmp4.CodecH264{ - SPS: testSPS, - PPS: testPPS, - }, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) - - router.GET("/part1.mp4", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `video/mp4`) - err := mp4ToWriter(&fmp4.Part{ - Tracks: []*fmp4.PartTrack{ - { - ID: 1, - Samples: []*fmp4.PartSample{ + case 1: + q, err := url.ParseQuery(r.URL.RawQuery) + require.NoError(t, err) + require.Equal(t, "YES", q.Get("_HLS_skip")) + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:9\n" + + "#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=5.00000,CAN-SKIP-UNTIL=24.00000\n" + + "#EXT-X-MEDIA-SEQUENCE:20\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MAP:URI=\"init.mp4\"\n" + + "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + + "#EXTINF:2,\n" + + "segment.mp4\n" + + "#EXT-X-PART:DURATION=0.066666666,URI=\"part1.mp4\",INDEPENDENT=YES\n" + + "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=part2.mp4\n")) + + case 2: + q, err := url.ParseQuery(r.URL.RawQuery) + require.NoError(t, err) + require.Equal(t, "YES", q.Get("_HLS_skip")) + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:9\n" + + "#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=5.00000,CAN-SKIP-UNTIL=24.00000\n" + + "#EXT-X-MEDIA-SEQUENCE:20\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MAP:URI=\"init.mp4\"\n" + + "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + + "#EXTINF:2,\n" + + "segment.mp4\n" + + "#EXT-X-PART:DURATION=0.066666666,URI=\"part1.mp4\",INDEPENDENT=YES\n" + + "#EXT-X-PART:DURATION=0.033333333,URI=\"part2.mp4\"\n" + + "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=part3.mp4\n")) + } + count++ + + case r.Method == http.MethodGet && r.URL.Path == "/init.mp4": + w.Header().Set("Content-Type", `video/mp4`) + err := mp4ToWriter(&fmp4.Init{ + Tracks: []*fmp4.InitTrack{ { - Duration: 90000 / 30, - Payload: mustMarshalAVCC([][]byte{ - {7, 1, 2, 3}, // SPS - {8}, // PPS - {5}, // IDR - }), + ID: 1, + TimeScale: 90000, + Codec: &fmp4.CodecH264{ + SPS: testSPS, + PPS: testPPS, + }, }, + }, + }, w) + require.NoError(t, err) + + case r.Method == http.MethodGet && r.URL.Path == "/part1.mp4": + w.Header().Set("Content-Type", `video/mp4`) + err := mp4ToWriter(&fmp4.Part{ + Tracks: []*fmp4.PartTrack{ { - Duration: 90000 / 30, - Payload: mustMarshalAVCC([][]byte{ - {1, 4, 5, 6}, - }), + ID: 1, + Samples: []*fmp4.PartSample{ + { + Duration: 90000 / 30, + Payload: mustMarshalAVCC([][]byte{ + {7, 1, 2, 3}, // SPS + {8}, // PPS + {5}, // IDR + }), + }, + { + Duration: 90000 / 30, + Payload: mustMarshalAVCC([][]byte{ + {1, 4, 5, 6}, + }), + }, + }, }, }, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) - - router.GET("/part2.mp4", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `video/mp4`) - err := mp4ToWriter(&fmp4.Part{ - Tracks: []*fmp4.PartTrack{ - { - ID: 1, - BaseTime: (90000 / 30) * 2, - Samples: []*fmp4.PartSample{{ - Duration: 90000 / 30, - Payload: mustMarshalAVCC([][]byte{ - {1, 7, 8, 9}, - }), - }}, - }, - }, - }, ctx.Writer) - require.NoError(t, err) - }) + }, w) + require.NoError(t, err) - closeRequest := make(chan struct{}) + case r.Method == http.MethodGet && r.URL.Path == "/part2.mp4": + w.Header().Set("Content-Type", `video/mp4`) + err := mp4ToWriter(&fmp4.Part{ + Tracks: []*fmp4.PartTrack{ + { + ID: 1, + BaseTime: (90000 / 30) * 2, + Samples: []*fmp4.PartSample{{ + Duration: 90000 / 30, + Payload: mustMarshalAVCC([][]byte{ + {1, 7, 8, 9}, + }), + }}, + }, + }, + }, w) + require.NoError(t, err) - router.GET("/part3.mp4", func(_ *gin.Context) { - <-closeRequest - }) + case r.Method == http.MethodGet && r.URL.Path == "/part3.mp4": + <-closeRequest + } + }), + } ln, err := net.Listen("tcp", "localhost:5780") require.NoError(t, err) - s := &http.Server{Handler: router} - go s.Serve(ln) - defer s.Shutdown(context.Background()) + go httpServ.Serve(ln) + defer httpServ.Shutdown(context.Background()) packetRecv := make(chan struct{}) recvCount := 0 @@ -873,68 +871,67 @@ func TestClientFMP4LowLatency(t *testing.T) { } func TestClientErrorInvalidSequenceID(t *testing.T) { - router := gin.New() first := true - router.GET("/stream.m3u8", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) - - if first { - first = false - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:3\n" + - "#EXT-X-ALLOW-CACHE:NO\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MEDIA-SEQUENCE:2\n" + - "#EXTINF:2,\n" + - "segment1.ts\n" + - "#EXTINF:2,\n" + - "segment1.ts\n" + - "#EXTINF:2,\n" + - "segment1.ts\n")) - } else { - ctx.Writer.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:3\n" + - "#EXT-X-ALLOW-CACHE:NO\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MEDIA-SEQUENCE:4\n" + - "#EXTINF:2,\n" + - "segment1.ts\n" + - "#EXTINF:2,\n" + - "segment1.ts\n" + - "#EXTINF:2,\n" + - "segment1.ts\n")) - } - }) - - router.GET("/segment1.ts", func(ctx *gin.Context) { - ctx.Writer.Header().Set("Content-Type", `video/MP2T`) + httpServ := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && r.URL.Path == "/stream.m3u8" { + w.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) + if first { + first = false + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-ALLOW-CACHE:NO\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MEDIA-SEQUENCE:2\n" + + "#EXTINF:2,\n" + + "segment1.ts\n" + + "#EXTINF:2,\n" + + "segment1.ts\n" + + "#EXTINF:2,\n" + + "segment1.ts\n")) + } else { + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-ALLOW-CACHE:NO\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MEDIA-SEQUENCE:4\n" + + "#EXTINF:2,\n" + + "segment1.ts\n" + + "#EXTINF:2,\n" + + "segment1.ts\n" + + "#EXTINF:2,\n" + + "segment1.ts\n")) + } + } else if r.Method == http.MethodGet && r.URL.Path == "/segment1.ts" { + w.Header().Set("Content-Type", `video/MP2T`) - h264Track := &mpegts.Track{ - Codec: &mpegts.CodecH264{}, - } - mw := mpegts.NewWriter(ctx.Writer, []*mpegts.Track{h264Track}) - - err := mw.WriteH26x( - h264Track, - 90000, // +1 sec - 0x1FFFFFFFF-90000+1, // -1 sec - true, - [][]byte{ - {7, 1, 2, 3}, // SPS - {8}, // PPS - {5}, // IDR - }, - ) - require.NoError(t, err) - }) + h264Track := &mpegts.Track{ + Codec: &mpegts.CodecH264{}, + } + mw := mpegts.NewWriter(w, []*mpegts.Track{h264Track}) + + err := mw.WriteH26x( + h264Track, + 90000, // +1 sec + 0x1FFFFFFFF-90000+1, // -1 sec + true, + [][]byte{ + {7, 1, 2, 3}, // SPS + {8}, // PPS + {5}, // IDR + }, + ) + require.NoError(t, err) + } + }), + } ln, err := net.Listen("tcp", "localhost:5780") require.NoError(t, err) - s := &http.Server{Handler: router} - go s.Serve(ln) - defer s.Shutdown(context.Background()) + go httpServ.Serve(ln) + defer httpServ.Shutdown(context.Background()) tr := &http.Transport{} defer tr.CloseIdleConnections() diff --git a/go.mod b/go.mod index d7cc2b3..685a8ff 100644 --- a/go.mod +++ b/go.mod @@ -5,40 +5,14 @@ go 1.20 require ( github.com/asticode/go-astits v1.13.0 github.com/bluenviron/mediacommon v1.10.0 - github.com/gin-gonic/gin v1.10.0 github.com/stretchr/testify v1.9.0 ) require ( github.com/abema/go-mp4 v1.2.0 // indirect github.com/asticode/go-astikit v0.30.0 // indirect - github.com/bytedance/sonic v1.11.6 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 723fa48..88e3d4a 100644 --- a/go.sum +++ b/go.sum @@ -6,103 +6,32 @@ github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwf github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/bluenviron/mediacommon v1.10.0 h1:ffIWaS+1vYpPLV6QOt4VEvIlb/OKtodzagzsY6EDOnw= github.com/bluenviron/mediacommon v1.10.0/go.mod h1:HDyW2CzjvhYJXtdxstdFPio3G0qSocPhqkhUt/qffec= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw= github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -110,8 +39,5 @@ gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=