Live video on the AT Protocol

rtcrec: basic serialization and deserialization

+197 -18
+2
go.mod
··· 21 github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 22 github.com/dunglas/httpsfv v1.0.2 23 github.com/ethereum/go-ethereum v1.14.7 24 github.com/go-git/go-git/v5 v5.12.0 25 github.com/go-gst/go-glib v1.4.0 26 github.com/go-gst/go-gst v1.4.0 ··· 387 github.com/valyala/fasttemplate v1.2.2 // indirect 388 github.com/vektah/gqlparser/v2 v2.5.22 // indirect 389 github.com/wlynxg/anet v0.0.5 // indirect 390 github.com/xanzy/ssh-agent v0.3.3 // indirect 391 github.com/xen0n/gosmopolitan v1.3.0 // indirect 392 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
··· 21 github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 22 github.com/dunglas/httpsfv v1.0.2 23 github.com/ethereum/go-ethereum v1.14.7 24 + github.com/fxamacker/cbor/v2 v2.8.0 25 github.com/go-git/go-git/v5 v5.12.0 26 github.com/go-gst/go-glib v1.4.0 27 github.com/go-gst/go-gst v1.4.0 ··· 388 github.com/valyala/fasttemplate v1.2.2 // indirect 389 github.com/vektah/gqlparser/v2 v2.5.22 // indirect 390 github.com/wlynxg/anet v0.0.5 // indirect 391 + github.com/x448/float16 v0.8.4 // indirect 392 github.com/xanzy/ssh-agent v0.3.3 // indirect 393 github.com/xen0n/gosmopolitan v1.3.0 // indirect 394 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
+4
go.sum
··· 261 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 262 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 263 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 264 github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= 265 github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= 266 github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= ··· 996 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 997 github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= 998 github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= 999 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 1000 github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 1001 github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=
··· 261 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 262 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 263 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 264 + github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= 265 + github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 266 github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= 267 github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= 268 github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= ··· 998 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 999 github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= 1000 github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= 1001 + github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 1002 + github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 1003 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 1004 github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 1005 github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=
+9 -9
pkg/rtcrec/peerproxy.go
··· 36 type RTPReceiver interface { 37 } 38 39 - func TranscieverPlease() RTPTransceiver { 40 - return &webrtc.RTPTransceiver{} 41 - } 42 - 43 - func TranscieverThankYou() *webrtc.RTPTransceiver { 44 - t, ok := TranscieverPlease().(*webrtc.RTPTransceiver) 45 - if !ok { 46 - panic("TranscieverPlease() is not a webrtc.RTPTransceiver") 47 } 48 - return t 49 }
··· 36 type RTPReceiver interface { 37 } 38 39 + func GatheringCompletePromise(pc PeerConnection) <-chan struct{} { 40 + wrapped, ok := pc.(*WrappedPeerConnection) 41 + if ok { 42 + return webrtc.GatheringCompletePromise(wrapped.pionpc) 43 } 44 + recorder, ok := pc.(*RecorderPeerConnection) 45 + if ok { 46 + return webrtc.GatheringCompletePromise(recorder.pionpc) 47 + } 48 + panic("unknown peer connection type") 49 }
+78
pkg/rtcrec/recorder.go
···
··· 1 + package rtcrec 2 + 3 + import ( 4 + "github.com/pion/rtcp" 5 + "github.com/pion/webrtc/v4" 6 + ) 7 + 8 + type RecorderPeerConnection struct { 9 + pionpc *webrtc.PeerConnection 10 + } 11 + 12 + func NewRecorderPeerConnection(pionpc *webrtc.PeerConnection) PeerConnection { 13 + return &RecorderPeerConnection{ 14 + pionpc: pionpc, 15 + } 16 + } 17 + 18 + func (pc *RecorderPeerConnection) Close() error { 19 + return pc.pionpc.Close() 20 + } 21 + 22 + func (pc *RecorderPeerConnection) CreateAnswer(options *webrtc.AnswerOptions) (webrtc.SessionDescription, error) { 23 + return pc.pionpc.CreateAnswer(options) 24 + } 25 + 26 + func (pc *RecorderPeerConnection) CreateOffer(options *webrtc.OfferOptions) (webrtc.SessionDescription, error) { 27 + return pc.pionpc.CreateOffer(options) 28 + } 29 + 30 + func (pc *RecorderPeerConnection) SetLocalDescription(desc webrtc.SessionDescription) error { 31 + return pc.pionpc.SetLocalDescription(desc) 32 + } 33 + 34 + func (pc *RecorderPeerConnection) SetRemoteDescription(desc webrtc.SessionDescription) error { 35 + return pc.pionpc.SetRemoteDescription(desc) 36 + } 37 + 38 + func (pc *RecorderPeerConnection) LocalDescription() *webrtc.SessionDescription { 39 + return pc.pionpc.LocalDescription() 40 + } 41 + 42 + func (pc *RecorderPeerConnection) RemoteDescription() *webrtc.SessionDescription { 43 + return pc.pionpc.RemoteDescription() 44 + } 45 + 46 + func (pc *RecorderPeerConnection) OnICEConnectionStateChange(f func(webrtc.ICEConnectionState)) { 47 + pc.pionpc.OnICEConnectionStateChange(f) 48 + } 49 + 50 + func (pc *RecorderPeerConnection) OnConnectionStateChange(f func(webrtc.PeerConnectionState)) { 51 + pc.pionpc.OnConnectionStateChange(f) 52 + } 53 + 54 + func (pc *RecorderPeerConnection) OnTrack(f func(TrackRemote, RTPReceiver)) { 55 + pc.pionpc.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { 56 + f(track, receiver) 57 + }) 58 + } 59 + 60 + func (pc *RecorderPeerConnection) WriteRTCP(pkts []rtcp.Packet) error { 61 + return pc.pionpc.WriteRTCP(pkts) 62 + } 63 + 64 + func (pc *RecorderPeerConnection) AddTransceiverFromKind(kind webrtc.RTPCodecType, init ...webrtc.RTPTransceiverInit) (RTPTransceiver, error) { 65 + return pc.pionpc.AddTransceiverFromKind(kind, init...) 66 + } 67 + 68 + func (pc *RecorderPeerConnection) ICEGatheringState() webrtc.ICEGatheringState { 69 + return pc.pionpc.ICEGatheringState() 70 + } 71 + 72 + func (pc *RecorderPeerConnection) OnDataChannel(f func(*webrtc.DataChannel)) { 73 + pc.pionpc.OnDataChannel(f) 74 + } 75 + 76 + func (pc *RecorderPeerConnection) OnNegotiationNeeded(f func()) { 77 + pc.pionpc.OnNegotiationNeeded(f) 78 + }
-1
pkg/rtcrec/recording.go
··· 1 - package rtcrec
···
+52
pkg/rtcrec/webrtc_recording.go
···
··· 1 + package rtcrec 2 + 3 + import ( 4 + "io" 5 + "time" 6 + 7 + "github.com/fxamacker/cbor/v2" 8 + ) 9 + 10 + type WebRTCRecording struct { 11 + Events []WebRTCEvent 12 + } 13 + 14 + type WebRTCEvent struct { 15 + Offer *OfferEvent 16 + Answer *AnswerEvent 17 + Time time.Time 18 + } 19 + 20 + type OfferEvent struct { 21 + Offer string 22 + } 23 + 24 + type AnswerEvent struct { 25 + Answer string 26 + } 27 + 28 + type RecorderStream struct { 29 + encoder *cbor.Encoder 30 + } 31 + 32 + func NewRecorderStream(w io.Writer) (*RecorderStream, error) { 33 + encoder := cbor.NewEncoder(w) 34 + 35 + err := encoder.StartIndefiniteArray() 36 + if err != nil { 37 + return nil, err 38 + } 39 + 40 + return &RecorderStream{ 41 + encoder: encoder, 42 + }, nil 43 + } 44 + 45 + func (s *RecorderStream) Event(event WebRTCEvent) error { 46 + event.Time = time.Now() 47 + return s.encoder.Encode(event) 48 + } 49 + 50 + func (s *RecorderStream) Close() error { 51 + return s.encoder.EndIndefinite() 52 + }
+52
pkg/rtcrec/webrtc_recording_test.go
···
··· 1 + package rtcrec 2 + 3 + import ( 4 + "os" 5 + "testing" 6 + 7 + "github.com/fxamacker/cbor/v2" 8 + "github.com/stretchr/testify/require" 9 + ) 10 + 11 + func TestWebRTCRecording(t *testing.T) { 12 + // Create a temporary file for testing 13 + tmpfile, err := os.CreateTemp("", "webrtc-recording-test-*") 14 + require.NoError(t, err) 15 + 16 + // Create recorder stream writing to temp file 17 + recorder, err := NewRecorderStream(tmpfile) 18 + require.NoError(t, err) 19 + 20 + // Test recording an offer event 21 + offerEvent := WebRTCEvent{ 22 + Offer: &OfferEvent{ 23 + Offer: "test-offer", 24 + }, 25 + } 26 + require.NoError(t, recorder.Event(offerEvent)) 27 + 28 + // Test recording an answer event 29 + answerEvent := WebRTCEvent{ 30 + Answer: &AnswerEvent{ 31 + Answer: "test-answer", 32 + }, 33 + } 34 + require.NoError(t, recorder.Event(answerEvent)) 35 + 36 + err = recorder.Close() 37 + require.NoError(t, err) 38 + err = tmpfile.Close() 39 + require.NoError(t, err) 40 + 41 + // Read the file and verify the contents 42 + contents, err := os.ReadFile(tmpfile.Name()) 43 + require.NoError(t, err) 44 + 45 + var evs []WebRTCEvent 46 + err = cbor.Unmarshal(contents, &evs) 47 + require.NoError(t, err) 48 + 49 + require.Equal(t, 2, len(evs)) 50 + require.Equal(t, offerEvent.Offer, evs[0].Offer) 51 + require.Equal(t, answerEvent.Answer, evs[1].Answer) 52 + }
-8
pkg/rtcrec/wrapped.go
··· 15 } 16 } 17 18 - func GatheringCompletePromise(pc PeerConnection) <-chan struct{} { 19 - wrapped, ok := pc.(*WrappedPeerConnection) 20 - if !ok { 21 - panic("pc is not a *WrappedPeerConnection") 22 - } 23 - return webrtc.GatheringCompletePromise(wrapped.pionpc) 24 - } 25 - 26 func (pc *WrappedPeerConnection) Close() error { 27 return pc.pionpc.Close() 28 }
··· 15 } 16 } 17 18 func (pc *WrappedPeerConnection) Close() error { 19 return pc.pionpc.Close() 20 }