mirror of
				https://github.com/aler9/rtsp-simple-server
				synced 2025-10-31 19:13:22 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			673 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			673 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package playback
 | |
| 
 | |
| import (
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
 | |
| 	"github.com/bluenviron/mediacommon/pkg/formats/fmp4"
 | |
| 	"github.com/bluenviron/mediacommon/pkg/formats/fmp4/seekablebuffer"
 | |
| 	"github.com/bluenviron/mediamtx/internal/auth"
 | |
| 	"github.com/bluenviron/mediamtx/internal/conf"
 | |
| 	"github.com/bluenviron/mediamtx/internal/test"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| func writeSegment1(t *testing.T, fpath string) {
 | |
| 	init := fmp4.Init{
 | |
| 		Tracks: []*fmp4.InitTrack{
 | |
| 			{
 | |
| 				ID:        1,
 | |
| 				TimeScale: 90000,
 | |
| 				Codec: &fmp4.CodecH264{
 | |
| 					SPS: test.FormatH264.SPS,
 | |
| 					PPS: test.FormatH264.PPS,
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				ID:        2,
 | |
| 				TimeScale: 90000,
 | |
| 				Codec: &fmp4.CodecMPEG4Audio{
 | |
| 					Config: mpeg4audio.Config{
 | |
| 						Type:         mpeg4audio.ObjectTypeAACLC,
 | |
| 						SampleRate:   48000,
 | |
| 						ChannelCount: 2,
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	var buf1 seekablebuffer.Buffer
 | |
| 	err := init.Marshal(&buf1)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	var buf2 seekablebuffer.Buffer
 | |
| 	parts := fmp4.Parts{
 | |
| 		{
 | |
| 			SequenceNumber: 1,
 | |
| 			Tracks: []*fmp4.PartTrack{{
 | |
| 				ID:       1,
 | |
| 				BaseTime: 0,
 | |
| 				Samples:  []*fmp4.PartSample{},
 | |
| 			}},
 | |
| 		},
 | |
| 		{
 | |
| 			SequenceNumber: 2,
 | |
| 			Tracks: []*fmp4.PartTrack{
 | |
| 				{
 | |
| 					ID:       1,
 | |
| 					BaseTime: 30 * 90000,
 | |
| 					Samples: []*fmp4.PartSample{
 | |
| 						{
 | |
| 							Duration:        30 * 90000,
 | |
| 							IsNonSyncSample: false,
 | |
| 							Payload:         []byte{1, 2},
 | |
| 						},
 | |
| 						{
 | |
| 							Duration:        1 * 90000,
 | |
| 							IsNonSyncSample: false,
 | |
| 							Payload:         []byte{3, 4},
 | |
| 						},
 | |
| 						{
 | |
| 							Duration:        1 * 90000,
 | |
| 							IsNonSyncSample: true,
 | |
| 							Payload:         []byte{5, 6},
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 				{
 | |
| 					ID:       2,
 | |
| 					BaseTime: 29 * 90000,
 | |
| 					Samples: []*fmp4.PartSample{
 | |
| 						{
 | |
| 							Duration:        30 * 90000,
 | |
| 							IsNonSyncSample: false,
 | |
| 							Payload:         []byte{1, 2},
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	err = parts.Marshal(&buf2)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	err = os.WriteFile(fpath, append(buf1.Bytes(), buf2.Bytes()...), 0o644)
 | |
| 	require.NoError(t, err)
 | |
| }
 | |
| 
 | |
| func writeSegment2(t *testing.T, fpath string) {
 | |
| 	init := fmp4.Init{
 | |
| 		Tracks: []*fmp4.InitTrack{
 | |
| 			{
 | |
| 				ID:        1,
 | |
| 				TimeScale: 90000,
 | |
| 				Codec: &fmp4.CodecH264{
 | |
| 					SPS: test.FormatH264.SPS,
 | |
| 					PPS: test.FormatH264.PPS,
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				ID:        2,
 | |
| 				TimeScale: 90000,
 | |
| 				Codec: &fmp4.CodecMPEG4Audio{
 | |
| 					Config: mpeg4audio.Config{
 | |
| 						Type:         mpeg4audio.ObjectTypeAACLC,
 | |
| 						SampleRate:   48000,
 | |
| 						ChannelCount: 2,
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	var buf1 seekablebuffer.Buffer
 | |
| 	err := init.Marshal(&buf1)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	var buf2 seekablebuffer.Buffer
 | |
| 	parts := fmp4.Parts{
 | |
| 		{
 | |
| 			SequenceNumber: 3,
 | |
| 			Tracks: []*fmp4.PartTrack{{
 | |
| 				ID:       1,
 | |
| 				BaseTime: 0,
 | |
| 				Samples: []*fmp4.PartSample{
 | |
| 					{
 | |
| 						Duration:        1 * 90000,
 | |
| 						IsNonSyncSample: false,
 | |
| 						Payload:         []byte{7, 8},
 | |
| 					},
 | |
| 					{
 | |
| 						Duration:        1 * 90000,
 | |
| 						IsNonSyncSample: false,
 | |
| 						Payload:         []byte{9, 10},
 | |
| 					},
 | |
| 				},
 | |
| 			}},
 | |
| 		},
 | |
| 		{
 | |
| 			SequenceNumber: 4,
 | |
| 			Tracks: []*fmp4.PartTrack{{
 | |
| 				ID:       1,
 | |
| 				BaseTime: 2 * 90000,
 | |
| 				Samples: []*fmp4.PartSample{
 | |
| 					{
 | |
| 						Duration:        1 * 90000,
 | |
| 						IsNonSyncSample: false,
 | |
| 						Payload:         []byte{11, 12},
 | |
| 					},
 | |
| 				},
 | |
| 			}},
 | |
| 		},
 | |
| 	}
 | |
| 	err = parts.Marshal(&buf2)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	err = os.WriteFile(fpath, append(buf1.Bytes(), buf2.Bytes()...), 0o644)
 | |
| 	require.NoError(t, err)
 | |
| }
 | |
| 
 | |
| func writeSegment3(t *testing.T, fpath string) {
 | |
| 	init := fmp4.Init{
 | |
| 		Tracks: []*fmp4.InitTrack{
 | |
| 			{
 | |
| 				ID:        1,
 | |
| 				TimeScale: 90000,
 | |
| 				Codec: &fmp4.CodecH264{
 | |
| 					SPS: test.FormatH264.SPS,
 | |
| 					PPS: test.FormatH264.PPS,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	var buf1 seekablebuffer.Buffer
 | |
| 	err := init.Marshal(&buf1)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	var buf2 seekablebuffer.Buffer
 | |
| 	parts := fmp4.Parts{
 | |
| 		{
 | |
| 			SequenceNumber: 1,
 | |
| 			Tracks: []*fmp4.PartTrack{{
 | |
| 				ID:       1,
 | |
| 				BaseTime: 0,
 | |
| 				Samples: []*fmp4.PartSample{
 | |
| 					{
 | |
| 						Duration:        1 * 90000,
 | |
| 						IsNonSyncSample: false,
 | |
| 						Payload:         []byte{13, 14},
 | |
| 					},
 | |
| 				},
 | |
| 			}},
 | |
| 		},
 | |
| 	}
 | |
| 	err = parts.Marshal(&buf2)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	err = os.WriteFile(fpath, append(buf1.Bytes(), buf2.Bytes()...), 0o644)
 | |
| 	require.NoError(t, err)
 | |
| }
 | |
| 
 | |
| var authManager = &auth.Manager{
 | |
| 	Method: conf.AuthMethodInternal,
 | |
| 	InternalUsers: []conf.AuthInternalUser{
 | |
| 		{
 | |
| 			User: "myuser",
 | |
| 			Pass: "mypass",
 | |
| 			Permissions: []conf.AuthInternalUserPermission{
 | |
| 				{
 | |
| 					Action: conf.AuthActionPlayback,
 | |
| 					Path:   "mypath",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	},
 | |
| 	RTSPAuthMethods: nil,
 | |
| }
 | |
| 
 | |
| func TestOnGet(t *testing.T) {
 | |
| 	for _, format := range []string{"fmp4", "mp4"} {
 | |
| 		t.Run(format, func(t *testing.T) {
 | |
| 			dir, err := os.MkdirTemp("", "mediamtx-playback")
 | |
| 			require.NoError(t, err)
 | |
| 			defer os.RemoveAll(dir)
 | |
| 
 | |
| 			err = os.Mkdir(filepath.Join(dir, "mypath"), 0o755)
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			writeSegment1(t, filepath.Join(dir, "mypath", "2008-11-07_11-22-00-500000.mp4"))
 | |
| 			writeSegment2(t, filepath.Join(dir, "mypath", "2008-11-07_11-23-02-500000.mp4"))
 | |
| 			writeSegment2(t, filepath.Join(dir, "mypath", "2008-11-07_11-23-04-500000.mp4"))
 | |
| 
 | |
| 			s := &Server{
 | |
| 				Address:     "127.0.0.1:9996",
 | |
| 				ReadTimeout: conf.StringDuration(10 * time.Second),
 | |
| 				PathConfs: map[string]*conf.Path{
 | |
| 					"mypath": {
 | |
| 						RecordPath: filepath.Join(dir, "%path/%Y-%m-%d_%H-%M-%S-%f"),
 | |
| 					},
 | |
| 				},
 | |
| 				AuthManager: authManager,
 | |
| 				Parent:      &test.NilLogger{},
 | |
| 			}
 | |
| 			err = s.Initialize()
 | |
| 			require.NoError(t, err)
 | |
| 			defer s.Close()
 | |
| 
 | |
| 			u, err := url.Parse("http://myuser:mypass@localhost:9996/get")
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			v := url.Values{}
 | |
| 			v.Set("path", "mypath")
 | |
| 			v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
 | |
| 			v.Set("duration", "3")
 | |
| 			v.Set("format", format)
 | |
| 			u.RawQuery = v.Encode()
 | |
| 
 | |
| 			req, err := http.NewRequest(http.MethodGet, u.String(), nil)
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			res, err := http.DefaultClient.Do(req)
 | |
| 			require.NoError(t, err)
 | |
| 			defer res.Body.Close()
 | |
| 
 | |
| 			require.Equal(t, http.StatusOK, res.StatusCode)
 | |
| 
 | |
| 			buf, err := io.ReadAll(res.Body)
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			if format == "fmp4" {
 | |
| 				var parts fmp4.Parts
 | |
| 				err = parts.Unmarshal(buf)
 | |
| 				require.NoError(t, err)
 | |
| 
 | |
| 				require.Equal(t, fmp4.Parts{
 | |
| 					{
 | |
| 						SequenceNumber: 0,
 | |
| 						Tracks: []*fmp4.PartTrack{
 | |
| 							{
 | |
| 								ID: 1,
 | |
| 								Samples: []*fmp4.PartSample{
 | |
| 									{
 | |
| 										Duration: 0,
 | |
| 										Payload:  []byte{3, 4},
 | |
| 									},
 | |
| 									{
 | |
| 										Duration:        90000,
 | |
| 										IsNonSyncSample: true,
 | |
| 										Payload:         []byte{5, 6},
 | |
| 									},
 | |
| 									{
 | |
| 										Duration: 90000,
 | |
| 										Payload:  []byte{7, 8},
 | |
| 									},
 | |
| 								},
 | |
| 							},
 | |
| 						},
 | |
| 					},
 | |
| 					{
 | |
| 						SequenceNumber: 1,
 | |
| 						Tracks: []*fmp4.PartTrack{
 | |
| 							{
 | |
| 								ID:       1,
 | |
| 								BaseTime: 180000,
 | |
| 								Samples: []*fmp4.PartSample{
 | |
| 									{
 | |
| 										Duration: 90000,
 | |
| 										Payload:  []byte{9, 10},
 | |
| 									},
 | |
| 								},
 | |
| 							},
 | |
| 						},
 | |
| 					},
 | |
| 				}, parts)
 | |
| 			} else {
 | |
| 				require.Equal(t, []byte{
 | |
| 					0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70,
 | |
| 					0x69, 0x73, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x01,
 | |
| 					0x69, 0x73, 0x6f, 0x6d, 0x69, 0x73, 0x6f, 0x32,
 | |
| 					0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
 | |
| 					0x00, 0x00, 0x04, 0xcf, 0x6d, 0x6f, 0x6f, 0x76,
 | |
| 					0x00, 0x00, 0x00, 0x6c, 0x6d, 0x76, 0x68, 0x64,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
 | |
| 					0xff, 0xff, 0xf8, 0x30, 0x00, 0x01, 0x00, 0x00,
 | |
| 					0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x02, 0x5b,
 | |
| 					0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
 | |
| 					0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x03,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
 | |
| 					0x07, 0x80, 0x00, 0x00, 0x04, 0x38, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73,
 | |
| 					0x00, 0x00, 0x00, 0x1c, 0x65, 0x6c, 0x73, 0x74,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
 | |
| 					0x00, 0x00, 0x13, 0x88, 0x00, 0x01, 0x5f, 0x90,
 | |
| 					0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd3,
 | |
| 					0x6d, 0x64, 0x69, 0x61, 0x00, 0x00, 0x00, 0x20,
 | |
| 					0x6d, 0x64, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x01, 0x5f, 0x90, 0x00, 0x04, 0x1e, 0xb0,
 | |
| 					0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d,
 | |
| 					0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x65,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x64, 0x65,
 | |
| 					0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72,
 | |
| 					0x00, 0x00, 0x00, 0x01, 0x7e, 0x6d, 0x69, 0x6e,
 | |
| 					0x66, 0x00, 0x00, 0x00, 0x14, 0x76, 0x6d, 0x68,
 | |
| 					0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00,
 | |
| 					0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
 | |
| 					0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00,
 | |
| 					0x01, 0x00, 0x00, 0x01, 0x3e, 0x73, 0x74, 0x62,
 | |
| 					0x6c, 0x00, 0x00, 0x00, 0x96, 0x73, 0x74, 0x73,
 | |
| 					0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x01, 0x00, 0x00, 0x00, 0x86, 0x61, 0x76, 0x63,
 | |
| 					0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x07, 0x80, 0x04, 0x38, 0x00, 0x48, 0x00,
 | |
| 					0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00,
 | |
| 					0x00, 0x00, 0x30, 0x61, 0x76, 0x63, 0x43, 0x01,
 | |
| 					0x42, 0xc0, 0x28, 0x03, 0x01, 0x00, 0x19, 0x67,
 | |
| 					0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, 0x27,
 | |
| 					0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00,
 | |
| 					0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20,
 | |
| 					0x01, 0x00, 0x04, 0x08, 0x06, 0x07, 0x08, 0x00,
 | |
| 					0x00, 0x00, 0x18, 0x73, 0x74, 0x74, 0x73, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
 | |
| 					0x00, 0x00, 0x04, 0x00, 0x01, 0x5f, 0x90, 0x00,
 | |
| 					0x00, 0x00, 0x1c, 0x73, 0x74, 0x73, 0x73, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00,
 | |
| 					0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x18, 0x63,
 | |
| 					0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x73,
 | |
| 					0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
 | |
| 					0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00,
 | |
| 					0x00, 0x00, 0x24, 0x73, 0x74, 0x73, 0x7a, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00,
 | |
| 					0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00,
 | |
| 					0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x73,
 | |
| 					0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0xf9, 0x00,
 | |
| 					0x00, 0x02, 0x00, 0x74, 0x72, 0x61, 0x6b, 0x00,
 | |
| 					0x00, 0x00, 0x5c, 0x74, 0x6b, 0x68, 0x64, 0x00,
 | |
| 					0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x30, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x65,
 | |
| 					0x64, 0x74, 0x73, 0x00, 0x00, 0x00, 0x1c, 0x65,
 | |
| 					0x6c, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0xf2, 0x30, 0x00,
 | |
| 					0x2b, 0xf2, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x01, 0x78, 0x6d, 0x64, 0x69, 0x61, 0x00,
 | |
| 					0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x01, 0x5f, 0x90, 0xff,
 | |
| 					0xfd, 0x40, 0xe0, 0x55, 0xc4, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x2d, 0x68, 0x64, 0x6c, 0x72, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73,
 | |
| 					0x6f, 0x75, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53,
 | |
| 					0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64,
 | |
| 					0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01, 0x23,
 | |
| 					0x6d, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x10,
 | |
| 					0x73, 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
 | |
| 					0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x1c,
 | |
| 					0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c,
 | |
| 					0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00, 0x01,
 | |
| 					0x00, 0x00, 0x00, 0xe7, 0x73, 0x74, 0x62, 0x6c,
 | |
| 					0x00, 0x00, 0x00, 0x67, 0x73, 0x74, 0x73, 0x64,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
 | |
| 					0x00, 0x00, 0x00, 0x57, 0x6d, 0x70, 0x34, 0x61,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0xbb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33,
 | |
| 					0x65, 0x73, 0x64, 0x73, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x03, 0x80, 0x80, 0x80, 0x22, 0x00, 0x02, 0x00,
 | |
| 					0x04, 0x80, 0x80, 0x80, 0x14, 0x40, 0x15, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x01, 0xf7, 0x39, 0x00, 0x01,
 | |
| 					0xf7, 0x39, 0x05, 0x80, 0x80, 0x80, 0x02, 0x11,
 | |
| 					0x90, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02, 0x00,
 | |
| 					0x00, 0x00, 0x18, 0x73, 0x74, 0x74, 0x73, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x29, 0x32, 0xe0, 0x00,
 | |
| 					0x00, 0x00, 0x18, 0x63, 0x74, 0x74, 0x73, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x1c, 0x73, 0x74, 0x73, 0x63, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x73,
 | |
| 					0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
 | |
| 					0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x73,
 | |
| 					0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00,
 | |
| 					0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0xf7, 0x00,
 | |
| 					0x00, 0x00, 0x12, 0x6d, 0x64, 0x61, 0x74, 0x01,
 | |
| 					0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
 | |
| 					0x0a,
 | |
| 				}, buf)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestOnGetDifferentInit(t *testing.T) {
 | |
| 	dir, err := os.MkdirTemp("", "mediamtx-playback")
 | |
| 	require.NoError(t, err)
 | |
| 	defer os.RemoveAll(dir)
 | |
| 
 | |
| 	err = os.Mkdir(filepath.Join(dir, "mypath"), 0o755)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	writeSegment1(t, filepath.Join(dir, "mypath", "2008-11-07_11-22-00-500000.mp4"))
 | |
| 	writeSegment3(t, filepath.Join(dir, "mypath", "2008-11-07_11-23-02-500000.mp4"))
 | |
| 
 | |
| 	s := &Server{
 | |
| 		Address:     "127.0.0.1:9996",
 | |
| 		ReadTimeout: conf.StringDuration(10 * time.Second),
 | |
| 		PathConfs: map[string]*conf.Path{
 | |
| 			"mypath": {
 | |
| 				RecordPath: filepath.Join(dir, "%path/%Y-%m-%d_%H-%M-%S-%f"),
 | |
| 			},
 | |
| 		},
 | |
| 		AuthManager: authManager,
 | |
| 		Parent:      &test.NilLogger{},
 | |
| 	}
 | |
| 	err = s.Initialize()
 | |
| 	require.NoError(t, err)
 | |
| 	defer s.Close()
 | |
| 
 | |
| 	u, err := url.Parse("http://myuser:mypass@localhost:9996/get")
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	v := url.Values{}
 | |
| 	v.Set("path", "mypath")
 | |
| 	v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
 | |
| 	v.Set("duration", "2")
 | |
| 	v.Set("format", "fmp4")
 | |
| 	u.RawQuery = v.Encode()
 | |
| 
 | |
| 	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	res, err := http.DefaultClient.Do(req)
 | |
| 	require.NoError(t, err)
 | |
| 	defer res.Body.Close()
 | |
| 
 | |
| 	require.Equal(t, http.StatusOK, res.StatusCode)
 | |
| 
 | |
| 	buf, err := io.ReadAll(res.Body)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	var parts fmp4.Parts
 | |
| 	err = parts.Unmarshal(buf)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	require.Equal(t, fmp4.Parts{
 | |
| 		{
 | |
| 			SequenceNumber: 0,
 | |
| 			Tracks: []*fmp4.PartTrack{
 | |
| 				{
 | |
| 					ID: 1,
 | |
| 					Samples: []*fmp4.PartSample{
 | |
| 						{
 | |
| 							Duration: 0,
 | |
| 							Payload:  []byte{3, 4},
 | |
| 						},
 | |
| 						{
 | |
| 							Duration:        90000,
 | |
| 							IsNonSyncSample: true,
 | |
| 							Payload:         []byte{5, 6},
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}, parts)
 | |
| }
 | |
| 
 | |
| func TestOnGetNTPCompensation(t *testing.T) {
 | |
| 	dir, err := os.MkdirTemp("", "mediamtx-playback")
 | |
| 	require.NoError(t, err)
 | |
| 	defer os.RemoveAll(dir)
 | |
| 
 | |
| 	err = os.Mkdir(filepath.Join(dir, "mypath"), 0o755)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	writeSegment1(t, filepath.Join(dir, "mypath", "2008-11-07_11-22-00-500000.mp4"))
 | |
| 	writeSegment2(t, filepath.Join(dir, "mypath", "2008-11-07_11-23-02-000000.mp4")) // remove 0.5 secs
 | |
| 
 | |
| 	s := &Server{
 | |
| 		Address:     "127.0.0.1:9996",
 | |
| 		ReadTimeout: conf.StringDuration(10 * time.Second),
 | |
| 		PathConfs: map[string]*conf.Path{
 | |
| 			"mypath": {
 | |
| 				RecordPath: filepath.Join(dir, "%path/%Y-%m-%d_%H-%M-%S-%f"),
 | |
| 			},
 | |
| 		},
 | |
| 		AuthManager: authManager,
 | |
| 		Parent:      &test.NilLogger{},
 | |
| 	}
 | |
| 	err = s.Initialize()
 | |
| 	require.NoError(t, err)
 | |
| 	defer s.Close()
 | |
| 
 | |
| 	u, err := url.Parse("http://myuser:mypass@localhost:9996/get")
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	v := url.Values{}
 | |
| 	v.Set("path", "mypath")
 | |
| 	v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
 | |
| 	v.Set("duration", "3")
 | |
| 	v.Set("format", "fmp4")
 | |
| 	u.RawQuery = v.Encode()
 | |
| 
 | |
| 	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	res, err := http.DefaultClient.Do(req)
 | |
| 	require.NoError(t, err)
 | |
| 	defer res.Body.Close()
 | |
| 
 | |
| 	require.Equal(t, http.StatusOK, res.StatusCode)
 | |
| 
 | |
| 	buf, err := io.ReadAll(res.Body)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	var parts fmp4.Parts
 | |
| 	err = parts.Unmarshal(buf)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	require.Equal(t, fmp4.Parts{
 | |
| 		{
 | |
| 			SequenceNumber: 0,
 | |
| 			Tracks: []*fmp4.PartTrack{
 | |
| 				{
 | |
| 					ID: 1,
 | |
| 					Samples: []*fmp4.PartSample{
 | |
| 						{
 | |
| 							Duration: 0,
 | |
| 							Payload:  []byte{3, 4},
 | |
| 						},
 | |
| 						{
 | |
| 							Duration:        45000, // 90 - 45
 | |
| 							IsNonSyncSample: true,
 | |
| 							Payload:         []byte{5, 6},
 | |
| 						},
 | |
| 						{
 | |
| 							Duration: 90000,
 | |
| 							Payload:  []byte{7, 8},
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			SequenceNumber: 1,
 | |
| 			Tracks: []*fmp4.PartTrack{
 | |
| 				{
 | |
| 					ID:       1,
 | |
| 					BaseTime: 135000,
 | |
| 					Samples: []*fmp4.PartSample{
 | |
| 						{
 | |
| 							Duration: 90000,
 | |
| 							Payload:  []byte{9, 10},
 | |
| 						},
 | |
| 						{
 | |
| 							Duration: 90000,
 | |
| 							Payload:  []byte{11, 12},
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}, parts)
 | |
| }
 | 
