Improvements.
Sign in/Urls: Sign in now adds all servers; however refresh guide/refresh channels must be edited through the json. Now uses local/remote server https url instead of local 34440 port url with no cert. Video Playback: Remove code not enforcing time limit for streams. Make default stream protocol http instread of hls which was used previously. Add option to choose. Add option to specify video codecs (which is prone to user error). Added mpeg2video to default video codecs. Add option to specify direct stream/transcode media buffer size. Not sure how much of a difference this makes. Add in safeguard to ffmpeg's kill so failed streams don't crash the application M3Us: Add group-title="PseudoTV" for easier management in xteve
This commit is contained in:
parent
8555dfaacb
commit
ba8673e3b4
7
index.js
7
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'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
11
src/api.js
11
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)
|
||||
|
||||
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
23
src/plex.js
23
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
|
||||
module.exports = Plex
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
})
|
||||
|
||||
@ -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"}
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,54 +1,16 @@
|
||||
<div>
|
||||
<h5>Plex Settings</h5>
|
||||
<h6 ng-init="visible = false">Plex Servers
|
||||
<button ng-hide="visible" class="pull-right btn btn-sm btn-primary" ng-click="toggleVisiblity()">
|
||||
<span class="fa fa-plus"></span>
|
||||
<h6>Plex Servers
|
||||
<button class="pull-right btn btn-sm btn-success" style="margin-bottom:10px;" ng-disabled="isProcessing" ng-click="addPlexServer()">
|
||||
Sign In/Add Servers
|
||||
</button>
|
||||
</h6>
|
||||
<div ng-if="visible">
|
||||
<form>
|
||||
<h6>Add a Plex Server
|
||||
<span class="pull-right text-danger">{{error}}</span>
|
||||
<div ng-if="isProcessing">
|
||||
<br>
|
||||
<h6>
|
||||
<span class="pull-right text-info">{{ isProcessing ? 'You have 2 minutes to sign into your Plex Account.' : ''}}</span>
|
||||
</h6>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-sm-2">
|
||||
<select class="form-control form-control-sm" ng-model="plex.protocol">
|
||||
<option value="http">http</option>
|
||||
<option value="https">https</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-sm-5">
|
||||
<input class="form-control form-control-sm" type="text" ng-model="plex.host" ng-disabled="isProcessing" placeholder="Use network address. Do NOT use 127.0.0.1 or localhost)"/>
|
||||
</div>
|
||||
<div class="form-group col-sm-5">
|
||||
<input class="form-control form-control-sm" type="text" ng-model="plex.port" ng-disabled="isProcessing" placeholder="Plex port"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-sm-4">
|
||||
<div class="form-control form-control-sm">
|
||||
<input id="arGuide" type="checkbox" ng-model="plex.arGuide" ng-disabled="isProcessing">
|
||||
<label for="arGuide">Auto Refresh Guide</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-sm-4">
|
||||
<div class="form-control form-control-sm">
|
||||
<input id="arChannels" type="checkbox" ng-model="plex.arChannels" ng-disabled="isProcessing">
|
||||
<label for="arChannels">Auto Map Channels</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-sm-4">
|
||||
<span class="pull-right">
|
||||
<button class="btn btn-sm btn-link" type="button" ng-click="toggleVisiblity()" ng-disabled="isProcessing">Cancel</button>
|
||||
<input class="btn btn-sm btn-success" type="submit" ng-click="addPlexServer(plex)" ng-disabled="isProcessing" value="Sign In/Add Server"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<p class="text-danger text-center">
|
||||
<b>WARNING - Do not check "Auto Map Channels" unless the PseudoTV tuner is added to this specific Plex server.</b>
|
||||
</p>
|
||||
<br>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
@ -65,7 +27,7 @@
|
||||
</tr>
|
||||
<tr ng-repeat="x in servers">
|
||||
<td>{{ x.name }}</td>
|
||||
<td>{{ x.protocol }}://{{ x.host }}:{{ x.port }}</td>
|
||||
<td>{{ x.uri }}</td>
|
||||
<td>{{ x.arGuide }}</td>
|
||||
<td>{{ x.arChannels }}</td>
|
||||
<td>
|
||||
@ -90,6 +52,10 @@
|
||||
<div class="row" >
|
||||
<div class="col-sm-6">
|
||||
<h6 style="font-weight: bold">Video Options</h6>
|
||||
<div class="form-group">
|
||||
<label>Supported Video Formats</label>
|
||||
<input type="text" class="form-control form-control-sm" ng-model="settings.videoCodecs" ria-describedby="videoCodecsHelp"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Max Playable Resolution</label>
|
||||
<select ng-model="settings.maxPlayableResolution"
|
||||
@ -100,10 +66,6 @@
|
||||
<select ng-model="settings.maxTranscodeResolution"
|
||||
ng-options="o.id as o.description for o in resolutionOptions "/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input id="enableHEVC" type="checkbox" ng-model="settings.enableHEVC"/>
|
||||
<label for="enableHEVC">Enable H265</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h6 style="font-weight: bold">Audio Options</h6>
|
||||
@ -130,11 +92,24 @@
|
||||
<label>Max Transcode Bitrate</label>
|
||||
<input type="text" class="form-control form-control-sm" ng-model="settings.transcodeBitrate" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Direct Stream Media Buffer Size</label>
|
||||
<input type="text" class="form-control form-control-sm" ng-model="settings.mediaBufferSize" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Transcode Media Buffer Size</label>
|
||||
<input type="text" class="form-control form-control-sm" ng-model="settings.transcodeMediaBufferSize" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input id="updatePlayStatus" type="checkbox" ng-model="settings.updatePlayStatus" ria-describedby="updatePlayStatusHelp"/>
|
||||
<label for="updatePlayStatus">Send play status to Plex</label>
|
||||
<small id="updatePlayStatusHelp" class="form-text text-muted">Note: This affects the "on deck" for your plex account.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Stream Protocol</label>
|
||||
<select ng-model="settings.streamProtocol"
|
||||
ng-options="o.id as o.description for o in streamProtocols" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h6 style="font-weight: bold">Subtitle Options</h6>
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
const Plex = require('../../src/plex')
|
||||
const Plex = require('../../src/plex');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
module.exports = function ($http, $window, $interval) {
|
||||
return {
|
||||
login: async (plex) => {
|
||||
var client = new Plex({ protocol: plex.protocol, host: plex.host, port: plex.port })
|
||||
//const res = await client.SignIn(plex.username, plex.password)
|
||||
login: async () => {
|
||||
const headers = {
|
||||
'Accept': 'application/json',
|
||||
'X-Plex-Product': 'PseudoTV',
|
||||
'X-Plex-Version': 'Plex OAuth',
|
||||
'X-Plex-Client-Identifier': 'rg14zekk3pa5zp4safjwaa8z',
|
||||
'X-Plex-Model': 'Plex OAuth'
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: 'https://plex.tv/api/v2/pins?strong=true',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Plex-Product': 'PseudoTV',
|
||||
'X-Plex-Version': 'Plex OAuth',
|
||||
'X-Plex-Client-Identifier': 'rg14zekk3pa5zp4safjwaa8z',
|
||||
'X-Plex-Model': 'Plex OAuth'
|
||||
}
|
||||
headers: headers
|
||||
}).then((res) => {
|
||||
$window.open('https://app.plex.tv/auth/#!?clientID=rg14zekk3pa5zp4safjwaa8z&context[device][version]=Plex OAuth&context[device][model]=Plex OAuth&code=' + res.data.code + '&context[device][product]=Plex Web')
|
||||
let limit = 120000 // 2 minute time out limit
|
||||
@ -23,13 +25,7 @@ module.exports = function ($http, $window, $interval) {
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: `https://plex.tv/api/v2/pins/${res.data.id}`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Plex-Product': 'PseudoTV',
|
||||
'X-Plex-Version': 'Plex OAuth',
|
||||
'X-Plex-Client-Identifier': 'rg14zekk3pa5zp4safjwaa8z',
|
||||
'X-Plex-Model': 'Plex OAuth'
|
||||
}
|
||||
headers: headers
|
||||
}).then(async (r2) => {
|
||||
limit -= poll
|
||||
if (limit <= 0) {
|
||||
@ -38,11 +34,30 @@ module.exports = function ($http, $window, $interval) {
|
||||
}
|
||||
if (r2.data.authToken !== null) {
|
||||
$interval.cancel(interval)
|
||||
client._token = r2.data.authToken
|
||||
try {
|
||||
const _res = await client.Get('/')
|
||||
res.name = _res.friendlyName
|
||||
res.token = client._token
|
||||
try {
|
||||
headers['X-Plex-Token'] = r2.data.authToken
|
||||
let res_servers = []
|
||||
const getServers = await fetch(`https://plex.tv/api/v2/resources?includeHttps=1`, {
|
||||
method: 'GET', headers: headers
|
||||
});
|
||||
const servers = await getServers.json();
|
||||
|
||||
servers.forEach((server) => {
|
||||
// not pms, skip
|
||||
if (server.provides != `server`)
|
||||
return;
|
||||
|
||||
// true = local server, false = remote
|
||||
const i = (server.publicAddressMatches == true) ? 0 : 2
|
||||
server.uri = server.connections[i].uri
|
||||
server.protocol = server.connections[i].protocol
|
||||
server.address = server.connections[i].address
|
||||
server.port = server.connections[i].port
|
||||
|
||||
res_servers.push(server);
|
||||
});
|
||||
|
||||
res.servers = res_servers
|
||||
resolve(res)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
@ -68,7 +83,7 @@ module.exports = function ($http, $window, $interval) {
|
||||
sections.push({
|
||||
title: res.Directory[i].title,
|
||||
key: `/library/sections/${res.Directory[i].key}/all`,
|
||||
icon: `${server.protocol}://${server.host}:${server.port}${res.Directory[i].composite}?X-Plex-Token=${server.token}`,
|
||||
icon: `${server.uri}${res.Directory[i].composite}?X-Plex-Token=${server.accessToken}`,
|
||||
type: res.Directory[i].type
|
||||
})
|
||||
return sections
|
||||
@ -82,7 +97,7 @@ module.exports = function ($http, $window, $interval) {
|
||||
playlists.push({
|
||||
title: res.Metadata[i].title,
|
||||
key: res.Metadata[i].key,
|
||||
icon: `${server.protocol}://${server.host}:${server.port}${res.Metadata[i].composite}?X-Plex-Token=${server.token}`,
|
||||
icon: `${server.uri}${res.Metadata[i].composite}?X-Plex-Token=${server.accessToken}`,
|
||||
duration: res.Metadata[i].duration
|
||||
})
|
||||
return playlists
|
||||
@ -93,7 +108,7 @@ module.exports = function ($http, $window, $interval) {
|
||||
let streams = res.Metadata[0].Media[0].Part[0].Stream
|
||||
for (let i = 0, l = streams.length; i < l; i++) {
|
||||
if (typeof streams[i].key !== 'undefined') {
|
||||
streams[i].key = `${server.protocol}://${server.host}:${server.port}${streams[i].key}?X-Plex-Token=${server.token}`
|
||||
streams[i].key = `${server.uri}${streams[i].key}?X-Plex-Token=${server.accessToken}`
|
||||
}
|
||||
}
|
||||
return streams
|
||||
@ -114,7 +129,7 @@ module.exports = function ($http, $window, $interval) {
|
||||
key: res.Metadata[i].key,
|
||||
ratingKey: res.Metadata[i].ratingKey,
|
||||
server: server,
|
||||
icon: `${server.protocol}://${server.host}:${server.port}${res.Metadata[i].thumb}?X-Plex-Token=${server.token}`,
|
||||
icon: `${server.uri}${res.Metadata[i].thumb}?X-Plex-Token=${server.accessToken}`,
|
||||
type: res.Metadata[i].type,
|
||||
duration: res.Metadata[i].duration,
|
||||
actualDuration: res.Metadata[i].duration,
|
||||
@ -129,10 +144,10 @@ module.exports = function ($http, $window, $interval) {
|
||||
program.showTitle = res.Metadata[i].grandparentTitle
|
||||
program.episode = res.Metadata[i].index
|
||||
program.season = res.Metadata[i].parentIndex
|
||||
program.icon = `${server.protocol}://${server.host}:${server.port}${res.Metadata[i].grandparentThumb}?X-Plex-Token=${server.token}`
|
||||
program.episodeIcon = `${server.protocol}://${server.host}:${server.port}${res.Metadata[i].thumb}?X-Plex-Token=${server.token}`
|
||||
program.seasonIcon = `${server.protocol}://${server.host}:${server.port}${res.Metadata[i].parentThumb}?X-Plex-Token=${server.token}`
|
||||
program.showIcon = `${server.protocol}://${server.host}:${server.port}${res.Metadata[i].grandparentThumb}?X-Plex-Token=${server.token}`
|
||||
program.icon = `${server.uri}${res.Metadata[i].grandparentThumb}?X-Plex-Token=${server.accessToken}`
|
||||
program.episodeIcon = `${server.uri}${res.Metadata[i].thumb}?X-Plex-Token=${server.accessToken}`
|
||||
program.seasonIcon = `${server.uri}${res.Metadata[i].parentThumb}?X-Plex-Token=${server.accessToken}`
|
||||
program.showIcon = `${server.uri}${res.Metadata[i].grandparentThumb}?X-Plex-Token=${server.accessToken}`
|
||||
}
|
||||
else if (program.type === 'movie') {
|
||||
program.showTitle = res.Metadata[i].title
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user