diff --git a/config.example.json b/config.example.json
index 7b869b3..990e2a7 100644
--- a/config.example.json
+++ b/config.example.json
@@ -34,6 +34,11 @@
"address": "toke@42069.weed",
"pass": "CHANGE_ME"
},
+ "iceServers":[
+ {
+ "url":"stun:stun.moonlight-stream.org:3478"
+ }
+ ],
"links":{
"About": "/about",
"Code": "https://git.ourfore.st/rainbownapkin/canopy",
diff --git a/config.example.jsonc b/config.example.jsonc
index 3669d09..c9dfb42 100644
--- a/config.example.jsonc
+++ b/config.example.jsonc
@@ -65,6 +65,12 @@
"address": "toke@42069.weed",
"pass": "CHANGE_ME"
},
+ //Privdes STUN/TURN servers for WebRTC functionality
+ "iceServers":[
+ {
+ "url":"stun:stun.moonlight-stream.org:3478"
+ }
+ ],
//Provides customizable links for navbar
"links":{
"About": "/about",
diff --git a/src/app/channel/activeChannel.js b/src/app/channel/activeChannel.js
index 54e4fc5..44b170d 100644
--- a/src/app/channel/activeChannel.js
+++ b/src/app/channel/activeChannel.js
@@ -19,7 +19,8 @@ const connectedUser = require('./connectedUser');
const chatBuffer = require('./chatBuffer');
const queue = require('./media/queue');
const channelModel = require('../../schemas/channel/channelSchema');
-const playlistHandler = require('./media/playlistHandler')
+const playlistHandler = require('./media/playlistHandler');
+const webRTCHandler = require('./media/webRTCHandler');
/**
* Class representing a single active channel
@@ -67,6 +68,11 @@ class activeChannel{
*/
this.playlistHandler = new playlistHandler(server, this);
+ /**
+ * Child WebRTC Handler Object
+ */
+ this.webRTCHandler = new webRTCHandler(server, this);
+
/**
* Child Chat Buffer Object
*/
@@ -108,6 +114,7 @@ class activeChannel{
//Define per-channel event listeners
this.queue.defineListeners(socket);
this.playlistHandler.defineListeners(socket);
+ this.webRTCHandler.defineListeners(socket);
//Hand off the connection initiation to it's user object
const activeUser = await userObj.handleConnection(userDB, chanDB, socket)
diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js
index ac2eb75..e6d9c2c 100644
--- a/src/app/channel/media/queue.js
+++ b/src/app/channel/media/queue.js
@@ -329,7 +329,11 @@ class queue{
*/
async goLive(socket, data){
try{
+ //Define and/or declare required variables
let title = "Livestream";
+ let filename = '/';
+ let streamURL = '/';
+ let rawLink = {combo: [['default', config.iceServers]]};
if(data != null && data.title != null){
//If the title is too long
@@ -365,11 +369,6 @@ class queue{
return;
}
- if(chanDB.settings.streamType == "webrtc"){
- console.log("WEBRTC ENABLED, BAILING!");
- return;
- }
-
//If something is playing
if(this.nowPlaying != null){
//Capture currently playing object
@@ -383,24 +382,33 @@ class queue{
//Kill schedule timers to prevent items from starting during the stream
await this.stopScheduleTimers();
- //Syntatic sugar because I'm lazy :P
- const streamURL = chanDB.settings.streamURL;
+ //If we're streaming via HLS
+ if(chanDB.settings.streamType == "livehls"){
+ //Syntatic sugar because I'm lazy :P
+ streamURL = chanDB.settings.streamURL;
- if(streamURL == ''){
- throw loggerUtils.exceptionSmith('This channel\'s HLS Livestream Source has not been set!', 'queue');
- }
+ if(streamURL == ''){
+ throw loggerUtils.exceptionSmith('This channel\'s HLS Livestream Source has not been set!', 'queue');
+ }
- //Pull filename from streamURL
- let filename = streamURL.match(/^.+\..+\/(.+)$/);
+ //Pull filename from streamURL
+ filename = streamURL.match(/^.+\..+\/(.+)$/);
- //If we're streaming from the root of the domain
- if(filename == null){
- //Set filename to root
- filename = '/';
- }else{
- //Otherwise, hand over the filename
- filename = filename[1];
+ //If we're streaming from the root of the domain
+ if(filename == null){
+ //Set filename to root
+ filename = '/';
+ }else{
+ //Otherwise, hand over the filename
+ filename = filename[1];
+ }
+
+ rawLink = {combo:[['default',streamURL]]};
+ //Otherwise if we're starting a web RTC Stream
+ }else if(chanDB.settings.streamType == "webrtc"){
+ //Let our webRTCHandler know
+ this.channel.webRTCHandler.startStream(socket);
}
//Create queued media object from stream URL and set it to nowPlaying
@@ -409,9 +417,9 @@ class queue{
filename,
streamURL,
streamURL,
- "livehls",
+ chanDB.settings.streamType,
0,
- streamURL,
+ rawLink,
new Date().getTime()
);
@@ -1261,7 +1269,7 @@ class queue{
}
//If we're ending an HLS Livestream
- if(wasPlaying.type == "livehls"){
+ if(wasPlaying.type == "livehls" || wasPlaying.type == "webrtc"){
//Redirect to the endLivestream function
return this.endLivestream(wasPlaying, chanDB)
}
@@ -1342,6 +1350,11 @@ class queue{
//Set duration from start and end time
wasPlaying.duration = (now - wasPlaying.startTime) / 1000;
+ //If we're ending a webrtc stream
+ if(wasPlaying.type == "webrtc"){
+ this.channel.webRTCHandler.endStream();
+ }
+
//If we're in pushback mode
if(this.liveMode == "pushback"){
await this.livestreamPushbackSchedule(wasPlaying, chanDB);
@@ -1569,7 +1582,7 @@ class queue{
const stoppedMedia = this.nowPlaying;
//Ignore early end for livestreams
- if(this.nowPlaying.type != 'livehls'){
+ if(this.nowPlaying.type != 'livehls' && this.nowPlaying.type != 'webrtc'){
//Get difference between current time and start time and set as early end
stoppedMedia.earlyEnd = (new Date().getTime() - stoppedMedia.startTime) / 1000;
}
@@ -1806,7 +1819,7 @@ class queue{
const now = new Date().getTime();
//If something was playing
- if(chanDB.media.nowPlaying != null && chanDB.media.nowPlaying.type != 'livehls'){
+ if(chanDB.media.nowPlaying != null && (chanDB.media.nowPlaying.type != 'livehls' && chanDB.media.nowPlaying.type != 'webrtc')){
//Rehydrate the currently playing item int oa queued media object
const wasPlaying = chanDB.media.nowPlaying.rehydrate();
diff --git a/src/app/channel/media/webRTCHandler.js b/src/app/channel/media/webRTCHandler.js
new file mode 100644
index 0000000..19f7c08
--- /dev/null
+++ b/src/app/channel/media/webRTCHandler.js
@@ -0,0 +1,103 @@
+/*Canopy - The next generation of stoner streaming software
+Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .*/
+
+/**
+ * Object to relay WebRTC Negotiation data for a single channel
+ */
+class webRTCHandler{
+ /**
+ * Instantiates a new object to relay WebRTC Negotiation data for a single channel
+ * @param {channelManager} server - Parent server object
+ * @param {activeChannel} channel - Parent Channel object
+ */
+ constructor(server, channel){
+ /**
+ * Parent Server Object
+ */
+ this.server = server
+
+ /**
+ * Parent Channel Object
+ */
+ this.channel = channel;
+
+ /**
+ * Declare class property to hold socked it of the current device/browser-tab we're streaming from
+ */
+ this.currentStreamer;
+ }
+
+ /**
+ * Defines server-side socket.io listeners for newly connected sockets
+ * @param {Socket} socket - Newly connected socket to define listeners against
+ */
+ defineListeners(socket){
+ //Queueing Functions
+ socket.on("relayRTCData", (data) => {this.relayRTCData(socket, data)});
+ socket.on("disconnect", (data) => {this.handleStreamerDisconnect(socket, data)});
+ }
+
+ handleStreamerDisconnect(socket){
+ //If the disconnecting socket is the current streamer
+ if(socket.id == this.currentStreamer){
+ //Tell the queue to stop current stream
+ this.channel.queue.stop();
+ }
+ }
+
+ startStream(socket){
+ this.currentStreamer = socket.id;
+ }
+
+ endStream(){
+ this.currentStreamer = null;
+ }
+
+ relayRTCData(socket, data){
+ //Set the recipient to the current streamer by default, assuming that this isn't coming from the streaming socket
+ let recipientID = this.currentStreamer;
+
+ //Create object to hold data being relayed to client
+ let webRTCData = {
+ type: data.type,
+ body: data.body
+ };
+
+ //if data is being relayed from streaming socket to a viewer
+ if(socket.id == this.currentStreamer){
+ //Change recipient to reflect streaming socket's request
+ recipientID = data.recipient;
+ //Otherwise if the data is being relayed from a viewer to the streaming socket
+ }else{
+ //Add viewers socket id
+ webRTCData.source = {
+ user: socket.user.user,
+ socket: socket.id
+ };
+ }
+
+ //Get recipient socket
+ const recipient = this.server.io.sockets.sockets.get(recipientID);
+
+ //If we have a recipient
+ if(recipient != null){
+ //Relay RTC Data
+ recipient.emit("RTCData", webRTCData);
+ }
+ }
+}
+
+module.exports = webRTCHandler;
\ No newline at end of file
diff --git a/src/schemas/channel/channelSchema.js b/src/schemas/channel/channelSchema.js
index 8bfa484..bd02fe0 100644
--- a/src/schemas/channel/channelSchema.js
+++ b/src/schemas/channel/channelSchema.js
@@ -38,7 +38,7 @@ const { exceptionHandler, errorHandler } = require('../../utils/loggerUtils');
/**
* "Enum" for emote type property
*/
-const streamMethodEnum = ["webrtc","hls"];
+const streamMethodEnum = ["webrtc","livehls"];
/**
* DB Schema for Documents containing de-hydrated representations of Canopy Stream/Chat Channels
diff --git a/src/validators/channelValidator.js b/src/validators/channelValidator.js
index 9c87f55..0e9be56 100644
--- a/src/validators/channelValidator.js
+++ b/src/validators/channelValidator.js
@@ -85,7 +85,7 @@ module.exports.settingsMap = function(){
optional: true,
errorMessage: "Invalid Stream Type",
matches: {
- options: [/\b(?:webrtc|hls)\b/]
+ options: [/\b(?:webrtc|livehls)\b/]
}
}
})
diff --git a/src/views/channel.ejs b/src/views/channel.ejs
index 4dac445..30dbbd4 100644
--- a/src/views/channel.ejs
+++ b/src/views/channel.ejs
@@ -48,6 +48,7 @@ along with this program. If not, see . %>
+
<%# panels %>
diff --git a/src/views/partial/channelSettings/settings.ejs b/src/views/partial/channelSettings/settings.ejs
index d8dd2ea..feb30c4 100644
--- a/src/views/partial/channelSettings/settings.ejs
+++ b/src/views/partial/channelSettings/settings.ejs
@@ -42,10 +42,10 @@ along with this program. If not, see . %>
- checked <% }%> >
+ checked <% }%> >
- disabled <%}%> id="channel-preference-list-stream-url" placeholder="Stream URL" value="<%= channel.settings["streamURL"] %>">
+ disabled <%}%> id="channel-preference-list-stream-url" placeholder="Stream URL" value="<%= channel.settings["streamURL"] %>">
diff --git a/www/js/channel/channel.js b/www/js/channel/channel.js
index 840b939..bd6b181 100644
--- a/www/js/channel/channel.js
+++ b/www/js/channel/channel.js
@@ -56,6 +56,8 @@ class channel{
* Child PM Handler
*/
this.pmHandler = new pmHandler(this);
+
+ this.webRTCHandler = new webRTCHandler(this);
/**
* Child Canopy Panel Object
diff --git a/www/js/channel/mediaHandler.js b/www/js/channel/mediaHandler.js
index 0923876..97708c5 100644
--- a/www/js/channel/mediaHandler.js
+++ b/www/js/channel/mediaHandler.js
@@ -926,4 +926,74 @@ class hlsLiveStreamHandler extends hlsBase{
}
}
}
+}
+
+/**
+ * Wrapper handler for WebRTC Streams, calling back to webRTCHandler.js for heavy lifting
+ * @extends rawFileBase
+ */
+class WebRTCLivestreamHandler extends rawFileBase{
+ /**
+ /**
+ * Instantiates a new WebRTCMediaHandler
+ * @param {channel} client - Parent Client Management Object
+ * @param {player} player - Parent Canopy Player Object
+ * @param {Object} media - De-hydrated media object from server
+ */
+ constructor(client, player, media){
+ //Call derived constructor
+ super(client, player, media, "webrtc");
+
+ this.defineListeners();
+ }
+
+ defineListeners(){
+ //Run derived method
+ super.defineListeners();
+
+ //Disable right clicking
+ this.video.addEventListener('contextmenu', (e)=>{e.preventDefault()});
+ }
+
+
+ async buildPlayer(){
+ //Call derived buildPlayer function
+ super.buildPlayer();
+
+ /**
+ * set Internet Connectivity Establishment configuration
+ */
+ this.client.webRTCHandler.iceConfig = {
+ iceServers: new Map(this.nowPlaying.rawLink.combo).get('default')
+ };
+
+ //If we're not streaming
+ if(!this.client.webRTCHandler.isStreaming){
+ //Send stream request
+ await this.client.webRTCHandler.requestStream();
+ }
+ }
+
+ start(){
+ //call derived start function
+ super.start();
+
+ //Lock the player
+ this.setPlayerLock(true);
+ }
+
+ setVideoTitle(title){
+ //Add title as text content for security :P
+ this.player.title.innerText = `: ${utils.unescapeEntities(title)}`;
+
+ //Create glow span
+ const glowSpan = document.createElement('span');
+ //Fill glow span content
+ glowSpan.innerText = "🔴LIVE";
+ //Set glowspan class
+ glowSpan.classList.add('critical-danger-text');
+
+ //Inject glowspan into title in a way that allows it to be easily replaced
+ this.player.title.prepend(glowSpan);
+ }
}
\ No newline at end of file
diff --git a/www/js/channel/panels/queuePanel/queuePanel.js b/www/js/channel/panels/queuePanel/queuePanel.js
index 7f258e3..4224c47 100644
--- a/www/js/channel/panels/queuePanel/queuePanel.js
+++ b/www/js/channel/panels/queuePanel/queuePanel.js
@@ -188,7 +188,7 @@ class queuePanel extends panelObj{
*/
handleStart(data){
//If we're starting an HLS Livestream
- if(data.media != null && data.media.type == 'livehls'){
+ if(data.media != null && (data.media.type == 'livehls' || data.media.type == 'webrtc' )){
//Hide the 'goLive' controls
this.goLiveDiv.style.display = 'none';
}
@@ -282,7 +282,7 @@ class queuePanel extends panelObj{
client.socket.emit('getStreamType');
//If we're not livestreaming
- if(client.player.mediaHandler.type != "livehls"){
+ if(client.player.mediaHandler.type != "livehls" && client.player.mediaHandler.type != "webrtc"){
//If the div is hidden
if(this.goLiveDiv.style.display == 'none'){
//Show the div
@@ -306,7 +306,14 @@ class queuePanel extends panelObj{
* Handles sending request to server to start a live stream
* @param {Event} event - Event passed down from Event Listener
*/
- goLive(event){
+ async goLive(event){
+ //If we're broadcastinv via webrtc
+ if(this.client.streamType == "webrtc"){
+ this.client.webRTCHandler.isBroadcasting = true;
+
+ this.client.webRTCHandler.broadcastingStream = await navigator.mediaDevices.getDisplayMedia();
+ }
+
//Start a livestream
client.socket.emit('goLive',{title: this.goLiveNamePrompt.value, mode: event.target.dataset['mode']});
}
@@ -1188,7 +1195,7 @@ class queuePanel extends panelObj{
const staleEntry = this.queueContainer.querySelector('.queue-entry.live');
//If we're not livestreaming
- if(client.player.mediaHandler.type != "livehls"){
+ if(client.player.mediaHandler.type != "livehls" && client.player.mediaHandler.type != "webrtc"){
//If we have a stale entry
if(staleEntry != null){
//Remove stale entry since we're no longer streaming
diff --git a/www/js/channel/player.js b/www/js/channel/player.js
index 26413d6..a454e2e 100644
--- a/www/js/channel/player.js
+++ b/www/js/channel/player.js
@@ -188,6 +188,9 @@ class player{
}else if(data.media.type == "livehls"){
//Create a new HLS Livestream Handler for it
this.mediaHandler = new hlsLiveStreamHandler(this.client, this, data.media);
+ }else if(data.media.type == "webrtc"){
+ //Create a new HLS Livestream Handler for it
+ this.mediaHandler = new WebRTCLivestreamHandler(this.client, this, data.media);
}else if(data.media.type == 'dm'){
this.mediaHandler = new hlsDailymotionHandler(this.client, this, data.media);
//Otherwise, if we have a raw-file compatible source
diff --git a/www/js/channel/webRTCHandler.js b/www/js/channel/webRTCHandler.js
new file mode 100644
index 0000000..d5a08f1
--- /dev/null
+++ b/www/js/channel/webRTCHandler.js
@@ -0,0 +1,276 @@
+/*Canopy - The next generation of stoner streaming software
+Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .*/
+
+/**
+ * Class for handling negotiation between peers during WebRTC Livestreams
+ */
+class webRTCHandler{
+ /**
+ * Instantiates a new webRTCHandler object
+ * @param {channel} client - Parent client mgmt object
+ */
+ constructor(client){
+ /**
+ * Parent Client Management object
+ */
+ this.client = client
+
+ /**
+ * Flag thrown when this device is broadcasting via webRTC
+ */
+ this.isBroadcasting = false;
+
+ /**
+ * Create variable to hold the peer connection object for the currently playing remote stream
+ */
+ this.currentStream;
+
+ /**
+ * Create variable to hold currently broadcasting stream
+ */
+ this.broadcastingStream;
+
+ /**
+ * Create map to hold objects representing viewers receiving our broadcasted stream, organized by username
+ */
+ this.currentViewers = new Map();
+
+ /**
+ * Create variable to hold Internet Connectivity Establishment configuration
+ */
+ this.iceConfig;
+
+ //Call setup functions
+ this.setupInput();
+ this.defineListeners();
+ }
+
+ /**
+ * Defines input-related event listeners
+ */
+ setupInput(){
+ }
+
+ /**
+ * Defines network-related event listeners
+ */
+ defineListeners(){
+ this.client.socket.on("RTCData", this.handleRTCData.bind(this));
+ }
+
+ /**
+ * Handles and routes incoming RTC Messages
+ * Normally I'd use dedicated socket.io message types, however throwing user input seems like bad security practice
+ * I'll take ugly code over insecure code any day of the week
+ */
+ handleRTCData(data){
+ //If we're brodcasting
+ if(this.isBroadcasting){
+ switch(data.type){
+ case "request":
+ this.handleViewer(data);
+ break;
+ case "candidate":
+ this.handleIceCandidate(data);
+ break;
+ }
+ //otherwise
+ }else{
+ switch(data.type){
+ case "answer":
+ this.handleAnswer(data);
+ break;
+ case "candidate":
+ this.handleIceCandidate(data);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Requests stream from streamer
+ */
+ async requestStream(){
+ //If we're streaming
+ if(this.client.webRTCHandler.isBroadcasting){
+ //fack off and die
+ return;
+ }
+
+ //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");
+
+ this.currentStream.addEventListener('icecandidate', this.sendIceCandidate.bind(this));
+
+ //Create offer from peerConnection
+ const offer = await this.currentStream.createOffer();
+
+ //Set offer as peerConnections local description
+ await this.currentStream.setLocalDescription(offer);
+
+ //Send RTC connection offer to server to be relayed to streamer's device
+ this.client.socket.emit("relayRTCData", {type:"request",body:offer});
+ }
+
+ /**
+ * Handles request to connect to streamer from channel viewer
+ * @param {data} data - Data from requesting socket
+ */
+ async handleViewer(data){
+ //Check if user is already viewing stream
+ const viewer = this.currentViewers.get(data.source.user);
+
+ //If not
+ if(data.user == null){
+ //Create a new connected viewer object from the received data, and store it in the currentViewers map by username
+ this.currentViewers.set(data.source.user, new connectedViewer(
+ data.source.user,
+ data.source.socket,
+ data.body,
+ this.broadcastingStream,
+ this.iceConfig,
+ this.client));
+ //Otherwise, if they're already watching elsewhere via the same account
+ }else{
+ //Hand over new socket ID and offer to the currentViewer object assigned to the current user
+ await viewer.addSocket(data.source.socket, data.body);
+
+ //Update the object within the currentViewers map
+ this.currentViewers.set(data.source.user, viewer);
+ }
+ }
+
+ async handleAnswer(data){
+ //Get answer from received data
+ const answer = new RTCSessionDescription(data.body);
+
+ //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
+ });
+ }
+ }
+
+ handleIceCandidate(data){
+ //If we're broadcasting
+ if(this.isBroadcasting){
+ const user = this.currentViewers.get(data.source.user);
+ user.handleIceCandidate(data);
+ //If we're receiving a stream
+ }else{
+ this.currentStream.addIceCandidate(data.body);
+ }
+ }
+}
+
+/**
+ * Class representing a single connected viewer
+ */
+class connectedViewer{
+ constructor(user, socket, offer, broadcastingStream, iceConfig, client){
+ /**
+ * Name of user
+ */
+ this.user = user;
+
+ /**
+ * Map of user's connected peers by socket
+ */
+ this.peers = new Map();
+
+ /**
+ * Broadcasting Stream
+ */
+ this.broadcastingStream = broadcastingStream;
+
+ /**
+ * ICE Config
+ */
+ this.iceConfig = iceConfig;
+
+ /**
+ * Client channe object
+ */
+ this.client = client;
+
+ //Intake initial connection offer from user
+ this.addSocket(socket, offer);
+ }
+
+ async addSocket(socket, offer){
+ //Create peer from iceConfig
+ const peer = new RTCPeerConnection(this.iceConfig);
+
+ for(const track of this.broadcastingStream.getTracks()){
+ 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));
+
+ //Generate answer to connection offer
+ const answer = await peer.createAnswer();
+
+ //Set answer as local description
+ await peer.setLocalDescription(answer);
+
+ //Save peer by socket identifier
+ this.peers.set(socket, peer);
+
+ //Send answer to server to relay to peer
+ this.client.socket.emit('relayRTCData',{
+ type:'answer',
+ body:answer,
+ recipient: socket
+ });
+ }
+
+ 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,
+ recipient: this.socket
+ });
+ }
+ }
+
+ 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
+ this.peers.set(data.source.socket, peer);
+ }
+}
\ No newline at end of file
diff --git a/www/js/channelSettings.js b/www/js/channelSettings.js
index 74bbe49..a048b76 100644
--- a/www/js/channelSettings.js
+++ b/www/js/channelSettings.js
@@ -412,7 +412,7 @@ class livestreamPreferences{
handleTypeUpdate(data){
//If the stream type is HLS
- if(data.streamType == "hls"){
+ if(data.streamType == "livehls" || data.streamType == "webrtc"){
//Ensure the hls button is checked
this.hlsButton.checked = true;
//Ensure the stream url input is enabled