diff --git a/config.example.json b/config.example.json index 990e2a7..7b869b3 100644 --- a/config.example.json +++ b/config.example.json @@ -34,11 +34,6 @@ "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 c9dfb42..3669d09 100644 --- a/config.example.jsonc +++ b/config.example.jsonc @@ -65,12 +65,6 @@ "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 44b170d..54e4fc5 100644 --- a/src/app/channel/activeChannel.js +++ b/src/app/channel/activeChannel.js @@ -19,8 +19,7 @@ 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 webRTCHandler = require('./media/webRTCHandler'); +const playlistHandler = require('./media/playlistHandler') /** * Class representing a single active channel @@ -68,11 +67,6 @@ class activeChannel{ */ this.playlistHandler = new playlistHandler(server, this); - /** - * Child WebRTC Handler Object - */ - this.webRTCHandler = new webRTCHandler(server, this); - /** * Child Chat Buffer Object */ @@ -114,7 +108,6 @@ 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 e6d9c2c..ac2eb75 100644 --- a/src/app/channel/media/queue.js +++ b/src/app/channel/media/queue.js @@ -329,11 +329,7 @@ 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 @@ -369,6 +365,11 @@ 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 @@ -382,33 +383,24 @@ class queue{ //Kill schedule timers to prevent items from starting during the stream await this.stopScheduleTimers(); - //If we're streaming via HLS - if(chanDB.settings.streamType == "livehls"){ - //Syntatic sugar because I'm lazy :P - streamURL = chanDB.settings.streamURL; + //Syntatic sugar because I'm lazy :P + const 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 - filename = streamURL.match(/^.+\..+\/(.+)$/); + //Pull filename from streamURL + let 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]; - } - - 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); + //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]; } //Create queued media object from stream URL and set it to nowPlaying @@ -417,9 +409,9 @@ class queue{ filename, streamURL, streamURL, - chanDB.settings.streamType, + "livehls", 0, - rawLink, + streamURL, new Date().getTime() ); @@ -1269,7 +1261,7 @@ class queue{ } //If we're ending an HLS Livestream - if(wasPlaying.type == "livehls" || wasPlaying.type == "webrtc"){ + if(wasPlaying.type == "livehls"){ //Redirect to the endLivestream function return this.endLivestream(wasPlaying, chanDB) } @@ -1350,11 +1342,6 @@ 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); @@ -1582,7 +1569,7 @@ class queue{ const stoppedMedia = this.nowPlaying; //Ignore early end for livestreams - if(this.nowPlaying.type != 'livehls' && this.nowPlaying.type != 'webrtc'){ + if(this.nowPlaying.type != 'livehls'){ //Get difference between current time and start time and set as early end stoppedMedia.earlyEnd = (new Date().getTime() - stoppedMedia.startTime) / 1000; } @@ -1819,7 +1806,7 @@ class queue{ const now = new Date().getTime(); //If something was playing - if(chanDB.media.nowPlaying != null && (chanDB.media.nowPlaying.type != 'livehls' && chanDB.media.nowPlaying.type != 'webrtc')){ + if(chanDB.media.nowPlaying != null && chanDB.media.nowPlaying.type != 'livehls'){ //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 deleted file mode 100644 index 19f7c08..0000000 --- a/src/app/channel/media/webRTCHandler.js +++ /dev/null @@ -1,103 +0,0 @@ -/*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 bd02fe0..8bfa484 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","livehls"]; +const streamMethodEnum = ["webrtc","hls"]; /** * 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 0e9be56..9c87f55 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|livehls)\b/] + options: [/\b(?:webrtc|hls)\b/] } } }) diff --git a/src/views/channel.ejs b/src/views/channel.ejs index 30dbbd4..4dac445 100644 --- a/src/views/channel.ejs +++ b/src/views/channel.ejs @@ -48,7 +48,6 @@ 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 feb30c4..d8dd2ea 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 bd6b181..840b939 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -56,8 +56,6 @@ 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 97708c5..0923876 100644 --- a/www/js/channel/mediaHandler.js +++ b/www/js/channel/mediaHandler.js @@ -926,74 +926,4 @@ 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 4224c47..7f258e3 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' || data.media.type == 'webrtc' )){ + if(data.media != null && data.media.type == 'livehls'){ //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" && client.player.mediaHandler.type != "webrtc"){ + if(client.player.mediaHandler.type != "livehls"){ //If the div is hidden if(this.goLiveDiv.style.display == 'none'){ //Show the div @@ -306,14 +306,7 @@ class queuePanel extends panelObj{ * Handles sending request to server to start a live stream * @param {Event} event - Event passed down from Event Listener */ - 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(); - } - + goLive(event){ //Start a livestream client.socket.emit('goLive',{title: this.goLiveNamePrompt.value, mode: event.target.dataset['mode']}); } @@ -1195,7 +1188,7 @@ class queuePanel extends panelObj{ const staleEntry = this.queueContainer.querySelector('.queue-entry.live'); //If we're not livestreaming - if(client.player.mediaHandler.type != "livehls" && client.player.mediaHandler.type != "webrtc"){ + if(client.player.mediaHandler.type != "livehls"){ //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 a454e2e..26413d6 100644 --- a/www/js/channel/player.js +++ b/www/js/channel/player.js @@ -188,9 +188,6 @@ 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 deleted file mode 100644 index d5a08f1..0000000 --- a/www/js/channel/webRTCHandler.js +++ /dev/null @@ -1,276 +0,0 @@ -/*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 a048b76..74bbe49 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 == "livehls" || data.streamType == "webrtc"){ + if(data.streamType == "hls"){ //Ensure the hls button is checked this.hlsButton.checked = true; //Ensure the stream url input is enabled