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:
Jordan Koehn 2020-06-01 22:00:08 +00:00
parent 8555dfaacb
commit ba8673e3b4
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