Compare commits
No commits in common. "86e9c586b6232c0777486fd8dc63785d6eb13b84" and "6d8135d1e38d48c566c3fe3308259fbf682168bb" have entirely different histories.
86e9c586b6
...
6d8135d1e3
15 changed files with 34 additions and 527 deletions
|
|
@ -34,11 +34,6 @@
|
||||||
"address": "toke@42069.weed",
|
"address": "toke@42069.weed",
|
||||||
"pass": "CHANGE_ME"
|
"pass": "CHANGE_ME"
|
||||||
},
|
},
|
||||||
"iceServers":[
|
|
||||||
{
|
|
||||||
"url":"stun:stun.moonlight-stream.org:3478"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links":{
|
"links":{
|
||||||
"About": "/about",
|
"About": "/about",
|
||||||
"Code": "https://git.ourfore.st/rainbownapkin/canopy",
|
"Code": "https://git.ourfore.st/rainbownapkin/canopy",
|
||||||
|
|
|
||||||
|
|
@ -65,12 +65,6 @@
|
||||||
"address": "toke@42069.weed",
|
"address": "toke@42069.weed",
|
||||||
"pass": "CHANGE_ME"
|
"pass": "CHANGE_ME"
|
||||||
},
|
},
|
||||||
//Privdes STUN/TURN servers for WebRTC functionality
|
|
||||||
"iceServers":[
|
|
||||||
{
|
|
||||||
"url":"stun:stun.moonlight-stream.org:3478"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
//Provides customizable links for navbar
|
//Provides customizable links for navbar
|
||||||
"links":{
|
"links":{
|
||||||
"About": "/about",
|
"About": "/about",
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ const connectedUser = require('./connectedUser');
|
||||||
const chatBuffer = require('./chatBuffer');
|
const chatBuffer = require('./chatBuffer');
|
||||||
const queue = require('./media/queue');
|
const queue = require('./media/queue');
|
||||||
const channelModel = require('../../schemas/channel/channelSchema');
|
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
|
* Class representing a single active channel
|
||||||
|
|
@ -68,11 +67,6 @@ class activeChannel{
|
||||||
*/
|
*/
|
||||||
this.playlistHandler = new playlistHandler(server, this);
|
this.playlistHandler = new playlistHandler(server, this);
|
||||||
|
|
||||||
/**
|
|
||||||
* Child WebRTC Handler Object
|
|
||||||
*/
|
|
||||||
this.webRTCHandler = new webRTCHandler(server, this);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Child Chat Buffer Object
|
* Child Chat Buffer Object
|
||||||
*/
|
*/
|
||||||
|
|
@ -114,7 +108,6 @@ class activeChannel{
|
||||||
//Define per-channel event listeners
|
//Define per-channel event listeners
|
||||||
this.queue.defineListeners(socket);
|
this.queue.defineListeners(socket);
|
||||||
this.playlistHandler.defineListeners(socket);
|
this.playlistHandler.defineListeners(socket);
|
||||||
this.webRTCHandler.defineListeners(socket);
|
|
||||||
|
|
||||||
//Hand off the connection initiation to it's user object
|
//Hand off the connection initiation to it's user object
|
||||||
const activeUser = await userObj.handleConnection(userDB, chanDB, socket)
|
const activeUser = await userObj.handleConnection(userDB, chanDB, socket)
|
||||||
|
|
|
||||||
|
|
@ -329,11 +329,7 @@ class queue{
|
||||||
*/
|
*/
|
||||||
async goLive(socket, data){
|
async goLive(socket, data){
|
||||||
try{
|
try{
|
||||||
//Define and/or declare required variables
|
|
||||||
let title = "Livestream";
|
let title = "Livestream";
|
||||||
let filename = '/';
|
|
||||||
let streamURL = '/';
|
|
||||||
let rawLink = {combo: [['default', config.iceServers]]};
|
|
||||||
|
|
||||||
if(data != null && data.title != null){
|
if(data != null && data.title != null){
|
||||||
//If the title is too long
|
//If the title is too long
|
||||||
|
|
@ -369,6 +365,11 @@ class queue{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(chanDB.settings.streamType == "webrtc"){
|
||||||
|
console.log("WEBRTC ENABLED, BAILING!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//If something is playing
|
//If something is playing
|
||||||
if(this.nowPlaying != null){
|
if(this.nowPlaying != null){
|
||||||
//Capture currently playing object
|
//Capture currently playing object
|
||||||
|
|
@ -382,10 +383,8 @@ class queue{
|
||||||
//Kill schedule timers to prevent items from starting during the stream
|
//Kill schedule timers to prevent items from starting during the stream
|
||||||
await this.stopScheduleTimers();
|
await this.stopScheduleTimers();
|
||||||
|
|
||||||
//If we're streaming via HLS
|
|
||||||
if(chanDB.settings.streamType == "livehls"){
|
|
||||||
//Syntatic sugar because I'm lazy :P
|
//Syntatic sugar because I'm lazy :P
|
||||||
streamURL = chanDB.settings.streamURL;
|
const streamURL = chanDB.settings.streamURL;
|
||||||
|
|
||||||
if(streamURL == ''){
|
if(streamURL == ''){
|
||||||
throw loggerUtils.exceptionSmith('This channel\'s HLS Livestream Source has not been set!', 'queue');
|
throw loggerUtils.exceptionSmith('This channel\'s HLS Livestream Source has not been set!', 'queue');
|
||||||
|
|
@ -393,7 +392,7 @@ class queue{
|
||||||
|
|
||||||
|
|
||||||
//Pull filename from streamURL
|
//Pull filename from streamURL
|
||||||
filename = streamURL.match(/^.+\..+\/(.+)$/);
|
let filename = streamURL.match(/^.+\..+\/(.+)$/);
|
||||||
|
|
||||||
//If we're streaming from the root of the domain
|
//If we're streaming from the root of the domain
|
||||||
if(filename == null){
|
if(filename == null){
|
||||||
|
|
@ -404,22 +403,15 @@ class queue{
|
||||||
filename = filename[1];
|
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
|
//Create queued media object from stream URL and set it to nowPlaying
|
||||||
this.nowPlaying = new queuedMedia(
|
this.nowPlaying = new queuedMedia(
|
||||||
title,
|
title,
|
||||||
filename,
|
filename,
|
||||||
streamURL,
|
streamURL,
|
||||||
streamURL,
|
streamURL,
|
||||||
chanDB.settings.streamType,
|
"livehls",
|
||||||
0,
|
0,
|
||||||
rawLink,
|
streamURL,
|
||||||
new Date().getTime()
|
new Date().getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1269,7 +1261,7 @@ class queue{
|
||||||
}
|
}
|
||||||
|
|
||||||
//If we're ending an HLS Livestream
|
//If we're ending an HLS Livestream
|
||||||
if(wasPlaying.type == "livehls" || wasPlaying.type == "webrtc"){
|
if(wasPlaying.type == "livehls"){
|
||||||
//Redirect to the endLivestream function
|
//Redirect to the endLivestream function
|
||||||
return this.endLivestream(wasPlaying, chanDB)
|
return this.endLivestream(wasPlaying, chanDB)
|
||||||
}
|
}
|
||||||
|
|
@ -1350,11 +1342,6 @@ class queue{
|
||||||
//Set duration from start and end time
|
//Set duration from start and end time
|
||||||
wasPlaying.duration = (now - wasPlaying.startTime) / 1000;
|
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 we're in pushback mode
|
||||||
if(this.liveMode == "pushback"){
|
if(this.liveMode == "pushback"){
|
||||||
await this.livestreamPushbackSchedule(wasPlaying, chanDB);
|
await this.livestreamPushbackSchedule(wasPlaying, chanDB);
|
||||||
|
|
@ -1582,7 +1569,7 @@ class queue{
|
||||||
const stoppedMedia = this.nowPlaying;
|
const stoppedMedia = this.nowPlaying;
|
||||||
|
|
||||||
//Ignore early end for livestreams
|
//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
|
//Get difference between current time and start time and set as early end
|
||||||
stoppedMedia.earlyEnd = (new Date().getTime() - stoppedMedia.startTime) / 1000;
|
stoppedMedia.earlyEnd = (new Date().getTime() - stoppedMedia.startTime) / 1000;
|
||||||
}
|
}
|
||||||
|
|
@ -1819,7 +1806,7 @@ class queue{
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
|
|
||||||
//If something was playing
|
//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
|
//Rehydrate the currently playing item int oa queued media object
|
||||||
const wasPlaying = chanDB.media.nowPlaying.rehydrate();
|
const wasPlaying = chanDB.media.nowPlaying.rehydrate();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 <https://www.gnu.org/licenses/>.*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
@ -38,7 +38,7 @@ const { exceptionHandler, errorHandler } = require('../../utils/loggerUtils');
|
||||||
/**
|
/**
|
||||||
* "Enum" for emote type property
|
* "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
|
* DB Schema for Documents containing de-hydrated representations of Canopy Stream/Chat Channels
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ module.exports.settingsMap = function(){
|
||||||
optional: true,
|
optional: true,
|
||||||
errorMessage: "Invalid Stream Type",
|
errorMessage: "Invalid Stream Type",
|
||||||
matches: {
|
matches: {
|
||||||
options: [/\b(?:webrtc|livehls)\b/]
|
options: [/\b(?:webrtc|hls)\b/]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
<script src="/js/channel/mediaHandler.js"></script>
|
<script src="/js/channel/mediaHandler.js"></script>
|
||||||
<script src="/js/channel/player.js"></script>
|
<script src="/js/channel/player.js"></script>
|
||||||
<script src="/js/channel/pmHandler.js"></script>
|
<script src="/js/channel/pmHandler.js"></script>
|
||||||
<script src="/js/channel/webRTCHandler.js"></script>
|
|
||||||
<script src="/js/channel/cpanel.js"></script>
|
<script src="/js/channel/cpanel.js"></script>
|
||||||
<%# panels %>
|
<%# panels %>
|
||||||
<script src="/js/channel/panels/emotePanel.js"></script>
|
<script src="/js/channel/panels/emotePanel.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
<label for="channel-preference-list-stream-type-webrtc">Peer-to-Peer (WebRTC/STUN)</label>
|
<label for="channel-preference-list-stream-type-webrtc">Peer-to-Peer (WebRTC/STUN)</label>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<input type="radio" name="streamType" id="channel-preference-list-stream-type-hls" value="livehls" <% if(channel.settings["streamType"] == "livehls"){ %> checked <% }%> >
|
<input type="radio" name="streamType" id="channel-preference-list-stream-type-hls" value="hls" <% if(channel.settings["streamType"] == "hls"){ %> checked <% }%> >
|
||||||
<label for="channel-preference-list-stream-type-hls">Self-Hosted (HLS)</label>
|
<label for="channel-preference-list-stream-type-hls">Self-Hosted (HLS)</label>
|
||||||
</span>
|
</span>
|
||||||
<input <% if(channel.settings["streamType"] != "livehls"){ %> disabled <%}%> id="channel-preference-list-stream-url" placeholder="Stream URL" value="<%= channel.settings["streamURL"] %>">
|
<input <% if(channel.settings["streamType"] != "hls"){ %> disabled <%}%> id="channel-preference-list-stream-url" placeholder="Stream URL" value="<%= channel.settings["streamURL"] %>">
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,6 @@ class channel{
|
||||||
*/
|
*/
|
||||||
this.pmHandler = new pmHandler(this);
|
this.pmHandler = new pmHandler(this);
|
||||||
|
|
||||||
this.webRTCHandler = new webRTCHandler(this);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Child Canopy Panel Object
|
* Child Canopy Panel Object
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -927,73 +927,3 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -188,7 +188,7 @@ class queuePanel extends panelObj{
|
||||||
*/
|
*/
|
||||||
handleStart(data){
|
handleStart(data){
|
||||||
//If we're starting an HLS Livestream
|
//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
|
//Hide the 'goLive' controls
|
||||||
this.goLiveDiv.style.display = 'none';
|
this.goLiveDiv.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +282,7 @@ class queuePanel extends panelObj{
|
||||||
client.socket.emit('getStreamType');
|
client.socket.emit('getStreamType');
|
||||||
|
|
||||||
//If we're not livestreaming
|
//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 the div is hidden
|
||||||
if(this.goLiveDiv.style.display == 'none'){
|
if(this.goLiveDiv.style.display == 'none'){
|
||||||
//Show the div
|
//Show the div
|
||||||
|
|
@ -306,14 +306,7 @@ class queuePanel extends panelObj{
|
||||||
* Handles sending request to server to start a live stream
|
* Handles sending request to server to start a live stream
|
||||||
* @param {Event} event - Event passed down from Event Listener
|
* @param {Event} event - Event passed down from Event Listener
|
||||||
*/
|
*/
|
||||||
async goLive(event){
|
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
|
//Start a livestream
|
||||||
client.socket.emit('goLive',{title: this.goLiveNamePrompt.value, mode: event.target.dataset['mode']});
|
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');
|
const staleEntry = this.queueContainer.querySelector('.queue-entry.live');
|
||||||
|
|
||||||
//If we're not livestreaming
|
//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 we have a stale entry
|
||||||
if(staleEntry != null){
|
if(staleEntry != null){
|
||||||
//Remove stale entry since we're no longer streaming
|
//Remove stale entry since we're no longer streaming
|
||||||
|
|
|
||||||
|
|
@ -188,9 +188,6 @@ class player{
|
||||||
}else if(data.media.type == "livehls"){
|
}else if(data.media.type == "livehls"){
|
||||||
//Create a new HLS Livestream Handler for it
|
//Create a new HLS Livestream Handler for it
|
||||||
this.mediaHandler = new hlsLiveStreamHandler(this.client, this, data.media);
|
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'){
|
}else if(data.media.type == 'dm'){
|
||||||
this.mediaHandler = new hlsDailymotionHandler(this.client, this, data.media);
|
this.mediaHandler = new hlsDailymotionHandler(this.client, this, data.media);
|
||||||
//Otherwise, if we have a raw-file compatible source
|
//Otherwise, if we have a raw-file compatible source
|
||||||
|
|
|
||||||
|
|
@ -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 <https://www.gnu.org/licenses/>.*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -412,7 +412,7 @@ class livestreamPreferences{
|
||||||
|
|
||||||
handleTypeUpdate(data){
|
handleTypeUpdate(data){
|
||||||
//If the stream type is HLS
|
//If the stream type is HLS
|
||||||
if(data.streamType == "livehls" || data.streamType == "webrtc"){
|
if(data.streamType == "hls"){
|
||||||
//Ensure the hls button is checked
|
//Ensure the hls button is checked
|
||||||
this.hlsButton.checked = true;
|
this.hlsButton.checked = true;
|
||||||
//Ensure the stream url input is enabled
|
//Ensure the stream url input is enabled
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue