From ad08fca6716cccc6cd90b52a9b1a66489d1e503d Mon Sep 17 00:00:00 2001 From: vexorian Date: Sun, 22 Nov 2020 11:10:52 -0400 Subject: [PATCH] Time Slots improvement. Now by default tries to distribute flex times between videos in the same slot #184. Option to make it work like before. 10 minutes padding option. I don't care about lateness option. Fix bug that could be caused by two consecutive time slots with the same tv show (or both are movies) causing HUGE flex times. I basically rewrote the whole thing, so enjoy. --- src/services/time-slots-service.js | 147 +++++++++++++----- web/directives/time-slots-schedule-editor.js | 15 ++ .../templates/time-slots-schedule-editor.html | 17 +- 3 files changed, 136 insertions(+), 43 deletions(-) diff --git a/src/services/time-slots-service.js b/src/services/time-slots-service.js index d50ad78..15f6ac0 100644 --- a/src/services/time-slots-service.js +++ b/src/services/time-slots-service.js @@ -5,6 +5,7 @@ const DAY = 24*60*MINUTE; const LIMIT = 40000; + //This is a triplicate code, but maybe it doesn't have to be? function getShow(program) { //used for equalize and frequency tweak @@ -208,6 +209,13 @@ module.exports = async( programs, schedule ) => { if (typeof(schedule.maxDays) == 'undefined') { return { userError: "schedule.maxDays must be defined." }; } + if (typeof(schedule.flexPreference) === 'undefined') { + schedule.flexPreference = "distribute"; + } + if (schedule.flexPreference !== "distribute" && schedule.flexPreference !== "end") { + return { userError: `Invalid schedule.flexPreference value: "${schedule.flexPreference}"` }; + } + let flexBetween = ( schedule.flexPreference !== "end" ); // throttle so that the stream is not affected negatively let steps = 0; @@ -222,6 +230,8 @@ module.exports = async( programs, schedule ) => { let shows = []; function getNextForSlot(slot, remaining) { + //remaining doesn't restrict what next show is picked. It is only used + //for shows with flexible length (flex and redirects) if (slot.showId === "flex.") { return { isOffline: true, @@ -255,6 +265,21 @@ module.exports = async( programs, schedule ) => { } } + function makePadded(item) { + let x = item.duration; + let m = x % schedule.pad; + let f = 0; + if ( (m > constants.SLACK) && (schedule.pad - m > constants.SLACK) ) { + f = schedule.pad - m; + } + return { + item: item, + pad: f, + totalDuration: item.duration + f, + } + + } + // load the programs for (let i = 0; i < programs.length; i++) { let p = programs[i]; @@ -282,7 +307,6 @@ module.exports = async( programs, schedule ) => { let t0 = d.getTime(); let p = []; let t = t0; - let previous = null; let hardLimit = t0 + schedule.maxDays * DAY; let pushFlex = (d) => { @@ -299,11 +323,19 @@ module.exports = async( programs, schedule ) => { } } - for (let i = 0; i < LIMIT; i++) { + while ( (t < hardLimit) && (p.length < LIMIT) ) { await throttle(); + //ensure t is padded + let m = t % schedule.pad; + if ( (t % schedule.pad > constants.SLACK) && (schedule.pad - m > constants.SLACK) ) { + pushFlex( schedule.pad - m ); + continue; + } + let dayTime = t % DAY; let slot = null; let remaining = null; + let late = null; for (let i = 0; i < s.length; i++) { let endTime; if (i == s.length - 1) { @@ -315,61 +347,94 @@ module.exports = async( programs, schedule ) => { if ((s[i].time <= dayTime) && (dayTime < endTime)) { slot = s[i]; remaining = endTime - dayTime; + late = dayTime - s[i].time; break; } if ((s[i].time <= dayTime + DAY) && (dayTime + DAY < endTime)) { slot = s[i]; dayTime += DAY; remaining = endTime - dayTime; + late = dayTime + DAY - s[i].time; break; } } if (slot == null) { throw Error("Unexpected. Unable to find slot for time of day " + t + " " + dayTime); } - - let first = (previous !== slot.showId); - let skip = false; //skips to the next one - if (first) { - //check if it's too late - let d = dayTime - slot.time; - if (d >= schedule.lateness + constants.SLACK) { - skip = true; - } - } let item = getNextForSlot(slot, remaining); - if ( (item.duration >= remaining + constants.SLACK) && !first) { - skip = true; - } - if (t + item.duration - constants.SLACK >= hardLimit) { - pushFlex( hardLimit - t ); - break; - } - if (item.isOffline && item.type != 'redirect') { - //it's the same, really - skip = true; - } - if (skip) { - pushFlex(remaining); - } else { - previous = slot.showId; - let clone = JSON.parse( JSON.stringify(item) ); - clone.$index = p.length; - p.push( clone ); - t += clone.duration; - - advanceSlot(slot); - } - let nt = t; - let m = t % schedule.pad; - if (m != 0) { - nt = t - m + schedule.pad; - let remaining = nt - t; - if (remaining >= constants.SLACK) { - pushFlex(remaining); + if (late >= schedule.lateness + constants.SLACK ) { + //it's late. + item = { + isOffline : true, + duration: remaining, } } + + if (item.isOffline) { + //flex or redirect. We can just use the whole duration + p.push(item); + t += remaining; + continue; + } + if (item.duration > remaining) { + // Slide + p.push(item); + t += item.duration; + advanceSlot(slot); + continue; + } + + let padded = makePadded(item); + let total = padded.totalDuration; + advanceSlot(slot); + let pads = [ padded ]; + + while(true) { + let item2 = getNextForSlot(slot); + if (total + item2.duration > remaining) { + break; + } + let padded2 = makePadded(item2); + pads.push(padded2); + advanceSlot(slot); + total += padded2.totalDuration; + } + let rem = Math.max(0, remaining - total); + + if (flexBetween) { + let div = Math.floor(rem / schedule.pad ); + let mod = rem % schedule.pad; + // add mod to the latest item + pads[ pads.length - 1].pad += mod; + pads[ pads.length - 1].totalDuration += mod; + + let sortedPads = pads.map( (p, $index) => { + return { + pad: p.pad, + index : $index, + } + }); + sortedPads.sort( (a,b) => { return a.pad - b.pad; } ); + for (let i = 0; i < pads.length; i++) { + let q = Math.floor( div / pads.length ); + if (i < div % pads.length) { + q++; + } + let j = sortedPads[i].index; + pads[j].pad += q * schedule.pad; + } + } else { + //also add div to the latest item + pads[ pads.length - 1].pad += rem; + pads[ pads.length - 1].totalDuration += rem; + } + // now unroll them all + for (let i = 0; i < pads.length; i++) { + p.push( pads[i].item ); + t += pads[i].item.duration; + pushFlex( pads[i].pad ); + } } return { diff --git a/web/directives/time-slots-schedule-editor.js b/web/directives/time-slots-schedule-editor.js index 6a11334..9abe540 100644 --- a/web/directives/time-slots-schedule-editor.js +++ b/web/directives/time-slots-schedule-editor.js @@ -26,6 +26,7 @@ module.exports = function ($timeout, dizquetv) { scope.schedule = { lateness : 0, maxDays: 365, + flexPreference : "distribute", slots : [], pad: 1, fake: { time: -1 }, @@ -52,6 +53,9 @@ module.exports = function ($timeout, dizquetv) { slots[i].order = "shuffle"; } } + if (typeof(scope.schedule.flexPreference) === 'undefined') { + scope.schedule.flexPreference = "distribute"; + } scope.schedule.fake = { time: -1, } @@ -71,13 +75,23 @@ module.exports = function ($timeout, dizquetv) { { id: 10*60*1000 , description: "10 minutes" }, { id: 15*60*1000 , description: "15 minutes" }, { id: 1*60*60*1000 , description: "1 hour" }, + { id: 2*60*60*1000 , description: "2 hours" }, + { id: 3*60*60*1000 , description: "3 hours" }, + { id: 4*60*60*1000 , description: "4 hours" }, + { id: 8*60*60*1000 , description: "8 hours" }, + { id: 24*60*60*1000 , description: "I don't care about lateness" }, ]; + scope.flexOptions = [ + { id: "distribute", description: "Between videos" }, + { id: "end", description: "End of the slot" }, + ] scope.fakeTimeOptions = JSON.parse( JSON.stringify( scope.timeOptions ) ); scope.fakeTimeOptions.push( {id: -1, description: "Add slot"} ); scope.padOptions = [ {id: 1, description: "Do not pad" }, {id: 5*60*1000, description: "0:00, 0:05, 0:10, ..., 0:55" }, + {id: 10*60*1000, description: "0:00, 0:10, 0:20, ..., 0:50" }, {id: 15*60*1000, description: "0:00, 0:15, 0:30, ..., 0:45" }, {id: 30*60*1000, description: "0:00, 0:30" }, {id: 1*60*60*1000, description: "0:00" }, @@ -137,6 +151,7 @@ module.exports = function ($timeout, dizquetv) { } ); scope.finished = async (cancel) => { + scope.error = null; if (!cancel) { try { scope.loading = true; diff --git a/web/public/templates/time-slots-schedule-editor.html b/web/public/templates/time-slots-schedule-editor.html index e273ca2..7eb8139 100644 --- a/web/public/templates/time-slots-schedule-editor.html +++ b/web/public/templates/time-slots-schedule-editor.html @@ -89,12 +89,25 @@ aria-describedby="padHelp" > - + Ensures programs have a nice-looking start time, it will add Flex time to fill the gaps. - + +
+ + + + Usually slots need to add flex time to ensure that the next slot starts at the correct time. When there are multiple videos in the slot, you might prefer to distribute the flex time between the videos or to place most of the flex time at the end of the slot. + +
+