* Fix Plex mobile apps spamming a new notification every time a video plays.
* Loading Screen. * Minor log improvements. * Minor defaults improvements. * FFMPEG concat process to use 1 thread. * Fix av1 bug when plex has to transcode the audio. * /m3u8 endpoint
This commit is contained in:
parent
db70e56129
commit
b54b5d9112
20
index.js
20
index.js
@ -15,7 +15,16 @@ const Plex = require('./src/plex');
|
||||
const channelCache = require('./src/channel-cache');
|
||||
const constants = require('./src/constants')
|
||||
|
||||
console.log("dizqueTV Version: " + constants.VERSION_NAME)
|
||||
console.log(
|
||||
` \\
|
||||
dizqueTV ${constants.VERSION_NAME}
|
||||
.------------.
|
||||
|###:::||| o |
|
||||
|###:::||| |
|
||||
'###:::||| o |
|
||||
'------------'
|
||||
`);
|
||||
|
||||
|
||||
for (let i = 0, l = process.argv.length; i < l; i++) {
|
||||
if ((process.argv[i] === "-p" || process.argv[i] === "--port") && i + 1 !== l)
|
||||
@ -37,7 +46,7 @@ if (!fs.existsSync(process.env.DATABASE)) {
|
||||
if(!fs.existsSync(path.join(process.env.DATABASE, 'images')))
|
||||
fs.mkdirSync(path.join(process.env.DATABASE, 'images'))
|
||||
|
||||
db.connect(process.env.DATABASE, ['channels', 'plex-servers', 'ffmpeg-settings', 'plex-settings', 'xmltv-settings', 'hdhr-settings', 'db-version'])
|
||||
db.connect(process.env.DATABASE, ['channels', 'plex-servers', 'ffmpeg-settings', 'plex-settings', 'xmltv-settings', 'hdhr-settings', 'db-version', 'client-id'])
|
||||
|
||||
initDB(db)
|
||||
|
||||
@ -65,7 +74,7 @@ let xmltvInterval = {
|
||||
if (plexServers[i].arChannels && channels.length !== 0)
|
||||
plex.RefreshChannels(channels, dvrs).then(() => { }, (err) => { console.error(err, i) })
|
||||
}).catch( (err) => {
|
||||
console.error("There was an error when fetching Plex DVRs. This means dizqueTV couldn't trigger Plex to update its TV guide." + err);
|
||||
console.log("Couldn't tell Plex to refresh channels for some reason.");
|
||||
});
|
||||
}
|
||||
}, (err) => {
|
||||
@ -123,6 +132,9 @@ function initDB(db) {
|
||||
let data = fs.readFileSync(path.resolve(path.join(__dirname, 'resources/generic-offline-screen.png')))
|
||||
fs.writeFileSync(process.env.DATABASE + '/images/generic-offline-screen.png', data)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(process.env.DATABASE + '/images/loading-screen.png')) {
|
||||
let data = fs.readFileSync(path.resolve(path.join(__dirname, 'resources/loading-screen.png')))
|
||||
fs.writeFileSync(process.env.DATABASE + '/images/loading-screen.png', data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BIN
resources/loading-screen.png
Normal file
BIN
resources/loading-screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@ -17,15 +17,29 @@
|
||||
* but with time it will be worth it, really.
|
||||
*
|
||||
***/
|
||||
const TARGET_VERSION = 300;
|
||||
const TARGET_VERSION = 400;
|
||||
|
||||
const STEPS = [
|
||||
// [v, v2, x] : if the current version is v, call x(db), and version becomes v2
|
||||
[ 0, 100, (db) => basicDB(db) ],
|
||||
[ 100, 200, (db) => commercialsRemover(db) ],
|
||||
[ 200, 300, (db) => appNameChange(db) ],
|
||||
[ 300, 400, (db) => createDeviceId(db) ],
|
||||
]
|
||||
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
function createDeviceId(db) {
|
||||
let deviceId = db['client-id'].find();
|
||||
if (deviceId.length == 0) {
|
||||
let clientId = uuidv4().replace(/-/g,"").slice(0,16) + "-org-dizquetv-" + process.platform
|
||||
let dev = {
|
||||
clientId: clientId,
|
||||
}
|
||||
db['client-id'].save( dev );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function appNameChange(db) {
|
||||
let xmltv = db['xmltv-settings'].find()
|
||||
@ -66,7 +80,7 @@ function basicDB(db) {
|
||||
maxPlayableResolution: "1920x1080",
|
||||
maxTranscodeResolution: "1920x1080",
|
||||
videoCodecs: 'h264,hevc,mpeg2video',
|
||||
audioCodecs: 'ac3',
|
||||
audioCodecs: 'ac3,aac',
|
||||
maxAudioChannels: '2',
|
||||
audioBoost: '100',
|
||||
enableSubtitles: false,
|
||||
@ -105,7 +119,7 @@ function basicDB(db) {
|
||||
let hdhrSettings = db['hdhr-settings'].find()
|
||||
if (hdhrSettings.length === 0) {
|
||||
db['hdhr-settings'].save({
|
||||
tunerCount: 1,
|
||||
tunerCount: 2,
|
||||
autoDiscovery: true
|
||||
})
|
||||
}
|
||||
|
||||
@ -73,8 +73,9 @@ class FFMPEG extends events.EventEmitter {
|
||||
this.spawn( {errorTitle: 'offline'}, streamStats, undefined, `${duration}ms`, true, false, 'offline', false);
|
||||
}
|
||||
async spawn(streamUrl, streamStats, startTime, duration, limitRead, enableIcon, type, isConcatPlaylist) {
|
||||
|
||||
let ffmpegArgs = [
|
||||
`-threads`, this.opts.threads,
|
||||
`-threads`, isConcatPlaylist? 1 : this.opts.threads,
|
||||
`-fflags`, `+genpts+discardcorrupt+igndts`];
|
||||
|
||||
if (limitRead === true)
|
||||
@ -94,6 +95,17 @@ class FFMPEG extends events.EventEmitter {
|
||||
|
||||
//TODO: Do something about missing audio stream
|
||||
if (!isConcatPlaylist) {
|
||||
let inputFiles = 0;
|
||||
let audioFile = -1;
|
||||
let videoFile = -1;
|
||||
let overlayFile = -1;
|
||||
if ( typeof(streamUrl.errorTitle) === 'undefined') {
|
||||
ffmpegArgs.push(`-i`, streamUrl);
|
||||
videoFile = inputFiles++;
|
||||
audioFile = videoFile;
|
||||
}
|
||||
|
||||
|
||||
// When we have an individual stream, there is a pipeline of possible
|
||||
// filters to apply.
|
||||
//
|
||||
@ -108,8 +120,8 @@ class FFMPEG extends events.EventEmitter {
|
||||
// Initially, videoComplex does nothing besides assigning the label
|
||||
// to the input stream
|
||||
var videoIndex = 'v';
|
||||
var audioComplex = `;[0:${audioIndex}]anull[audio]`;
|
||||
var videoComplex = `;[0:${videoIndex}]null[video]`;
|
||||
var audioComplex = `;[${audioFile}:${audioIndex}]anull[audio]`;
|
||||
var videoComplex = `;[${videoFile}:${videoIndex}]null[video]`;
|
||||
// Depending on the options we will apply multiple filters
|
||||
// each filter modifies the current video stream. Adds a filter to
|
||||
// the videoComplex variable. The result of the filter becomes the
|
||||
@ -197,11 +209,10 @@ class FFMPEG extends events.EventEmitter {
|
||||
audioComplex += ';[audioy]arealtime[audiox]';
|
||||
currentVideo = "[videox]";
|
||||
currentAudio = "[audiox]";
|
||||
} else {
|
||||
ffmpegArgs.push(`-i`, streamUrl);
|
||||
}
|
||||
if (doOverlay) {
|
||||
ffmpegArgs.push(`-i`, `${this.channel.icon}` );
|
||||
overlayFile = inputFiles++;
|
||||
}
|
||||
|
||||
// Resolution fix: Add scale filter, current stream becomes [siz]
|
||||
@ -223,7 +234,7 @@ class FFMPEG extends events.EventEmitter {
|
||||
if (this.channel.iconDuration > 0)
|
||||
icnDur = `:enable='between(t,0,${this.channel.iconDuration})'`
|
||||
|
||||
videoComplex += `;[1:v]scale=${this.channel.iconWidth}:-1[icn];${currentVideo}[icn]overlay=${posAry[this.channel.iconPosition]}${icnDur}[comb]`
|
||||
videoComplex += `;[${overlayFile}:v]scale=${this.channel.iconWidth}:-1[icn];${currentVideo}[icn]overlay=${posAry[this.channel.iconPosition]}${icnDur}[comb]`
|
||||
currentVideo = '[comb]';
|
||||
}
|
||||
if (this.volumePercent != 100) {
|
||||
@ -250,14 +261,14 @@ class FFMPEG extends events.EventEmitter {
|
||||
transcodeVideo = true; //this is useful so that it adds some lines below
|
||||
filterComplex += videoComplex;
|
||||
} else {
|
||||
currentVideo = `0:${videoIndex}`;
|
||||
currentVideo = `${videoFile}:${videoIndex}`;
|
||||
}
|
||||
// same with audio:
|
||||
if (currentAudio != '[audio]') {
|
||||
transcodeAudio = true;
|
||||
filterComplex += audioComplex;
|
||||
} else {
|
||||
currentAudio = `0:${audioIndex}`;
|
||||
currentAudio = `${audioFile}:${audioIndex}`;
|
||||
}
|
||||
|
||||
//If there is a filter complex, add it.
|
||||
@ -309,7 +320,7 @@ class FFMPEG extends events.EventEmitter {
|
||||
} else {
|
||||
//Concat stream is simpler and should always copy the codec
|
||||
ffmpegArgs.push(
|
||||
`-probesize`, `100000000`,
|
||||
`-probesize`, 32 /*`100000000`*/,
|
||||
`-i`, streamUrl,
|
||||
`-map`, `0:v`,
|
||||
`-map`, `0:${audioIndex}`,
|
||||
|
||||
@ -13,6 +13,11 @@ class OfflinePlayer {
|
||||
constructor(error, context) {
|
||||
this.context = context;
|
||||
this.error = error;
|
||||
if (context.isLoading === true) {
|
||||
context.channel = JSON.parse( JSON.stringify(context.channel) );
|
||||
context.channel.offlinePicture = `http://localhost:${process.env.PORT}/images/loading-screen.png`;
|
||||
context.channel.offlineSoundtrack = undefined;
|
||||
}
|
||||
this.ffmpeg = new FFMPEG(context.ffmpegSettings, context.channel);
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,8 @@ const EventEmitter = require('events');
|
||||
const helperFuncs = require('./helperFuncs')
|
||||
const FFMPEG = require('./ffmpeg')
|
||||
|
||||
let USED_CLIENTS = {};
|
||||
|
||||
class PlexPlayer {
|
||||
|
||||
constructor(context) {
|
||||
@ -17,9 +19,17 @@ class PlexPlayer {
|
||||
this.ffmpeg = null;
|
||||
this.plexTranscoder = null;
|
||||
this.killed = false;
|
||||
let coreClientId = this.context.db['client-id'].find()[0].clientId;
|
||||
let i = 0;
|
||||
while ( USED_CLIENTS[coreClientId+"-"+i]===true) {
|
||||
i++;
|
||||
}
|
||||
this.clientId = coreClientId+"-"+i;
|
||||
USED_CLIENTS[this.clientId] = true;
|
||||
}
|
||||
|
||||
cleanUp() {
|
||||
USED_CLIENTS[this.clientId] = false;
|
||||
this.killed = true;
|
||||
if (this.plexTranscoder != null) {
|
||||
this.plexTranscoder.stopUpdatingPlex();
|
||||
@ -39,7 +49,7 @@ class PlexPlayer {
|
||||
|
||||
try {
|
||||
let plexSettings = db['plex-settings'].find()[0];
|
||||
let plexTranscoder = new PlexTranscoder(plexSettings, channel, lineupItem);
|
||||
let plexTranscoder = new PlexTranscoder(this.clientId, plexSettings, channel, lineupItem);
|
||||
this.plexTranscoder = plexTranscoder;
|
||||
let enableChannelIcon = this.context.enableChannelIcon;
|
||||
let ffmpeg = new FFMPEG(ffmpegSettings, channel); // Set the transcoder options
|
||||
|
||||
@ -2,12 +2,12 @@ const { v4: uuidv4 } = require('uuid');
|
||||
const axios = require('axios');
|
||||
|
||||
class PlexTranscoder {
|
||||
constructor(settings, channel, lineupItem) {
|
||||
constructor(clientId, settings, channel, lineupItem) {
|
||||
this.session = uuidv4()
|
||||
|
||||
this.device = "channel-" + channel.number;
|
||||
this.deviceName = this.device;
|
||||
this.clientIdentifier = this.session.replace(/-/g,"").slice(0,16) + "-org-dizquetv-" + process.platform;
|
||||
this.clientIdentifier = clientId;
|
||||
this.product = "dizqueTV";
|
||||
|
||||
this.settings = settings
|
||||
@ -60,7 +60,10 @@ class PlexTranscoder {
|
||||
stream.directPlay = true;
|
||||
}
|
||||
}
|
||||
if (stream.directPlay) {
|
||||
if (stream.directPlay || this.isAV1() ) {
|
||||
if (! stream.directPlay) {
|
||||
this.log("Plex doesn't support av1, so we are forcing direct play, including for audio because otherwise plex breaks the stream.")
|
||||
}
|
||||
this.log("Direct play forced or native paths enabled")
|
||||
stream.directPlay = true
|
||||
this.setTranscodingArgs(stream.directPlay, true, false)
|
||||
@ -78,14 +81,15 @@ class PlexTranscoder {
|
||||
await this.getDecision(stream.directPlay);
|
||||
stream.streamUrl = `${this.transcodeUrlBase}${this.transcodingArgs}`
|
||||
} else {
|
||||
//This case sounds complex. Apparently plex is sending us just the audio, so we would need to get the video in a separate stream.
|
||||
this.log("Decision: Direct stream. Audio is being transcoded")
|
||||
stream.separateVideoStream = (this.settings.streamPath === 'direct') ? this.file : this.plexFile;
|
||||
stream.streamUrl = `${this.transcodeUrlBase}${this.transcodingArgs}`
|
||||
}
|
||||
stream.streamStats = this.getVideoStats();
|
||||
|
||||
// use correct audio stream if direct play
|
||||
let audioIndex = await this.getAudioIndex();
|
||||
stream.streamStats.audioIndex = (stream.directPlay) ? audioIndex : 'a'
|
||||
stream.streamStats.audioIndex = (stream.directPlay) ? ( await this.getAudioIndex() ) : 'a'
|
||||
|
||||
this.log(stream)
|
||||
|
||||
@ -178,6 +182,14 @@ lang=en`
|
||||
}
|
||||
}
|
||||
|
||||
isAV1() {
|
||||
try {
|
||||
return this.getVideoStats().videoCodec === 'av1';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
isDirectPlay() {
|
||||
try {
|
||||
return this.getVideoStats().videoDecision === "copy" && this.getVideoStats().audioDecision === "copy";
|
||||
|
||||
@ -32,6 +32,11 @@ class ProgramPlayer {
|
||||
if (program.err instanceof Error) {
|
||||
console.log("About to play error stream");
|
||||
this.delegate = new OfflinePlayer(true, context);
|
||||
} else if (program.type === 'loading') {
|
||||
console.log("About to play loading stream");
|
||||
/* loading */
|
||||
context.isLoading = true;
|
||||
this.delegate = new OfflinePlayer(false, context);
|
||||
} else if (program.type === 'offline') {
|
||||
console.log("About to play offline stream");
|
||||
/* offline */
|
||||
|
||||
107
src/svg/loading-screen.svg
Normal file
107
src/svg/loading-screen.svg
Normal file
@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="1920"
|
||||
height="1080"
|
||||
viewBox="0 0 507.99999 285.75001"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||
sodipodi:docname="loading-screen.svg"
|
||||
inkscape:export-filename="/home/vx/dev/pseudotv/resources/loading-screen.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.37433674"
|
||||
inkscape:cx="1004.7641"
|
||||
inkscape:cy="545.11626"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1056"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1"
|
||||
units="px" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Capa 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-11.249983)">
|
||||
<rect
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.20000029;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect836"
|
||||
width="508"
|
||||
height="285.75"
|
||||
x="0"
|
||||
y="11.249983" />
|
||||
<g
|
||||
id="g6050"
|
||||
transform="translate(-8.4960767,30.053154)">
|
||||
<rect
|
||||
transform="rotate(0.52601418)"
|
||||
y="85.000603"
|
||||
x="214.56714"
|
||||
height="73.832573"
|
||||
width="32.814484"
|
||||
id="rect4518"
|
||||
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" />
|
||||
<rect
|
||||
transform="rotate(1.4727575)"
|
||||
style="opacity:1;fill:#289bbc;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4520"
|
||||
width="32.81448"
|
||||
height="73.832573"
|
||||
x="248.74632"
|
||||
y="80.901688" />
|
||||
<rect
|
||||
transform="rotate(-3.2986121)"
|
||||
y="103.78287"
|
||||
x="269.35843"
|
||||
height="73.832588"
|
||||
width="32.814476"
|
||||
id="rect4522"
|
||||
style="opacity:1;fill:#bc289b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="228.70648"
|
||||
y="210.99644"
|
||||
id="text939"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan937"
|
||||
x="228.70648"
|
||||
y="210.99644"
|
||||
style="fill:#f9f9f9;stroke-width:0.26458332px">Loading...</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
58
src/video.js
58
src/video.js
@ -143,6 +143,11 @@ function video(db) {
|
||||
res.status(404).send("Channel doesn't exist")
|
||||
return
|
||||
}
|
||||
let isLoading = false;
|
||||
if ( (typeof req.query.first !== 'undefined') && (req.query.first=='0') ) {
|
||||
isLoading = true;
|
||||
}
|
||||
|
||||
let isFirst = false;
|
||||
if ( (typeof req.query.first !== 'undefined') && (req.query.first=='1') ) {
|
||||
isFirst = true;
|
||||
@ -164,7 +169,14 @@ function video(db) {
|
||||
// Get video lineup (array of video urls with calculated start times and durations.)
|
||||
let t0 = (new Date()).getTime();
|
||||
let lineupItem = channelCache.getCurrentLineupItem( channel.number, t0);
|
||||
if (lineupItem == null) {
|
||||
if (isLoading) {
|
||||
lineupItem = {
|
||||
type: 'loading',
|
||||
streamDuration: 1000,
|
||||
duration: 1000,
|
||||
start: 0,
|
||||
};
|
||||
} else if (lineupItem == null) {
|
||||
let prog = helperFuncs.getCurrentProgramAndTimeElapsed(t0, channel)
|
||||
|
||||
if (prog.program.isOffline && channel.programs.length == 1) {
|
||||
@ -207,7 +219,9 @@ function video(db) {
|
||||
}
|
||||
console.log("=========================================================");
|
||||
|
||||
channelCache.recordPlayback(channel.number, t0, lineupItem);
|
||||
if (! isLoading) {
|
||||
channelCache.recordPlayback(channel.number, t0, lineupItem);
|
||||
}
|
||||
|
||||
let playerContext = {
|
||||
lineupItem : lineupItem,
|
||||
@ -280,6 +294,41 @@ function video(db) {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
router.get('/m3u8', (req, res) => {
|
||||
res.type('text')
|
||||
|
||||
// Check if channel queried is valid
|
||||
if (typeof req.query.channel === 'undefined') {
|
||||
res.status(500).send("No Channel Specified")
|
||||
return
|
||||
}
|
||||
|
||||
let channelNum = parseInt(req.query.channel, 10)
|
||||
let channel = channelCache.getChannelConfig(db, channelNum );
|
||||
if (channel.length === 0) {
|
||||
res.status(500).send("Channel doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
// Maximum number of streams to concatinate beyond channel starting
|
||||
// If someone passes this number then they probably watch too much television
|
||||
let maxStreamsToPlayInARow = 100;
|
||||
|
||||
var data = "#EXTM3U\n"
|
||||
|
||||
let ffmpegSettings = db['ffmpeg-settings'].find()[0]
|
||||
|
||||
if ( ffmpegSettings.enableFFMPEGTranscoding === true) {
|
||||
data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=0\n`;
|
||||
}
|
||||
data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}&first=1\n`
|
||||
for (var i = 0; i < maxStreamsToPlayInARow - 1; i++) {
|
||||
data += `${req.protocol}://${req.get('host')}/stream?channel=${channelNum}\n`
|
||||
}
|
||||
|
||||
res.send(data)
|
||||
})
|
||||
router.get('/playlist', (req, res) => {
|
||||
res.type('text')
|
||||
|
||||
@ -302,6 +351,11 @@ function video(db) {
|
||||
|
||||
var data = "ffconcat version 1.0\n"
|
||||
|
||||
let ffmpegSettings = db['ffmpeg-settings'].find()[0]
|
||||
|
||||
if ( ffmpegSettings.enableFFMPEGTranscoding === 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=1'\n`
|
||||
for (var i = 0; i < maxStreamsToPlayInARow - 1; i++) {
|
||||
data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}'\n`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user