diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js
index 6638f57..0887723 100644
--- a/src/app/channel/media/queue.js
+++ b/src/app/channel/media/queue.js
@@ -235,67 +235,76 @@ module.exports = class{
}
async goLive(socket, data){
- //Grab the channel from DB
- const chanDB = await channelModel.findOne({name:this.channel.name});
+ try{
+ //Grab the channel from DB
+ const chanDB = await channelModel.findOne({name:this.channel.name});
- let title = "Livestream";
+ let title = "Livestream";
- if(data != null && data.title != null){
- //If the title is too long
- if(!validator.isLength(data.title, {max:30})){
- //Bitch, moan, complain...
- loggerUtils.socketErrorHandler(socket, "Title too long!", "validation");
- //and ignore it!
- return;
+ if(data != null && data.title != null){
+ //If the title is too long
+ if(!validator.isLength(data.title, {max:30})){
+ //Bitch, moan, complain...
+ loggerUtils.socketErrorHandler(socket, "Title too long!", "validation");
+ //and ignore it!
+ return;
+ }
+
+ //Set title
+ title = validator.escape(validator.trim(data.title));
+
+ //If we've got no title
+ if(title == null || title == ''){
+ title = "Livestream";
+ }
}
- //Set title
- title = validator.escape(validator.trim(data.title));
-
- //If we've got no title
- if(title == null || title == ''){
- title = "Livestream";
+ //If we couldn't find the channel
+ if(chanDB == null){
+ //FUCK
+ throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while queue item!`, "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(streamURL == ''){
+ throw loggerUtils.exceptionSmith('This channel\'s HLS Livestream Source has not been set!', 'queue');
+ }
+
+
+ //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];
+ }
+
+ //Create queued media object from stream URL and set it to nowPlaying
+ this.nowPlaying = new queuedMedia(
+ title,
+ filename,
+ streamURL,
+ streamURL,
+ "livehls",
+ 0,
+ streamURL,
+ new Date().getTime()
+ );
+
+ //Broadcast new media object to users
+ this.sendMedia();
+ }catch(err){
+ return loggerUtils.socketExceptionHandler(socket, err);
}
-
- //If we couldn't find the channel
- if(chanDB == null){
- //FUCK
- throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while queue item!`, "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;
-
- //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];
- }
-
- //Create queued media object from stream URL and set it to nowPlaying
- this.nowPlaying = new queuedMedia(
- title,
- filename,
- streamURL,
- streamURL,
- "livehls",
- 0,
- streamURL,
- new Date().getTime()
- );
-
- //Broadcast new media object to users
- this.sendMedia();
}
//--- INTERNAL USE ONLY QUEUEING FUNCTIONS ---
@@ -553,8 +562,11 @@ module.exports = class{
//otherwise
}else{
try{
- //DO everything ourselves since we don't have a fance end() function to do it
- chanDB = await channelModel.findOne({name:this.channel.name});
+ //If we wheren't handed a channel
+ if(chanDB == null){
+ //DO everything ourselves since we don't have a fance end() function to do it
+ chanDB = await channelModel.findOne({name:this.channel.name});
+ }
//If we couldn't find the channel
if(chanDB == null){
@@ -814,6 +826,9 @@ module.exports = class{
//Send play signal out to the channel
this.sendMedia();
+ //Kill existing sync timers to prevent kicking-off ghost timer loops
+ clearTimeout(this.syncTimer);
+
//Kick off the sync timer
this.syncTimer = setTimeout(this.sync.bind(this), this.syncDelta);
@@ -879,9 +894,19 @@ module.exports = class{
this.server.io.in(this.channel.name).emit('end', {});
}
+ //If we're ending an HLS Livestream
+ if(wasPlaying.type == "livehls"){
+ //Redirect to the endLivestream function
+ return this.endLivestream(chanDB);
+ }
+
+ //If we're not in volatile mode and we're not ending a livestream
if(!volatile){
- //Now that everything is clean, we can take our time with the DB :P
- chanDB = await channelModel.findOne({name:this.channel.name});
+ //If we wheren't handed a channel
+ if(chanDB == null){
+ //Now that everything is clean, we can take our time with the DB :P
+ chanDB = await channelModel.findOne({name:this.channel.name});
+ }
//If we couldn't find the channel
if(chanDB == null){
@@ -898,12 +923,13 @@ module.exports = class{
//Take it out of the active schedule
this.schedule.delete(wasPlaying.startTime);
+ //If archiving is enabled
if(!noArchive){
//Add the item to the channel archive
chanDB.media.archived.push(wasPlaying);
}
- //broadcast queue using unsaved archive
+ //broadcast queue using unsaved archive, run this before chanDB.save() for better responsiveness
this.broadcastQueue(chanDB);
//Save our changes to the DB
@@ -918,6 +944,22 @@ module.exports = class{
}
}
+ async endLivestream(chanDB){
+ try{
+ //Refresh next timer
+ this.refreshNextTimer();
+
+ //Broadcast Queue
+ this.broadcastQueue();
+ //ACK
+ }catch(err){
+ //Broadcast queue
+ this.broadcastQueue();
+ //Handle the error
+ loggerUtils.localExceptionHandler(err);
+ }
+ }
+
async stop(chanDB){
//If we wheren't handed a channel
if(chanDB == null){
diff --git a/src/server.js b/src/server.js
index f6dcd98..f584830 100644
--- a/src/server.js
+++ b/src/server.js
@@ -160,8 +160,6 @@ app.use('/passwordReset', passwordResetRouter);
app.use('/emailChange', emailChangeRouter);
//Panel
app.use('/panel', panelRouter);
-//Popup
-//app.use('/popup', popupRouter);
//tooltip
app.use('/tooltip', tooltipRouter);
//Bot-Ready
@@ -169,9 +167,9 @@ app.use('/api', apiRouter);
//Static File Server
//Serve client-side libraries
-app.use('/lib/bootstrap-icons',express.static(path.join(__dirname, '../node_modules/bootstrap-icons')));
-app.use('/lib/altcha',express.static(path.join(__dirname, '../node_modules/altcha/dist_external')));
-app.use('/lib/hls.js',express.static(path.join(__dirname, '../node_modules/hls.js/dist')));
+app.use('/lib/bootstrap-icons',express.static(path.join(__dirname, '../node_modules/bootstrap-icons'))); //Icon set
+app.use('/lib/altcha',express.static(path.join(__dirname, '../node_modules/altcha/dist_external'))); //Self-Hosted PoW-based Captcha
+app.use('/lib/hls.js',express.static(path.join(__dirname, '../node_modules/hls.js/dist'))); //HLS Media Handler
//Server public 'www' folder
app.use(express.static(path.join(__dirname, '../www')));
diff --git a/src/views/channel.ejs b/src/views/channel.ejs
index d66fdc3..561702d 100644
--- a/src/views/channel.ejs
+++ b/src/views/channel.ejs
@@ -34,7 +34,7 @@ along with this program. If not, see . %>
<%- include('partial/scripts', {user}); %>
<%# 3rd party code %>
-
+
<%# 1st party code %>
<%# admin gunk %>
diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css
index 1f28d4e..6df7a64 100644
--- a/www/css/theme/movie-night.css
+++ b/www/css/theme/movie-night.css
@@ -172,6 +172,11 @@ textarea{
box-shadow: var(--danger-glow0-alt1);
}
+.critical-danger-text{
+ color: var(--danger0-alt1);
+ text-shadow: var(--danger-glow0);
+}
+
.danger-link, .danger-text{
color: var(--danger0);
}
diff --git a/www/js/channel/mediaHandler.js b/www/js/channel/mediaHandler.js
index 4f64926..aa272c5 100644
--- a/www/js/channel/mediaHandler.js
+++ b/www/js/channel/mediaHandler.js
@@ -141,6 +141,10 @@ class mediaHandler{
//reset self act flag
this.selfAct = false;
}
+
+ onBuffer(){
+ this.selfAct = true;
+ }
}
//Basic building blocks for anything that touches a