Merge branch 'dev/1.0.x' into dev/1.1.x
This commit is contained in:
commit
cd3012a042
19
LICENSE
Normal file
19
LICENSE
Normal 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.
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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
45
src/throttler.js
Normal 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;
|
||||
30
src/video.js
30
src/video.js
@ -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)
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -84,5 +84,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<plex-library height="300" visible="showPlexLibrary" on-finish="selectedCommercials"></plex-library>
|
||||
|
||||
</div>
|
||||
Loading…
x
Reference in New Issue
Block a user