]> Sergey Matveev's repositories - btrtrc.git/blob - webtorrent/transport.go
Use offer_id for webrtc conn string
[btrtrc.git] / webtorrent / transport.go
1 package webtorrent
2
3 import (
4         "fmt"
5         "log"
6         "sync"
7
8         "github.com/pion/datachannel"
9
10         "github.com/pion/webrtc/v2"
11 )
12
13 var (
14         api = func() *webrtc.API {
15                 // Enable the detach API (since it's non-standard but more idiomatic)
16                 // (This should be done once globally)
17                 s := webrtc.SettingEngine{}
18                 s.DetachDataChannels()
19                 return webrtc.NewAPI(webrtc.WithSettingEngine(s))
20         }()
21         config              = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}}
22         newPeerConnectionMu sync.Mutex
23 )
24
25 func newPeerConnection() (*webrtc.PeerConnection, error) {
26         newPeerConnectionMu.Lock()
27         defer newPeerConnectionMu.Unlock()
28         return api.NewPeerConnection(config)
29 }
30
31 type Transport struct {
32         pc *webrtc.PeerConnection
33         dc *webrtc.DataChannel
34
35         lock sync.Mutex
36 }
37
38 // NewTransport creates a transport and returns a WebRTC offer to be announced
39 func NewTransport() (*Transport, webrtc.SessionDescription, error) {
40         peerConnection, err := newPeerConnection()
41         if err != nil {
42                 return nil, webrtc.SessionDescription{}, fmt.Errorf("failed to peer connection: %v\n", err)
43         }
44         dataChannel, err := peerConnection.CreateDataChannel("webrtc-datachannel", nil)
45         if err != nil {
46                 return nil, webrtc.SessionDescription{}, fmt.Errorf("failed to data channel: %v\n", err)
47         }
48         peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
49                 fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
50         })
51
52         dataChannel.OnMessage(func(msg webrtc.DataChannelMessage) {
53                 fmt.Printf("Message from DataChannel '%s': '%s'\n", dataChannel.Label(), string(msg.Data))
54         })
55         offer, err := peerConnection.CreateOffer(nil)
56         if err != nil {
57                 return nil, webrtc.SessionDescription{}, fmt.Errorf("failed to create offer: %v\n", err)
58         }
59         err = peerConnection.SetLocalDescription(offer)
60         if err != nil {
61                 return nil, webrtc.SessionDescription{}, fmt.Errorf("failed to set local description: %v\n", err)
62         }
63
64         t := &Transport{pc: peerConnection, dc: dataChannel}
65         return t, offer, nil
66 }
67
68 // NewTransportFromOffer creates a transport from a WebRTC offer and and returns a WebRTC answer to
69 // be announced.
70 func NewTransportFromOffer(offer webrtc.SessionDescription, onOpen onDataChannelOpen, offerId string) (*Transport, webrtc.SessionDescription, error) {
71         peerConnection, err := newPeerConnection()
72         if err != nil {
73                 return nil, webrtc.SessionDescription{}, fmt.Errorf("failed to peer connection: %v", err)
74         }
75         peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
76                 fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
77         })
78
79         t := &Transport{pc: peerConnection}
80
81         err = peerConnection.SetRemoteDescription(offer)
82         if err != nil {
83                 return nil, webrtc.SessionDescription{}, fmt.Errorf("%v", err)
84         }
85         answer, err := peerConnection.CreateAnswer(nil)
86         if err != nil {
87                 return nil, webrtc.SessionDescription{}, fmt.Errorf("%v", err)
88         }
89         peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
90                 fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID())
91                 t.lock.Lock()
92                 t.dc = d
93                 t.lock.Unlock()
94                 t.handleOpen(func(dc datachannel.ReadWriteCloser) {
95                         onOpen(dc, DataChannelContext{answer, offer, offerId, false})
96                 })
97         })
98         err = peerConnection.SetLocalDescription(answer)
99         if err != nil {
100                 return nil, webrtc.SessionDescription{}, fmt.Errorf("%v", err)
101         }
102
103         return t, answer, nil
104 }
105
106 // SetAnswer sets the WebRTC answer
107 func (t *Transport) SetAnswer(answer webrtc.SessionDescription, onOpen func(datachannel.ReadWriteCloser)) error {
108         t.handleOpen(onOpen)
109
110         err := t.pc.SetRemoteDescription(answer)
111         if err != nil {
112                 return err
113         }
114         return nil
115 }
116
117 func (t *Transport) handleOpen(onOpen func(datachannel.ReadWriteCloser)) {
118         t.lock.Lock()
119         dc := t.dc
120         t.lock.Unlock()
121         dc.OnOpen(func() {
122                 fmt.Printf("Data channel '%s'-'%d' open.\n", dc.Label(), dc.ID())
123
124                 // Detach the data channel
125                 raw, err := dc.Detach()
126                 if err != nil {
127                         log.Fatalf("failed to detach: %v", err) // TODO: Error handling
128                 }
129
130                 onOpen(raw)
131         })
132 }