9 "github.com/anacrolix/missinggo/v2/pproffd"
10 "github.com/pion/datachannel"
11 "github.com/pion/logging"
13 "github.com/pion/webrtc/v3"
16 type DiscardLoggerFactory struct{}
18 func (f *DiscardLoggerFactory) NewLogger(scope string) logging.LeveledLogger {
19 return logging.NewDefaultLeveledLoggerForScope(scope, logging.LogLevelInfo, io.Discard)
23 metrics = expvar.NewMap("webtorrent")
24 api = func() *webrtc.API {
25 // Enable the detach API (since it's non-standard but more idiomatic).
26 s := webrtc.SettingEngine{
27 LoggerFactory: &DiscardLoggerFactory{},
29 s.DetachDataChannels()
30 return webrtc.NewAPI(webrtc.WithSettingEngine(s))
32 config = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}}
33 newPeerConnectionMu sync.Mutex
36 type wrappedPeerConnection struct {
37 *webrtc.PeerConnection
42 func (me *wrappedPeerConnection) Close() error {
44 defer me.closeMu.Unlock()
45 return me.CloseWrapper.Close()
48 func newPeerConnection() (*wrappedPeerConnection, error) {
49 newPeerConnectionMu.Lock()
50 defer newPeerConnectionMu.Unlock()
51 pc, err := api.NewPeerConnection(config)
55 return &wrappedPeerConnection{
57 CloseWrapper: pproffd.NewCloseWrapper(pc),
61 // newOffer creates a transport and returns a WebRTC offer to be announced
63 peerConnection *wrappedPeerConnection,
64 dataChannel *webrtc.DataChannel,
65 offer webrtc.SessionDescription,
68 peerConnection, err = newPeerConnection()
72 dataChannel, err = peerConnection.CreateDataChannel("webrtc-datachannel", nil)
74 peerConnection.Close()
77 offer, err = peerConnection.CreateOffer(nil)
79 peerConnection.Close()
83 gatherComplete := webrtc.GatheringCompletePromise(peerConnection.PeerConnection)
84 err = peerConnection.SetLocalDescription(offer)
86 peerConnection.Close()
91 offer = *peerConnection.LocalDescription()
95 func initAnsweringPeerConnection(
96 peerConnection *wrappedPeerConnection,
97 offer webrtc.SessionDescription,
98 ) (answer webrtc.SessionDescription, err error) {
99 err = peerConnection.SetRemoteDescription(offer)
103 answer, err = peerConnection.CreateAnswer(nil)
108 gatherComplete := webrtc.GatheringCompletePromise(peerConnection.PeerConnection)
109 err = peerConnection.SetLocalDescription(answer)
115 answer = *peerConnection.LocalDescription()
119 // newAnsweringPeerConnection creates a transport from a WebRTC offer and and returns a WebRTC answer to be
121 func newAnsweringPeerConnection(offer webrtc.SessionDescription) (
122 peerConn *wrappedPeerConnection, answer webrtc.SessionDescription, err error,
124 peerConn, err = newPeerConnection()
126 err = fmt.Errorf("failed to create new connection: %w", err)
129 answer, err = initAnsweringPeerConnection(peerConn, offer)
136 func (t *outboundOffer) setAnswer(answer webrtc.SessionDescription, onOpen func(datachannel.ReadWriteCloser)) error {
137 setDataChannelOnOpen(t.dataChannel, t.peerConnection, onOpen)
138 err := t.peerConnection.SetRemoteDescription(answer)
142 type datachannelReadWriter interface {
149 type ioCloserFunc func() error
151 func (me ioCloserFunc) Close() error {
155 func setDataChannelOnOpen(
156 dc *webrtc.DataChannel,
157 pc *wrappedPeerConnection,
158 onOpen func(closer datachannel.ReadWriteCloser),
161 raw, err := dc.Detach()
163 // This shouldn't happen if the API is configured correctly, and we call from OnOpen.
166 onOpen(hookDataChannelCloser(raw, pc))
170 // Hooks the datachannel's Close to Close the owning PeerConnection. The datachannel takes ownership
171 // and responsibility for the PeerConnection.
172 func hookDataChannelCloser(dcrwc datachannel.ReadWriteCloser, pc *wrappedPeerConnection) datachannel.ReadWriteCloser {
174 datachannelReadWriter
178 ioCloserFunc(pc.Close),