github.com/pion/webrtc/v4@v4.0.1/examples/insertable-streams/main.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build !js 5 // +build !js 6 7 // insertable-streams demonstrates how to use insertable streams with Pion 8 package main 9 10 import ( 11 "bufio" 12 "context" 13 "encoding/base64" 14 "encoding/json" 15 "errors" 16 "fmt" 17 "io" 18 "os" 19 "strings" 20 "time" 21 22 "github.com/pion/webrtc/v4" 23 "github.com/pion/webrtc/v4/pkg/media" 24 "github.com/pion/webrtc/v4/pkg/media/ivfreader" 25 ) 26 27 const cipherKey = 0xAA 28 29 // nolint:gocognit 30 func main() { 31 peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{ 32 ICEServers: []webrtc.ICEServer{ 33 { 34 URLs: []string{"stun:stun.l.google.com:19302"}, 35 }, 36 }, 37 }) 38 if err != nil { 39 panic(err) 40 } 41 defer func() { 42 if cErr := peerConnection.Close(); cErr != nil { 43 fmt.Printf("cannot close peerConnection: %v\n", cErr) 44 } 45 }() 46 47 // Create a video track 48 videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion") 49 if err != nil { 50 panic(err) 51 } 52 rtpSender, err := peerConnection.AddTrack(videoTrack) 53 if err != nil { 54 panic(err) 55 } 56 57 // Read incoming RTCP packets 58 // Before these packets are returned they are processed by interceptors. For things 59 // like NACK this needs to be called. 60 go func() { 61 rtcpBuf := make([]byte, 1500) 62 for { 63 if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { 64 return 65 } 66 } 67 }() 68 69 iceConnectedCtx, iceConnectedCtxCancel := context.WithCancel(context.Background()) 70 go func() { 71 // Open a IVF file and start reading using our IVFReader 72 file, ivfErr := os.Open("output.ivf") 73 if ivfErr != nil { 74 panic(ivfErr) 75 } 76 77 ivf, header, ivfErr := ivfreader.NewWith(file) 78 if ivfErr != nil { 79 panic(ivfErr) 80 } 81 82 // Wait for connection established 83 <-iceConnectedCtx.Done() 84 85 // Send our video file frame at a time. Pace our sending so we send it at the same speed it should be played back as. 86 // This isn't required since the video is timestamped, but we will such much higher loss if we send all at once. 87 // 88 // It is important to use a time.Ticker instead of time.Sleep because 89 // * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data 90 // * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343) 91 ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000)) 92 defer ticker.Stop() 93 for range ticker.C { 94 frame, _, ivfErr := ivf.ParseNextFrame() 95 if errors.Is(ivfErr, io.EOF) { 96 fmt.Printf("All frames parsed and sent") 97 os.Exit(0) 98 } 99 100 if ivfErr != nil { 101 panic(ivfErr) 102 } 103 104 // Encrypt video using XOR Cipher 105 for i := range frame { 106 frame[i] ^= cipherKey 107 } 108 109 if ivfErr = videoTrack.WriteSample(media.Sample{Data: frame, Duration: time.Second}); ivfErr != nil { 110 panic(ivfErr) 111 } 112 } 113 }() 114 115 // Set the handler for ICE connection state 116 // This will notify you when the peer has connected/disconnected 117 peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { 118 fmt.Printf("Connection State has changed %s \n", connectionState.String()) 119 if connectionState == webrtc.ICEConnectionStateConnected { 120 iceConnectedCtxCancel() 121 } 122 }) 123 124 // Set the handler for Peer connection state 125 // This will notify you when the peer has connected/disconnected 126 peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { 127 fmt.Printf("Peer Connection State has changed: %s\n", s.String()) 128 129 if s == webrtc.PeerConnectionStateFailed { 130 // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 131 // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 132 // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 133 fmt.Println("Peer Connection has gone to failed exiting") 134 os.Exit(0) 135 } 136 137 if s == webrtc.PeerConnectionStateClosed { 138 // PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify 139 fmt.Println("Peer Connection has gone to closed exiting") 140 os.Exit(0) 141 } 142 }) 143 144 // Wait for the offer to be pasted 145 offer := webrtc.SessionDescription{} 146 decode(readUntilNewline(), &offer) 147 148 // Set the remote SessionDescription 149 if err = peerConnection.SetRemoteDescription(offer); err != nil { 150 panic(err) 151 } 152 153 // Create answer 154 answer, err := peerConnection.CreateAnswer(nil) 155 if err != nil { 156 panic(err) 157 } 158 159 // Create channel that is blocked until ICE Gathering is complete 160 gatherComplete := webrtc.GatheringCompletePromise(peerConnection) 161 162 // Sets the LocalDescription, and starts our UDP listeners 163 if err = peerConnection.SetLocalDescription(answer); err != nil { 164 panic(err) 165 } 166 167 // Block until ICE Gathering is complete, disabling trickle ICE 168 // we do this because we only can exchange one signaling message 169 // in a production application you should exchange ICE Candidates via OnICECandidate 170 <-gatherComplete 171 172 // Output the answer in base64 so we can paste it in browser 173 fmt.Println(encode(peerConnection.LocalDescription())) 174 175 // Block forever 176 select {} 177 } 178 179 // Read from stdin until we get a newline 180 func readUntilNewline() (in string) { 181 var err error 182 183 r := bufio.NewReader(os.Stdin) 184 for { 185 in, err = r.ReadString('\n') 186 if err != nil && !errors.Is(err, io.EOF) { 187 panic(err) 188 } 189 190 if in = strings.TrimSpace(in); len(in) > 0 { 191 break 192 } 193 } 194 195 fmt.Println("") 196 return 197 } 198 199 // JSON encode + base64 a SessionDescription 200 func encode(obj *webrtc.SessionDescription) string { 201 b, err := json.Marshal(obj) 202 if err != nil { 203 panic(err) 204 } 205 206 return base64.StdEncoding.EncodeToString(b) 207 } 208 209 // Decode a base64 and unmarshal JSON into a SessionDescription 210 func decode(in string, obj *webrtc.SessionDescription) { 211 b, err := base64.StdEncoding.DecodeString(in) 212 if err != nil { 213 panic(err) 214 } 215 216 if err = json.Unmarshal(b, obj); err != nil { 217 panic(err) 218 } 219 }