mirror of
https://github.com/chrisbenincasa/tunarr.git
synced 2026-04-18 09:03:35 -04:00
Fixing up frontend with breaking changes made to API/DB schemas. Frontend should not depend directly on backend files...sign
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,4 +9,5 @@ package-lock.json
|
||||
|
||||
config/
|
||||
.vscode/
|
||||
build/
|
||||
build/
|
||||
log.txt
|
||||
@@ -5,8 +5,8 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node index.js",
|
||||
"build": "browserify ./web/app.js -p tsify -p esmify -o ./web/public/bundle.js",
|
||||
"dev-client": "watchify ./web/app.js -p tsify -p esmify -o ./web/public/bundle.js",
|
||||
"build": "browserify ./web/app.js -o ./web/public/bundle.js",
|
||||
"dev-client": "watchify ./web/app.js -o ./web/public/bundle.js",
|
||||
"dev-server": "nodemon index.js --ignore ./web/ --ignore ./db/ --ignore ./xmltv.xml",
|
||||
"compile": "babel index.js -d dist && babel src -d dist/src",
|
||||
"package": "sh ./make_dist.sh",
|
||||
|
||||
16
src/api.ts
16
src/api.ts
@@ -2,7 +2,7 @@ import JSONStream from 'JSONStream';
|
||||
import express from 'express';
|
||||
import fileUpload from 'express-fileupload';
|
||||
import fs from 'fs';
|
||||
import { isUndefined } from 'lodash-es';
|
||||
import { find, isUndefined } from 'lodash-es';
|
||||
import path from 'path';
|
||||
import constants from './constants.js';
|
||||
import { Channel, getDB } from './dao/db.js';
|
||||
@@ -1010,6 +1010,20 @@ export function makeApi(
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/api/plex', async (req, res) => {
|
||||
const db = await getDB();
|
||||
const servers = db.plexServers();
|
||||
const server = find(servers, { name: req.query['name'] as string });
|
||||
if (isUndefined(server)) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: 'No server found with name: ' + req.query.name });
|
||||
}
|
||||
|
||||
const plex = new Plex(server);
|
||||
return res.json(await plex.Get(req.query['path']));
|
||||
});
|
||||
|
||||
function updateXmltv() {
|
||||
xmltvInterval.updateXML();
|
||||
xmltvInterval.restartInterval();
|
||||
|
||||
@@ -66,7 +66,7 @@ export type Channel = {
|
||||
number: number;
|
||||
watermark?: Watermark;
|
||||
fillerCollections?: FillerCollection[];
|
||||
programs?: Program[];
|
||||
programs: Program[];
|
||||
icon: ChannelIcon;
|
||||
guideMinimumDurationSeconds: number;
|
||||
groupTitle: string;
|
||||
|
||||
@@ -138,9 +138,7 @@ function convertProgram(program: JSONObject): Program {
|
||||
plexFile: program['plexFile'] as string,
|
||||
ratingKey: program['ratingKey'] as string,
|
||||
serverKey: program['serverKey'] as string,
|
||||
showTitle: !isMovie
|
||||
? (program['showTitle'] as string | undefined)
|
||||
: undefined,
|
||||
showTitle: program['showTitle'] as string | undefined,
|
||||
summary: program['summary'] as string,
|
||||
title: program['title'] as string,
|
||||
type: program['type'] as string,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import request from 'request';
|
||||
import { isUndefined } from 'lodash-es';
|
||||
import { PlexServerSettings } from './dao/db.js';
|
||||
export class Plex {
|
||||
private _accessToken: string;
|
||||
private _server: any;
|
||||
private _headers: object;
|
||||
|
||||
constructor(opts) {
|
||||
constructor(opts: Partial<PlexServerSettings>) {
|
||||
this._accessToken =
|
||||
typeof opts.accessToken !== 'undefined' ? opts.accessToken : '';
|
||||
let uri = 'http://127.0.0.1:32400';
|
||||
@@ -17,9 +18,6 @@ export class Plex {
|
||||
}
|
||||
this._server = {
|
||||
uri: uri,
|
||||
host: typeof opts.host !== 'undefined' ? opts.host : '127.0.0.1',
|
||||
port: typeof opts.port !== 'undefined' ? opts.port : '32400',
|
||||
protocol: typeof opts.protocol !== 'undefined' ? opts.protocol : 'http',
|
||||
};
|
||||
this._headers = {
|
||||
Accept: 'application/json',
|
||||
@@ -83,6 +81,7 @@ export class Plex {
|
||||
}
|
||||
|
||||
async Get(path, optionalHeaders = {}) {
|
||||
console.log(path);
|
||||
let req = {
|
||||
method: 'get',
|
||||
url: `${this.URL}${path}`,
|
||||
|
||||
@@ -12,7 +12,6 @@ import { EventService } from './services/event-service.js';
|
||||
import { FileCacheService } from './services/file-cache-service.js';
|
||||
import { M3uService } from './services/m3u-service.js';
|
||||
import { TVGuideService } from './services/tv-guide-service.js';
|
||||
import * as xmltv from './xmltv.js';
|
||||
|
||||
// Temp
|
||||
import { dirname } from 'node:path';
|
||||
@@ -20,6 +19,7 @@ import { fileURLToPath } from 'node:url';
|
||||
import { getDB } from './dao/db.js';
|
||||
import { serverOptions } from './globals.js';
|
||||
import { ChannelCache } from './channel-cache.js';
|
||||
import { XmlTvWriter } from './xmltv.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -93,12 +93,12 @@ export const serverContext = once(async () => {
|
||||
const cacheImageService = new CacheImageService(db, fileCache);
|
||||
const m3uService = new M3uService(channelDB, fileCache, channelCache);
|
||||
const eventService = new EventService();
|
||||
const xmltv = new XmlTvWriter();
|
||||
|
||||
await initDB(db, channelDB);
|
||||
|
||||
const guideService = new TVGuideService(
|
||||
xmltv,
|
||||
db,
|
||||
cacheImageService,
|
||||
eventService,
|
||||
);
|
||||
@@ -115,5 +115,6 @@ export const serverContext = once(async () => {
|
||||
hdhrService: hdhr(db, channelDB),
|
||||
customShowDB: new CustomShowDB(path.join(opts.database, 'custom-shows')),
|
||||
channelCache,
|
||||
xmltv,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@ import constants from './constants.js';
|
||||
import createLogger from './logger.js';
|
||||
import { serverContext } from './server-context.js';
|
||||
import { video } from './video.js';
|
||||
import * as xmltv from './xmltv.js';
|
||||
import { xmltvInterval } from './xmltv-generator.js';
|
||||
import { onShutdown } from 'node-graceful-shutdown';
|
||||
|
||||
@@ -192,5 +191,6 @@ onShutdown('log', [], async () => {
|
||||
await _wait(2000);
|
||||
});
|
||||
onShutdown('xmltv-writer', [], async () => {
|
||||
await xmltv.shutdown();
|
||||
const ctx = await serverContext();
|
||||
await ctx.xmltv.shutdown();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//Adds a slight pause so that long operations
|
||||
export default function () {
|
||||
export default function (): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setImmediate(() => resolve(void 0));
|
||||
});
|
||||
|
||||
@@ -4,6 +4,8 @@ import createLogger from '../logger.js';
|
||||
import throttle from './throttle.js';
|
||||
import { CacheImageService } from './cache-image-service.js';
|
||||
import { EventService } from './event-service.js';
|
||||
import { Channel, getDB } from '../dao/db.js';
|
||||
import { XmlTvWriter } from '../xmltv.js';
|
||||
|
||||
const logger = createLogger(import.meta);
|
||||
|
||||
@@ -11,32 +13,30 @@ const FALLBACK_ICON =
|
||||
'https://raw.githubusercontent.com/vexorain/dizquetv/main/resources/dizquetv.png';
|
||||
|
||||
export class TVGuideService {
|
||||
cached: any;
|
||||
cached: Record<number, any>;
|
||||
lastUpdate: number;
|
||||
lastBackoff: number;
|
||||
updateTime: number;
|
||||
currentUpdate: number;
|
||||
currentLimit: number;
|
||||
currentChannels: any;
|
||||
xmltv: any;
|
||||
db: any;
|
||||
xmltv: XmlTvWriter;
|
||||
cacheImageService: any;
|
||||
eventService: any;
|
||||
_throttle: any;
|
||||
eventService: EventService;
|
||||
_throttle: () => Promise<void>;
|
||||
updateLimit: any;
|
||||
updateChannels: any[];
|
||||
accumulateTable: any;
|
||||
channelsByNumber: any;
|
||||
channelsByNumber: Record<number, Channel>;
|
||||
/****
|
||||
*
|
||||
**/
|
||||
constructor(
|
||||
xmltv,
|
||||
db,
|
||||
xmltv: XmlTvWriter,
|
||||
cacheImageService: CacheImageService,
|
||||
eventService: EventService,
|
||||
) {
|
||||
this.cached = null;
|
||||
this.cached = {};
|
||||
this.lastUpdate = 0;
|
||||
this.lastBackoff = 100;
|
||||
this.updateTime = 0;
|
||||
@@ -44,7 +44,6 @@ export class TVGuideService {
|
||||
this.currentLimit = -1;
|
||||
this.currentChannels = null;
|
||||
this.xmltv = xmltv;
|
||||
this.db = db;
|
||||
this.cacheImageService = cacheImageService;
|
||||
this.eventService = eventService;
|
||||
this._throttle = throttle;
|
||||
@@ -100,7 +99,7 @@ export class TVGuideService {
|
||||
return await this.get();
|
||||
}
|
||||
|
||||
async makeAccumulated(channel) {
|
||||
async makeAccumulated(channel: Channel) {
|
||||
if (isUndefined(channel.programs)) {
|
||||
throw Error(JSON.stringify(channel).slice(0, 200));
|
||||
}
|
||||
@@ -114,8 +113,8 @@ export class TVGuideService {
|
||||
return arr;
|
||||
}
|
||||
|
||||
async getCurrentPlayingIndex(channel, t): Promise<any> {
|
||||
let s = new Date(channel.startTime).getTime();
|
||||
async getCurrentPlayingIndex(channel: Channel, t): Promise<any> {
|
||||
let s = new Date(channel.startTimeEpoch).getTime();
|
||||
if (t < s) {
|
||||
//it's flex time
|
||||
return {
|
||||
@@ -157,14 +156,14 @@ export class TVGuideService {
|
||||
}
|
||||
|
||||
async getChannelPlaying(
|
||||
channel,
|
||||
channel: Channel,
|
||||
previousKnown,
|
||||
t,
|
||||
t: number,
|
||||
depth: any[] = [],
|
||||
): Promise<any> {
|
||||
let playing: Record<string, any> = {};
|
||||
if (
|
||||
typeof previousKnown !== 'undefined' &&
|
||||
!isUndefined(previousKnown) &&
|
||||
previousKnown.index !== -1 &&
|
||||
previousKnown.program.duration ==
|
||||
channel.programs[previousKnown.index].duration &&
|
||||
@@ -233,7 +232,7 @@ export class TVGuideService {
|
||||
return playing;
|
||||
}
|
||||
|
||||
async getChannelPrograms(t0, t1, channel) {
|
||||
async getChannelPrograms(t0: number, t1: number, channel: Channel) {
|
||||
if (isUndefined(channel)) {
|
||||
throw Error("Couldn't find channel?");
|
||||
}
|
||||
@@ -414,7 +413,7 @@ export class TVGuideService {
|
||||
}
|
||||
|
||||
async refreshXML() {
|
||||
let xmltvSettings = this.db['xmltv-settings'].find()[0];
|
||||
let xmltvSettings = (await getDB()).xmlTvSettings();
|
||||
await this.xmltv.WriteXMLTV(
|
||||
this.cached,
|
||||
xmltvSettings,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Plex } from './plex.js';
|
||||
import { serverContext } from './server-context.js';
|
||||
import createLogger from './logger.js';
|
||||
import { getDB } from './dao/db.js';
|
||||
|
||||
const logger = createLogger(import.meta);
|
||||
|
||||
@@ -20,10 +21,10 @@ const updateXML = async () => {
|
||||
|
||||
try {
|
||||
channels = await getChannelsCached();
|
||||
let xmltvSettings = ctx.db['xmltv-settings'].find()[0];
|
||||
let xmltvSettings = (await getDB()).xmlTvSettings();
|
||||
let t = ctx.guideService.prepareRefresh(
|
||||
channels,
|
||||
xmltvSettings.cache * 60 * 60 * 1000,
|
||||
xmltvSettings.refreshHours * 60 * 60 * 1000,
|
||||
);
|
||||
channels = [];
|
||||
|
||||
|
||||
357
src/xmltv.ts
357
src/xmltv.ts
@@ -1,207 +1,214 @@
|
||||
import XMLWriter from 'xml-writer';
|
||||
import fs from 'fs';
|
||||
import { Channel } from './dao/db.js';
|
||||
import { Channel, XmlTvSettings } from './dao/db.js';
|
||||
import { CacheImageService } from './services/cache-image-service.js';
|
||||
|
||||
let isShutdown = false;
|
||||
let isWorking = false;
|
||||
|
||||
export async function WriteXMLTV(
|
||||
json,
|
||||
xmlSettings,
|
||||
throttle,
|
||||
cacheImageService,
|
||||
) {
|
||||
if (isShutdown) {
|
||||
return;
|
||||
}
|
||||
if (isWorking) {
|
||||
console.log('Concurrent xmltv write attempt detected, skipping');
|
||||
return;
|
||||
}
|
||||
isWorking = true;
|
||||
try {
|
||||
await writePromise(json, xmlSettings, throttle, cacheImageService);
|
||||
} catch (err) {
|
||||
console.error('Error writing xmltv', err);
|
||||
}
|
||||
isWorking = false;
|
||||
}
|
||||
|
||||
function writePromise(json, xmlSettings, throttle, cacheImageService) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let ws = fs.createWriteStream(xmlSettings.file);
|
||||
let xw = new XMLWriter(true, (str, enc) => ws.write(str, enc));
|
||||
ws.on('close', () => {
|
||||
resolve(void 0);
|
||||
});
|
||||
ws.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
_writeDocStart(xw);
|
||||
async function middle() {
|
||||
let channelNumbers: any[] = [];
|
||||
Object.keys(json).forEach((key) => channelNumbers.push(key));
|
||||
let channels = channelNumbers.map((number) => json[number].channel);
|
||||
_writeChannels(xw, channels);
|
||||
for (let i = 0; i < channelNumbers.length; i++) {
|
||||
let number = channelNumbers[i];
|
||||
await _writePrograms(
|
||||
xw,
|
||||
json[number].channel,
|
||||
json[number].programs,
|
||||
throttle,
|
||||
xmlSettings,
|
||||
cacheImageService,
|
||||
);
|
||||
}
|
||||
export class XmlTvWriter {
|
||||
async WriteXMLTV(
|
||||
json,
|
||||
xmlSettings: XmlTvSettings,
|
||||
throttle: () => Promise<void>,
|
||||
cacheImageService,
|
||||
) {
|
||||
if (isShutdown) {
|
||||
return;
|
||||
}
|
||||
middle()
|
||||
.then(() => {
|
||||
_writeDocEnd(xw, ws);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error', err);
|
||||
})
|
||||
.then(() => ws.end());
|
||||
});
|
||||
}
|
||||
if (isWorking) {
|
||||
console.log('Concurrent xmltv write attempt detected, skipping');
|
||||
return;
|
||||
}
|
||||
isWorking = true;
|
||||
try {
|
||||
await this.writePromise(json, xmlSettings, throttle, cacheImageService);
|
||||
} catch (err) {
|
||||
console.error('Error writing xmltv', err);
|
||||
}
|
||||
isWorking = false;
|
||||
}
|
||||
|
||||
function _writeDocStart(xw) {
|
||||
xw.startDocument();
|
||||
xw.startElement('tv');
|
||||
xw.writeAttribute('generator-info-name', 'psuedotv-plex');
|
||||
}
|
||||
function _writeDocEnd(xw, _) {
|
||||
xw.endElement();
|
||||
xw.endDocument();
|
||||
}
|
||||
private writePromise(
|
||||
json,
|
||||
xmlSettings: XmlTvSettings,
|
||||
throttle: () => Promise<void>,
|
||||
cacheImageService: CacheImageService,
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let ws = fs.createWriteStream(xmlSettings.outputPath);
|
||||
let xw = new XMLWriter(true, (str, enc) => ws.write(str, enc));
|
||||
ws.on('close', () => {
|
||||
resolve(void 0);
|
||||
});
|
||||
ws.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
this._writeDocStart(xw);
|
||||
const middle = async () => {
|
||||
let channelNumbers: any[] = [];
|
||||
Object.keys(json).forEach((key) => channelNumbers.push(key));
|
||||
let channels = channelNumbers.map((number) => json[number].channel);
|
||||
this._writeChannels(xw, channels);
|
||||
for (let i = 0; i < channelNumbers.length; i++) {
|
||||
let number = channelNumbers[i];
|
||||
await this._writePrograms(
|
||||
xw,
|
||||
json[number].channel,
|
||||
json[number].programs,
|
||||
throttle,
|
||||
xmlSettings,
|
||||
cacheImageService,
|
||||
);
|
||||
}
|
||||
};
|
||||
middle()
|
||||
.then(() => {
|
||||
this._writeDocEnd(xw, ws);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error', err);
|
||||
})
|
||||
.then(() => ws.end());
|
||||
});
|
||||
}
|
||||
|
||||
function _writeChannels(xw, channels: Channel[]) {
|
||||
for (let i = 0; i < channels.length; i++) {
|
||||
xw.startElement('channel');
|
||||
xw.writeAttribute('id', channels[i].number);
|
||||
xw.startElement('display-name');
|
||||
xw.writeAttribute('lang', 'en');
|
||||
xw.text(channels[i].name);
|
||||
private _writeDocStart(xw) {
|
||||
xw.startDocument();
|
||||
xw.startElement('tv');
|
||||
xw.writeAttribute('generator-info-name', 'psuedotv-plex');
|
||||
}
|
||||
|
||||
private _writeDocEnd(xw, _) {
|
||||
xw.endElement();
|
||||
if (channels[i].icon) {
|
||||
xw.startElement('icon');
|
||||
xw.writeAttribute('src', channels[i].icon.path);
|
||||
xw.endDocument();
|
||||
}
|
||||
|
||||
private _writeChannels(xw, channels: Channel[]) {
|
||||
for (let i = 0; i < channels.length; i++) {
|
||||
xw.startElement('channel');
|
||||
xw.writeAttribute('id', channels[i].number);
|
||||
xw.startElement('display-name');
|
||||
xw.writeAttribute('lang', 'en');
|
||||
xw.text(channels[i].name);
|
||||
xw.endElement();
|
||||
if (channels[i].icon) {
|
||||
xw.startElement('icon');
|
||||
xw.writeAttribute('src', channels[i].icon.path);
|
||||
xw.endElement();
|
||||
}
|
||||
xw.endElement();
|
||||
}
|
||||
xw.endElement();
|
||||
}
|
||||
}
|
||||
|
||||
async function _writePrograms(
|
||||
xw,
|
||||
channel,
|
||||
programs,
|
||||
throttle,
|
||||
xmlSettings,
|
||||
cacheImageService,
|
||||
) {
|
||||
for (let i = 0; i < programs.length; i++) {
|
||||
if (!isShutdown) {
|
||||
await throttle();
|
||||
async _writePrograms(
|
||||
xw,
|
||||
channel,
|
||||
programs,
|
||||
throttle,
|
||||
xmlSettings,
|
||||
cacheImageService,
|
||||
) {
|
||||
for (let i = 0; i < programs.length; i++) {
|
||||
if (!isShutdown) {
|
||||
await throttle();
|
||||
}
|
||||
await this._writeProgramme(
|
||||
channel,
|
||||
programs[i],
|
||||
xw,
|
||||
xmlSettings,
|
||||
cacheImageService,
|
||||
);
|
||||
}
|
||||
await _writeProgramme(
|
||||
channel,
|
||||
programs[i],
|
||||
xw,
|
||||
xmlSettings,
|
||||
cacheImageService,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function _writeProgramme(
|
||||
channel,
|
||||
program,
|
||||
xw,
|
||||
xmlSettings,
|
||||
cacheImageService,
|
||||
) {
|
||||
// Programme
|
||||
xw.startElement('programme');
|
||||
xw.writeAttribute('start', _createXMLTVDate(program.start));
|
||||
xw.writeAttribute('stop', _createXMLTVDate(program.stop));
|
||||
xw.writeAttribute('channel', channel.number);
|
||||
// Title
|
||||
xw.startElement('title');
|
||||
xw.writeAttribute('lang', 'en');
|
||||
xw.text(program.title);
|
||||
xw.endElement();
|
||||
xw.writeRaw('\n <previously-shown/>');
|
||||
|
||||
//sub-title
|
||||
if (typeof program.sub !== 'undefined') {
|
||||
xw.startElement('sub-title');
|
||||
async _writeProgramme(channel, program, xw, xmlSettings, cacheImageService) {
|
||||
// Programme
|
||||
xw.startElement('programme');
|
||||
xw.writeAttribute('start', this._createXMLTVDate(program.start));
|
||||
xw.writeAttribute('stop', this._createXMLTVDate(program.stop));
|
||||
xw.writeAttribute('channel', channel.number);
|
||||
// Title
|
||||
xw.startElement('title');
|
||||
xw.writeAttribute('lang', 'en');
|
||||
xw.text(program.sub.title);
|
||||
xw.text(program.title);
|
||||
xw.endElement();
|
||||
xw.writeRaw('\n <previously-shown/>');
|
||||
|
||||
xw.startElement('episode-num');
|
||||
xw.writeAttribute('system', 'onscreen');
|
||||
xw.text('S' + program.sub.season + ' E' + program.sub.episode);
|
||||
xw.endElement();
|
||||
//sub-title
|
||||
if (typeof program.sub !== 'undefined') {
|
||||
xw.startElement('sub-title');
|
||||
xw.writeAttribute('lang', 'en');
|
||||
xw.text(program.sub.title);
|
||||
xw.endElement();
|
||||
|
||||
xw.startElement('episode-num');
|
||||
xw.writeAttribute('system', 'xmltv_ns');
|
||||
xw.text(program.sub.season - 1 + '.' + (program.sub.episode - 1) + '.0/1');
|
||||
xw.endElement();
|
||||
}
|
||||
// Icon
|
||||
if (typeof program.icon !== 'undefined') {
|
||||
xw.startElement('icon');
|
||||
let icon = program.icon;
|
||||
if (xmlSettings.enableImageCache === true) {
|
||||
const imgUrl = cacheImageService.registerImageOnDatabase(icon);
|
||||
icon = `{{host}}/cache/images/${imgUrl}`;
|
||||
xw.startElement('episode-num');
|
||||
xw.writeAttribute('system', 'onscreen');
|
||||
xw.text('S' + program.sub.season + ' E' + program.sub.episode);
|
||||
xw.endElement();
|
||||
|
||||
xw.startElement('episode-num');
|
||||
xw.writeAttribute('system', 'xmltv_ns');
|
||||
xw.text(
|
||||
program.sub.season - 1 + '.' + (program.sub.episode - 1) + '.0/1',
|
||||
);
|
||||
xw.endElement();
|
||||
}
|
||||
xw.writeAttribute('src', icon);
|
||||
// Icon
|
||||
if (typeof program.icon !== 'undefined') {
|
||||
xw.startElement('icon');
|
||||
let icon = program.icon;
|
||||
if (xmlSettings.enableImageCache === true) {
|
||||
const imgUrl = cacheImageService.registerImageOnDatabase(icon);
|
||||
icon = `{{host}}/cache/images/${imgUrl}`;
|
||||
}
|
||||
xw.writeAttribute('src', icon);
|
||||
xw.endElement();
|
||||
}
|
||||
// Desc
|
||||
xw.startElement('desc');
|
||||
xw.writeAttribute('lang', 'en');
|
||||
if (typeof program.summary !== 'undefined' && program.summary.length > 0) {
|
||||
xw.text(program.summary);
|
||||
} else {
|
||||
xw.text(channel.name);
|
||||
}
|
||||
xw.endElement();
|
||||
// Rating
|
||||
if (program.rating != null && typeof program.rating !== 'undefined') {
|
||||
xw.startElement('rating');
|
||||
xw.writeAttribute('system', 'MPAA');
|
||||
xw.writeElement('value', program.rating);
|
||||
xw.endElement();
|
||||
}
|
||||
// End of Programme
|
||||
xw.endElement();
|
||||
}
|
||||
// Desc
|
||||
xw.startElement('desc');
|
||||
xw.writeAttribute('lang', 'en');
|
||||
if (typeof program.summary !== 'undefined' && program.summary.length > 0) {
|
||||
xw.text(program.summary);
|
||||
} else {
|
||||
xw.text(channel.name);
|
||||
|
||||
private _createXMLTVDate(d) {
|
||||
return d.substring(0, 19).replace(/[-T:]/g, '') + ' +0000';
|
||||
}
|
||||
xw.endElement();
|
||||
// Rating
|
||||
if (program.rating != null && typeof program.rating !== 'undefined') {
|
||||
xw.startElement('rating');
|
||||
xw.writeAttribute('system', 'MPAA');
|
||||
xw.writeElement('value', program.rating);
|
||||
xw.endElement();
|
||||
|
||||
async shutdown() {
|
||||
isShutdown = true;
|
||||
console.log('Shutting down xmltv writer.');
|
||||
if (isWorking) {
|
||||
let s = 'Wait for xmltv writer...';
|
||||
while (isWorking) {
|
||||
console.log(s);
|
||||
await wait(100);
|
||||
s = 'Still waiting for xmltv writer...';
|
||||
}
|
||||
console.log('Write finished.');
|
||||
} else {
|
||||
console.log('xmltv writer had no pending jobs.');
|
||||
}
|
||||
}
|
||||
// End of Programme
|
||||
xw.endElement();
|
||||
}
|
||||
function _createXMLTVDate(d) {
|
||||
return d.substring(0, 19).replace(/[-T:]/g, '') + ' +0000';
|
||||
}
|
||||
|
||||
function wait(x) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, x);
|
||||
});
|
||||
}
|
||||
|
||||
export async function shutdown() {
|
||||
isShutdown = true;
|
||||
console.log('Shutting down xmltv writer.');
|
||||
if (isWorking) {
|
||||
let s = 'Wait for xmltv writer...';
|
||||
while (isWorking) {
|
||||
console.log(s);
|
||||
await wait(100);
|
||||
s = 'Still waiting for xmltv writer...';
|
||||
}
|
||||
console.log('Write finished.');
|
||||
} else {
|
||||
console.log('xmltv writer had no pending jobs.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,364 +1,370 @@
|
||||
const MINUTE = 60 * 1000;
|
||||
|
||||
module.exports = function ($scope, $timeout, dizquetv) {
|
||||
$scope.offset = 0;
|
||||
$scope.M = 60 * MINUTE;
|
||||
$scope.zoomLevel = 3;
|
||||
$scope.T = 190 * MINUTE;
|
||||
$scope.before = 15 * MINUTE;
|
||||
$scope.enableNext = false;
|
||||
$scope.enableBack = false;
|
||||
$scope.showNow = false;
|
||||
$scope.nowPosition = 0;
|
||||
$scope.refreshHandle = null;
|
||||
|
||||
$scope.offset = 0;
|
||||
$scope.M = 60 * MINUTE;
|
||||
$scope.zoomLevel = 3
|
||||
$scope.T = 190 * MINUTE;
|
||||
$scope.before = 15 * MINUTE;
|
||||
$scope.enableNext = false;
|
||||
$scope.enableBack = false;
|
||||
$scope.showNow = false;
|
||||
$scope.nowPosition = 0;
|
||||
$scope.refreshHandle = null;
|
||||
const intl = new Intl.DateTimeFormat('default', {
|
||||
hour12: true,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
});
|
||||
|
||||
const intl = new Intl.DateTimeFormat('default',
|
||||
{
|
||||
hour12: true,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
});
|
||||
|
||||
let hourMinute = (d) => {
|
||||
return intl.format(d);
|
||||
};
|
||||
|
||||
$scope.updateBasics = () => {
|
||||
$scope.channelNumberWidth = 5;
|
||||
$scope.channelIconWidth = 8;
|
||||
$scope.channelWidth = $scope.channelNumberWidth + $scope.channelIconWidth;
|
||||
//we want 1 minute = 1 colspan
|
||||
$scope.colspanPercent = (100 - $scope.channelWidth) / ($scope.T / MINUTE);
|
||||
$scope.channelColspan = Math.floor($scope.channelWidth / $scope.colspanPercent);
|
||||
$scope.channelNumberColspan = Math.floor($scope.channelNumberWidth / $scope.colspanPercent);
|
||||
$scope.channelIconColspan = $scope.channelColspan - $scope.channelNumberColspan;
|
||||
$scope.totalSpan = Math.floor($scope.T / MINUTE);
|
||||
$scope.colspanPercent = (100 - $scope.channelWidth) / ($scope.T / MINUTE);
|
||||
$scope.channelColspan = Math.floor($scope.channelWidth / $scope.colspanPercent);
|
||||
$scope.channelNumberColspan = Math.floor($scope.channelNumberWidth / $scope.colspanPercent);
|
||||
$scope.channelIconColspan = $scope.channelColspan - $scope.channelNumberColspan;
|
||||
|
||||
}
|
||||
$scope.updateBasics();
|
||||
let hourMinute = (d) => {
|
||||
return intl.format(d);
|
||||
};
|
||||
|
||||
$scope.updateBasics = () => {
|
||||
$scope.channelNumberWidth = 5;
|
||||
$scope.channelIconWidth = 8;
|
||||
$scope.channelWidth = $scope.channelNumberWidth + $scope.channelIconWidth;
|
||||
//we want 1 minute = 1 colspan
|
||||
|
||||
$scope.colspanPercent = (100 - $scope.channelWidth) / ($scope.T / MINUTE);
|
||||
$scope.channelColspan = Math.floor(
|
||||
$scope.channelWidth / $scope.colspanPercent,
|
||||
);
|
||||
$scope.channelNumberColspan = Math.floor(
|
||||
$scope.channelNumberWidth / $scope.colspanPercent,
|
||||
);
|
||||
$scope.channelIconColspan =
|
||||
$scope.channelColspan - $scope.channelNumberColspan;
|
||||
$scope.totalSpan = Math.floor($scope.T / MINUTE);
|
||||
$scope.colspanPercent = (100 - $scope.channelWidth) / ($scope.T / MINUTE);
|
||||
$scope.channelColspan = Math.floor(
|
||||
$scope.channelWidth / $scope.colspanPercent,
|
||||
);
|
||||
$scope.channelNumberColspan = Math.floor(
|
||||
$scope.channelNumberWidth / $scope.colspanPercent,
|
||||
);
|
||||
$scope.channelIconColspan =
|
||||
$scope.channelColspan - $scope.channelNumberColspan;
|
||||
};
|
||||
$scope.updateBasics();
|
||||
|
||||
$scope.channelNumberWidth = 5;
|
||||
$scope.channelIconWidth = 8;
|
||||
$scope.channelWidth = $scope.channelNumberWidth + $scope.channelIconWidth;
|
||||
//we want 1 minute = 1 colspan
|
||||
|
||||
$scope.applyLater = () => {
|
||||
$timeout( () => $scope.$apply(), 0 );
|
||||
$scope.applyLater = () => {
|
||||
$timeout(() => $scope.$apply(), 0);
|
||||
};
|
||||
|
||||
$scope.channelNumbers = [];
|
||||
$scope.channels = {};
|
||||
$scope.lastUpdate = -1;
|
||||
|
||||
$scope.updateJustNow = () => {
|
||||
$scope.t1 = new Date().getTime();
|
||||
if ($scope.t0 <= $scope.t1 && $scope.t1 < $scope.t0 + $scope.T) {
|
||||
let n = ($scope.t1 - $scope.t0) / MINUTE;
|
||||
$scope.nowPosition = ($scope.channelColspan + n) * $scope.colspanPercent;
|
||||
if ($scope.nowPosition >= 50 && $scope.offset >= 0) {
|
||||
$scope.offset = 0;
|
||||
$scope.adjustZoom();
|
||||
}
|
||||
$scope.showNow = true;
|
||||
} else {
|
||||
$scope.showNow = false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.nowTimer = () => {
|
||||
$scope.updateJustNow();
|
||||
$timeout(() => $scope.nowTimer(), 10000);
|
||||
};
|
||||
$timeout(() => $scope.nowTimer(), 10000);
|
||||
|
||||
$scope.refreshManaged = async (skipStatus) => {
|
||||
$scope.t1 = new Date().getTime();
|
||||
$scope.t1 = $scope.t1 - ($scope.t1 % MINUTE);
|
||||
$scope.t0 = $scope.t1 - $scope.before + $scope.offset;
|
||||
$scope.title = 'TV Guide';
|
||||
$scope.times = [];
|
||||
|
||||
$scope.updateJustNow();
|
||||
let pending = 0;
|
||||
let addDuration = (d) => {
|
||||
let m = (pending + d) % MINUTE;
|
||||
let r = pending + d - m;
|
||||
pending = m;
|
||||
return Math.floor(r / MINUTE);
|
||||
};
|
||||
let deleteIfZero = () => {
|
||||
if (
|
||||
$scope.times.length > 0 &&
|
||||
$scope.times[$scope.times.length - 1].duration < 1
|
||||
) {
|
||||
$scope.times = $scope.times.slice(0, $scope.times.length - 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.channelNumbers = [];
|
||||
$scope.channels = {};
|
||||
$scope.lastUpdate = -1;
|
||||
|
||||
$scope.updateJustNow = () => {
|
||||
$scope.t1 = (new Date()).getTime();
|
||||
if ($scope.t0 <= $scope.t1 && $scope.t1 < $scope.t0 + $scope.T) {
|
||||
let n = ($scope.t1 - $scope.t0) / MINUTE;
|
||||
$scope.nowPosition = ($scope.channelColspan + n) * $scope.colspanPercent
|
||||
if ($scope.nowPosition >= 50 && $scope.offset >= 0) {
|
||||
$scope.offset = 0;
|
||||
$scope.adjustZoom();
|
||||
}
|
||||
$scope.showNow = true;
|
||||
} else {
|
||||
$scope.showNow = false;
|
||||
}
|
||||
let rem = $scope.T;
|
||||
let t = $scope.t0;
|
||||
if (t % $scope.M != 0) {
|
||||
let dif = $scope.M - (t % $scope.M);
|
||||
$scope.times.push({
|
||||
duration: addDuration(dif),
|
||||
});
|
||||
deleteIfZero();
|
||||
t += dif;
|
||||
rem -= dif;
|
||||
}
|
||||
while (rem > 0) {
|
||||
let d = Math.min(rem, $scope.M);
|
||||
$scope.times.push({
|
||||
duration: addDuration(d),
|
||||
label: hourMinute(new Date(t)),
|
||||
});
|
||||
t += d;
|
||||
rem -= d;
|
||||
}
|
||||
|
||||
$scope.nowTimer = () => {
|
||||
$scope.updateJustNow();
|
||||
$timeout( () => $scope.nowTimer() , 10000);
|
||||
if (skipStatus !== true) {
|
||||
$scope.channelNumbers = [0];
|
||||
$scope.channels = {};
|
||||
$scope.channels[0] = {
|
||||
loading: true,
|
||||
};
|
||||
$scope.applyLater();
|
||||
console.log('getting status...');
|
||||
let status = await dizquetv.getGuideStatus();
|
||||
$scope.lastUpdate = new Date(status.lastUpdate).getTime();
|
||||
console.log('got status: ' + JSON.stringify(status));
|
||||
$scope.channelNumbers = status.channelNumbers;
|
||||
$scope.channels = {};
|
||||
}
|
||||
$timeout( () => $scope.nowTimer() , 10000);
|
||||
|
||||
for (let i = 0; i < $scope.channelNumbers.length; i++) {
|
||||
if (typeof $scope.channels[$scope.channelNumbers[i]] === 'undefined') {
|
||||
$scope.channels[$scope.channelNumbers[i]] = {};
|
||||
}
|
||||
$scope.channels[$scope.channelNumbers[i]].loading = true;
|
||||
}
|
||||
$scope.applyLater();
|
||||
$scope.enableBack = false;
|
||||
$scope.enableNext = false;
|
||||
await Promise.all($scope.channelNumbers.map($scope.loadChannel));
|
||||
setupTimer();
|
||||
};
|
||||
|
||||
$scope.refreshManaged = async (skipStatus) => {
|
||||
$scope.t1 = (new Date()).getTime();
|
||||
$scope.t1 = ($scope.t1 - $scope.t1 % MINUTE );
|
||||
$scope.t0 = $scope.t1 - $scope.before + $scope.offset;
|
||||
$scope.title = "TV Guide";
|
||||
$scope.times = [];
|
||||
let cancelTimerIfExists = () => {
|
||||
if ($scope.refreshHandle != null) {
|
||||
$timeout.cancel($scope.refreshHandle);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.updateJustNow();
|
||||
let pending = 0;
|
||||
let addDuration = (d) => {
|
||||
let m = (pending + d) % MINUTE;
|
||||
let r = (pending + d) - m;
|
||||
pending = m;
|
||||
return Math.floor( r / MINUTE );
|
||||
}
|
||||
let deleteIfZero = () => {
|
||||
if ( $scope.times.length > 0 && $scope.times[$scope.times.length - 1].duration < 1) {
|
||||
$scope.times = $scope.times.slice(0, $scope.times.length - 1);
|
||||
}
|
||||
}
|
||||
$scope.$on('$locationChangeStart', () => {
|
||||
console.log('$locationChangeStart');
|
||||
cancelTimerIfExists();
|
||||
});
|
||||
|
||||
let setupTimer = () => {
|
||||
cancelTimerIfExists();
|
||||
$scope.refreshHandle = $timeout(() => $scope.checkUpdates(), 60000);
|
||||
};
|
||||
|
||||
let rem = $scope.T;
|
||||
let t = $scope.t0;
|
||||
if (t % $scope.M != 0) {
|
||||
let dif = $scope.M - t % $scope.M;
|
||||
$scope.times.push( {
|
||||
duration : addDuration(dif),
|
||||
} );
|
||||
deleteIfZero();
|
||||
t += dif;
|
||||
rem -= dif;
|
||||
}
|
||||
while (rem > 0) {
|
||||
let d = Math.min(rem, $scope.M );
|
||||
$scope.times.push( {
|
||||
duration : addDuration(d),
|
||||
label: hourMinute( new Date(t) ),
|
||||
} );
|
||||
t += d;
|
||||
rem -= d;
|
||||
}
|
||||
|
||||
if (skipStatus !== true) {
|
||||
$scope.channelNumbers = [0];
|
||||
$scope.channels = {} ;
|
||||
$scope.channels[0] = {
|
||||
loading: true,
|
||||
}
|
||||
$scope.applyLater();
|
||||
console.log("getting status...");
|
||||
let status = await dizquetv.getGuideStatus();
|
||||
$scope.lastUpdate = new Date(status.lastUpdate).getTime();
|
||||
console.log("got status: " + JSON.stringify(status) );
|
||||
$scope.channelNumbers = status.channelNumbers;
|
||||
$scope.channels = {} ;
|
||||
}
|
||||
$scope.adjustZoom = async () => {
|
||||
switch ($scope.zoomLevel) {
|
||||
case 1:
|
||||
$scope.T = 50 * MINUTE;
|
||||
$scope.M = 10 * MINUTE;
|
||||
$scope.before = 5 * MINUTE;
|
||||
break;
|
||||
case 2:
|
||||
$scope.T = 100 * MINUTE;
|
||||
$scope.M = 15 * MINUTE;
|
||||
$scope.before = 10 * MINUTE;
|
||||
break;
|
||||
case 3:
|
||||
$scope.T = 190 * MINUTE;
|
||||
$scope.M = 30 * MINUTE;
|
||||
$scope.before = 15 * MINUTE;
|
||||
break;
|
||||
case 4:
|
||||
$scope.T = 270 * MINUTE;
|
||||
$scope.M = 60 * MINUTE;
|
||||
$scope.before = 15 * MINUTE;
|
||||
break;
|
||||
case 5:
|
||||
$scope.T = 380 * MINUTE;
|
||||
$scope.M = 90 * MINUTE;
|
||||
$scope.before = 15 * MINUTE;
|
||||
break;
|
||||
}
|
||||
|
||||
for (let i = 0; i < $scope.channelNumbers.length; i++) {
|
||||
if ( typeof($scope.channels[$scope.channelNumbers[i]]) === 'undefined') {
|
||||
$scope.channels[$scope.channelNumbers[i]] = {};
|
||||
}
|
||||
$scope.channels[$scope.channelNumbers[i]].loading = true;
|
||||
$scope.updateBasics();
|
||||
await $scope.refresh(true);
|
||||
};
|
||||
|
||||
$scope.zoomOut = async () => {
|
||||
$scope.zoomLevel = Math.min(5, $scope.zoomLevel + 1);
|
||||
await $scope.adjustZoom();
|
||||
};
|
||||
$scope.zoomIn = async () => {
|
||||
$scope.zoomLevel = Math.max(1, $scope.zoomLevel - 1);
|
||||
await $scope.adjustZoom();
|
||||
};
|
||||
$scope.zoomOutEnabled = () => {
|
||||
return $scope.zoomLevel < 5;
|
||||
};
|
||||
$scope.zoomInEnabled = () => {
|
||||
return $scope.zoomLevel > 1;
|
||||
};
|
||||
|
||||
$scope.next = async () => {
|
||||
$scope.offset += ($scope.M * 7) / 8;
|
||||
await $scope.adjustZoom();
|
||||
};
|
||||
$scope.back = async () => {
|
||||
$scope.offset -= ($scope.M * 7) / 8;
|
||||
await $scope.adjustZoom();
|
||||
};
|
||||
$scope.backEnabled = () => {
|
||||
return $scope.enableBack;
|
||||
};
|
||||
$scope.nextEnabled = () => {
|
||||
return $scope.enableNext;
|
||||
};
|
||||
|
||||
$scope.loadChannel = async (number) => {
|
||||
console.log(`number=${number}`);
|
||||
let d0 = new Date($scope.t0);
|
||||
let d1 = new Date($scope.t0 + $scope.T);
|
||||
let lineup = await dizquetv.getChannelLineup(number, d0, d1);
|
||||
let ch = {
|
||||
icon: lineup.icon,
|
||||
number: lineup.number,
|
||||
name: lineup.name,
|
||||
altTitle: `${lineup.number} - ${lineup.name}`,
|
||||
programs: [],
|
||||
};
|
||||
|
||||
let pending = 0;
|
||||
let totalAdded = 0;
|
||||
let addDuration = (d) => {
|
||||
totalAdded += d;
|
||||
let m = (pending + d) % MINUTE;
|
||||
let r = pending + d - m;
|
||||
pending = m;
|
||||
return Math.floor(r / MINUTE);
|
||||
};
|
||||
|
||||
let deleteIfZero = () => {
|
||||
if (
|
||||
ch.programs.length > 0 &&
|
||||
ch.programs[ch.programs.length - 1].duration < 1
|
||||
) {
|
||||
ch.programs = ch.programs.slice(0, ch.programs.length - 1);
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < lineup.programs.length; i++) {
|
||||
let program = lineup.programs[i];
|
||||
let ad = new Date(program.start);
|
||||
let bd = new Date(program.stop);
|
||||
let a = ad.getTime();
|
||||
let b = bd.getTime();
|
||||
let hasStart = true;
|
||||
let hasStop = true;
|
||||
if (a < $scope.t0) {
|
||||
//cut-off
|
||||
a = $scope.t0;
|
||||
hasStart = false;
|
||||
$scope.enableBack = true;
|
||||
} else if (a > $scope.t0 && i == 0) {
|
||||
ch.programs.push({
|
||||
duration: addDuration(a - $scope.t0),
|
||||
showTitle: '',
|
||||
start: false,
|
||||
end: true,
|
||||
});
|
||||
deleteIfZero();
|
||||
}
|
||||
if (b > $scope.t0 + $scope.T) {
|
||||
b = $scope.t0 + $scope.T;
|
||||
hasStop = false;
|
||||
$scope.enableNext = true;
|
||||
}
|
||||
let subTitle = undefined;
|
||||
let episodeTitle = undefined;
|
||||
let altTitle = hourMinute(ad) + '-' + hourMinute(bd);
|
||||
if (typeof program.title !== 'undefined') {
|
||||
altTitle = altTitle + ' · ' + program.title;
|
||||
}
|
||||
|
||||
if (typeof program.sub !== 'undefined') {
|
||||
ps = '' + program.sub.season;
|
||||
if (ps.length < 2) {
|
||||
ps = '0' + ps;
|
||||
}
|
||||
$scope.applyLater();
|
||||
$scope.enableBack = false;
|
||||
$scope.enableNext = false;
|
||||
await Promise.all($scope.channelNumbers.map( $scope.loadChannel) );
|
||||
pe = '' + program.sub.episode;
|
||||
if (pe.length < 2) {
|
||||
pe = '0' + pe;
|
||||
}
|
||||
subTitle = `S${ps} · E${pe}`;
|
||||
altTitle = altTitle + ' ' + subTitle;
|
||||
episodeTitle = program.sub.title;
|
||||
} else if (typeof program.date === 'undefined') {
|
||||
subTitle = '.';
|
||||
} else {
|
||||
subTitle = program.date.slice(0, 4);
|
||||
}
|
||||
ch.programs.push({
|
||||
duration: addDuration(b - a),
|
||||
altTitle: altTitle,
|
||||
showTitle: program.title,
|
||||
subTitle: subTitle,
|
||||
episodeTitle: episodeTitle,
|
||||
start: hasStart,
|
||||
end: hasStop,
|
||||
});
|
||||
deleteIfZero();
|
||||
}
|
||||
if (totalAdded < $scope.T) {
|
||||
ch.programs.push({
|
||||
duration: addDuration($scope.T - totalAdded),
|
||||
showTitle: '',
|
||||
start: false,
|
||||
end: true,
|
||||
});
|
||||
deleteIfZero();
|
||||
}
|
||||
$scope.channels[number] = ch;
|
||||
$scope.applyLater();
|
||||
};
|
||||
|
||||
$scope.refresh = async (skipStatus) => {
|
||||
try {
|
||||
await $scope.refreshManaged(skipStatus);
|
||||
} catch (err) {
|
||||
console.error('Refresh failed?', err);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.adjustZoom();
|
||||
$scope.refresh();
|
||||
|
||||
$scope.checkUpdates = async () => {
|
||||
try {
|
||||
console.log('get status ' + new Date());
|
||||
let status = await dizquetv.getGuideStatus();
|
||||
let t = new Date(status.lastUpdate).getTime();
|
||||
if (t > $scope.lastUpdate) {
|
||||
$scope.refreshManaged();
|
||||
} else {
|
||||
setupTimer();
|
||||
};
|
||||
|
||||
let cancelTimerIfExists = () => {
|
||||
if ($scope.refreshHandle != null) {
|
||||
$timeout.cancel($scope.refreshHandle);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
$scope.$on('$locationChangeStart', () => {
|
||||
console.log("$locationChangeStart" );
|
||||
cancelTimerIfExists();
|
||||
} );
|
||||
|
||||
|
||||
let setupTimer = () => {
|
||||
cancelTimerIfExists();
|
||||
$scope.refreshHandle = $timeout( () => $scope.checkUpdates(), 60000 );
|
||||
}
|
||||
|
||||
$scope.adjustZoom = async() => {
|
||||
switch ($scope.zoomLevel) {
|
||||
case 1:
|
||||
$scope.T = 50 * MINUTE;
|
||||
$scope.M = 10 * MINUTE;
|
||||
$scope.before = 5 * MINUTE;
|
||||
break;
|
||||
case 2:
|
||||
$scope.T = 100 * MINUTE;
|
||||
$scope.M = 15 * MINUTE;
|
||||
$scope.before = 10 * MINUTE;
|
||||
break;
|
||||
case 3:
|
||||
$scope.T = 190 * MINUTE;
|
||||
$scope.M = 30 * MINUTE;
|
||||
$scope.before = 15 * MINUTE;
|
||||
break;
|
||||
case 4:
|
||||
$scope.T = 270 * MINUTE;
|
||||
$scope.M = 60 * MINUTE;
|
||||
$scope.before = 15 * MINUTE;
|
||||
break;
|
||||
case 5:
|
||||
$scope.T = 380 * MINUTE;
|
||||
$scope.M = 90 * MINUTE;
|
||||
$scope.before = 15 * MINUTE;
|
||||
break;
|
||||
}
|
||||
|
||||
$scope.updateBasics();
|
||||
await $scope.refresh(true);
|
||||
}
|
||||
|
||||
$scope.zoomOut = async() => {
|
||||
$scope.zoomLevel = Math.min( 5, $scope.zoomLevel + 1 );
|
||||
await $scope.adjustZoom();
|
||||
}
|
||||
$scope.zoomIn = async() => {
|
||||
$scope.zoomLevel = Math.max( 1, $scope.zoomLevel - 1 );
|
||||
await $scope.adjustZoom();
|
||||
}
|
||||
$scope.zoomOutEnabled = () => {
|
||||
return $scope.zoomLevel < 5;
|
||||
}
|
||||
$scope.zoomInEnabled = () => {
|
||||
return $scope.zoomLevel > 1;
|
||||
}
|
||||
|
||||
$scope.next = async() => {
|
||||
$scope.offset += $scope.M * 7 / 8
|
||||
await $scope.adjustZoom();
|
||||
}
|
||||
$scope.back = async() => {
|
||||
$scope.offset -= $scope.M * 7 / 8
|
||||
await $scope.adjustZoom();
|
||||
}
|
||||
$scope.backEnabled = () => {
|
||||
return $scope.enableBack;
|
||||
}
|
||||
$scope.nextEnabled = () => {
|
||||
return $scope.enableNext;
|
||||
}
|
||||
|
||||
$scope.loadChannel = async (number) => {
|
||||
console.log(`number=${number}` );
|
||||
let d0 = new Date($scope.t0);
|
||||
let d1 = new Date($scope.t0 + $scope.T);
|
||||
let lineup = await dizquetv.getChannelLineup(number, d0, d1);
|
||||
let ch = {
|
||||
icon : lineup.icon,
|
||||
number : lineup.number,
|
||||
name: lineup.name,
|
||||
altTitle: `${lineup.number} - ${lineup.name}`,
|
||||
programs: [],
|
||||
};
|
||||
|
||||
let pending = 0;
|
||||
let totalAdded = 0;
|
||||
let addDuration = (d) => {
|
||||
totalAdded += d;
|
||||
let m = (pending + d) % MINUTE;
|
||||
let r = (pending + d) - m;
|
||||
pending = m;
|
||||
return Math.floor( r / MINUTE );
|
||||
}
|
||||
|
||||
let deleteIfZero = () => {
|
||||
if ( ch.programs.length > 0 && ch.programs[ ch.programs.length - 1].duration < 1) {
|
||||
ch.programs = ch.programs.slice(0, ch.programs.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < lineup.programs.length; i++) {
|
||||
let program = lineup.programs[i];
|
||||
let ad = new Date(program.start);
|
||||
let bd = new Date(program.stop);
|
||||
let a = ad.getTime();
|
||||
let b = bd.getTime();
|
||||
let hasStart = true;
|
||||
let hasStop = true;
|
||||
if (a < $scope.t0) {
|
||||
//cut-off
|
||||
a = $scope.t0;
|
||||
hasStart = false;
|
||||
$scope.enableBack = true;
|
||||
} else if ( (a > $scope.t0) && (i == 0) ) {
|
||||
ch.programs.push( {
|
||||
duration: addDuration( (a - $scope.t0) ),
|
||||
showTitle: "",
|
||||
start: false,
|
||||
end: true,
|
||||
} );
|
||||
deleteIfZero();
|
||||
}
|
||||
if (b > $scope.t0 + $scope.T) {
|
||||
b = $scope.t0 + $scope.T;
|
||||
hasStop = false;
|
||||
$scope.enableNext = true;
|
||||
}
|
||||
let subTitle = undefined;
|
||||
let episodeTitle = undefined;
|
||||
let altTitle = hourMinute(ad) + "-" + hourMinute(bd);
|
||||
if (typeof(program.title) !== 'undefined') {
|
||||
altTitle = altTitle + " · " + program.title;
|
||||
}
|
||||
|
||||
if (typeof(program.sub) !== 'undefined') {
|
||||
ps = "" + program.sub.season;
|
||||
if (ps.length < 2) {
|
||||
ps = "0" + ps;
|
||||
}
|
||||
pe = "" + program.sub.episode;
|
||||
if (pe.length < 2) {
|
||||
pe = "0" + pe;
|
||||
}
|
||||
subTitle = `S${ps} · E${pe}`;
|
||||
altTitle = altTitle + " " + subTitle;
|
||||
episodeTitle = program.sub.title;
|
||||
} else if ( typeof(program.date) === 'undefined' ) {
|
||||
subTitle = '.';
|
||||
} else {
|
||||
subTitle = program.date.slice(0,4);
|
||||
}
|
||||
ch.programs.push( {
|
||||
duration: addDuration(b - a),
|
||||
altTitle: altTitle,
|
||||
showTitle: program.title,
|
||||
subTitle: subTitle,
|
||||
episodeTitle : episodeTitle,
|
||||
start: hasStart,
|
||||
end: hasStop,
|
||||
} );
|
||||
deleteIfZero();
|
||||
}
|
||||
if (totalAdded < $scope.T) {
|
||||
ch.programs.push( {
|
||||
duration: addDuration( $scope.T - totalAdded ),
|
||||
showTitle: "",
|
||||
start: false,
|
||||
end: true,
|
||||
} );
|
||||
deleteIfZero();
|
||||
}
|
||||
$scope.channels[number] = ch;
|
||||
$scope.applyLater();
|
||||
}
|
||||
|
||||
|
||||
$scope.refresh = async (skipStatus) => {
|
||||
try {
|
||||
await $scope.refreshManaged(skipStatus);
|
||||
} catch (err) {
|
||||
console.error("Refresh failed?", err);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.adjustZoom();
|
||||
$scope.refresh();
|
||||
|
||||
$scope.checkUpdates = async () => {
|
||||
try {
|
||||
console.log("get status " + new Date() );
|
||||
let status = await dizquetv.getGuideStatus();
|
||||
let t = new Date(status.lastUpdate).getTime();
|
||||
if ( t > $scope.lastUpdate) {
|
||||
$scope.refreshManaged();
|
||||
} else {
|
||||
setupTimer();
|
||||
}
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<span ng-show="!x.pending">{{x.number}}</span>
|
||||
</td>
|
||||
<td style="padding: 0" class="text-center">
|
||||
<img ng-if="x.icon !== ''" ng-src="{{x.icon}}" alt="{{x.name}}" style="max-height: 40px;"></img>
|
||||
<img ng-if="x.icon !== ''" ng-src="{{x.icon.path}}" alt="{{x.name}}" style="max-height: 40px;"></img>
|
||||
<div ng-if="x.icon === ''" style="padding-top: 14px;"><small>{{x.name}}</small></div>
|
||||
</td>
|
||||
<td>{{x.name}} <span ng-if='x.stealth===true' class='text-muted'>(Stealth)</span></td>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td title='{{channels[channelNumber].altTitle}}' class='even channel-icon' colspan="{{channelIconColspan}}" >
|
||||
<img src='{{channels[channelNumber].icon}}' alt='{{channels[channelNumber].name}}' ng-click='channels[channelNumber].mouse=true' ></img>
|
||||
<img src='{{channels[channelNumber].icon.path}}' alt='{{channels[channelNumber].name}}' ng-click='channels[channelNumber].mouse=true' ></img>
|
||||
</td>
|
||||
|
||||
<td class='odd program' colspan="{{totalSpan}}" ng-if="channels[channelNumber].loading">
|
||||
|
||||
@@ -159,7 +159,6 @@ module.exports = function ($http, $q) {
|
||||
},
|
||||
getChannels: () => {
|
||||
return $http.get('/api/channels').then((d) => {
|
||||
console.log(d);
|
||||
return d.data;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { Plex } from '../../build/src/plex.js';
|
||||
|
||||
export default function ($http, $window, $interval) {
|
||||
module.exports = function ($http, $window, $interval, dizquetv) {
|
||||
let exported = {
|
||||
login: async () => {
|
||||
const headers = {
|
||||
@@ -103,9 +101,15 @@ export default function ($http, $window, $interval) {
|
||||
},
|
||||
|
||||
check: async (server) => {
|
||||
let client = new Plex(server);
|
||||
try {
|
||||
const res = await client.Get('/');
|
||||
await $http({
|
||||
method: 'GET',
|
||||
url: '/api/plex',
|
||||
params: {
|
||||
path: '/',
|
||||
name: server.name,
|
||||
},
|
||||
});
|
||||
return 1;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -114,8 +118,14 @@ export default function ($http, $window, $interval) {
|
||||
},
|
||||
|
||||
getLibrary: async (server) => {
|
||||
var client = new Plex(server);
|
||||
const res = await client.Get('/library/sections');
|
||||
const res = await $http({
|
||||
method: 'GET',
|
||||
url: '/api/plex',
|
||||
params: {
|
||||
path: '/library/sections',
|
||||
name: server.name,
|
||||
},
|
||||
});
|
||||
var sections = [];
|
||||
for (
|
||||
let i = 0,
|
||||
@@ -163,8 +173,14 @@ export default function ($http, $window, $interval) {
|
||||
return sections;
|
||||
},
|
||||
getPlaylists: async (server) => {
|
||||
var client = new Plex(server);
|
||||
const res = await client.Get('/playlists');
|
||||
const res = await $http({
|
||||
method: 'GET',
|
||||
url: '/api/plex',
|
||||
params: {
|
||||
path: '/playlists',
|
||||
name: server.name,
|
||||
},
|
||||
});
|
||||
var playlists = [];
|
||||
for (
|
||||
let i = 0,
|
||||
@@ -186,23 +202,34 @@ export default function ($http, $window, $interval) {
|
||||
return playlists;
|
||||
},
|
||||
getStreams: async (server, key) => {
|
||||
var client = new Plex(server);
|
||||
return client.Get(key).then((res) => {
|
||||
let streams = res.Metadata[0].Media[0].Part[0].Stream;
|
||||
for (let i = 0, l = streams.length; i < l; i++) {
|
||||
if (typeof streams[i].key !== 'undefined') {
|
||||
streams[
|
||||
i
|
||||
].key = `${server.uri}${streams[i].key}?X-Plex-Token=${server.accessToken}`;
|
||||
}
|
||||
}
|
||||
return streams;
|
||||
const res = await $http({
|
||||
method: 'GET',
|
||||
url: '/api/plex',
|
||||
params: {
|
||||
path: key,
|
||||
name: server.name,
|
||||
},
|
||||
});
|
||||
let streams = res.Metadata[0].Media[0].Part[0].Stream;
|
||||
for (let i = 0, l = streams.length; i < l; i++) {
|
||||
if (typeof streams[i].key !== 'undefined') {
|
||||
streams[
|
||||
i
|
||||
].key = `${server.uri}${streams[i].key}?X-Plex-Token=${server.accessToken}`;
|
||||
}
|
||||
}
|
||||
return streams;
|
||||
},
|
||||
getNested: async (server, lib, includeCollections, errors) => {
|
||||
var client = new Plex(server);
|
||||
const key = lib.key;
|
||||
const res = await client.Get(key);
|
||||
const res = await $http({
|
||||
method: 'GET',
|
||||
url: '/api/plex',
|
||||
params: {
|
||||
path: key,
|
||||
name: server.name,
|
||||
},
|
||||
});
|
||||
|
||||
const size =
|
||||
typeof res.Metadata !== 'undefined' ? res.Metadata.length : 0;
|
||||
@@ -365,7 +392,7 @@ export default function ($http, $window, $interval) {
|
||||
},
|
||||
};
|
||||
return exported;
|
||||
}
|
||||
};
|
||||
|
||||
function msToTime(duration) {
|
||||
var milliseconds = parseInt((duration % 1000) / 100),
|
||||
|
||||
Reference in New Issue
Block a user