commit
e64dc93dca
6
index.js
6
index.js
@ -187,7 +187,11 @@ app.listen(process.env.PORT, () => {
|
||||
})
|
||||
|
||||
function initDB(db, channelDB) {
|
||||
dbMigration.initDB(db, channelDB);
|
||||
if (!fs.existsSync(process.env.DATABASE + '/images/dizquetv.png')) {
|
||||
let data = fs.readFileSync(path.resolve(path.join(__dirname, 'resources/dizquetv.png')))
|
||||
fs.writeFileSync(process.env.DATABASE + '/images/dizquetv.png', data)
|
||||
}
|
||||
dbMigration.initDB(db, channelDB, __dirname);
|
||||
if (!fs.existsSync(process.env.DATABASE + '/font.ttf')) {
|
||||
let data = fs.readFileSync(path.resolve(path.join(__dirname, 'resources/font.ttf')))
|
||||
fs.writeFileSync(process.env.DATABASE + '/font.ttf', data)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
@ -29,7 +29,7 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.0547013"
|
||||
inkscape:cx="173.01248"
|
||||
inkscape:cx="55.816079"
|
||||
inkscape:cy="84.726326"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
@ -48,7 +48,7 @@
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
@ -58,7 +58,7 @@
|
||||
id="layer1"
|
||||
transform="translate(0,-244.08278)">
|
||||
<rect
|
||||
style="opacity:1;fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:1.46508551;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
style="opacity:1;fill:#a1a1a1;fill-opacity:0.86666667;stroke:none;stroke-width:1.46508551;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4712"
|
||||
width="52.211964"
|
||||
height="51.512306"
|
||||
@ -67,7 +67,7 @@
|
||||
transform="matrix(0.99980416,-0.01978974,0.00448328,0.99998995,0,0)" />
|
||||
<g
|
||||
id="g4581"
|
||||
style="fill:#1f1f1f;fill-opacity:1;stroke-width:0.68901283"
|
||||
style="fill:#080808;fill-opacity:1;stroke-width:0.68901283"
|
||||
transform="matrix(1.2119871,0,0,1.7379906,-82.577875,-167.18505)">
|
||||
<rect
|
||||
transform="rotate(-0.94645665)"
|
||||
@ -76,7 +76,7 @@
|
||||
height="27.75024"
|
||||
width="41.471352"
|
||||
id="rect4524"
|
||||
style="opacity:1;fill:#1f1f1f;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
style="opacity:1;fill:#080808;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
<rect
|
||||
style="opacity:1;fill:#9cbc28;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
|
||||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
@ -20,7 +20,7 @@
|
||||
const path = require('path');
|
||||
var fs = require('fs');
|
||||
|
||||
const TARGET_VERSION = 702;
|
||||
const TARGET_VERSION = 800;
|
||||
|
||||
const STEPS = [
|
||||
// [v, v2, x] : if the current version is v, call x(db), and version becomes v2
|
||||
@ -34,7 +34,12 @@ const STEPS = [
|
||||
[ 600, 601, (db) => addFPS(db) ],
|
||||
[ 601, 700, (db) => migrateWatermark(db) ],
|
||||
[ 700, 701, (db) => addScalingAlgorithm(db) ],
|
||||
[ 701, 702, (db) => addDeinterlaceFilter(db) ]
|
||||
[ 701, 703, (db,channels,dir) => reAddIcon(dir) ],
|
||||
[ 703, 800, (db) => addDeinterlaceFilter(db) ],
|
||||
// there was a bit of thing in which for a while 1.3.x migrated 701 to 702 using
|
||||
// the addDeinterlaceFilter step. This 702 step no longer exists as a target
|
||||
// but we have to migrate it to 800 using the reAddIcon.
|
||||
[ 702, 800, (db,channels,dir) => reAddIcon(dir) ],
|
||||
]
|
||||
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
@ -333,7 +338,7 @@ function commercialsRemover(db) {
|
||||
}
|
||||
|
||||
|
||||
function initDB(db, channelDB ) {
|
||||
function initDB(db, channelDB, dir ) {
|
||||
if (typeof(channelDB) === 'undefined') {
|
||||
throw Error("???");
|
||||
}
|
||||
@ -348,7 +353,7 @@ function initDB(db, channelDB ) {
|
||||
ran = true;
|
||||
console.log("Migrating from db version " + dbVersion.version + " to: " + STEPS[i][1] + "...");
|
||||
try {
|
||||
STEPS[i][2](db, channelDB);
|
||||
STEPS[i][2](db, channelDB, dir);
|
||||
if (typeof(dbVersion._id) === 'undefined') {
|
||||
db['db-version'].save( {'version': STEPS[i][1] } );
|
||||
} else {
|
||||
@ -398,7 +403,7 @@ function ffmpeg() {
|
||||
normalizeAudio: true,
|
||||
maxFPS: 60,
|
||||
scalingAlgorithm: "bicubic",
|
||||
deinterlaceFilter: "none"
|
||||
deinterlaceFilter: "none",
|
||||
}
|
||||
}
|
||||
|
||||
@ -757,6 +762,40 @@ function addScalingAlgorithm(db) {
|
||||
fs.writeFileSync( f, JSON.stringify( [ffmpegSettings] ) );
|
||||
}
|
||||
|
||||
function moveBackup(path) {
|
||||
if (fs.existsSync(`${process.env.DATABASE}${path}`) ) {
|
||||
let i = 0;
|
||||
while (fs.existsSync( `${process.env.DATABASE}${path}.bak.${i}`) ) {
|
||||
i++;
|
||||
}
|
||||
fs.renameSync(`${process.env.DATABASE}${path}`, `${process.env.DATABASE}${path}.bak.${i}` );
|
||||
}
|
||||
}
|
||||
|
||||
function reAddIcon(dir) {
|
||||
moveBackup('/images/dizquetv.png');
|
||||
let data = fs.readFileSync(path.resolve(path.join(dir, 'resources/dizquetv.png')));
|
||||
fs.writeFileSync(process.env.DATABASE + '/images/dizquetv.png', data);
|
||||
|
||||
if (fs.existsSync(`${process.env.DATABASE}/images/pseudotv.png`) ) {
|
||||
moveBackup('/images/pseudotv.png');
|
||||
let data = fs.readFileSync(path.resolve(path.join(dir, 'resources/dizquetv.png')));
|
||||
fs.writeFileSync(process.env.DATABASE + '/images/pseudotv.png', data);
|
||||
}
|
||||
|
||||
moveBackup('/images/generic-error-screen.png');
|
||||
data = fs.readFileSync(path.resolve(path.join(dir, 'resources/generic-error-screen.png')))
|
||||
fs.writeFileSync(process.env.DATABASE + '/images/generic-error-screen.png', data)
|
||||
|
||||
moveBackup('/images/generic-offline-screen.png');
|
||||
data = fs.readFileSync(path.resolve(path.join(dir, 'resources/generic-offline-screen.png')));
|
||||
fs.writeFileSync(process.env.DATABASE + '/images/generic-offline-screen.png', data);
|
||||
|
||||
moveBackup('/images/loading-screen.png');
|
||||
data = fs.readFileSync(path.resolve(path.join(dir, 'resources/loading-screen.png')))
|
||||
fs.writeFileSync(process.env.DATABASE + '/images/loading-screen.png', data)
|
||||
}
|
||||
|
||||
function addDeinterlaceFilter(db) {
|
||||
let ffmpegSettings = db['ffmpeg-settings'].find()[0];
|
||||
let f = path.join(process.env.DATABASE, 'ffmpeg-settings.json');
|
||||
|
||||
@ -62,6 +62,10 @@ class FFMPEG extends events.EventEmitter {
|
||||
this.ensureResolution = this.opts.normalizeResolution;
|
||||
this.volumePercent = this.opts.audioVolumePercent;
|
||||
this.hasBeenKilled = false;
|
||||
this.audioOnly = false;
|
||||
}
|
||||
setAudioOnly(audioOnly) {
|
||||
this.audioOnly = audioOnly;
|
||||
}
|
||||
async spawnConcat(streamUrl) {
|
||||
return await this.spawn(streamUrl, undefined, undefined, undefined, true, false, undefined, true)
|
||||
@ -108,8 +112,17 @@ class FFMPEG extends events.EventEmitter {
|
||||
`-threads`, isConcatPlaylist? 1 : this.opts.threads,
|
||||
`-fflags`, `+genpts+discardcorrupt+igndts`];
|
||||
|
||||
if (limitRead === true)
|
||||
ffmpegArgs.push(`-re`)
|
||||
if (
|
||||
(limitRead === true)
|
||||
&&
|
||||
(
|
||||
(this.audioOnly !== true)
|
||||
||
|
||||
( typeof(streamUrl.errorTitle) === 'undefined')
|
||||
)
|
||||
) {
|
||||
ffmpegArgs.push(`-re`);
|
||||
}
|
||||
|
||||
|
||||
if (typeof startTime !== 'undefined')
|
||||
@ -186,24 +199,27 @@ class FFMPEG extends events.EventEmitter {
|
||||
iH = this.wantedH;
|
||||
}
|
||||
|
||||
if ( this.audioOnly !== true) {
|
||||
ffmpegArgs.push("-r" , "24");
|
||||
if ( streamUrl.errorTitle == 'offline' ) {
|
||||
ffmpegArgs.push(
|
||||
'-loop', '1',
|
||||
'-i', `${this.channel.offlinePicture}`,
|
||||
);
|
||||
videoComplex = `;[0:0]loop=loop=-1:size=1:start=0[looped];[looped]scale=${iW}:${iH}[videoy];[videoy]realtime[videox]`;
|
||||
videoComplex = `;[${inputFiles++}:0]loop=loop=-1:size=1:start=0[looped];[looped]scale=${iW}:${iH}[videoy];[videoy]realtime[videox]`;
|
||||
} else if (this.opts.errorScreen == 'static') {
|
||||
ffmpegArgs.push(
|
||||
'-f', 'lavfi',
|
||||
'-i', `nullsrc=s=64x36`);
|
||||
videoComplex = `;geq=random(1)*255:128:128[videoz];[videoz]scale=${iW}:${iH}[videoy];[videoy]realtime[videox]`;
|
||||
inputFiles++;
|
||||
} else if (this.opts.errorScreen == 'testsrc') {
|
||||
ffmpegArgs.push(
|
||||
'-f', 'lavfi',
|
||||
'-i', `testsrc=size=${iW}x${iH}`,
|
||||
);
|
||||
videoComplex = `;realtime[videox]`;
|
||||
inputFiles++;
|
||||
} else if (this.opts.errorScreen == 'text') {
|
||||
var sz2 = Math.ceil( (iH) / 33.0);
|
||||
var sz1 = Math.ceil( sz2 * 3. / 2. );
|
||||
@ -213,6 +229,7 @@ class FFMPEG extends events.EventEmitter {
|
||||
'-f', 'lavfi',
|
||||
'-i', `color=c=black:s=${iW}x${iH}`
|
||||
);
|
||||
inputFiles++;
|
||||
|
||||
videoComplex = `;drawtext=fontfile=${process.env.DATABASE}/font.ttf:fontsize=${sz1}:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:text='${streamUrl.errorTitle}',drawtext=fontfile=${process.env.DATABASE}/font.ttf:fontsize=${sz2}:fontcolor=white:x=(w-text_w)/2:y=(h+text_h+${sz3})/2:text='${streamUrl.subtitle}'[videoy];[videoy]realtime[videox]`;
|
||||
} else if (this.opts.errorScreen == 'blank') {
|
||||
@ -220,14 +237,17 @@ class FFMPEG extends events.EventEmitter {
|
||||
'-f', 'lavfi',
|
||||
'-i', `color=c=black:s=${iW}x${iH}`
|
||||
);
|
||||
inputFiles++;
|
||||
videoComplex = `;realtime[videox]`;
|
||||
} else {//'pic'
|
||||
ffmpegArgs.push(
|
||||
'-loop', '1',
|
||||
'-i', `${this.errorPicturePath}`,
|
||||
);
|
||||
videoComplex = `;[0:0]scale=${iW}:${iH}[videoy];[videoy]realtime[videox]`;
|
||||
inputFiles++;
|
||||
videoComplex = `;[${videoFile+1}:0]scale=${iW}:${iH}[videoy];[videoy]realtime[videox]`;
|
||||
}
|
||||
}
|
||||
let durstr = `duration=${streamStats.duration}ms`;
|
||||
//silent
|
||||
audioComplex = `;aevalsrc=0:${durstr}[audioy]`;
|
||||
@ -239,14 +259,26 @@ class FFMPEG extends events.EventEmitter {
|
||||
ffmpegArgs.push('-i', `${this.channel.offlineSoundtrack}`);
|
||||
// I don't really understand why, but you need to use this
|
||||
// 'size' in order to make the soundtrack actually loop
|
||||
audioComplex = `;[1:a]aloop=loop=-1:size=2147483647[audioy]`;
|
||||
audioComplex = `;[${inputFiles++}:a]aloop=loop=-1:size=2147483647[audioy]`;
|
||||
}
|
||||
} else if (this.opts.errorAudio == 'whitenoise') {
|
||||
audioComplex = `;aevalsrc=-2+0.1*random(0):${durstr}[audioy]`;
|
||||
} else if (
|
||||
(this.opts.errorAudio == 'whitenoise')
|
||||
||
|
||||
(
|
||||
!(this.opts.errorAudio == 'sine')
|
||||
&&
|
||||
(this.audioOnly === true) //when it's in audio-only mode, silent stream is confusing for errors.
|
||||
)
|
||||
) {
|
||||
audioComplex = `;aevalsrc=random(0):${durstr}[audioy]`;
|
||||
this.volumePercent = Math.min(70, this.volumePercent);
|
||||
} else if (this.opts.errorAudio == 'sine') {
|
||||
audioComplex = `;sine=f=440:${durstr}[audiox];[audiox]volume=-35dB[audioy]`;
|
||||
audioComplex = `;sine=f=440:${durstr}[audioy]`;
|
||||
this.volumePercent = Math.min(70, this.volumePercent);
|
||||
}
|
||||
if ( this.audioOnly !== true ) {
|
||||
ffmpegArgs.push('-pix_fmt' , 'yuv420p' );
|
||||
}
|
||||
ffmpegArgs.push('-pix_fmt' , 'yuv420p' );
|
||||
audioComplex += ';[audioy]arealtime[audiox]';
|
||||
currentVideo = "[videox]";
|
||||
currentAudio = "[audiox]";
|
||||
@ -319,7 +351,7 @@ class FFMPEG extends events.EventEmitter {
|
||||
}
|
||||
|
||||
// Channel watermark:
|
||||
if (doOverlay) {
|
||||
if (doOverlay && (this.audioOnly !== true) ) {
|
||||
var pW =watermark.width;
|
||||
var w = Math.round( pW * iW / 100.0 );
|
||||
var mpHorz = watermark.horizontalMargin;
|
||||
@ -361,7 +393,8 @@ class FFMPEG extends events.EventEmitter {
|
||||
currentAudio = '[boosted]';
|
||||
}
|
||||
// Align audio is just the apad filter applied to audio stream
|
||||
if (this.apad) {
|
||||
if (this.apad && (this.audioOnly !== true) ) {
|
||||
//it doesn't make much sense to pad audio when there is no video
|
||||
audioComplex += `;${currentAudio}apad=whole_dur=${streamStats.duration}ms[padded]`;
|
||||
currentAudio = '[padded]';
|
||||
} else if (this.audioChannelsSampleRate) {
|
||||
@ -382,11 +415,13 @@ class FFMPEG extends events.EventEmitter {
|
||||
} else {
|
||||
console.log(resizeMsg)
|
||||
}
|
||||
if (currentVideo != '[video]') {
|
||||
transcodeVideo = true; //this is useful so that it adds some lines below
|
||||
filterComplex += videoComplex;
|
||||
} else {
|
||||
currentVideo = `${videoFile}:${videoIndex}`;
|
||||
if (this.audioOnly !== true) {
|
||||
if (currentVideo != '[video]') {
|
||||
transcodeVideo = true; //this is useful so that it adds some lines below
|
||||
filterComplex += videoComplex;
|
||||
} else {
|
||||
currentVideo = `${videoFile}:${videoIndex}`;
|
||||
}
|
||||
}
|
||||
// same with audio:
|
||||
if (currentAudio != '[audio]') {
|
||||
@ -403,15 +438,18 @@ class FFMPEG extends events.EventEmitter {
|
||||
ffmpegArgs.push('-shortest');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.audioOnly !== true) {
|
||||
ffmpegArgs.push(
|
||||
'-map', currentVideo,
|
||||
`-c:v`, (transcodeVideo ? this.opts.videoEncoder : 'copy'),
|
||||
`-sc_threshold`, `1000000000`,
|
||||
);
|
||||
}
|
||||
ffmpegArgs.push(
|
||||
'-map', currentVideo,
|
||||
'-map', currentAudio,
|
||||
`-c:v`, (transcodeVideo ? this.opts.videoEncoder : 'copy'),
|
||||
`-flags`, `cgop+ilme`,
|
||||
`-sc_threshold`, `1000000000`
|
||||
);
|
||||
if ( transcodeVideo ) {
|
||||
if ( transcodeVideo && (this.audioOnly !== true) ) {
|
||||
// add the video encoder flags
|
||||
ffmpegArgs.push(
|
||||
`-b:v`, `${this.opts.videoBitrate}k`,
|
||||
@ -453,8 +491,11 @@ class FFMPEG extends events.EventEmitter {
|
||||
//Concat stream is simpler and should always copy the codec
|
||||
ffmpegArgs.push(
|
||||
`-probesize`, 32 /*`100000000`*/,
|
||||
`-i`, streamUrl,
|
||||
`-map`, `0:v`,
|
||||
`-i`, streamUrl );
|
||||
if (this.audioOnly !== true) {
|
||||
ffmpegArgs.push( `-map`, `0:v` );
|
||||
}
|
||||
ffmpegArgs.push(
|
||||
`-map`, `0:${audioIndex}`,
|
||||
`-c`, `copy`,
|
||||
`-muxdelay`, this.opts.concatMuxDelay,
|
||||
|
||||
@ -19,6 +19,7 @@ class OfflinePlayer {
|
||||
context.channel.offlineSoundtrack = undefined;
|
||||
}
|
||||
this.ffmpeg = new FFMPEG(context.ffmpegSettings, context.channel);
|
||||
this.ffmpeg.setAudioOnly(this.context.audioOnly);
|
||||
}
|
||||
|
||||
cleanUp() {
|
||||
@ -37,7 +38,7 @@ class OfflinePlayer {
|
||||
} else {
|
||||
ff = await ffmpeg.spawnOffline(duration);
|
||||
}
|
||||
ff.pipe(outStream);
|
||||
ff.pipe(outStream, {'end':false} );
|
||||
|
||||
ffmpeg.on('end', () => {
|
||||
emitter.emit('end');
|
||||
@ -45,8 +46,33 @@ class OfflinePlayer {
|
||||
ffmpeg.on('close', () => {
|
||||
emitter.emit('close');
|
||||
});
|
||||
ffmpeg.on('error', (err) => {
|
||||
emitter.emit('error', err);
|
||||
ffmpeg.on('error', async (err) => {
|
||||
//wish this code wasn't repeated.
|
||||
if (! this.error ) {
|
||||
console.log("Replacing failed stream with error stream");
|
||||
ff.unpipe(outStream);
|
||||
ffmpeg.removeAllListeners('data');
|
||||
ffmpeg.removeAllListeners('end');
|
||||
ffmpeg.removeAllListeners('error');
|
||||
ffmpeg.removeAllListeners('close');
|
||||
ffmpeg = new FFMPEG(this.context.ffmpegSettings, this.context.channel); // Set the transcoder options
|
||||
ffmpeg.setAudioOnly(this.context.audioOnly);
|
||||
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(duration, 60000) );
|
||||
ff.pipe(outStream);
|
||||
} else {
|
||||
emitter.emit('error', err);
|
||||
}
|
||||
|
||||
});
|
||||
return emitter;
|
||||
} catch(err) {
|
||||
|
||||
@ -62,6 +62,7 @@ class PlexPlayer {
|
||||
this.plexTranscoder = plexTranscoder;
|
||||
let watermark = this.context.watermark;
|
||||
let ffmpeg = new FFMPEG(ffmpegSettings, channel); // Set the transcoder options
|
||||
ffmpeg.setAudioOnly( this.context.audioOnly );
|
||||
this.ffmpeg = ffmpeg;
|
||||
let streamDuration;
|
||||
if (typeof(lineupItem.streamDuration)!=='undefined') {
|
||||
@ -97,13 +98,14 @@ class PlexPlayer {
|
||||
emitter.emit('close');
|
||||
});
|
||||
ffmpeg.on('error', async (err) => {
|
||||
console.log("Replacing failed stream with error streram");
|
||||
console.log("Replacing failed stream with error stream");
|
||||
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.setAudioOnly(this.context.audioOnly);
|
||||
ffmpeg.on('close', () => {
|
||||
emitter.emit('close');
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
|
||||
class PlexTranscoder {
|
||||
constructor(clientId, server, settings, channel, lineupItem) {
|
||||
@ -73,6 +74,14 @@ class PlexTranscoder {
|
||||
// Update transcode decision for session
|
||||
await this.getDecision(stream.directPlay);
|
||||
stream.streamUrl = (this.settings.streamPath === 'direct') ? this.file : this.plexFile;
|
||||
if(this.settings.streamPath === 'direct') {
|
||||
fs.access(this.file, fs.F_OK, (err) => {
|
||||
if (err) {
|
||||
throw Error("Can't access this file", err);
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
if (typeof(stream.streamUrl) == 'undefined') {
|
||||
throw Error("Direct path playback is not possible for this program because it was registered at a time when the direct path settings were not set. To fix this, you must either revert the direct path setting or rebuild this channel.");
|
||||
}
|
||||
@ -291,10 +300,6 @@ lang=en`
|
||||
}
|
||||
|
||||
async getDecisionUnmanaged(directPlay) {
|
||||
if (this.settings.streamPath === 'direct') {
|
||||
console.log("Skip get transcode decision because direct path is enabled");
|
||||
return;
|
||||
}
|
||||
let res = await axios.get(`${this.server.uri}/video/:/transcode/universal/decision?${this.transcodingArgs}`, {
|
||||
headers: { Accept: 'application/json' }
|
||||
})
|
||||
|
||||
@ -16,9 +16,9 @@
|
||||
id="svg8"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||
sodipodi:docname="dizquetv.svg"
|
||||
inkscape:export-filename="/home/vx/dev/dizqueanimation/01.png"
|
||||
inkscape:export-xdpi="245.75999"
|
||||
inkscape:export-ydpi="245.75999">
|
||||
inkscape:export-filename="/home/vx/dev/pseudotv/resources/dizquetv.png"
|
||||
inkscape:export-xdpi="240"
|
||||
inkscape:export-ydpi="240">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
@ -29,8 +29,8 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="-91.800821"
|
||||
inkscape:cy="221.87049"
|
||||
inkscape:cx="74.610162"
|
||||
inkscape:cy="39.873047"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
@ -48,7 +48,7 @@
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
@ -58,7 +58,7 @@
|
||||
id="layer1"
|
||||
transform="translate(0,-244.08278)">
|
||||
<rect
|
||||
style="opacity:1;fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:1.46501946;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
style="opacity:1;fill:#3f3f3f;fill-opacity:0.86666667;stroke:none;stroke-width:1.46501946;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4712"
|
||||
width="52.206699"
|
||||
height="35.866219"
|
||||
@ -67,7 +67,7 @@
|
||||
transform="matrix(0.99990505,-0.01378015,0.00643904,0.99997927,0,0)" />
|
||||
<g
|
||||
id="g4581"
|
||||
style="fill:#1f1f1f;fill-opacity:1;stroke-width:0.82573813"
|
||||
style="fill:#080808;fill-opacity:1;stroke-width:0.82573813"
|
||||
transform="matrix(1.2119871,0,0,1.2100891,-82.577875,-32.337926)">
|
||||
<rect
|
||||
transform="rotate(-0.94645665)"
|
||||
@ -76,7 +76,7 @@
|
||||
height="27.75024"
|
||||
width="41.471352"
|
||||
id="rect4524"
|
||||
style="opacity:1;fill:#1f1f1f;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
style="opacity:1;fill:#080808;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
<rect
|
||||
style="opacity:1;fill:#9cbc28;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
|
||||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
26
src/video.js
26
src/video.js
@ -45,7 +45,7 @@ function video( channelDB , fillerDB, db) {
|
||||
})
|
||||
})
|
||||
// Continuously stream video to client. Leverage ffmpeg concat for piecing together videos
|
||||
router.get('/video', async (req, res) => {
|
||||
let concat = async (req, res, audioOnly) => {
|
||||
// Check if channel queried is valid
|
||||
if (typeof req.query.channel === 'undefined') {
|
||||
res.status(500).send("No Channel Specified")
|
||||
@ -75,6 +75,7 @@ function video( channelDB , fillerDB, db) {
|
||||
console.log(`\r\nStream starting. Channel: ${channel.number} (${channel.name})`)
|
||||
|
||||
let ffmpeg = new FFMPEG(ffmpegSettings, channel); // Set the transcoder options
|
||||
ffmpeg.setAudioOnly(audioOnly);
|
||||
let stopped = false;
|
||||
|
||||
function stop() {
|
||||
@ -109,9 +110,16 @@ 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}`);
|
||||
let ff = await ffmpeg.spawnConcat(`http://localhost:${process.env.PORT}/playlist?channel=${channelNum}&audioOnly=${audioOnly}`);
|
||||
ff.pipe(res );
|
||||
})
|
||||
};
|
||||
router.get('/video', async(req, res) => {
|
||||
return await concat(req, res, false);
|
||||
} );
|
||||
router.get('/radio', async(req, res) => {
|
||||
return await concat(req, res, true);
|
||||
} );
|
||||
|
||||
// Stream individual video to ffmpeg concat above. This is used by the server, NOT the client
|
||||
router.get('/stream', async (req, res) => {
|
||||
// Check if channel queried is valid
|
||||
@ -119,6 +127,8 @@ function video( channelDB , fillerDB, db) {
|
||||
res.status(400).send("No Channel Specified")
|
||||
return
|
||||
}
|
||||
let audioOnly = ("true" == req.query.audioOnly);
|
||||
console.log(`/stream audioOnly=${audioOnly}`);
|
||||
let session = parseInt(req.query.session);
|
||||
let m3u8 = (req.query.m3u8 === '1');
|
||||
let number = parseInt(req.query.channel);
|
||||
@ -296,6 +306,7 @@ function video( channelDB , fillerDB, db) {
|
||||
channel: combinedChannel,
|
||||
db: db,
|
||||
m3u8: m3u8,
|
||||
audioOnly : audioOnly,
|
||||
}
|
||||
|
||||
let player = new ProgramPlayer(playerContext);
|
||||
@ -416,6 +427,7 @@ function video( channelDB , fillerDB, db) {
|
||||
let ffmpegSettings = db['ffmpeg-settings'].find()[0]
|
||||
|
||||
let sessionId = StreamCount++;
|
||||
let audioOnly = ("true" == req.query.audioOnly);
|
||||
|
||||
if (
|
||||
(ffmpegSettings.enableFFMPEGTranscoding === true)
|
||||
@ -423,12 +435,14 @@ function video( channelDB , fillerDB, db) {
|
||||
&& (ffmpegSettings.normalizeAudioCodec === true)
|
||||
&& (ffmpegSettings.normalizeResolution === true)
|
||||
&& (ffmpegSettings.normalizeAudio === true)
|
||||
&& (audioOnly !== true) /* loading screen is pointless in audio mode (also for some reason it makes it fail when codec is aac, and I can't figure out why) */
|
||||
) {
|
||||
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=0&session=${sessionId}'\n`;
|
||||
//loading screen
|
||||
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=0&session=${sessionId}&audioOnly=${audioOnly}'\n`;
|
||||
}
|
||||
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=1&session=${sessionId}'\n`
|
||||
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=1&session=${sessionId}&audioOnly=${audioOnly}'\n`
|
||||
for (var i = 0; i < maxStreamsToPlayInARow - 1; i++) {
|
||||
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&session=${sessionId}'\n`
|
||||
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&session=${sessionId}&audioOnly=${audioOnly}'\n`
|
||||
}
|
||||
|
||||
res.send(data)
|
||||
|
||||
@ -13,7 +13,9 @@
|
||||
|
||||
<body ng-app="myApp" style="min-width: 340px;">
|
||||
<div class="container">
|
||||
<h1>dizqueTV
|
||||
<h1>
|
||||
<a href="#!/guide"><img id='dizquetv-logo' src="images/dizquetv.png" alt="logo" /></a>
|
||||
dizqueTV
|
||||
<small class="pull-right" style="padding: 5px;">
|
||||
<a href="https://github.com/vexorian/dizquetv" title='Git Repository'>
|
||||
<span class="fab fa-github text-sm"></span>
|
||||
|
||||
@ -356,3 +356,10 @@ div.programming-programs div.list-group-item {
|
||||
.watermark-preview .alternate-aspect {
|
||||
background : rgba(255,255,255, 0.1);
|
||||
}
|
||||
|
||||
|
||||
#dizquetv-logo {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user