Channel redirects + 'Channel At night'
This commit is contained in:
parent
c4a0b7af96
commit
2138176689
2
index.js
2
index.js
@ -131,7 +131,7 @@ app.use('/images', express.static(path.join(process.env.DATABASE, 'images')))
|
||||
app.use(express.static(path.join(__dirname, 'web/public')))
|
||||
app.use('/images', express.static(path.join(process.env.DATABASE, 'images')))
|
||||
app.use(api.router(db, channelDB, xmltvInterval))
|
||||
app.use(video.router(db))
|
||||
app.use(video.router( channelDB, db))
|
||||
app.use(hdhr.router)
|
||||
app.listen(process.env.PORT, () => {
|
||||
console.log(`HTTP server running on port: http://*:${process.env.PORT}`)
|
||||
|
||||
@ -9,8 +9,12 @@ async function getChannelConfig(channelDB, channelId) {
|
||||
|
||||
if ( typeof(configCache[channelId]) === 'undefined') {
|
||||
let channel = await channelDB.getChannel(channelId)
|
||||
//console.log("channel=" + JSON.stringify(channel) );
|
||||
configCache[channelId] = [channel];
|
||||
if (channel == null) {
|
||||
configCache[channelId] = [];
|
||||
} else {
|
||||
//console.log("channel=" + JSON.stringify(channel) );
|
||||
configCache[channelId] = [channel];
|
||||
}
|
||||
}
|
||||
//console.log("channel=" + JSON.stringify(configCache[channelId]).slice(0,200) );
|
||||
return configCache[channelId];
|
||||
|
||||
@ -9,18 +9,23 @@ class ChannelDB {
|
||||
|
||||
async getChannel(number) {
|
||||
let f = path.join(this.folder, `${number}.json` );
|
||||
return await new Promise( (resolve, reject) => {
|
||||
fs.readFile(f, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
try {
|
||||
resolve( JSON.parse(data) )
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
try {
|
||||
return await new Promise( (resolve, reject) => {
|
||||
fs.readFile(f, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
try {
|
||||
resolve( JSON.parse(data) )
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async saveChannel(number, json) {
|
||||
|
||||
@ -42,6 +42,20 @@ function createLineup(obj, channel, isFirst) {
|
||||
|
||||
let lineup = []
|
||||
|
||||
if ( typeof(activeProgram.err) !== 'undefined') {
|
||||
let remaining = activeProgram.duration - timeElapsed;
|
||||
lineup.push( {
|
||||
type: 'offline',
|
||||
title: 'Error',
|
||||
err: activeProgram.err,
|
||||
streamDuration: remaining,
|
||||
duration: remaining,
|
||||
start: 0
|
||||
})
|
||||
return lineup;
|
||||
}
|
||||
|
||||
|
||||
if (activeProgram.isOffline === true) {
|
||||
//offline case
|
||||
let remaining = activeProgram.duration - timeElapsed;
|
||||
|
||||
@ -9,6 +9,7 @@ const PlexTranscoder = require('./plexTranscoder')
|
||||
const EventEmitter = require('events');
|
||||
const helperFuncs = require('./helperFuncs')
|
||||
const FFMPEG = require('./ffmpeg')
|
||||
const constants = require('./constants');
|
||||
|
||||
let USED_CLIENTS = {};
|
||||
|
||||
@ -60,8 +61,10 @@ class PlexPlayer {
|
||||
let ffmpeg = new FFMPEG(ffmpegSettings, channel); // Set the transcoder options
|
||||
this.ffmpeg = ffmpeg;
|
||||
let streamDuration;
|
||||
if (typeof(streamDuration)!=='undefined') {
|
||||
streamDuration = lineupItem.streamDuration / 1000;
|
||||
if (typeof(lineupItem.streamDuration)!=='undefined') {
|
||||
if (lineupItem.start + lineupItem.streamDuration + constants.SLACK < lineupItem.duration) {
|
||||
streamDuration = lineupItem.streamDuration / 1000;
|
||||
}
|
||||
}
|
||||
let deinterlace = ffmpegSettings.enableFFMPEGTranscoding; //for now it will always deinterlace when transcoding is enabled but this is sub-optimal
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ class ProgramPlayer {
|
||||
// people might want the codec normalization to stay because of player support
|
||||
context.ffmpegSettings.normalizeResolution = false;
|
||||
}
|
||||
if (program.err instanceof Error) {
|
||||
if ( typeof(program.err) !== 'undefined') {
|
||||
console.log("About to play error stream");
|
||||
this.delegate = new OfflinePlayer(true, context);
|
||||
} else if (program.type === 'loading') {
|
||||
|
||||
92
src/video.js
92
src/video.js
@ -9,7 +9,7 @@ const channelCache = require('./channel-cache')
|
||||
|
||||
module.exports = { router: video }
|
||||
|
||||
function video(db) {
|
||||
function video( channelDB , db) {
|
||||
var router = express.Router()
|
||||
|
||||
router.get('/setup', (req, res) => {
|
||||
@ -49,7 +49,7 @@ function video(db) {
|
||||
return
|
||||
}
|
||||
let number = parseInt(req.query.channel, 10);
|
||||
let channel = await channelCache.getChannelConfig(db, number);
|
||||
let channel = await channelCache.getChannelConfig(channelDB, number);
|
||||
if (channel.length === 0) {
|
||||
res.status(500).send("Channel doesn't exist")
|
||||
return
|
||||
@ -118,7 +118,7 @@ function video(db) {
|
||||
}
|
||||
let m3u8 = (req.query.m3u8 === '1');
|
||||
let number = parseInt(req.query.channel);
|
||||
let channel = await channelCache.getChannelConfig(db, number);
|
||||
let channel = await channelCache.getChannelConfig(channelDB, number);
|
||||
|
||||
if (channel.length === 0) {
|
||||
res.status(404).send("Channel doesn't exist")
|
||||
@ -150,6 +150,11 @@ function video(db) {
|
||||
// Get video lineup (array of video urls with calculated start times and durations.)
|
||||
let t0 = (new Date()).getTime();
|
||||
let lineupItem = channelCache.getCurrentLineupItem( channel.number, t0);
|
||||
let prog = null;
|
||||
let brandChannel = channel;
|
||||
let redirectChannels = [];
|
||||
let upperBounds = [];
|
||||
|
||||
if (isLoading) {
|
||||
lineupItem = {
|
||||
type: 'loading',
|
||||
@ -158,8 +163,57 @@ function video(db) {
|
||||
start: 0,
|
||||
};
|
||||
} else if (lineupItem == null) {
|
||||
let prog = helperFuncs.getCurrentProgramAndTimeElapsed(t0, channel)
|
||||
prog = helperFuncs.getCurrentProgramAndTimeElapsed(t0, channel);
|
||||
|
||||
while (true) {
|
||||
redirectChannels.push( brandChannel );
|
||||
upperBounds.push( prog.program.duration - prog.timeElapsed );
|
||||
|
||||
if ( !(prog.program.isOffline) || (prog.program.type != 'redirect') ) {
|
||||
break;
|
||||
}
|
||||
channelCache.recordPlayback( brandChannel.number, t0, {
|
||||
/*type: 'offline',*/
|
||||
title: 'Error',
|
||||
err: Error("Recursive channel redirect found"),
|
||||
duration : 60000,
|
||||
start: 0,
|
||||
});
|
||||
|
||||
|
||||
|
||||
let newChannelNumber= prog.program.channel;
|
||||
let newChannel = await channelCache.getChannelConfig(channelDB, newChannelNumber);
|
||||
|
||||
if (newChannel.length == 0) {
|
||||
let err = Error("Invalid redirect to a channel that doesn't exist");
|
||||
console.error("Invalid redirect to channel that doesn't exist.", err);
|
||||
prog = {
|
||||
program: {
|
||||
isOffline: true,
|
||||
err: err,
|
||||
duration : 60000,
|
||||
},
|
||||
timeElapsed: 0,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
newChannel = newChannel[0];
|
||||
brandChannel = newChannel;
|
||||
lineupItem = channelCache.getCurrentLineupItem( newChannel.number, t0);
|
||||
if (lineupItem != null) {
|
||||
lineupItem = JSON.parse( JSON.stringify(lineupItem)) ;
|
||||
break;
|
||||
} else {
|
||||
prog = helperFuncs.getCurrentProgramAndTimeElapsed(t0, newChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lineupItem == null) {
|
||||
if (prog == null) {
|
||||
res.status(500).send("server error");
|
||||
throw Error("Shouldn't prog be non-null?");
|
||||
}
|
||||
if (prog.program.isOffline && channel.programs.length == 1) {
|
||||
//there's only one program and it's offline. So really, the channel is
|
||||
//permanently offline, it doesn't matter what duration was set
|
||||
@ -180,15 +234,33 @@ function video(db) {
|
||||
if ( (prog == null) || (typeof(prog) === 'undefined') || (prog.program == null) || (typeof(prog.program) == "undefined") ) {
|
||||
throw "No video to play, this means there's a serious unexpected bug or the channel db is corrupted."
|
||||
}
|
||||
let lineup = helperFuncs.createLineup(prog, channel, isFirst)
|
||||
let lineup = helperFuncs.createLineup(prog, brandChannel, isFirst)
|
||||
lineupItem = lineup.shift()
|
||||
}
|
||||
|
||||
|
||||
if ( !isLoading && (lineupItem != null) ) {
|
||||
let upperBound = 1000000000;
|
||||
//adjust upper bounds and record playbacks
|
||||
for (let i = redirectChannels.length-1; i >= 0; i--) {
|
||||
lineupItem = JSON.parse( JSON.stringify(lineupItem ));
|
||||
let u = upperBounds[i];
|
||||
if (typeof(u) !== 'undefined') {
|
||||
let u2 = upperBound;
|
||||
if ( typeof(lineupItem.streamDuration) !== 'undefined') {
|
||||
u2 = Math.min(u2, lineupItem.streamDuration);
|
||||
}
|
||||
lineupItem.streamDuration = Math.min(u2, u);
|
||||
upperBound = lineupItem.streamDuration;
|
||||
}
|
||||
channelCache.recordPlayback( redirectChannels[i].number, t0, lineupItem );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log("=========================================================");
|
||||
console.log("! Start playback");
|
||||
console.log(`! Channel: ${channel.name} (${channel.number})`);
|
||||
if (typeof(lineupItem) === 'undefined') {
|
||||
if (typeof(lineupItem.title) === 'undefined') {
|
||||
lineupItem.title = 'Unknown';
|
||||
}
|
||||
console.log(`! Title: ${lineupItem.title}`);
|
||||
@ -206,7 +278,7 @@ function video(db) {
|
||||
let playerContext = {
|
||||
lineupItem : lineupItem,
|
||||
ffmpegSettings : ffmpegSettings,
|
||||
channel: channel,
|
||||
channel: brandChannel,
|
||||
db: db,
|
||||
m3u8: m3u8,
|
||||
}
|
||||
@ -267,7 +339,7 @@ function video(db) {
|
||||
}
|
||||
|
||||
let channelNum = parseInt(req.query.channel, 10)
|
||||
let channel = await channelCache.getChannelConfig(db, channelNum );
|
||||
let channel = await channelCache.getChannelConfig(channelDB, channelNum );
|
||||
if (channel.length === 0) {
|
||||
res.status(500).send("Channel doesn't exist")
|
||||
return
|
||||
@ -312,7 +384,7 @@ function video(db) {
|
||||
}
|
||||
|
||||
let channelNum = parseInt(req.query.channel, 10)
|
||||
let channel = await channelCache.getChannelConfig(db, channelNum );
|
||||
let channel = await channelCache.getChannelConfig(channelDB, channelNum );
|
||||
if (channel.length === 0) {
|
||||
res.status(500).send("Channel doesn't exist")
|
||||
return
|
||||
|
||||
@ -18,6 +18,7 @@ app.directive('programConfig', require('./directives/program-config'))
|
||||
app.directive('offlineConfig', require('./directives/offline-config'))
|
||||
app.directive('frequencyTweak', require('./directives/frequency-tweak'))
|
||||
app.directive('removeShows', require('./directives/remove-shows'))
|
||||
app.directive('channelRedirect', require('./directives/channel-redirect'))
|
||||
app.directive('plexServerEdit', require('./directives/plex-server-edit'))
|
||||
app.directive('channelConfig', require('./directives/channel-config'))
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
module.exports = function ($timeout, $location) {
|
||||
module.exports = function ($timeout, $location, dizquetv) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'templates/channel-config.html',
|
||||
replace: true,
|
||||
scope: {
|
||||
visible: "=visible",
|
||||
channels: "=channels",
|
||||
channel: "=channel",
|
||||
onDone: "=onDone"
|
||||
@ -93,6 +94,11 @@ module.exports = function ($timeout, $location) {
|
||||
updateChannelDuration();
|
||||
setTimeout( () => { scope.showRotatedNote = true }, 1, 'funky');
|
||||
}
|
||||
scope._selectedRedirect = {
|
||||
isOffline : true,
|
||||
type : "redirect",
|
||||
duration : 60*60*1000,
|
||||
}
|
||||
|
||||
scope.finshedProgramEdit = (program) => {
|
||||
scope.channel.programs[scope.selectedProgram] = program
|
||||
@ -151,7 +157,7 @@ module.exports = function ($timeout, $location) {
|
||||
let newProgs = []
|
||||
let progs = scope.channel.programs
|
||||
for (let i = 0, l = progs.length; i < l; i++) {
|
||||
if (progs[i].type === 'movie') {
|
||||
if ( progs[i].isOffline || (progs[i].type === 'movie') ) {
|
||||
movies.push(progs[i])
|
||||
} else {
|
||||
if (typeof shows[progs[i].showTitle] === 'undefined')
|
||||
@ -241,7 +247,9 @@ module.exports = function ($timeout, $location) {
|
||||
let tmpProgs = {}
|
||||
let progs = scope.channel.programs
|
||||
for (let i = 0, l = progs.length; i < l; i++) {
|
||||
if (progs[i].type === 'movie') {
|
||||
if ( progs[i].type ==='redirect' ) {
|
||||
tmpProgs['_redirect ' + progs[i].channel + ' _ '+ progs[i].duration ] = progs[i];
|
||||
} else if (progs[i].type === 'movie') {
|
||||
tmpProgs[progs[i].title + progs[i].durationStr] = progs[i]
|
||||
} else {
|
||||
tmpProgs[progs[i].showTitle + '-' + progs[i].season + '-' + progs[i].episode] = progs[i]
|
||||
@ -258,7 +266,7 @@ module.exports = function ($timeout, $location) {
|
||||
let tmpProgs = []
|
||||
let progs = scope.channel.programs
|
||||
for (let i = 0, l = progs.length; i < l; i++) {
|
||||
if (progs[i].isOffline !== true) {
|
||||
if ( (progs[i].isOffline !== true) || (progs[i].type === 'redirect') ) {
|
||||
tmpProgs.push(progs[i]);
|
||||
}
|
||||
}
|
||||
@ -278,9 +286,17 @@ module.exports = function ($timeout, $location) {
|
||||
updateChannelDuration()
|
||||
}
|
||||
|
||||
scope.getShowTitle = (program) => {
|
||||
if (program.isOffline && program.type == 'redirect') {
|
||||
return `Redirect to channel ${program.channel}`;
|
||||
} else {
|
||||
return program.showTitle;
|
||||
}
|
||||
}
|
||||
|
||||
scope.startRemoveShows = () => {
|
||||
scope._removablePrograms = scope.channel.programs
|
||||
.map(program => program.showTitle)
|
||||
.map(scope.getShowTitle)
|
||||
.reduce((dedupedArr, showTitle) => {
|
||||
if (!dedupedArr.includes(showTitle)) {
|
||||
dedupedArr.push(showTitle)
|
||||
@ -292,7 +308,9 @@ module.exports = function ($timeout, $location) {
|
||||
}
|
||||
scope.removeShows = (deletedShowNames) => {
|
||||
const p = scope.channel.programs;
|
||||
scope.channel.programs = p.filter(program => deletedShowNames.indexOf(program.showTitle) === -1);
|
||||
let set = {};
|
||||
deletedShowNames.forEach( (a) => set[a] = true );
|
||||
scope.channel.programs = p.filter( (a) => (set[scope.getShowTitle(a)]!==true) );
|
||||
}
|
||||
|
||||
scope.describeFallback = () => {
|
||||
@ -312,31 +330,45 @@ module.exports = function ($timeout, $location) {
|
||||
|
||||
scope.programSquareStyle = (program) => {
|
||||
let background ="";
|
||||
if (program.isOffline) {
|
||||
if ( (program.isOffline) && (program.type !== 'redirect') ) {
|
||||
background = "rgb(255, 255, 255)";
|
||||
} else {
|
||||
let r = 0, g = 0, b = 0, r2=0, g2=0,b2=0;
|
||||
let i = 0;
|
||||
let angle = 45;
|
||||
let w = 3;
|
||||
if (program.type === 'episode') {
|
||||
if (program.type === 'redirect') {
|
||||
angle = 0;
|
||||
w = 4 + (program.channel % 10);
|
||||
let c = (program.channel * 100019);
|
||||
//r = 255, g = 0, b = 0;
|
||||
//r2 = 0, g2 = 0, b2 = 255;
|
||||
|
||||
r = ( (c & 3) * 77 );
|
||||
g = ( ( (c >> 1) & 3) * 77 );
|
||||
b = ( ( (c >> 2) & 3) * 77 );
|
||||
r2 = ( ( (c >> 5) & 3) * 37 );
|
||||
g2 = ( ( (c >> 3) & 3) * 37 );
|
||||
b2 = ( ( (c >> 4) & 3) * 37 );
|
||||
|
||||
} else if (program.type === 'episode') {
|
||||
let h = Math.abs(scope.getHashCode(program.showTitle, false));
|
||||
let h2 = Math.abs(scope.getHashCode(program.showTitle, true));
|
||||
r = h % 256;
|
||||
g = (h / 256) % 256;
|
||||
b = (h / (256*256) ) % 256;
|
||||
i = h % 360;
|
||||
r2 = (h2 / (256*256) ) % 256;
|
||||
g2 = (h2 / (256*256) ) % 256;
|
||||
b2 = (h2 / (256*256) ) % 256;
|
||||
angle = -90 + h % 180
|
||||
angle = (360 - 90 + h % 180) % 360;
|
||||
if ( angle >= 350 || angle < 10 ) {
|
||||
angle += 53;
|
||||
}
|
||||
} else {
|
||||
r = 10, g = 10, b = 10;
|
||||
r2 = 245, g2 = 245, b2 = 245;
|
||||
angle = 45;
|
||||
w = 6;
|
||||
}
|
||||
|
||||
let rgb1 = "rgb("+ r + "," + g + "," + b +")";
|
||||
let rgb2 = "rgb("+ r2 + "," + g2 + "," + b2 +")"
|
||||
background = "repeating-linear-gradient( " + angle + "deg, " + rgb1 + ", " + rgb1 + " " + w + "px, " + rgb2 + " " + w + "px, " + rgb2 + " " + (w*2) + "px)";
|
||||
@ -376,7 +408,7 @@ module.exports = function ($timeout, $location) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
scope.nightChannel = (a, b) => {
|
||||
scope.nightChannel = (a, b, ch) => {
|
||||
let o =(new Date()).getTimezoneOffset() * 60 * 1000;
|
||||
let m = 24*60*60*1000;
|
||||
a = (m + a * 60 * 60 * 1000 + o) % m;
|
||||
@ -405,6 +437,8 @@ module.exports = function ($timeout, $location) {
|
||||
{
|
||||
duration: d,
|
||||
isOffline: true,
|
||||
channel: ch,
|
||||
type: (typeof(ch) === 'undefined') ? undefined: "redirect",
|
||||
}
|
||||
)
|
||||
t += d;
|
||||
@ -419,6 +453,8 @@ module.exports = function ($timeout, $location) {
|
||||
{
|
||||
duration: d,
|
||||
isOffline: true,
|
||||
type: (typeof(ch) === 'undefined') ? undefined: "redirect",
|
||||
channel: ch,
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -433,7 +469,7 @@ module.exports = function ($timeout, $location) {
|
||||
let tired = 0;
|
||||
for (let i = 0, l = scope.channel.programs.length; i <= l; i++) {
|
||||
let prog = scope.channel.programs[i % l];
|
||||
if (prog.isOffline) {
|
||||
if (prog.isOffline && prog.type != 'redirect') {
|
||||
tired = 0;
|
||||
} else {
|
||||
if (tired + prog.duration >= after) {
|
||||
@ -557,7 +593,7 @@ module.exports = function ($timeout, $location) {
|
||||
scope.startFrequencyTweak = () => {
|
||||
let programs = {};
|
||||
for (let i = 0; i < scope.channel.programs.length; i++) {
|
||||
if (! scope.channel.programs[i].isOffline) {
|
||||
if ( !scope.channel.programs[i].isOffline || (scope.channel.programs[i].type === 'redirect') ) {
|
||||
let c = getShowCode(scope.channel.programs[i]);
|
||||
if ( typeof(programs[c]) === 'undefined') {
|
||||
programs[c] = 0;
|
||||
@ -629,7 +665,9 @@ module.exports = function ($timeout, $location) {
|
||||
function getShowCode(program) {
|
||||
//used for equalize and frequency tweak
|
||||
let showName = "_internal.Unknown";
|
||||
if ( (program.type == 'episode') && ( typeof(program.showTitle) !== 'undefined' ) ) {
|
||||
if ( program.isOffline && (program.type == 'redirect') ) {
|
||||
showName = `Redirect to channel ${program.channel}`;
|
||||
} else if ( (program.type == 'episode') && ( typeof(program.showTitle) !== 'undefined' ) ) {
|
||||
showName = program.showTitle;
|
||||
} else {
|
||||
showName = "_internal.Movies";
|
||||
@ -657,11 +695,11 @@ module.exports = function ($timeout, $location) {
|
||||
let shows = {};
|
||||
let progs = [];
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (array[i].isOffline) {
|
||||
if (array[i].isOffline && array[i].type !== 'redirect') {
|
||||
continue;
|
||||
}
|
||||
vid = array[i];
|
||||
let code = getShowCode(array[i]);
|
||||
let vid = array[i];
|
||||
let code = getShowCode(vid);
|
||||
if ( typeof(shows[code]) === 'undefined') {
|
||||
shows[code] = {
|
||||
total: 0,
|
||||
@ -708,7 +746,7 @@ module.exports = function ($timeout, $location) {
|
||||
let counts = {};
|
||||
// some precalculation, useful to stop the shuffle from being quadratic...
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
var vid = array[i];
|
||||
let vid = array[i];
|
||||
if (vid.type === 'episode' && vid.season != 0) {
|
||||
let countKey = {
|
||||
title: vid.showTitle,
|
||||
@ -752,10 +790,10 @@ module.exports = function ($timeout, $location) {
|
||||
});
|
||||
shuffle(array);
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (array[i].type !== 'movie' && array[i].season != 0) {
|
||||
if (array[i].type === 'episode' && array[i].season != 0) {
|
||||
let title = array[i].showTitle;
|
||||
var sequence = shows[title];
|
||||
var j = next[title];
|
||||
let j = next[title];
|
||||
array[i] = sequence[j].it;
|
||||
|
||||
next[title] = (j + 1) % sequence.length;
|
||||
@ -827,12 +865,37 @@ module.exports = function ($timeout, $location) {
|
||||
}, 0
|
||||
);
|
||||
}
|
||||
scope.finishRedirect = (program) => {
|
||||
if (scope.selectedProgram == -1) {
|
||||
scope.channel.programs.splice(scope.minProgramIndex, 0, program);
|
||||
} else {
|
||||
scope.channel.programs[ scope.selectedProgram ] = program;
|
||||
}
|
||||
updateChannelDuration();
|
||||
}
|
||||
scope.addRedirect = () => {
|
||||
scope.selectedProgram = -1;
|
||||
scope._displayRedirect = true;
|
||||
scope._redirectTitle = "Add Redirect";
|
||||
scope._selectedRedirect = {
|
||||
isOffline : true,
|
||||
type : "redirect",
|
||||
duration : 60*60*1000,
|
||||
}
|
||||
|
||||
};
|
||||
scope.selectProgram = (index) => {
|
||||
scope.selectedProgram = index;
|
||||
let program = scope.channel.programs[index];
|
||||
|
||||
if(program.isOffline) {
|
||||
scope._selectedOffline = scope.makeOfflineFromChannel( Math.round( (program.duration + 500) / 1000 ) );
|
||||
if (program.type === 'redirect') {
|
||||
scope._displayRedirect = true;
|
||||
scope._redirectTitle = "Edit Redirect";
|
||||
scope._selectedRedirect = JSON.parse(angular.toJson(program));
|
||||
} else {
|
||||
scope._selectedOffline = scope.makeOfflineFromChannel( Math.round( (program.duration + 500) / 1000 ) );
|
||||
}
|
||||
} else {
|
||||
scope._selectedProgram = JSON.parse(angular.toJson(program));
|
||||
}
|
||||
@ -841,6 +904,32 @@ module.exports = function ($timeout, $location) {
|
||||
scope.channel.programs.splice(x, 1)
|
||||
updateChannelDuration()
|
||||
}
|
||||
scope.knownChannels = [
|
||||
{ id: -1, description: "# Channel #"},
|
||||
]
|
||||
scope.loadChannels = async () => {
|
||||
let channelNumbers = await dizquetv.getChannelNumbers();
|
||||
try {
|
||||
await Promise.all( channelNumbers.map( async(x) => {
|
||||
let desc = await dizquetv.getChannelDescription(x);
|
||||
if (desc.number != scope.channel.number) {
|
||||
scope.knownChannels.push( {
|
||||
id: desc.number,
|
||||
description: `${desc.number} - ${desc.name}`,
|
||||
});
|
||||
}
|
||||
}) );
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
scope.knownChannels.sort( (a,b) => a.id - b.id);
|
||||
scope.channelsDownloaded = true;
|
||||
$timeout( () => scope.$apply(), 0);
|
||||
|
||||
|
||||
};
|
||||
scope.loadChannels();
|
||||
|
||||
scope.paddingOptions = [
|
||||
{ id: -1, description: "Allowed start times", allow5: false },
|
||||
{ id: 30, description: ":00, :30", allow5: false },
|
||||
@ -894,6 +983,9 @@ module.exports = function ($timeout, $location) {
|
||||
scope.nightEndHours = [ { id: -1, description: "End" } ];
|
||||
scope.nightStart = -1;
|
||||
scope.nightEnd = -1;
|
||||
scope.atNightChannelNumber = -1;
|
||||
scope.atNightStart = -1;
|
||||
scope.atNightEnd = -1;
|
||||
for (let i=0; i < 24; i++) {
|
||||
let v = { id: i, description: ( (i<10) ? "0" : "") + i + ":00" };
|
||||
scope.nightStartHours.push(v);
|
||||
|
||||
85
web/directives/channel-redirect.js
Normal file
85
web/directives/channel-redirect.js
Normal file
@ -0,0 +1,85 @@
|
||||
module.exports = function ($timeout, dizquetv) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'templates/channel-redirect.html',
|
||||
replace: true,
|
||||
scope: {
|
||||
formTitle: "=formTitle",
|
||||
visible: "=visible",
|
||||
program: "=program",
|
||||
_onDone: "=onDone"
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
scope.error = "";
|
||||
scope.options = [];
|
||||
scope.loading = true;
|
||||
|
||||
scope.$watch('program', () => {
|
||||
if (typeof(scope.program) === 'undefined') {
|
||||
return;
|
||||
}
|
||||
if ( isNaN(scope.program.duration) ) {
|
||||
scope.program.duration = 15000;
|
||||
}
|
||||
scope.durationSeconds = Math.ceil( scope.program.duration / 1000.0 );;
|
||||
})
|
||||
|
||||
scope.refreshChannels = async() => {
|
||||
let channelNumbers = await dizquetv.getChannelNumbers();
|
||||
try {
|
||||
await Promise.all( channelNumbers.map( async(x) => {
|
||||
let desc = await dizquetv.getChannelDescription(x);
|
||||
let option = {
|
||||
id: x,
|
||||
description: `${x} - ${desc.name}`,
|
||||
};
|
||||
let i = 0;
|
||||
while (i < scope.options.length) {
|
||||
if (scope.options[i].id == x) {
|
||||
scope.options[i] = option;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i == scope.options.length) {
|
||||
scope.options.push(option);
|
||||
}
|
||||
scope.$apply();
|
||||
}) );
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
scope.options.sort( (a,b) => a.id - b.id );
|
||||
scope.loading = false;
|
||||
$timeout( () => scope.$apply(), 0);
|
||||
};
|
||||
scope.refreshChannels();
|
||||
|
||||
scope.onCancel = () => {
|
||||
scope.visible = false;
|
||||
}
|
||||
|
||||
scope.onDone = () => {
|
||||
scope.error = "";
|
||||
if (typeof(scope.program.channel) === 'undefined') {
|
||||
scope.error = "Please select a channel.";
|
||||
}
|
||||
if ( isNaN(scope.program.channel) ) {
|
||||
scope.error = "Channel must be a number.";
|
||||
}
|
||||
if ( isNaN(scope.durationSeconds) ) {
|
||||
scope.error = "Duration must be a number.";
|
||||
}
|
||||
if ( scope.error != "" ) {
|
||||
$timeout( () => scope.error = "", 60000);
|
||||
return;
|
||||
}
|
||||
scope.program.duration = scope.durationSeconds * 1000;
|
||||
scope._onDone( scope.program );
|
||||
scope.visible = false;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -150,6 +150,13 @@
|
||||
<h6>Add Breaks</h6>
|
||||
<p>Adds Flex breaks between programs, attempting to avoid groups of consecutive programs that exceed the specified number of minutes.</p>
|
||||
|
||||
<h6>Add Redirect</h6>
|
||||
<p>Adds a channel redirect. During this period of time, the channel will redirect to another channel.</p>
|
||||
|
||||
<h6>"Channel at Night"<h6>
|
||||
<p>Will redirect to another channel while between the selected hours.</p>
|
||||
|
||||
|
||||
<h6>Remove Duplicates</h6>
|
||||
<p>Removes repeated videos.</p>
|
||||
|
||||
@ -207,9 +214,10 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="input-group col-md-6" style="padding: 5px;">
|
||||
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="addOffline()">Add Flex</button>
|
||||
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="addOffline()">Add Flex...</button>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6" style="padding: 5px;">
|
||||
@ -252,6 +260,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="input-group col-md-6" style="padding: 5px;">
|
||||
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="addRedirect()">Add Redirect...</button>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6" style="padding: 5px;">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class='loader' ng-hide='channelsDownloaded'></div>
|
||||
<select ng-show='channelsDownloaded' style='width:5em;' ng-model="atNightChannelNumber"
|
||||
ng-options="o.id as o.description for o in knownChannels" ></select>
|
||||
<select ng-model="atNightStart"
|
||||
ng-options="o.id as o.description for o in nightStartHours" ></select>
|
||||
<select ng-model="atNightEnd"
|
||||
ng-options="o.id as o.description for o in nightEndHours" ></select>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-warning form-control form-control-sm" type="button" ng-click="nightChannel(atNightEnd, atNightStart, atNightChannelNumber)" ng-disabled="atNightChannelNumber==-1 || atNightStart==-1 || atNightEnd==-1">"Channel at Night"</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="input-group col" style="padding: 5px;">
|
||||
<button class="btn btn-sm btn-danger form-control form-control-sm" type="button" ng-click="removeDuplicates()">Remove Duplicates</button>
|
||||
@ -307,7 +339,8 @@
|
||||
{{ x.type === 'episode' ? x.showTitle + ' - S' + x.season.toString().padStart(2, '0') + 'E' + x.episode.toString().padStart(2, '0') : x.title}}
|
||||
</div>
|
||||
<div style="margin-right: 5px; font-weight:ligther" ng-show="x.isOffline">
|
||||
<i>Flex</i>
|
||||
<i ng-if="x.type !== 'redirect' " >Flex</i>
|
||||
<span ng-if="x.type === 'redirect' " ><i>Redirect to channel:</i> <b>{{x.channel}}</b></span>
|
||||
</div>
|
||||
<div class="flex-pull-right"></div>
|
||||
<button class="btn btn-sm btn-link" ng-click="removeItem($index); $event.stopPropagation()">
|
||||
@ -356,4 +389,5 @@
|
||||
<remove-shows program-titles="_removablePrograms" on-done="removeShows" deleted="_deletedProgramNames"></remove-shows>
|
||||
<offline-config offline-title="Add Flex Time" program="_addingOffline" on-done="finishedAddingOffline"></offline-config>
|
||||
<plex-library height="300" visible="displayPlexLibrary" on-finish="importPrograms"></plex-library>
|
||||
<channel-redirect visible="_displayRedirect" on-done="finishRedirect" form-title="_redirectTitle" program="_selectedRedirect" ></channel-redirect>
|
||||
</div>
|
||||
|
||||
35
web/public/templates/channel-redirect.html
Normal file
35
web/public/templates/channel-redirect.html
Normal file
@ -0,0 +1,35 @@
|
||||
<div ng-show="visible">
|
||||
<div class="modal" tabindex="-1" role="dialog" style="display: block; background-color: rgba(0, 0, 0, .5);">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ formTitle }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body container">
|
||||
<div class="form-group">
|
||||
<label for="duration">Duration (seconds):</label>
|
||||
<input id="duration" class="form-control" ng-model="durationSeconds" type="text" placeholder="{{state.server.name}}"></input>
|
||||
</div>
|
||||
|
||||
<div ng-if="state.channelReport == null" class="form-group">
|
||||
<label for="channel">Redirect to channel:</label>
|
||||
<select id="channel" class="form-control" ng-model="program.channel"
|
||||
ng-options="o.id as o.description for o in options" ></select>
|
||||
<div class="loader" ng-if="loading"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<span class="text-danger">{{ error }}</span>
|
||||
<button type="button" class="btn btn-sm btn-link" ng-click="onCancel()" >Cancel</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="onDone()" >Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
<div ng-if="state.channelReport == null" class="form-group">
|
||||
<label for="serverName">Name:</label>
|
||||
<input class="form-control" type="text" placeholder="{{state.server.name}}" readonly>
|
||||
<input class="form-control" type="text" placeholder="{{state.server.name}}" readonly></input>
|
||||
</div>
|
||||
|
||||
<div ng-if="state.channelReport == null" class="form-group">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user