Skip to content

Instantly share code, notes, and snippets.

@Sean-Der
Last active February 16, 2023 16:12
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sean-Der/9d0c78041a839809c27a9400a918b154 to your computer and use it in GitHub Desktop.
Save Sean-Der/9d0c78041a839809c27a9400a918b154 to your computer and use it in GitHub Desktop.
Example for metal3d
<html>
<body>
<video id="video"> </video>
<br />
<button onclick="start()">Call start</button>
<script>
function E(selector) {
return document.querySelector(selector);
}
function createPeerConnection() {
// create a connection and set received track to the video element
let config = {
sdpSemantics: 'unified-plan'
};
let pc = new RTCPeerConnection(config);
pc.ontrack = function (event) {
if (event.track.kind !== "video") {
return
}
console.log("TRACK");
var el = E('#video')
el.srcObject = event.streams[0]
el.autoplay = true
el.controls = true
}
return pc
}
function start() {
let pc = createPeerConnection();
// when captured webcam, add the streams to the peer connection to be sent
// to the server
navigator.mediaDevices.getUserMedia({
audio: false,
video: true,
}).then(stream => {
console.log("Add track");
stream.getTracks().forEach(track => {
pc.addTrack(track, stream)
});
// we can now try to negociate
// connection on server
return negociate(pc);
}, (err) => {
console.error(err);
});
}
function negociate(pc) {
// create an offer...
pc.createOffer().then((offer) => {
// .. then add offer to local description
return pc.setLocalDescription(offer);
}).then(() => {
// we need to wait that ICE Gathering is complete before to send offer to
// our server.
return new Promise(resolve => {
// add event listener on ice gathering state only if it's not complete.
if (pc.iceGatheringState === 'complete') {
resolve();
} else {
let waitComplete = () => {
if (pc.iceGatheringState === 'complete') {
pc.removeEventListener('icegatheringstatechange', waitComplete);
resolve();
}
}
pc.addEventListener('icegatheringstatechange', waitComplete);
}
});
}).then(() => {
// now that Ice Gathering is complete, we can send the offer
// to our live streaming server.
let offer = pc.localDescription;
// "fetch()" is wonderful, really !
return fetch('/offer', {
method: 'POST',
body: JSON.stringify({
sdp: offer.sdp,
type: offer.type,
}),
headers: {
'Content-Type': 'application/json',
}
});
}).then(response => {
return response.json(); // this is a Promise
}).then(answer => {
// we received an anwser (resolved fetch response) that is
// the sdp answer from server. The negociation seems to be OK, so
// we add it to our RTCPeerConnection object as "remote description".
return pc.setRemoteDescription(answer);
}).catch(error => {
console.error("Error in negociation", error);
});
}
</script>
</body>
</html>
package main
import (
"fmt"
"log"
"math/rand"
"time"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"github.com/pion/rtcp"
"github.com/pion/webrtc/v2"
)
func manage(offer webrtc.SessionDescription, answer chan<- *webrtc.SessionDescription) {
pc, err := webrtc.NewPeerConnection(webrtc.Configuration{})
if err != nil {
log.Panic(err)
}
ssrc := rand.Uint32()
fmt.Println(ssrc)
outputTrack, err := pc.NewTrack(webrtc.DefaultPayloadTypeVP8, ssrc, "video", "pion")
if err != nil {
log.Panic(err)
}
if _, err = pc.AddTrack(outputTrack); err != nil {
log.Panic(err)
}
pc.OnTrack(func(t *webrtc.Track, receiver *webrtc.RTPReceiver) {
go func() {
ticker := time.NewTicker(time.Second * 3)
for range ticker.C {
errSend := pc.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: t.SSRC()}})
if errSend != nil {
fmt.Println(errSend)
}
}
}()
log.Println("Track acquired", t.Kind(), t.Codec())
for {
rtp, err := t.ReadRTP()
if err != nil {
log.Panic(err)
}
rtp.SSRC = ssrc
rtp.PayloadType = webrtc.DefaultPayloadTypeVP8
if err := outputTrack.WriteRTP(rtp); err != nil {
log.Panic(err)
}
}
})
pc.OnICEConnectionStateChange(func(cs webrtc.ICEConnectionState) {
log.Println("Ice connection changed to ", cs.String())
if cs == webrtc.ICEConnectionStateFailed {
log.Println("Closing peer connection as ICE connection failed")
pc.Close()
}
})
if err := pc.SetRemoteDescription(offer); err != nil {
log.Panic("Set remote description failed:: ", err)
}
if a, err := pc.CreateAnswer(nil); err != nil {
log.Panic(err)
} else {
pc.SetLocalDescription(a)
answer <- pc.LocalDescription()
}
select {} // I do it for test, commenting this line does nothing
}
// Offer get client offer information and give back an answer.
func Offer(c *gin.Context) {
offer := webrtc.SessionDescription{}
c.BindJSON(&offer)
log.Println("Get offer", offer.Type)
answer := make(chan *webrtc.SessionDescription)
go manage(offer, answer)
c.JSON(200, <-answer)
}
func main() {
r := gin.Default()
r.Use(static.Serve("/", static.LocalFile(".", false)))
r.POST("/offer", Offer)
r.Run(":8080")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment