Created
November 5, 2020 07:25
-
-
Save Sean-Der/69e2f5a13557de7bdadb2c7a9b0adc73 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"io" | |
"os" | |
"time" | |
"github.com/pion/randutil" | |
"github.com/pion/webrtc/v3" | |
"github.com/pion/webrtc/v3/examples/internal/signal" | |
"github.com/pion/webrtc/v3/pkg/media" | |
"github.com/pion/webrtc/v3/pkg/media/h264reader" | |
) | |
const ( | |
videoFileName = "doom.h264" | |
) | |
func main() { | |
// Assert that we have an audio or video file | |
_, err := os.Stat(videoFileName) | |
haveVideoFile := !os.IsNotExist(err) | |
// Wait for the offer to be pasted | |
offer := webrtc.SessionDescription{} | |
signal.Decode(signal.MustReadStdin(), &offer) | |
// We make our own mediaEngine so we can place the sender's codecs in it. This because we must use the | |
// dynamic media type from the sender in our answer. This is not required if we are the offerer | |
mediaEngine := webrtc.MediaEngine{} | |
if err = mediaEngine.PopulateFromSDP(offer); err != nil { | |
panic(err) | |
} | |
// Create a new RTCPeerConnection | |
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine)) | |
peerConnection, err := api.NewPeerConnection(webrtc.Configuration{ | |
ICEServers: []webrtc.ICEServer{ | |
{ | |
URLs: []string{"stun:stun.l.google.com:19302"}, | |
}, | |
}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
iceConnectedCtx, iceConnectedCtxCancel := context.WithCancel(context.Background()) | |
if haveVideoFile { | |
// Create a video track | |
videoTrack, addTrackErr := peerConnection.NewTrack(getPayloadType(mediaEngine, webrtc.RTPCodecTypeVideo, "H264"), randutil.NewMathRandomGenerator().Uint32(), "video", "pion") | |
if addTrackErr != nil { | |
panic(addTrackErr) | |
} | |
if _, addTrackErr = peerConnection.AddTrack(videoTrack); addTrackErr != nil { | |
panic(addTrackErr) | |
} | |
go func() { | |
file, h264Err := os.Open(videoFileName) | |
if h264Err != nil { | |
panic(h264Err) | |
} | |
h264, h264Err := h264reader.NewWith(file) | |
if h264Err != nil { | |
panic(h264Err) | |
} | |
// Wait for connection established | |
<-iceConnectedCtx.Done() | |
spsAndPpsCache := []byte{} | |
for { | |
nal, h264Err := h264.NextNAL() | |
if h264Err == io.EOF { | |
fmt.Printf("All video frames parsed and sent") | |
os.Exit(0) | |
} | |
if h264Err != nil { | |
panic(h264Err) | |
} | |
time.Sleep(time.Millisecond * 33) | |
nal.Data = append([]byte{0x00, 0x00, 0x00, 0x01}, nal.Data...) | |
if nal.UnitType == h264reader.NalUnitTypeSPS || nal.UnitType == h264reader.NalUnitTypePPS { | |
spsAndPpsCache = append(spsAndPpsCache, nal.Data...) | |
continue | |
} else if nal.UnitType == h264reader.NalUnitTypeCodedSliceIdr { | |
nal.Data = append(spsAndPpsCache, nal.Data...) | |
spsAndPpsCache = []byte{} | |
} | |
if h264Err = videoTrack.WriteSample(media.Sample{Data: nal.Data, Samples: 90000}); h264Err != nil { | |
panic(h264Err) | |
} | |
} | |
}() | |
} | |
// Set the handler for ICE connection state | |
// This will notify you when the peer has connected/disconnected | |
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { | |
fmt.Printf("Connection State has changed %s \n", connectionState.String()) | |
if connectionState == webrtc.ICEConnectionStateConnected { | |
iceConnectedCtxCancel() | |
} | |
}) | |
// Set the remote SessionDescription | |
if err = peerConnection.SetRemoteDescription(offer); err != nil { | |
panic(err) | |
} | |
// Create answer | |
answer, err := peerConnection.CreateAnswer(nil) | |
if err != nil { | |
panic(err) | |
} | |
// Create channel that is blocked until ICE Gathering is complete | |
gatherComplete := webrtc.GatheringCompletePromise(peerConnection) | |
// Sets the LocalDescription, and starts our UDP listeners | |
if err = peerConnection.SetLocalDescription(answer); err != nil { | |
panic(err) | |
} | |
// Block until ICE Gathering is complete, disabling trickle ICE | |
// we do this because we only can exchange one signaling message | |
// in a production application you should exchange ICE Candidates via OnICECandidate | |
<-gatherComplete | |
// Output the answer in base64 so we can paste it in browser | |
fmt.Println(signal.Encode(*peerConnection.LocalDescription())) | |
// Block forever | |
select {} | |
} | |
// Search for Codec PayloadType | |
// | |
// Since we are answering we need to match the remote PayloadType | |
func getPayloadType(m webrtc.MediaEngine, codecType webrtc.RTPCodecType, codecName string) uint8 { | |
for _, codec := range m.GetCodecsByKind(codecType) { | |
if codec.Name == codecName { | |
return codec.PayloadType | |
} | |
} | |
panic(fmt.Sprintf("Remote peer does not support %s", codecName)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment