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.

This commit is contained in:
vexorian 2020-11-22 11:10:52 -04:00
parent 20ed585c99
commit ad08fca671
3 changed files with 136 additions and 43 deletions

View File

@ -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 {

View File

@ -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;

View File

@ -89,12 +89,25 @@
aria-describedby="padHelp"
>
</select>
<small id='latenessHelp' class='form-text text-muted'>
<small id='padHelp' class='form-text text-muted'>
Ensures programs have a nice-looking start time, it will add Flex time to fill the gaps.
</small>
</div>
<div class='form-group'>
<label for="pad">What to do with flex?</label>
<select
id="flexPreference" class="custom-select form-control"
ng-model="schedule.flexPreference" ng-options="o.id as o.description for o in flexOptions"
aria-describedby="flexPreferenceHelp"
>
</select>
<small id='flexPreferenceHelp' class='form-text text-muted'>
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.
</small>
</div>
<div class='form-group'>
<label for="lateness">Maximum days to precalculate</label>
<input