tangled
alpha
login
or
join now
stream.place
/
streamplace
Live video on the AT Protocol
74
fork
atom
overview
issues
1
pulls
pipelines
rtcrec: basic serialization and deserialization
Eli Mallon
8 months ago
95a51376
b7a3a40e
+197
-18
8 changed files
expand all
collapse all
unified
split
go.mod
go.sum
pkg
rtcrec
peerproxy.go
recorder.go
recording.go
webrtc_recording.go
webrtc_recording_test.go
wrapped.go
+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
0
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
0
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=
0
0
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=
0
0
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
0
0
0
0
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)
0
0
0
0
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
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
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
···
0
+52
pkg/rtcrec/webrtc_recording.go
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
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
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
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
0
0
0
0
0
0
0
0
18
func (pc *WrappedPeerConnection) Close() error {
19
return pc.pionpc.Close()
20
}