diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js
index c97aff3..e700b51 100644
--- a/src/app/channel/media/queue.js
+++ b/src/app/channel/media/queue.js
@@ -49,9 +49,9 @@ module.exports = class{
this.preSwitchTimer = null;
//Create variable to hold currently playing media object
this.nowPlaying = null;
+
//Create variable to lock standard queuing functions during livestreams
this.streamLock = false;
-
//create boolean to hold schedule lock
this.locked = false;
@@ -1179,7 +1179,7 @@ module.exports = class{
}
}
- //Create a new array to hold the new schedule so we only have to write to the DB once.
+ //Create a new array to hold the new schedule since we'll only be keeping select items
let newSched = [];
//For every saved scheduled item
diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css
index 6df7a64..84b4564 100644
--- a/www/css/theme/movie-night.css
+++ b/www/css/theme/movie-night.css
@@ -44,6 +44,7 @@ along with this program. If not, see .*/
--danger0: firebrick;
--danger0-alt0: rgb(121, 11, 11);
--danger0-alt1: rgb(255, 105, 105);
+ --danger0-alt2: rgb(242, 189, 189);
--danger-glow0: 2px 2px 3px var(--danger0), -2px 2px 3px var(--danger0), 2px -2px 3px var(--danger0), -2px -2px 3px var(--danger0);
--danger-glow0-alt1: 2px 2px 3px var(--danger0-alt1), -2px 2px 3px var(--danger0-alt1), 2px -2px 3px var(--danger0-alt1), -2px -2px 3px var(--danger0-alt1);
@@ -504,6 +505,17 @@ div.queue-entry{
text-align: center;
}
+div.queue-entry.live {
+ background-color: var(--danger0);
+ border: 1px solid var(--danger0-alt0);
+ box-shadow: 0px 9px 10px -3px var(--danger0-alt1);
+}
+
+
+.queue-entry.live p{
+ color: var(--danger0-alt2);
+}
+
.media-tooltip{
font-family: monospace;
}
diff --git a/www/js/channel/panels/queuePanel/queuePanel.js b/www/js/channel/panels/queuePanel/queuePanel.js
index d368f57..4567966 100644
--- a/www/js/channel/panels/queuePanel/queuePanel.js
+++ b/www/js/channel/panels/queuePanel/queuePanel.js
@@ -101,7 +101,7 @@ class queuePanel extends panelObj{
closer(){
//Clear any remaining timers
- clearTimeout(this.timeMarkerTimer);
+ clearTimeout(this.renderIntervalTimer);
//Clear timetips
this.killTimetips();
}
@@ -382,7 +382,7 @@ class queuePanel extends panelObj{
}
//Set scale label text to humie readable time scale
- this.scaleLabel.innerHTML = `Time Scale:
${this.humieFriendlyDuration(this.scale)}`
+ this.scaleLabel.innerHTML = `Time Scale:
${utils.ux.humieFriendlyDuration(this.scale)}`
//Clear any previous rescale timer
clearTimeout(this.rescaleTimer);
@@ -403,64 +403,7 @@ class queuePanel extends panelObj{
this.autoscroll = false;
//Unlight the indicator
this.scrollLockButton.classList.remove('positive-button');
- }
-
- humieFriendlyDuration(seconds){
- //If we have an invalid duration
- if(seconds <= 0){
- //bitch, moan, and complain!
- return('Invalid input duration');
- }
-
- //Create an empty array to hold the time strings
- const timeStrings = [];
-
- //Pull hours from time
- const hours = Math.floor(seconds / 3600);
- //Remove recorded hours
- seconds -= hours * 3600;
-
- //Pull minutes from time
- const minutes = Math.floor(seconds / 60);
- //Remove recorded minutes
- seconds -= minutes * 60;
-
- //If we have an hour
- if(hours == 1){
- //Add the string
- timeStrings.push('1 Hour');
- //If we have hours
- }else if(hours > 0){
- //Add the string
- timeStrings.push(`${hours} Hours`);
- }
-
- //If we have a minute
- if(minutes == 1){
- //Add the string
- timeStrings.push('1 Minute');
- //If we have minutes
- }else if(minutes > 0){
- //Add the string
- timeStrings.push(`${minutes} Minutes`);
- }
-
- //Add the 'and ' if we need it
- const secondsPrefix = timeStrings.length > 0 ? 'and ' : '';
-
- //If we have a second
- if(seconds == 1){
- //Add the string
- timeStrings.push(`${secondsPrefix}1 Second`);
- //If we have more than a second
- }else if(seconds > 1){
- //Add the string
- timeStrings.push(`${secondsPrefix}${seconds} Seconds`);
- }
-
- //Join the time strings together
- return timeStrings.join(', ');
- }
+ }
resizeRender(event){
const date = new Date();
@@ -490,7 +433,7 @@ class queuePanel extends panelObj{
}
//Clear any marker timers
- clearTimeout(this.timeMarkerTimer);
+ clearTimeout(this.renderIntervalTimer);
//If we have an existing time marker
if(this.timeMarker != null){
@@ -501,7 +444,6 @@ class queuePanel extends panelObj{
}
async fullRender(date = new Date()){
-
//Clear the queue
this.clearQueue();
@@ -524,9 +466,12 @@ class queuePanel extends panelObj{
await utils.ux.awaitNextFrame();
}
- //render the time marker
+ //render the time marker w/ force scroll
this.renderTimeMarker(date, true);
+ //Kick off render interval
+ this.renderInterval(date);
+
//render out the queue
this.renderQueue(date);
}
@@ -575,7 +520,7 @@ class queuePanel extends panelObj{
//Place item beginning at dawn
entryDiv.style.top = `${this.offsetByDate(dawn)}px`;
- //Run apply the rest of the styling rules
+ //Apply style rules for items that starrted yesterday
entryDiv.classList.add('started-yesterday');
}
@@ -620,14 +565,14 @@ class queuePanel extends panelObj{
`Title: ${entry[1].title}`,
`File Name: ${entry[1].fileName}`,
`Source: ${entry[1].type}`,
- `Duration: ${entry[1].duration}`,
+ `Duration: ${utils.ux.humieFriendlyDuration(entry[1].duration)}`,
`Start Time: ${new Date(entry[1].startTime).toLocaleString()}${entry[1].startTimeStamp == 0 ? '' : ' (Started Late)'}`,
`End Time: ${new Date(this.getMediaEnd(entry[1])).toLocaleString()}${entry[1].earlyEnd == null ? '' : ' (Ended Early)'}`
]){
//Create a 'p' node
const component = document.createElement('p');
//Fill it with the string
- component.textContent = string;
+ component.textContent = utils.unescapeEntities(string);
//Append it to the tooltip div
tooltipDiv.append(component);
}
@@ -690,6 +635,9 @@ class queuePanel extends panelObj{
//Append entry
this.queueContainer.append(entryDiv);
}
+
+ //Render out any playing livestreams
+ this.renderLiveStream(date);
}
function clickEntry(event){
@@ -888,6 +836,18 @@ class queuePanel extends panelObj{
}
}
+ renderInterval(date = new Date()){
+ this.renderTimeMarker(date);
+ this.renderLiveStream(date);
+
+
+ //Clear update timer
+ clearTimeout(this.renderIntervalTimer);
+
+ //Set timer to update marker every second
+ this.renderIntervalTimer = setTimeout(this.renderInterval.bind(this), 1000);
+ }
+
renderTimeMarker(date = new Date(), forceScroll = false){
//Calculate marker position by date
const markerPosition = Math.round(this.offsetByDate(date));
@@ -895,7 +855,7 @@ class queuePanel extends panelObj{
//If markers are null
if(markerPosition == null){
//Try again in a second since the user probably just popped the panel or something :P
- (smackTimer.bind(this))();
+ return;
}
//If we're not looking at today
@@ -940,16 +900,6 @@ class queuePanel extends panelObj{
behavior: scrollBehavior
});
}
-
- //Set the timer to run the function again
- (smackTimer.bind(this))();
-
- function smackTimer(){
- //Clear update timer
- clearTimeout(this.timeMarkerTimer);
- //Set timer to update marker every second
- this.timeMarkerTimer = setTimeout(this.renderTimeMarker.bind(this), 1000);
- }
}
renderQueueScale(inputDate = new Date()){
@@ -1015,6 +965,135 @@ class queuePanel extends panelObj{
}
}
+ renderLiveStream(date = new Date()){
+ //Grab all live queue entries
+ const staleEntry = this.queueContainer.querySelector('.queue-entry.live');
+
+ //If we're not livestreaming
+ if(client.player.mediaHandler.type != "livehls"){
+ //If we have a stale entry
+ if(staleEntry != null){
+ //Remove stale entry since we're no longer streaming
+ staleEntry.remove();
+ }
+
+ //Fuck off and die
+ return;
+ }
+
+ //Grab currently playing media
+ const nowPlaying = client.player.mediaHandler.nowPlaying;
+
+ //If we don't have a good stale entry to re-use
+ if(staleEntry == null || staleEntry.dataset.uuid != nowPlaying.uuid){
+ //If it wasn't null but just old
+ if(staleEntry != null){
+ //Nukem
+ staleEntry.remove();
+ }
+
+ //Create entry div
+ const entryDiv = document.createElement('div');
+ entryDiv.classList.add('queue-entry', 'live');
+
+ //Iterate through nowPlaying's properties
+ for(let key of Object.keys(nowPlaying)){
+ //Add them to the entry div's dataset
+ entryDiv.dataset[key] = nowPlaying[key];
+ }
+
+ //Convert start epoch to JS date object
+ const started = new Date(nowPlaying.startTime);
+
+ //If this started today
+ if(utils.isSameDate(this.day, started)){
+ //Set entryDiv top-border location based on start time
+ entryDiv.style.top = `${this.offsetByDate(started)}px`
+ }else{
+ //Get dawn
+ const dawn = new Date();
+ dawn.setHours(0,0,0,0)
+
+ //Place item at the beginning of dawn
+ entryDiv.style.top = `${this.offsetByDate(dawn)}px`;
+
+ //Apply rest of the styling rules for items that started yestarday
+ entryDiv.classList.add('started-yesterday')
+ }
+
+ //Create entry title
+ const entryTitle = document.createElement('p');
+ entryTitle.textContent = utils.unescapeEntities(nowPlaying.title);
+
+ //Set entry div bottom-border location based on current time
+ entryDiv.style.bottom = `${this.offsetByDate(date, true)}px`
+
+ //Assembly entryDiv
+ entryDiv.appendChild(entryTitle);
+
+ //Setup media tooltip
+ entryDiv.addEventListener('mouseenter',(event)=>{
+ //Construct tooltip on mouseover to re-calculate duration for live media
+ const tooltipDiv = buildTooltip();
+
+ //Display tooltip
+ utils.ux.displayTooltip(event, tooltipDiv, false, null, true, this.ownerDoc);
+ });
+
+ //Append entry div to queue container
+ this.queueContainer.appendChild(entryDiv);
+ }else{
+ //Update existing entry
+ staleEntry.style.bottom = `${this.offsetByDate(date, true)}px`
+ }
+
+ //Keep tooltip date seperate so it re-calculates live duration properly
+ function buildTooltip(date = new Date()){
+ //Tooltip generation
+ //tooltip div
+ const tooltipDiv = document.createElement('div');
+ tooltipDiv.classList.add('media-tooltip');
+
+ //tooltip components
+ const tooltipStrings = [
+ `Title: ${nowPlaying.title}`,
+ `File Name: ${nowPlaying.fileName}`,
+ `Source: HLS Livestream (${nowPlaying.url})`,
+ `Duration: ${utils.ux.humieFriendlyDuration((date.getTime() - nowPlaying.startTime) / 1000)}`,
+ `Start Time: ${new Date(nowPlaying.startTime).toLocaleString()}`,
+ ];
+
+ //For each string in the tooltip
+ for(const string of tooltipStrings){
+
+ //Create a 'p' node
+ const component = document.createElement('p');
+ //Fill it with the string
+ component.textContent = utils.unescapeEntities(string);
+
+ //Append it to the tooltip div
+ tooltipDiv.append(component);
+ }
+
+ //Create End Date
+ const source = document.createElement('p');
+ const liveSpan = document.createElement('span');
+
+ //Fill end date label
+ source.textContent = "End Time: "
+ liveSpan.textContent = 'LIVE'
+
+ //Set class
+ liveSpan.classList.add('critical-danger-text');
+
+ //Assemble end date
+ source.appendChild(liveSpan);
+ tooltipDiv.appendChild(source);
+
+ return tooltipDiv;
+ }
+ }
+
offsetByDate(date = new Date(), bottomOffset = false){
//Pull start of day epoch from given date, make sure to use a new date object so we don't fuck up any date objects passed to us
const dayEpoch = new Date(date).setHours(0,0,0,0);
diff --git a/www/js/utils.js b/www/js/utils.js
index 7685b17..b43c5a6 100644
--- a/www/js/utils.js
+++ b/www/js/utils.js
@@ -99,6 +99,63 @@ class canopyUXUtils{
return outString;
}
+ humieFriendlyDuration(seconds){
+ //If we have an invalid duration
+ if(seconds <= 0){
+ //bitch, moan, and complain!
+ return('Invalid input duration');
+ }
+
+ //Create an empty array to hold the time strings
+ const timeStrings = [];
+
+ //Pull hours from time
+ const hours = Math.floor(seconds / 3600);
+ //Remove recorded hours
+ seconds -= hours * 3600;
+
+ //Pull minutes from time
+ const minutes = Math.floor(seconds / 60);
+ //Remove recorded minutes
+ seconds -= minutes * 60;
+
+ //If we have an hour
+ if(hours == 1){
+ //Add the string
+ timeStrings.push('1 Hour');
+ //If we have hours
+ }else if(hours > 0){
+ //Add the string
+ timeStrings.push(`${hours} Hours`);
+ }
+
+ //If we have a minute
+ if(minutes == 1){
+ //Add the string
+ timeStrings.push('1 Minute');
+ //If we have minutes
+ }else if(minutes > 0){
+ //Add the string
+ timeStrings.push(`${minutes} Minutes`);
+ }
+
+ //Add the 'and ' if we need it
+ const secondsPrefix = timeStrings.length > 0 ? 'and ' : '';
+
+ //If we have a second
+ if(seconds == 1){
+ //Add the string
+ timeStrings.push(`${secondsPrefix}1 Second`);
+ //If we have more than a second
+ }else if(seconds > 1){
+ //Add the string
+ timeStrings.push(`${secondsPrefix}${Math.round(seconds)} Seconds`);
+ }
+
+ //Join the time strings together
+ return timeStrings.join(', ');
+ }
+
//Update this and popup class to use nodes
//and display multiple errors in one popup
displayResponseError(body){