Skip to content

Commit

Permalink
Support for websocket without turn server
Browse files Browse the repository at this point in the history
  • Loading branch information
slashblog committed Aug 14, 2024
1 parent fd0ec67 commit 5a5b512
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 18 deletions.
6 changes: 6 additions & 0 deletions pkg/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ package main

import "github.com/pion/webrtc/v3"

const (
TurnInternal = "internal"
TurnPublicIp = "ip"
)

type OpenRelay struct {
AppName string `yaml:"app_name"`
ApiKey string `yaml:"api_key"`
Expand All @@ -37,6 +42,7 @@ type UserCredentials struct {
}

type TurnConfiguration struct {
TurnType string `yaml:"type" validate:"oneof=ip internal" default:"ip"`
PublicIp string `yaml:"public_ip" validate:"required"`
UdpPort int `yaml:"port" validate:"required,number,gte=1,lte=65535" default:"8080"`
Users []UserCredentials `yaml:"users" validate:"required"`
Expand Down
4 changes: 3 additions & 1 deletion pkg/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ func setupCommon(config *Configuration) *os.File {
log.Fatalln("At least one user needs to be provided for server")
}

go setupTurnServer(config)
if config.TurnConfiguration.TurnType == TurnInternal {
go setupTurnServer(config)
}
}

return f
Expand Down
55 changes: 38 additions & 17 deletions pkg/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/go-gst/go-gst/gst"
"github.com/go-gst/go-gst/gst/app"
"github.com/pion/interceptor"
"github.com/pion/webrtc/v3"
"github.com/pion/webrtc/v3/pkg/media"
)
Expand All @@ -38,20 +39,42 @@ func StartStreaming(conf *Configuration, videoSrc, audioSrc, sdpFile string, wai
startExecuting(conf, videoSrc, audioSrc, sdpFile, wait)
}

func getWebrtcConfiguration(conf *Configuration) webrtc.Configuration {
func getWebrtcPeerConfiguration(conf *Configuration) (*webrtc.PeerConnection, error) {
config := webrtc.Configuration{}
if conf.UseInternalTurn {
config.ICEServers = make([]webrtc.ICEServer, 2*len(conf.TurnConfiguration.Users))

for i, user := range conf.TurnConfiguration.Users {
config.ICEServers[2*i].URLs = make([]string, 1)
config.ICEServers[2*i].URLs[0] = fmt.Sprintf("stun:%s:%d", "127.0.0.1", conf.TurnConfiguration.UdpPort)
config.ICEServers[2*i].Username = user.User
config.ICEServers[2*i].Credential = user.Password
config.ICEServers[2*i+1].URLs = make([]string, 1)
config.ICEServers[2*i+1].URLs[0] = fmt.Sprintf("turn:%s:%d", "127.0.0.1", conf.TurnConfiguration.UdpPort)
config.ICEServers[2*i+1].Username = user.User
config.ICEServers[2*i+1].Credential = user.Password
if conf.TurnConfiguration.TurnType == TurnInternal {
config.ICEServers = make([]webrtc.ICEServer, 2*len(conf.TurnConfiguration.Users))

for i, user := range conf.TurnConfiguration.Users {
config.ICEServers[2*i].URLs = make([]string, 1)
config.ICEServers[2*i].URLs[0] = fmt.Sprintf("stun:%s:%d", "127.0.0.1", conf.TurnConfiguration.UdpPort)
config.ICEServers[2*i].Username = user.User
config.ICEServers[2*i].Credential = user.Password
config.ICEServers[2*i+1].URLs = make([]string, 1)
config.ICEServers[2*i+1].URLs[0] = fmt.Sprintf("turn:%s:%d", "127.0.0.1", conf.TurnConfiguration.UdpPort)
config.ICEServers[2*i+1].Username = user.User
config.ICEServers[2*i+1].Credential = user.Password
}
} else {
m := &webrtc.MediaEngine{}
if err := m.RegisterDefaultCodecs(); err != nil {
return nil, err
}

i := &interceptor.Registry{}
if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil {
return nil, err
}

s := webrtc.SettingEngine{}
s.SetNAT1To1IPs([]string{conf.TurnConfiguration.PublicIp}, webrtc.ICECandidateTypeSrflx)

config.ICEServers = make([]webrtc.ICEServer, 1)
config.ICEServers[0].URLs = make([]string, 1)
config.ICEServers[0].URLs[0] = "stun:stun.l.google.com:19302"

api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i), webrtc.WithSettingEngine(s))
return api.NewPeerConnection(config)
}
} else if conf.OpenRelayConfig != nil {
fmt.Println("Found Open Relay Config")
Expand Down Expand Up @@ -90,7 +113,8 @@ func getWebrtcConfiguration(conf *Configuration) webrtc.Configuration {
config.ICEServers[0].URLs[0] = "stun:stun.l.google.com:19302"
}

return config
fmt.Printf("Webrtc config: %v\n", config)
return webrtc.NewPeerConnection(config)
}

func printAnswer(answer webrtc.SessionDescription) {
Expand All @@ -101,10 +125,7 @@ func printAnswer(answer webrtc.SessionDescription) {
func startExecuting(conf *Configuration, videoSrc, audioSrc, sdpFile string, wait bool) {
gst.Init(nil)

config := getWebrtcConfiguration(conf)
fmt.Printf("Webrtc config: %v\n", config)

peerConnection, err := webrtc.NewPeerConnection(config)
peerConnection, err := getWebrtcPeerConfiguration(conf)
if err != nil {
log.Fatalln(err)
}
Expand Down
218 changes: 218 additions & 0 deletions samples/websocket/stream-no-turn.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
<?php

const SERVER = '<ip>';
const HTTP_PORT = '<port>';
const WS_USERNAME = '<username>';
const WS_PASSWORD = '<password>';

?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Stream</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #000;
}
.video-container .image-container {
width: 640px;
height: 480px;
background-color: #000;
display: flex;
justify-content: center;
align-items: center;
}

.video-container video {
width: 100%;
height: 100%;
object-fit: contain;
}
@media (max-width: 768px) {
.video-container {
width: 100%;
height: auto;
aspect-ratio: 4 / 3;
}
.video-container video {
width: 100%;
height: 100%;
}
}

.image-container {
z-index: 10;
}
</style>
</head>
<body>
<script>

window.startVideo = async function () {
let remoteStream = new MediaStream()
//document.getElementById('video-player').srcObject = remoteStream

let wsConnected = false;

function wsConnect() {
if (wsConnected) {
return;
}

wsConnected = true;
let localSessionDescription = btoa(JSON.stringify(pc.localDescription));
console.log('Local: ' + localSessionDescription);
connectWebsocket(pc, localSessionDescription, remoteStream);
}


let pc = new RTCPeerConnection({
iceServers: [
{"urls":['stun:stun.l.google.com:19302']},
{"urls":['stun:stun1.l.google.com:19302']},
{"urls":['stun:stun2.l.google.com:19302']},
{"urls":['stun:stun3.l.google.com:19302']},
{"urls":['stun:stun4.l.google.com:19302']},
]
});

let log = msg => {
console.log(msg);
}

pc.ontrack = function(event) {
event.streams[0].getTracks().forEach((track) => {
console.log('Track added');
remoteStream.addTrack(track)
})
}

pc.oniceconnectionstatechange = e => log(pc.iceConnectionState)
pc.onicecandidate = async (event) => {
if (event.candidate) {
console.log(JSON.stringify(event.candidate));
//console.log('Updated candidate: ' + btoa(JSON.stringify(pc.localDescription)));
setTimeout(wsConnect, 2000);
} else {
wsConnect();
}
}

// Offer to receive 1 audio, and 1 video track
pc.addTransceiver('video', {
'direction': 'sendrecv'
})
pc.addTransceiver('audio', {
'direction': 'sendrecv'
})

pc.createOffer().then(d => {
pc.setLocalDescription(d);
}).catch(log)
}

class Event {
constructor(type, payload) {
this.type = type;
this.payload = payload;
}
}

class ConnectEvent {
constructor(sdp, user, password) {
this.sdp = sdp;
this.user = user;
this.password = password;
}
}

class AnswerEvent {
constructor(answer){
this.answer = answer
}
}

class NewCandidateEvent {
constructor() {
}
}

function routeEvent(event, pc, remoteStream, conn) {
if (event.type === undefined) {
alert("no 'type' field in event");
}
console.log("Event type is: " + event.type)
switch (event.type) {
case 'answer':
const answerEvent = Object.assign(new AnswerEvent, event.payload);
pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(answerEvent.answer))));
let p = document.getElementById('video-player')
p.srcObject = remoteStream;
p.onclick = function() {
p.play();
p.onclick = null;
}
break;
case 'new_candidate':
const candidateEvent = Object.assign(new NewCandidateEvent, event.payload);
pc.addIceCandidate(candidateEvent).catch((e) => {
console.log(`Failure during addIceCandidate(): ${e.name}`);
});
break;
case 'disconnect':
console.log('Closing connection');
conn.close()
break;
default:
alert("unsupported message type");
break;
}
}

function connectWebsocket(pc, sdp, remoteStream) {
if (window["WebSocket"]) {
console.log("supports websockets");
conn = new WebSocket("ws://" + <?php echo SERVER;?> + ":" + <?php echo HTTP_PORT;?> + "/ws");

conn.onopen = function (evt) {
console.log('Connected to Websocket: true');
let connectEvent = new ConnectEvent(sdp, '<?php echo WS_USERNAME;?>', '<?php echo WS_PASSWORD;?>');
let event = new Event('connect', connectEvent);
conn.send(JSON.stringify(event))
}

conn.onclose = function (evt) {
console.log('Connected to Websocket: false');
}

conn.onmessage = function (evt) {
console.log(evt);
const eventData = JSON.parse(evt.data);
const event = Object.assign(new Event, eventData);
routeEvent(event, pc, remoteStream, conn);
}

} else {
alert("Not supporting websockets");
}
}

window.onload = function () {
window.startVideo();
}

</script>

<div class="video-container">
<video preload="metadata" id="video-player"></video>
</div>
</body>
</html>

0 comments on commit 5a5b512

Please sign in to comment.