9 "github.com/anacrolix/missinggo/v2/pproffd"
10 "github.com/pion/datachannel"
11 "github.com/pion/webrtc/v3"
15 metrics = expvar.NewMap("webtorrent")
16 api = func() *webrtc.API {
17 // Enable the detach API (since it's non-standard but more idiomatic).
18 s.DetachDataChannels()
19 return webrtc.NewAPI(webrtc.WithSettingEngine(s))
21 config = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}}
22 newPeerConnectionMu sync.Mutex
25 type wrappedPeerConnection struct {
26 *webrtc.PeerConnection
31 func (me *wrappedPeerConnection) Close() error {
33 defer me.closeMu.Unlock()
34 return me.CloseWrapper.Close()
37 func newPeerConnection() (*wrappedPeerConnection, error) {
38 newPeerConnectionMu.Lock()
39 defer newPeerConnectionMu.Unlock()
40 pc, err := api.NewPeerConnection(config)
44 return &wrappedPeerConnection{
46 CloseWrapper: pproffd.NewCloseWrapper(pc),
50 // newOffer creates a transport and returns a WebRTC offer to be announced
52 peerConnection *wrappedPeerConnection,
53 dataChannel *webrtc.DataChannel,
54 offer webrtc.SessionDescription,
57 peerConnection, err = newPeerConnection()
61 dataChannel, err = peerConnection.CreateDataChannel("webrtc-datachannel", nil)
63 peerConnection.Close()
66 offer, err = peerConnection.CreateOffer(nil)
68 peerConnection.Close()
72 gatherComplete := webrtc.GatheringCompletePromise(peerConnection.PeerConnection)
73 err = peerConnection.SetLocalDescription(offer)
75 peerConnection.Close()
80 offer = *peerConnection.LocalDescription()
84 func initAnsweringPeerConnection(
85 peerConnection *wrappedPeerConnection,
86 offer webrtc.SessionDescription,
87 ) (answer webrtc.SessionDescription, err error) {
88 err = peerConnection.SetRemoteDescription(offer)
92 answer, err = peerConnection.CreateAnswer(nil)
97 gatherComplete := webrtc.GatheringCompletePromise(peerConnection.PeerConnection)
98 err = peerConnection.SetLocalDescription(answer)
104 answer = *peerConnection.LocalDescription()
108 // newAnsweringPeerConnection creates a transport from a WebRTC offer and and returns a WebRTC answer to be
110 func newAnsweringPeerConnection(offer webrtc.SessionDescription) (
111 peerConn *wrappedPeerConnection, answer webrtc.SessionDescription, err error,
113 peerConn, err = newPeerConnection()
115 err = fmt.Errorf("failed to create new connection: %w", err)
118 answer, err = initAnsweringPeerConnection(peerConn, offer)
125 func (t *outboundOffer) setAnswer(answer webrtc.SessionDescription, onOpen func(datachannel.ReadWriteCloser)) error {
126 setDataChannelOnOpen(t.dataChannel, t.peerConnection, onOpen)
127 err := t.peerConnection.SetRemoteDescription(answer)
131 type datachannelReadWriter interface {
138 type ioCloserFunc func() error
140 func (me ioCloserFunc) Close() error {
144 func setDataChannelOnOpen(
145 dc *webrtc.DataChannel,
146 pc *wrappedPeerConnection,
147 onOpen func(closer datachannel.ReadWriteCloser),
150 raw, err := dc.Detach()
152 // This shouldn't happen if the API is configured correctly, and we call from OnOpen.
155 onOpen(hookDataChannelCloser(raw, pc))
159 // Hooks the datachannel's Close to Close the owning PeerConnection. The datachannel takes ownership
160 // and responsibility for the PeerConnection.
161 func hookDataChannelCloser(dcrwc datachannel.ReadWriteCloser, pc *wrappedPeerConnection) datachannel.ReadWriteCloser {
163 datachannelReadWriter
167 ioCloserFunc(pc.Close),