Merge pull request #44 from jordankoehn/improvements

Improvements.
This commit is contained in:
Austin Tinius 2020-06-02 16:42:30 -07:00 committed by Jordan Koehn
commit 8fca6cdce1
9 changed files with 135 additions and 156 deletions

View File

@ -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'
})
}

View File

@ -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)

View File

@ -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')
}
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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();
})

View File

@ -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"}
];
}
};
}
}

View File

@ -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>

View File

@ -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