Merge branch 'dev/1.0.x' into dev/1.1.x

This commit is contained in:
vexorian 2020-09-24 19:00:17 -04:00
commit cd3012a042
10 changed files with 133 additions and 25 deletions

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
zlib License
Copyright (c) 2020 Dan Ferguson, Victor Hugo Soliz Kuncar
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@ -69,3 +69,8 @@ npm run dev-server
* Pull requests welcome but please read the [Code of Conduct](CODE_OF_CONDUCT.md) and the [Pull Request Template](pull_request_template.md) first.
* Tip Jar: https://buymeacoffee.com/vexorian
## License
* Original pseudotv-Plex code was released under [MIT license (c) 2020 Dan Ferguson](https://github.com/DEFENDORe/pseudotv/blob/665e71e24ee5e93d9c9c90545addb53fdc235ff6/LICENSE)
* dizqueTV's improvements are released under zlib license (c) 2020 Victor Hugo Soliz Kuncar

View File

@ -32,10 +32,21 @@ function getCurrentLineupItem(channelId, t1) {
let recorded = cache[channelId];
let lineupItem = JSON.parse( JSON.stringify(recorded.lineupItem) );
let diff = t1 - recorded.t0;
if ( (diff <= SLACK) && (lineupItem.duration >= 2*SLACK) ) {
let rem = lineupItem.duration - lineupItem.start;
if (typeof(lineupItem.streamDuration) !== 'undefined') {
rem = Math.min(rem, lineupItem.streamDuration);
}
if ( (diff <= SLACK) && (diff + SLACK < rem) ) {
//closed the stream and opened it again let's not lose seconds for
//no reason
return lineupItem;
let originalT0 = recorded.lineupItem.originalT0;
if (typeof(originalT0) === 'undefined') {
originalT0 = recorded.t0;
}
if (t1 - originalT0 <= SLACK) {
lineupItem.originalT0 = originalT0;
return lineupItem;
}
}
lineupItem.start += diff;

View File

@ -3,6 +3,7 @@ module.exports = {
TVGUIDE_MAXIMUM_PADDING_LENGTH_MS: 30*60*1000,
STEALTH_DURATION: 5 * 60* 1000,
TVGUIDE_MAXIMUM_FLEX_DURATION : 6 * 60 * 60 * 1000,
TOO_FREQUENT: 100,
VERSION_NAME: "1.1.1-prerelease"
}

View File

@ -82,18 +82,38 @@ class PlexPlayer {
let emitter = new EventEmitter();
//setTimeout( () => {
let ff = await ffmpeg.spawnStream(stream.streamUrl, stream.streamStats, streamStart, streamDuration, enableChannelIcon, lineupItem.type); // Spawn the ffmpeg process
ff.pipe(outStream);
ff.pipe(outStream, {'end':false} );
//}, 100);
plexTranscoder.startUpdatingPlex();
ffmpeg.on('end', () => {
emitter.emit('end');
});
ffmpeg.on('close', () => {
emitter.emit('close');
});
ffmpeg.on('error', (err) => {
ffmpeg.on('error', async (err) => {
console.log("Replacing failed stream with error streram");
ff.unpipe(outStream);
ffmpeg.removeAllListeners('data');
ffmpeg.removeAllListeners('end');
ffmpeg.removeAllListeners('error');
ffmpeg.removeAllListeners('close');
ffmpeg = new FFMPEG(ffmpegSettings, channel); // Set the transcoder options
ffmpeg.on('close', () => {
emitter.emit('close');
});
ffmpeg.on('end', () => {
emitter.emit('end');
});
ffmpeg.on('error', (err) => {
emitter.emit('error', err );
});
ff = await ffmpeg.spawnError('oops', 'oops', Math.min(streamStats.duration, 60000) );
ff.pipe(outStream);
emitter.emit('error', err);
});
return emitter;

45
src/throttler.js Normal file
View File

@ -0,0 +1,45 @@
let constants = require('./constants');
let cache = {}
function equalItems(a, b) {
if ( (typeof(a) === 'undefined') || a.isOffline || b.isOffline ) {
return false;
}
console.log("no idea how to compare this: " + JSON.stringify(a) );
console.log(" with this: " + JSON.stringify(b) );
return true;
}
function wereThereTooManyAttempts(sessionId, lineupItem) {
let obj = cache[sessionId];
let t1 = (new Date()).getTime();
if (typeof(obj) === 'undefined') {
previous = cache[sessionId] = {
t0: t1 - constants.TOO_FREQUENT * 5
};
} else {
clearTimeout(obj.timer);
}
previous.timer = setTimeout( () => {
cache[sessionId].timer = null;
delete cache[sessionId];
}, constants.TOO_FREQUENT*5 );
let result = false;
if (previous.t0 + constants.TOO_FREQUENT >= t1) {
//certainly too frequent
result = equalItems( previous.lineupItem, lineupItem );
}
cache[sessionId].t0 = t1;
cache[sessionId].lineupItem = lineupItem;
return result;
}
module.exports = wereThereTooManyAttempts;

View File

@ -6,9 +6,12 @@ const PlexTranscoder = require('./plexTranscoder')
const fs = require('fs')
const ProgramPlayer = require('./program-player');
const channelCache = require('./channel-cache')
const wereThereTooManyAttempts = require('./throttler');
module.exports = { router: video }
let StreamCount = 0;
function video( channelDB , fillerDB, db) {
var router = express.Router()
@ -107,7 +110,7 @@ function video( channelDB , fillerDB, db) {
let channelNum = parseInt(req.query.channel, 10)
let ff = await ffmpeg.spawnConcat(`http://localhost:${process.env.PORT}/playlist?channel=${channelNum}`);
ff.pipe(res);
ff.pipe(res );
})
// Stream individual video to ffmpeg concat above. This is used by the server, NOT the client
router.get('/stream', async (req, res) => {
@ -116,6 +119,7 @@ function video( channelDB , fillerDB, db) {
res.status(400).send("No Channel Specified")
return
}
let session = parseInt(req.query.session);
let m3u8 = (req.query.m3u8 === '1');
let number = parseInt(req.query.channel);
let channel = await channelCache.getChannelConfig(channelDB, number);
@ -275,6 +279,14 @@ function video( channelDB , fillerDB, db) {
if (! isLoading) {
channelCache.recordPlayback(channel.number, t0, lineupItem);
}
if (wereThereTooManyAttempts(session, lineupItem)) {
lineupItem = {
isOffline: true,
err: Error("Too many attempts, throttling.."),
duration : 60000,
};
}
let playerContext = {
lineupItem : lineupItem,
@ -330,6 +342,8 @@ function video( channelDB , fillerDB, db) {
router.get('/m3u8', async (req, res) => {
let sessionId = StreamCount++;
//res.type('application/vnd.apple.mpegurl')
res.type("application/x-mpegURL");
@ -364,13 +378,13 @@ function video( channelDB , fillerDB, db) {
if ( ffmpegSettings.enableFFMPEGTranscoding === true) {
//data += `#EXTINF:${cur},\n`;
data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=0&m3u8=1\n`;
data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=0&m3u8=1&session=${sessionId}\n`;
}
//data += `#EXTINF:${cur},\n`;
data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=1&m3u8=1\n`
data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=1&m3u8=1&session=${sessionId}\n`
for (var i = 0; i < maxStreamsToPlayInARow - 1; i++) {
//data += `#EXTINF:${cur},\n`;
data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&m3u8=1\n`
data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&m3u8=1&session=${sessionId}\n`
}
res.send(data)
@ -399,6 +413,8 @@ function video( channelDB , fillerDB, db) {
let ffmpegSettings = db['ffmpeg-settings'].find()[0]
let sessionId = StreamCount++;
if (
(ffmpegSettings.enableFFMPEGTranscoding === true)
&& (ffmpegSettings.normalizeVideoCodec === true)
@ -406,11 +422,11 @@ function video( channelDB , fillerDB, db) {
&& (ffmpegSettings.normalizeResolution === true)
&& (ffmpegSettings.normalizeAudio === true)
) {
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=0'\n`;
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=0&session=${sessionId}'\n`;
}
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=1'\n`
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=1&session=${sessionId}'\n`
for (var i = 0; i < maxStreamsToPlayInARow - 1; i++) {
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}'\n`
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&session=${sessionId}'\n`
}
res.send(data)

View File

@ -1096,8 +1096,9 @@ module.exports = function ($timeout, $location, dizquetv) {
}
scope.importPrograms = (selectedPrograms) => {
for (let i = 0, l = selectedPrograms.length; i < l; i++)
selectedPrograms[i].commercials = []
for (let i = 0, l = selectedPrograms.length; i < l; i++) {
delete selectedPrograms[i].commercials;
}
scope.channel.programs = scope.channel.programs.concat(selectedPrograms)
updateChannelDuration()
setTimeout(

View File

@ -9,13 +9,6 @@ module.exports = function ($timeout) {
onDone: "=onDone"
},
link: function (scope, element, attrs) {
scope.selectedCommercials = (items) => {
scope.program.commercials = scope.program.commercials.concat(items)
for (let i = 0, l = scope.program.commercials.length; i < l; i++) {
if (typeof scope.program.commercials[i].commercialPosition === 'undefined')
scope.program.commercials[i].commercialPosition = 0
}
}
scope.finished = (prog) => {
if (prog.title === "")
scope.error = { title: 'You must set a program title.' }
@ -37,9 +30,6 @@ module.exports = function ($timeout) {
return
}
prog.duration = prog.duration
for (let i = 0, l = prog.commercials.length; i < l; i++)
prog.duration += prog.commercials[i].duration
scope.onDone(JSON.parse(angular.toJson(prog)))
scope.program = null
}

View File

@ -84,5 +84,5 @@
</div>
</div>
</div>
<plex-library height="300" visible="showPlexLibrary" on-finish="selectedCommercials"></plex-library>
</div>