diff --git a/index.js b/index.js index fd9d6e4..a0b8d35 100644 --- a/index.js +++ b/index.js @@ -123,14 +123,17 @@ function initDB(db) { db['plex-settings'].save({ directStreamBitrate: '40000', transcodeBitrate: '3000', + mediaBufferSize: 1000, + transcodeMediaBufferSize: 20000, maxPlayableResolution: "1920x1080", maxTranscodeResolution: "1920x1080", - enableHEVC: true, + videoCodecs: 'h264,hevc,mpeg2video', audioCodecs: 'ac3,aac,mp3', maxAudioChannels: '6', enableSubtitles: false, subtitleSize: '100', - updatePlayStatus: false + updatePlayStatus: false, + streamProtocol: 'http' }) } diff --git a/src/api.js b/src/api.js index 230318b..efba488 100644 --- a/src/api.js +++ b/src/api.js @@ -92,14 +92,17 @@ function api(db, xmltvInterval) { db['plex-settings'].update({ _id: req.body._id }, { directStreamBitrate: '40000', transcodeBitrate: '3000', + mediaBufferSize: 1000, + transcodeMediaBufferSize: 20000, maxPlayableResolution: "1920x1080", maxTranscodeResolution: "1920x1080", - enableHEVC: true, + videoCodecs: 'h264,hevc,mpeg2video', audioCodecs: 'ac3,aac,mp3', maxAudioChannels: '6', enableSubtitles: false, subtitleSize: '100', - updatePlayStatus: false + updatePlayStatus: false, + streamProtocol: 'http' }) let plex = db['plex-settings'].find()[0] res.send(plex) @@ -167,11 +170,11 @@ function api(db, xmltvInterval) { let channels = db['channels'].find() var data = "#EXTM3U\n" for (var i = 0; i < channels.length; i++) { - data += `#EXTINF:0 tvg-id="${channels[i].number}" tvg-name="${channels[i].name}" tvg-logo="${channels[i].icon}",${channels[i].name}\n` + data += `#EXTINF:0 tvg-id="${channels[i].number}" tvg-name="${channels[i].name}" tvg-logo="${channels[i].icon}" group-title="PseudoTV",${channels[i].name}\n` data += `${req.protocol}://${req.get('host')}/video?channel=${channels[i].number}\n` } if (channels.length === 0) { - data += `#EXTINF:0 tvg-id="1" tvg-name="PseudoTV" tvg-logo="https://raw.githubusercontent.com/DEFENDORe/pseudotv/master/resources/pseudotv.png",PseudoTV\n` + data += `#EXTINF:0 tvg-id="1" tvg-name="PseudoTV" tvg-logo="https://raw.githubusercontent.com/DEFENDORe/pseudotv/master/resources/pseudotv.png" group-title="PseudoTV",PseudoTV\n` data += `${req.protocol}://${req.get('host')}/setup\n` } res.send(data) diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 12fc48f..e99416f 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -10,12 +10,11 @@ class FFMPEG extends events.EventEmitter { this.ffmpegPath = opts.ffmpegPath } async spawn(streamUrl, duration, enableIcon, videoResolution) { - let ffmpegArgs = [`-threads`, this.opts.threads]; - - if (duration > 0) - ffmpegArgs.push(`-t`, duration); - - ffmpegArgs.push(`-re`, `-i`, streamUrl); + let ffmpegArgs = [`-threads`, this.opts.threads, + `-t`, duration, + `-re`, + `-fflags`, `+genpts`, + `-i`, streamUrl]; if (enableIcon == true) { if (process.env.DEBUG) console.log('Channel Icon Overlay Enabled') @@ -83,7 +82,9 @@ class FFMPEG extends events.EventEmitter { }) } kill() { - this.ffmpeg.kill() + if (typeof this.ffmpeg != "undefined") { + this.ffmpeg.kill('SIGQUIT') + } } } diff --git a/src/plex.js b/src/plex.js index e9f0fdd..de00333 100644 --- a/src/plex.js +++ b/src/plex.js @@ -1,8 +1,9 @@ const request = require('request') class Plex { constructor(opts) { - this._token = typeof opts.token !== 'undefined' ? opts.token : '' + this._accessToken = typeof opts.accessToken !== 'undefined' ? opts.accessToken : '' this._server = { + uri: typeof opts.uri !== 'undefined' ? opts.uri : 'http://127.0.0.1:32400', 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' @@ -19,7 +20,7 @@ class Plex { } } - get URL() { return `${this._server.protocol}://${this._server.host}:${this._server.port}` } + get URL() { return `${this._server.uri}` } SignIn(username, password) { return new Promise((resolve, reject) => { @@ -41,8 +42,8 @@ class Plex { if (err || res.statusCode !== 201) reject("Plex 'SignIn' Error - Username/Email and Password is incorrect!.") else { - this._token = JSON.parse(body).user.authToken - resolve({ token: this._token }) + this._accessToken = JSON.parse(body).user.authToken + resolve({ accessToken: this._accessToken }) } }) }) @@ -55,9 +56,9 @@ class Plex { jar: false } Object.assign(req, optionalHeaders) - req.headers['X-Plex-Token'] = this._token + req.headers['X-Plex-Token'] = this._accessToken return new Promise((resolve, reject) => { - if (this._token === '') + if (this._accessToken === '') reject("No Plex token provided. Please use the SignIn method or provide a X-Plex-Token in the Plex constructor.") else request(req, (err, res) => { @@ -77,9 +78,9 @@ class Plex { jar: false } Object.assign(req, optionalHeaders) - req.headers['X-Plex-Token'] = this._token + req.headers['X-Plex-Token'] = this._accessToken return new Promise((resolve, reject) => { - if (this._token === '') + if (this._accessToken === '') reject("No Plex token provided. Please use the SignIn method or provide a X-Plex-Token in the Plex constructor.") else request(req, (err, res) => { @@ -99,9 +100,9 @@ class Plex { jar: false } Object.assign(req, optionalHeaders) - req.headers['X-Plex-Token'] = this._token + req.headers['X-Plex-Token'] = this._accessToken return new Promise((resolve, reject) => { - if (this._token === '') + if (this._accessToken === '') reject("No Plex token provided. Please use the SignIn method or provide a X-Plex-Token in the Plex constructor.") else request(req, (err, res) => { @@ -140,4 +141,4 @@ class Plex { } } -module.exports = Plex \ No newline at end of file +module.exports = Plex diff --git a/src/plexTranscoder.js b/src/plexTranscoder.js index 83615cf..21b00f1 100644 --- a/src/plexTranscoder.js +++ b/src/plexTranscoder.js @@ -36,50 +36,50 @@ class PlexTranscoder { await this.getDecision(); } - return `${this.server.protocol}://${this.server.host}:${this.server.port}/video/:/transcode/universal/start.m3u8?${this.transcodingArgs}` + return `${this.server.uri}/video/:/transcode/universal/start.m3u8?${this.transcodingArgs}` } setTranscodingArgs(directStream, deinterlace) { let resolution = (directStream == true) ? this.settings.maxPlayableResolution : this.settings.maxTranscodeResolution let bitrate = (directStream == true) ? this.settings.directStreamBitrate : this.settings.transcodeBitrate - let videoCodecs = (this.settings.enableHEVC == true) ? "h264,hevc" : "h264" + let mediaBufferSize = (directStream == true) ? this.settings.mediaBufferSize : this.settings.transcodeMediaBufferSize let subtitles = (this.settings.enableSubtitles == true) ? "burn" : "none" // subtitle options: burn, none, embedded, sidecar - + let streamContainer = "mpegts" // Other option is mkv, mkv has the option of copying it's subs for later processing + let videoQuality=`100` // Not sure how this applies, maybe this works if maxVideoBitrate is not set let audioBoost=`100` // only applies when downmixing to stereo I believe, add option later? - let mediaBufferSize=`30720` // Not sure what this should be set to let profileName=`Generic` // Blank profile, everything is specified through X-Plex-Client-Profile-Extra let resolutionArr = resolution.split("x") - let clientProfileHLS=`add-transcode-target(type=videoProfile&protocol=hls&container=mpegts&videoCodec=${videoCodecs}&audioCodec=${this.settings.audioCodecs}&subtitleCodec=&context=streaming&replace=true)+\ -add-transcode-target-settings(type=videoProfile&context=streaming&protocol=hls&CopyMatroskaAttachments=true)+\ + let clientProfile=`add-transcode-target(type=videoProfile&protocol=${this.settings.streamProtocol}&container=${streamContainer}&videoCodec=${this.settings.videoCodecs}&audioCodec=${this.settings.audioCodecs}&subtitleCodec=&context=streaming&replace=true)+\ +add-transcode-target-settings(type=videoProfile&context=streaming&protocol=${this.settings.streamProtocol}&CopyMatroskaAttachments=true)+\ add-limitation(scope=videoCodec&scopeName=*&type=upperBound&name=video.width&value=${resolutionArr[0]})+\ add-limitation(scope=videoCodec&scopeName=*&type=upperBound&name=video.height&value=${resolutionArr[1]})` // Set transcode settings per audio codec this.settings.audioCodecs.split(",").forEach(function (codec) { - clientProfileHLS+=`+add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=hls&audioCodec=${codec})` + clientProfile+=`+add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=${this.settings.streamProtocol}&audioCodec=${codec})` if (codec == "mp3") { - clientProfileHLS+=`+add-limitation(scope=videoAudioCodec&scopeName=${codec}type=upperBound&name=audio.channels&value=2)` + clientProfile+=`+add-limitation(scope=videoAudioCodec&scopeName=${codec}type=upperBound&name=audio.channels&value=2)` } else { - clientProfileHLS+=`+add-limitation(scope=videoAudioCodec&scopeName=${codec}type=upperBound&name=audio.channels&value=${this.settings.maxAudioChannels})` + clientProfile+=`+add-limitation(scope=videoAudioCodec&scopeName=${codec}type=upperBound&name=audio.channels&value=${this.settings.maxAudioChannels})` } }.bind(this)); // deinterlace video if specified, only useful if overlaying channel logo later if (deinterlace == true) { - clientProfileHLS+=`+add-limitation(scope=videoCodec&scopeName=*&type=notMatch&name=video.scanType&value=interlaced)` + clientProfile+=`+add-limitation(scope=videoCodec&scopeName=*&type=notMatch&name=video.scanType&value=interlaced)` } - let clientProfileHLS_enc=encodeURIComponent(clientProfileHLS) + let clientProfile_enc=encodeURIComponent(clientProfile) this.transcodingArgs=`X-Plex-Platform=${profileName}&\ X-Plex-Client-Platform=${profileName}&\ X-Plex-Client-Profile-Name=${profileName}&\ X-Plex-Platform=${profileName}&\ -X-Plex-Token=${this.server.token}&\ -X-Plex-Client-Profile-Extra=${clientProfileHLS_enc}&\ -protocol=hls&\ +X-Plex-Token=${this.server.accessToken}&\ +X-Plex-Client-Profile-Extra=${clientProfile_enc}&\ +protocol=${this.settings.streamProtocol}&\ Connection=keep-alive&\ hasMDE=1&\ path=${this.key}&\ @@ -146,7 +146,7 @@ lang=en` } async getDecision() { - const response = await fetch(`${this.server.protocol}://${this.server.host}:${this.server.port}/video/:/transcode/universal/decision?${this.transcodingArgs}`, { + const response = await fetch(`${this.server.uri}/video/:/transcode/universal/decision?${this.transcodingArgs}`, { method: 'GET', headers: { Accept: 'application/json' } @@ -160,7 +160,7 @@ lang=en` let containerKey=`/video/:/transcode/universal/decision?${this.transcodingArgs}`; let containerKey_enc=encodeURIComponent(containerKey); - let statusUrl=`${this.server.protocol}://${this.server.host}:${this.server.port}/:/timeline?\ + let statusUrl=`${this.server.uri}/:/timeline?\ containerKey=${containerKey_enc}&\ ratingKey=${this.ratingKey}&\ state=${this.playState}&\ @@ -174,7 +174,7 @@ X-Plex-Device-Name=PseudoTV-Plex&\ X-Plex-Device=PseudoTV-Plex&\ X-Plex-Client-Identifier=${this.session}&\ X-Plex-Platform=${profileName}&\ -X-Plex-Token=${this.server.token}`; +X-Plex-Token=${this.server.accessToken}`; return statusUrl; } diff --git a/src/video.js b/src/video.js index d0a6bd0..ff36ae6 100644 --- a/src/video.js +++ b/src/video.js @@ -103,9 +103,6 @@ function video(db) { deinterlace = enableChannelIcon streamDuration = lineupItem.streamDuration / 1000; - // Only episode in this lineup, or item is a commercial, let stream end naturally - if (lineup.length === 0 || lineupItem.type === 'commercial' || lineup.length === 1 && lineup[0].type === 'commercial') - streamDuration = -1 plexTranscoder = new PlexTranscoder(plexSettings, lineupItem); @@ -140,11 +137,7 @@ function video(db) { }) let streamDuration = lineupItem.streamDuration / 1000; - - // Only episode in this lineup, or item is a commercial, let stream end naturally - if (lineup.length === 0 || lineupItem.type === 'commercial' || lineup.length === 1 && lineup[0].type === 'commercial') - streamDuration = -1 - + plexTranscoder.getStreamUrl(deinterlace).then(streamUrl => ffmpeg.spawn(streamUrl, streamDuration, enableChannelIcon, plexTranscoder.getResolutionHeight())); // Spawn the ffmpeg process, fire this bitch up plexTranscoder.startUpdatingPlex(); }) diff --git a/web/directives/plex-settings.js b/web/directives/plex-settings.js index f4c8fcf..17e9f01 100644 --- a/web/directives/plex-settings.js +++ b/web/directives/plex-settings.js @@ -8,34 +8,21 @@ module.exports = function (plex, pseudotv, $timeout) { pseudotv.getPlexServers().then((servers) => { scope.servers = servers }) - scope.plex = { protocol: 'http', host: '', port: '32400', arGuide: false, arChannels: false } - scope.addPlexServer = function (p) { + scope.addPlexServer = function () { scope.isProcessing = true - if (scope.plex.host === '') { - scope.isProcessing = false - scope.error = 'Invalid HOST set' - $timeout(() => { - scope.error = null - }, 3500) - return - } else if (scope.plex.port <= 0) { - scope.isProcessing = false - scope.error = 'Invalid PORT set' - $timeout(() => { - scope.error = null - }, 3500) - return - } - plex.login(p) + plex.login() .then((result) => { - p.token = result.token - p.name = result.name - return pseudotv.addPlexServer(p) + result.servers.forEach((server) => { + // add in additional settings + server.arGuide = true + server.arChannels = false // should not be enabled unless PseudoTV tuner already added to plex + pseudotv.addPlexServer(server) + }); + return pseudotv.getPlexServers() }).then((servers) => { scope.$apply(() => { scope.servers = servers scope.isProcessing = false - scope.visible = false }) }, (err) => { scope.$apply(() => { @@ -53,9 +40,6 @@ module.exports = function (plex, pseudotv, $timeout) { scope.servers = servers }) } - scope.toggleVisiblity = function () { - scope.visible = !scope.visible - } pseudotv.getPlexSettings().then((settings) => { scope.settings = settings }) @@ -88,6 +72,10 @@ module.exports = function (plex, pseudotv, $timeout) { {id:"1920x1080",description:"1920x1080"}, {id:"3840x2160",description:"3840x2160"} ]; + scope.streamProtocols=[ + {id:"http",description:"HTTP"}, + {id:"hls",description:"HLS"} + ]; } }; -} \ No newline at end of file +} diff --git a/web/public/templates/plex-settings.html b/web/public/templates/plex-settings.html index 36e4b4d..879b119 100644 --- a/web/public/templates/plex-settings.html +++ b/web/public/templates/plex-settings.html @@ -1,54 +1,16 @@