-
-
Save e3ndr/28787b08db0f73a96c34d41efa1614c0 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" | |
"encoding/json" | |
"errors" | |
"fmt" | |
"io" | |
"net/http" | |
"os" | |
"signal" | |
"time" | |
webrtcsignal "signal" | |
"github.com/pion/webrtc/v3" | |
"github.com/pion/webrtc/v3/pkg/media" | |
"github.com/pion/webrtc/v3/pkg/media/oggreader" | |
) | |
const ( | |
oggPageDuration = time.Millisecond * 20 | |
) | |
func main() { | |
resp, err := http.Get("https://cdn.casterlabs.co/api.json") | |
if err != nil { | |
panic(err) | |
} | |
body, err := io.ReadAll(resp.Body) | |
if err != nil { | |
panic(err) | |
} | |
var api map[string]interface{} | |
json.Unmarshal([]byte(body), &api) | |
iceServers := api["ice_servers"].([]interface{}) | |
iceServerList := make([]webrtc.ICEServer, len(iceServers)) | |
for idx, _server := range iceServers { | |
server := _server.(map[string]interface{}) | |
ice := webrtc.ICEServer{ | |
URLs: []string{server["urls"].(string)}, | |
} | |
if _, hasCredential := server["credential"]; hasCredential { | |
ice.Username = server["username"].(string) | |
ice.Credential = server["credential"].(string) | |
ice.CredentialType = webrtc.ICECredentialTypePassword | |
} | |
iceServerList[idx] = ice | |
} | |
fmt.Fprintf(os.Stderr, "debug:Using ice servers: %s\n", iceServerList) | |
// Create a new RTCPeerConnection | |
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{ | |
ICEServers: iceServerList, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
defer func() { | |
peerConnection.Close() | |
}() | |
iceConnectedCtx, iceConnectedCtxCancel := context.WithCancel(context.Background()) | |
// Create a audio track | |
audioTrack, audioTrackErr := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, Channels: 2, ClockRate: 48000}, "audio", "pion") | |
if audioTrackErr != nil { | |
panic(audioTrackErr) | |
} | |
rtpSender, audioTrackErr := peerConnection.AddTrack(audioTrack) | |
if audioTrackErr != nil { | |
panic(audioTrackErr) | |
} | |
// Now that stdin is free, start reading audio. | |
go func() { | |
// Wait for connection established | |
<-iceConnectedCtx.Done() | |
// Open on oggfile in non-checksum mode. | |
ogg, _, oggErr := oggreader.NewWith(os.Stdin) | |
if oggErr != nil { | |
panic(oggErr) | |
} | |
// Keep track of last granule, the difference is the amount of samples in the buffer | |
var lastGranule uint64 | |
// It is important to use a time.Ticker instead of time.Sleep because | |
// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data | |
// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343) | |
ticker := time.NewTicker(oggPageDuration) | |
for ; true; <-ticker.C { | |
pageData, pageHeader, oggErr := ogg.ParseNextPage() | |
if errors.Is(oggErr, io.EOF) { | |
fmt.Fprintln(os.Stderr, "close:") | |
os.Exit(0) | |
} | |
if oggErr != nil { | |
panic(oggErr) | |
} | |
// The amount of samples is the difference between the last and current timestamp | |
sampleCount := float64(pageHeader.GranulePosition - lastGranule) | |
lastGranule = pageHeader.GranulePosition | |
sampleDuration := time.Duration((sampleCount/48000)*1000) * time.Millisecond | |
if oggErr = audioTrack.WriteSample(media.Sample{Data: pageData, Duration: sampleDuration}); oggErr != nil { | |
panic(oggErr) | |
} | |
} | |
}() | |
// Read incoming RTCP packets | |
// Before these packets are returned they are processed by interceptors. For things | |
// like NACK this needs to be called. | |
go func() { | |
rtcpBuf := make([]byte, 1500) | |
for { | |
if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { | |
return | |
} | |
} | |
}() | |
// Set the handler for ICE connection state | |
// This will notify you when the peer has connected/disconnected | |
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { | |
fmt.Fprintf(os.Stderr, "state:%s\n", connectionState.String()) | |
if connectionState == webrtc.ICEConnectionStateConnected { | |
fmt.Fprintln(os.Stderr, "ready:") | |
iceConnectedCtxCancel() | |
} | |
}) | |
// Set the handler for Peer connection state | |
// This will notify you when the peer has connected/disconnected | |
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { | |
fmt.Fprintf(os.Stderr, "peerstate:%s\n", s.String()) | |
if s == webrtc.PeerConnectionStateFailed { | |
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. | |
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. | |
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected. | |
os.Exit(0) | |
} | |
}) | |
// Wait for the offer to be pasted | |
fmt.Fprintln(os.Stderr, "initialized:") | |
offer := webrtc.SessionDescription{} | |
signal.Decode(signal.MustReadStdin(), &offer) | |
// 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 | |
fmt.Fprintf(os.Stderr, "sdpoffer:%s\n", webrtcsignal.Encode(*peerConnection.LocalDescription())) | |
// Block forever | |
select {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment