Implemented WebRTC peer-to-peer screen-roadcasting
This commit is contained in:
parent
62fc69489d
commit
96cdcfc840
5 changed files with 120 additions and 29 deletions
|
|
@ -36,7 +36,7 @@
|
|||
},
|
||||
"iceServers":[
|
||||
{
|
||||
"url":"stun:stun.moonlight-stream.org:3478"
|
||||
"urls":["stun:stun.moonlight-stream.org:3478"]
|
||||
}
|
||||
],
|
||||
"links":{
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
//Privdes STUN/TURN servers for WebRTC functionality
|
||||
"iceServers":[
|
||||
{
|
||||
"url":"stun:stun.moonlight-stream.org:3478"
|
||||
"urls":["stun:stun.moonlight-stream.org:3478"]
|
||||
}
|
||||
],
|
||||
//Provides customizable links for navbar
|
||||
|
|
|
|||
|
|
@ -944,6 +944,7 @@ class WebRTCLivestreamHandler extends rawFileBase{
|
|||
//Call derived constructor
|
||||
super(client, player, media, "webrtc");
|
||||
|
||||
//Define RTCHandler related listeners
|
||||
this.defineListeners();
|
||||
}
|
||||
|
||||
|
|
@ -968,18 +969,22 @@ class WebRTCLivestreamHandler extends rawFileBase{
|
|||
};
|
||||
|
||||
//If we're not streaming
|
||||
if(!this.client.webRTCHandler.isStreaming){
|
||||
if(!this.client.webRTCHandler.isBroadcasting){
|
||||
//Send stream request
|
||||
await this.client.webRTCHandler.requestStream();
|
||||
//otherwise
|
||||
}else{
|
||||
//Load broadcasting stream into the player for local playback
|
||||
this.startStream(this.client.webRTCHandler.broadcastingStream);
|
||||
}
|
||||
}
|
||||
|
||||
start(){
|
||||
//call derived start function
|
||||
super.start();
|
||||
startStream(stream){
|
||||
//Set video source object to stream
|
||||
this.video.srcObject = stream;
|
||||
|
||||
//Lock the player
|
||||
this.setPlayerLock(true);
|
||||
//Autoplay Stream
|
||||
this.video.play();
|
||||
}
|
||||
|
||||
setVideoTitle(title){
|
||||
|
|
@ -996,4 +1001,12 @@ class WebRTCLivestreamHandler extends rawFileBase{
|
|||
//Inject glowspan into title in a way that allows it to be easily replaced
|
||||
this.player.title.prepend(glowSpan);
|
||||
}
|
||||
|
||||
end(){
|
||||
//Let the webRTC handler know the stream ended
|
||||
this.client.webRTCHandler.end();
|
||||
|
||||
//Call derived method
|
||||
super.end();
|
||||
}
|
||||
}
|
||||
|
|
@ -309,9 +309,11 @@ class queuePanel extends panelObj{
|
|||
async goLive(event){
|
||||
//If we're broadcastinv via webrtc
|
||||
if(this.client.streamType == "webrtc"){
|
||||
this.client.webRTCHandler.isBroadcasting = true;
|
||||
await this.client.webRTCHandler.startStream();
|
||||
|
||||
this.client.webRTCHandler.broadcastingStream = await navigator.mediaDevices.getDisplayMedia();
|
||||
//this.client.webRTCHandler.isBroadcasting = true;
|
||||
|
||||
//this.client.webRTCHandler.broadcastingStream = await navigator.mediaDevices.getDisplayMedia();
|
||||
}
|
||||
|
||||
//Start a livestream
|
||||
|
|
|
|||
|
|
@ -27,12 +27,22 @@ class webRTCHandler{
|
|||
* Parent Client Management object
|
||||
*/
|
||||
this.client = client
|
||||
|
||||
|
||||
/**
|
||||
* Flag thrown when this device is broadcasting via webRTC
|
||||
*/
|
||||
this.isBroadcasting = false;
|
||||
|
||||
/**
|
||||
* Flag thrown when a request to view a stream is answered
|
||||
*/
|
||||
this.answered = false;
|
||||
|
||||
/**
|
||||
* Create variable to hold Internet Connectivity Establishment configuration
|
||||
*/
|
||||
this.iceConfig;
|
||||
|
||||
/**
|
||||
* Create variable to hold the peer connection object for the currently playing remote stream
|
||||
*/
|
||||
|
|
@ -49,9 +59,9 @@ class webRTCHandler{
|
|||
this.currentViewers = new Map();
|
||||
|
||||
/**
|
||||
* Create variable to hold Internet Connectivity Establishment configuration
|
||||
* Create array to hold candidates cached before
|
||||
*/
|
||||
this.iceConfig;
|
||||
this.cachedCandidates = [];
|
||||
|
||||
//Call setup functions
|
||||
this.setupInput();
|
||||
|
|
@ -71,6 +81,29 @@ class webRTCHandler{
|
|||
this.client.socket.on("RTCData", this.handleRTCData.bind(this));
|
||||
}
|
||||
|
||||
async startStream(){
|
||||
//Throw broadcasting flag
|
||||
this.isBroadcasting = true;
|
||||
|
||||
//Get media stream from browser input
|
||||
this.broadcastingStream = await navigator.mediaDevices.getDisplayMedia({
|
||||
video: true,
|
||||
audio: true
|
||||
});
|
||||
|
||||
//If we didn't get any audio tracks from the DisplayMedia portion of the API
|
||||
if(this.broadcastingStream == null || this.broadcastingStream.getAudioTracks().length == 0){
|
||||
//Bug user for an audio device
|
||||
const tempStream = await navigator.mediaDevices.getUserMedia({video: false, audio: true});
|
||||
|
||||
//For each track received from audio device
|
||||
for(const track of tempStream.getAudioTracks()){
|
||||
//Add to existing broadcast stream
|
||||
this.broadcastingStream.addTrack(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles and routes incoming RTC Messages
|
||||
* Normally I'd use dedicated socket.io message types, however throwing user input seems like bad security practice
|
||||
|
|
@ -113,14 +146,15 @@ class webRTCHandler{
|
|||
//Create new peer connection w/ provided ice config
|
||||
this.currentStream = new RTCPeerConnection(this.iceConfig)
|
||||
|
||||
//Not really used atm but it's necissary for most browsers to need either a data channel or media track
|
||||
//addeed to the requesting peer before they start pulling ICE candidates...
|
||||
this.currentStream.createDataChannel("aux");
|
||||
|
||||
//Setup peer event handlers
|
||||
this.currentStream.addEventListener('icecandidate', this.sendIceCandidate.bind(this));
|
||||
this.currentStream.addEventListener('track', this.handleVideoStream.bind(this));
|
||||
|
||||
//Create offer from peerConnection
|
||||
const offer = await this.currentStream.createOffer();
|
||||
const offer = await this.currentStream.createOffer({
|
||||
offerToReceiveAudio: true,
|
||||
offerToReceiveVideo: true
|
||||
});
|
||||
|
||||
//Set offer as peerConnections local description
|
||||
await this.currentStream.setLocalDescription(offer);
|
||||
|
|
@ -161,18 +195,35 @@ class webRTCHandler{
|
|||
//Get answer from received data
|
||||
const answer = new RTCSessionDescription(data.body);
|
||||
|
||||
//Throw answered flag
|
||||
this.answered = true;
|
||||
|
||||
//for each cached ICE candidate
|
||||
for(const candidate of this.cachedCandidates){
|
||||
//Send cached candidate, handing candidate to method packed in a phoney event object
|
||||
this.sendIceCandidate({candidate});
|
||||
}
|
||||
|
||||
//Set remote desription
|
||||
this.currentStream.setRemoteDescription(answer);
|
||||
|
||||
}
|
||||
|
||||
sendIceCandidate(event){
|
||||
//If we have a candidate
|
||||
if(event.candidate != null){
|
||||
//Send that shit off!
|
||||
this.client.socket.emit('relayRTCData',{
|
||||
type: 'candidate',
|
||||
body: event.candidate
|
||||
});
|
||||
//If our stream request was answered
|
||||
if(this.answered){
|
||||
//Send that shit off!
|
||||
this.client.socket.emit('relayRTCData',{
|
||||
type: 'candidate',
|
||||
body: event.candidate
|
||||
});
|
||||
//Otherwise
|
||||
}else{
|
||||
//Cache candidate
|
||||
this.cachedCandidates.push(event.candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,10 +237,34 @@ class webRTCHandler{
|
|||
this.currentStream.addIceCandidate(data.body);
|
||||
}
|
||||
}
|
||||
|
||||
async handleVideoStream(event){
|
||||
//If we where given a media stream
|
||||
if(event.streams != null && event.streams[0] != null){
|
||||
//hand it over to our player
|
||||
this.client.player.mediaHandler.startStream(event.streams[0]);
|
||||
}
|
||||
}
|
||||
|
||||
end(){
|
||||
//Drop flags
|
||||
this.isBroadcasting = false;
|
||||
this.answered = false;
|
||||
|
||||
//clear peer/media stream objects
|
||||
this.currentStream = null;
|
||||
this.broadcastingStream = null;
|
||||
|
||||
//clear currentViewers map
|
||||
this.currentViewers.clear();
|
||||
|
||||
//Clear candidate cache
|
||||
this.cachedCandidates = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a single connected viewer
|
||||
* Class representing a single connected remote viewer
|
||||
*/
|
||||
class connectedViewer{
|
||||
constructor(user, socket, offer, broadcastingStream, iceConfig, client){
|
||||
|
|
@ -214,7 +289,7 @@ class connectedViewer{
|
|||
this.iceConfig = iceConfig;
|
||||
|
||||
/**
|
||||
* Client channe object
|
||||
* Client channel object
|
||||
*/
|
||||
this.client = client;
|
||||
|
||||
|
|
@ -226,12 +301,12 @@ class connectedViewer{
|
|||
//Create peer from iceConfig
|
||||
const peer = new RTCPeerConnection(this.iceConfig);
|
||||
|
||||
//For every track from the local stream
|
||||
for(const track of this.broadcastingStream.getTracks()){
|
||||
//Add it to the list of available tracks for our peer
|
||||
peer.addTrack(track, this.broadcastingStream);
|
||||
}
|
||||
|
||||
peer.addEventListener('icecandidate', this.sendIceCandidate.bind(this));
|
||||
|
||||
//Set peer remote description to connection offer from viewer
|
||||
peer.setRemoteDescription(new RTCSessionDescription(offer));
|
||||
|
||||
|
|
@ -241,6 +316,9 @@ class connectedViewer{
|
|||
//Set answer as local description
|
||||
await peer.setLocalDescription(answer);
|
||||
|
||||
//Setup peer ICE Candidate event listner
|
||||
peer.addEventListener('icecandidate', this.sendIceCandidate.bind(this));
|
||||
|
||||
//Save peer by socket identifier
|
||||
this.peers.set(socket, peer);
|
||||
|
||||
|
|
@ -265,9 +343,7 @@ class connectedViewer{
|
|||
}
|
||||
|
||||
handleIceCandidate(data){
|
||||
console.log(this);
|
||||
const peer = this.peers.get(data.source.socket);
|
||||
console.log(peer);
|
||||
peer.addIceCandidate(data.body);
|
||||
|
||||
//Save peer by socket identifier
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue