commit
8fca6cdce1
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