Merge branch 'server_con_fix' into vuecli

This commit is contained in:
Travis Shivers 2020-06-07 03:04:32 -05:00
commit 30e161b7f7
8 changed files with 132 additions and 112 deletions

View File

@ -1,3 +1,4 @@
> 1%
last 2 versions
not dead
not ie <= 8

View File

@ -32,7 +32,7 @@ window.EventBus = new Vue();
window.EventBus.$on('command', (data) => {
if (router.app.route.fullPath.indexOf('/player') === -1) {
if (data.command === '/player/timeline/poll') {
return data.callback({
data.callback({
key: null,
ratingKey: null,
time: 0,
@ -41,7 +41,7 @@ window.EventBus.$on('command', (data) => {
duration: 0,
state: 'stopped',
});
} if (data.command === '/player/playback/playMedia') {
} else if (data.command === '/player/playback/playMedia') {
router.push({
path: '/player',
query: {
@ -51,7 +51,8 @@ window.EventBus.$on('command', (data) => {
offset: data.params.offset,
},
});
return data.callback(true);
data.callback(true);
}
}
});

View File

@ -1,16 +1,16 @@
import axios from 'axios';
import parser from 'fast-xml-parser';
import xmlutils from '@/utils/xmlutils';
import plexauth from './helpers/PlexAuth';
import PlexServer from './helpers/PlexServer';
import PlexClient from './helpers/PlexClient';
const PlexAuthMaker = require('./helpers/PlexAuth.js');
const PlexConnection = require('./helpers/PlexConnection.js');
const PlexServer = require('./helpers/PlexServer.js');
const PlexClient = require('./helpers/PlexClient.js');
const PlexAuth = new PlexAuthMaker();
export default {
PLEX_LOGIN_TOKEN: async ({ commit, dispatch, rootGetters }, token) => {
const config = PlexAuth.getRequestConfig(token, 5000);
const config = plexauth.getRequestConfig(token, 5000);
config.headers['X-Plex-Client-Identifier'] = rootGetters['settings/GET_CLIENTIDENTIFIER'];
try {
@ -40,20 +40,18 @@ export default {
}
try {
const { data } = await axios.get('https://plex.tv/api/resources?includeHttps=1', {
...PlexAuth.getRequestConfig(state.user.authToken, 5000),
transformResponse: parser.parse,
...plexauth.getRequestConfig(state.user.authToken, 5000),
transformResponse: xmlutils.parseXML,
});
console.log(data);
data.MediaContainer.Device.forEach(({ $: device, Connection: connections }) => {
data.MediaContainer[0].Device.forEach((device) => {
// Create a temporary array of object:PlexConnection
// Exclude local IPs starting with 169.254
const tempConnectionsArray = connections
.filter(({ $: connection }) => !connection.address.startsWith('169.254'))
.flatMap(({ $: connection }) => {
const tempConnectionsArray = device.Connection
.filter((connection) => !connection.address.startsWith('169.254'))
.flatMap((connection) => {
const tempConnection = new PlexConnection();
Object.assign(tempConnection, connection);
if (connection.local === '1' && connection.uri.indexOf('plex') > -1) {
const rawConnection = new PlexConnection();
Object.assign(rawConnection, connection);
@ -66,6 +64,10 @@ export default {
return [tempConnection];
});
tempConnectionsArray.sort(
(con1, con2) => parseInt(con1.port, 10) - parseInt(con2.port, 10),
);
// If device is a player
if (device.provides.indexOf('player') !== -1) {
// If device is not Plex Web
@ -189,10 +191,12 @@ export default {
throw new Error('Sign in before getting devices');
}
const { data } = await axios.get('https://plex.tv/pms/servers.xml',
PlexAuth.getRequestConfig(token, 5000));
const result = await parser.parse(data);
state.user.servers = result.MediaContainer.Server;
const { data } = await axios.get('https://plex.tv/pms/servers.xml', {
...plexauth.getRequestConfig(token, 5000),
transformResponse: xmlutils.parseXML,
});
state.user.servers = data.MediaContainer.Server;
},
PLEX_CHECK_AUTH: async ({ state, dispatch, getters }, authToken) => {

View File

@ -6,8 +6,8 @@
* @param method
* @returns {{url: *, time: boolean, headers: {X-Plex-Client-Identifier: string, Accept: string, X-Plex-Token: *}, timeout: *, method: *}}
*/
module.exports = function PlexAuth() {
this.getApiOptions = function (url, accessToken, timeout, method) {
export default {
getApiOptions(url, accessToken, timeout, method) {
return {
url,
time: true,
@ -19,9 +19,9 @@ module.exports = function PlexAuth() {
timeout,
method,
};
};
},
this.getRequestConfig = function (accessToken, timeout) {
getRequestConfig(accessToken, timeout) {
return {
headers: {
'X-Plex-Client-Identifier': 'SyncLounge',
@ -30,7 +30,7 @@ module.exports = function PlexAuth() {
},
timeout,
};
};
},
/**
*
@ -40,7 +40,7 @@ module.exports = function PlexAuth() {
* @param timeout
* @returns {{url: *, time: boolean, headers: {X-Plex-Device-Name: string, X-Plex-Client-Identifier: string, X-Plex-Provides: string, X-Plex-Target-Client-Identifier: *}, timeout: *, method: string}}
*/
this.getClientApiOptions = function (clientIdentifier, timeout, token) {
getClientApiOptions(clientIdentifier, timeout, token) {
let sBrowser;
const sUsrAg = navigator.userAgent;
if (sUsrAg.indexOf('Chrome') > -1) {
@ -74,5 +74,5 @@ module.exports = function PlexAuth() {
},
timeout,
};
};
},
};

View File

@ -1,10 +1,9 @@
import axios from 'axios';
import parser from 'fast-xml-parser';
import plexauth from './PlexAuth';
const EventEmitter = require('events');
const _PlexAuth = require('./PlexAuth.js');
const PlexAuth = new _PlexAuth();
const stringSimilarity = require('string-similarity');
class PlexClient {
@ -48,18 +47,18 @@ class PlexClient {
this.commit = null;
this.dispatch = null;
let previousTimeline = {};
const differenceCache = [];
this.previousTimeline = {};
this.differenceCache = [];
this.uuid = this.generateGuid();
}
setValue (key, value) {
setValue(key, value) {
this[key] = value;
this.commit('PLEX_CLIENT_SET_VALUE', [this, key, value]);
};
}
generateGuid () {
generateGuid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
@ -67,9 +66,9 @@ class PlexClient {
}
return `${s4() + s4()}-${s4()}`;
};
}
async hitApi (command, params, connection, needResponse, dontSub) {
async hitApi(command, params, connection, needResponse, dontSub) {
if (this.clientIdentifier === 'PTPLAYER9PLUS10') {
return new Promise(async (resolve, reject) => {
// We are using the SyncLounge Player
@ -105,15 +104,15 @@ class PlexClient {
}
const _url = `${connection.uri + command}?${query}`;
this.setValue('commandId', this.commandId + 1);
const options = PlexAuth.getClientApiOptions(this.clientIdentifier, 5000, this.accessToken);
const options = plexauth.getClientApiOptions(this.clientIdentifier, 5000, this.accessToken);
const { data } = await axios.get(_url, options);
if (needResponse) {
return parser.parse(data);
}
return true;
};
}
getTimeline () {
getTimeline() {
return new Promise(async (resolve, reject) => {
let data;
try {
@ -127,9 +126,9 @@ class PlexClient {
}
});
// Get the timeline object from the client
};
}
updateTimelineObject (result) {
updateTimelineObject(result) {
// Check if we are the SLPlayer
if (this.clientIdentifier === 'PTPLAYER9PLUS10') {
// SLPLAYER
@ -139,10 +138,10 @@ class PlexClient {
},
};
result = tempObj;
if (!previousTimeline.MediaContainer || result.MediaContainer.Timeline[0].ratingKey !== previousTimeline.MediaContainer.Timeline[0].ratingKey) {
if (!this.previousTimeline.MediaContainer || result.MediaContainer.Timeline[0].ratingKey !== this.previousTimeline.MediaContainer.Timeline[0].ratingKey) {
window.EventBus.$emit('PLAYBACK_CHANGE', [this, result.MediaContainer.Timeline[0].ratingKey, result.MediaContainer.Timeline[0]]);
}
previousTimeline = tempObj;
this.previousTimeline = tempObj;
this.lastTimelineObject = result.MediaContainer.Timeline[0];
this.lastTimelineObject.recievedAt = new Date().getTime();
window.EventBus.$emit('NEW_TIMELINE', result.MediaContainer.Timeline[0]);
@ -151,44 +150,44 @@ class PlexClient {
// Standard player
const timelines = result.MediaContainer.Timeline;
let videoTimeline = {};
for (let i = 0; i < timelines.length; i++) {
for (let i = 0; i < timelines.length; i += 1) {
const _timeline = timelines[i].$;
if (_timeline.type === 'video') {
videoTimeline = _timeline;
if (videoTimeline.ratingKey !== previousTimeline.ratingKey) {
if (videoTimeline.ratingKey !== this.previousTimeline.ratingKey) {
window.EventBus.$emit('PLAYBACK_CHANGE', [this, videoTimeline.ratingKey, videoTimeline]);
}
}
}
window.EventBus.$emit('NEW_TIMELINE', videoTimeline);
previousTimeline = videoTimeline;
this.previousTimeline = videoTimeline;
this.lastTimelineObject = videoTimeline;
this.lastTimelineObject.recievedAt = new Date().getTime();
// this.setValue('lastTimelineObject', videoTimeline)
return videoTimeline;
};
}
pressPlay () {
pressPlay() {
// Press play on the client
return this.hitApi('/player/playback/play', { wait: 0 });
};
}
pressPause () {
pressPause() {
// Press pause on the client
return this.hitApi('/player/playback/pause', { wait: 0 });
};
}
pressStop () {
pressStop() {
// Press pause on the client
return this.hitApi('/player/playback/stop', { wait: 0 });
};
}
seekTo (time, params) {
seekTo(time, params) {
// Seek to a time (in ms)
return this.hitApi('/player/playback/seekTo', { wait: 0, offset: Math.round(time), ...params });
};
}
waitForMovement (startTime) {
waitForMovement(startTime) {
return new Promise((resolve, reject) => {
let time = 500;
if (this.clientIdentifier === 'PTPLAYER9PLUS10') {
@ -203,9 +202,9 @@ class PlexClient {
}
}, time);
});
};
}
skipAhead (current, duration) {
skipAhead(current, duration) {
return new Promise(async (resolve, reject) => {
const startedAt = new Date().getTime();
const now = this.lastTimelineObject.time;
@ -219,16 +218,16 @@ class PlexClient {
await this.pressPlay();
resolve();
});
};
}
cleanSeek (time, isSoft) {
cleanSeek(time, isSoft) {
if (isSoft && this.clientIdentifier === 'PTPLAYER9PLUS10') {
return this.seekTo(time, { softSeek: true });
}
return this.seekTo(time);
};
}
sync (hostTimeline, SYNCFLEXIBILITY, SYNCMODE, POLLINTERVAL) {
sync(hostTimeline, SYNCFLEXIBILITY, SYNCMODE, POLLINTERVAL) {
return new Promise(async (resolve, reject) => {
if (this.clientIdentifier === 'PTPLAYER9PLUS10') {
await this.getTimeline();
@ -247,7 +246,7 @@ class PlexClient {
// console.log('Difference with host is', difference);
const bothPaused = hostTimeline.playerState === 'paused' && this.lastTimelineObject.state === 'paused';
if (parseInt(difference) > parseInt(SYNCFLEXIBILITY) || (bothPaused && difference > 10)) {
if (parseInt(difference, 10) > parseInt(SYNCFLEXIBILITY, 10) || (bothPaused && difference > 10)) {
// We need to seek!
this.lastSyncCommand = new Date().getTime();
// Decide what seeking method we want to use
@ -262,24 +261,24 @@ class PlexClient {
}
// Calc the average delay of the last 10 host timeline updates
// We do this to avoid any issues with random lag spikes
differenceCache.unshift(difference);
if (differenceCache.length > 5) {
differenceCache.pop();
this.differenceCache.unshift(difference);
if (this.differenceCache.length > 5) {
this.differenceCache.pop();
}
let total = 0;
for (let i = 0; i < differenceCache.length; i++) {
total += differenceCache[i];
for (let i = 0; i < this.differenceCache.length; i += 1) {
total += this.differenceCache[i];
}
const avg = total / differenceCache.length;
const avg = total / this.differenceCache.length;
if (this.clientIdentifier === 'PTPLAYER9PLUS10' && avg > 1500) {
console.log('Soft syncing because difference is', difference);
return resolve(await this.cleanSeek(hostTimeline.time, true));
}
return resolve('No sync needed');
});
};
}
async playMedia (data) {
async playMedia(data) {
// Play a media item given a mediaId key and a server to play from
// We need the following variables to build our paramaters:
// MediaId Key, Offset, server MachineId,
@ -318,9 +317,9 @@ class PlexClient {
await this.waitForMovement();
resolve(true);
});
};
}
playContentAutomatically (client, hostData, servers, offset) {
playContentAutomatically(client, hostData, servers, offset) {
// Automatically play content on the client searching all servers based on the title
return new Promise(async (resolve, reject) => {
// First lets find all of our playable items
@ -397,5 +396,7 @@ class PlexClient {
return false;
}
});
};
};
}
}
export default PlexClient;

View File

@ -1,8 +1,6 @@
import axios from 'axios';
const _PlexAuth = require('./PlexAuth.js');
const PlexAuth = new _PlexAuth();
import promiseutils from '@/utils/promiseutils';
import plexauth from './PlexAuth';
class PlexServer {
@ -47,7 +45,7 @@ class PlexServer {
return reject(new Error('Failed to find a connection'));
}
}
const options = PlexAuth.getApiOptions('', this.accessToken, 15000, 'GET');
const options = plexauth.getApiOptions('', this.accessToken, 15000, 'GET');
axios
.get(this.chosenConnection.uri + command, {
params,
@ -66,11 +64,10 @@ class PlexServer {
});
}
async hitApiTestConnection(command, connection) {
const _url = connection.uri + command;
const config = PlexAuth.getRequestConfig(this.accessToken, 7500);
const { data } = await axios.get(_url, config);
return data;
hitApiTestConnection(command, connection) {
const url = `${connection.uri}${command}`;
const config = plexauth.getRequestConfig(this.accessToken, 7500);
return axios.get(url, config);
}
setChosenConnection(con) {
@ -81,34 +78,30 @@ class PlexServer {
// This function iterates through all available connections and
// if any of them return a valid response we'll set that connection
// as the chosen connection for future use.
let resolved = false;
return new Promise(async (resolve, reject) => {
await Promise.all(
this.plexConnections.map(
async (connection, index) =>
/*eslint-disable */
new Promise(async (_resolve, _reject) => {
try {
let result = await this.hitApiTestConnection('', connection);
if (result) {
resolved = true;
// console.log('Succesfully connected to', server, 'via', connection)
this.setValue('chosenConnection', connection);
resolve(true);
}
_resolve(false);
} catch (e) {
_resolve(false);
}
}),
/* eslint-enable */
),
// Prefer secure connections first.
const secureConnections = this.plexConnections.filter((connection) => connection.protocol === 'https');
try {
const secureConnection = promiseutils.any(
secureConnections.map((connection) => this.hitApiTestConnection('', connection).then(() => connection)),
);
if (!resolved) {
reject(new Error('Unable to find a connection'));
}
});
this.setValue('chosenConnection', secureConnection);
return true;
} catch (e) {
console.log('No secure connections found');
}
// If we are using synclounge over https, we can't access connections over http because
// most modern web browsers block mixed content
if (window.location.protocol === 'http:') {
const insecureConnections = this.plexConnections.filter((connection) => connection.protocol === 'http');
const insecureConnection = promiseutils.any(insecureConnections.map((connection) => this.hitApiTestConnection('', connection).then(() => connection)));
this.setValue('chosenConnection', insecureConnection);
return true;
}
throw new Error('Unable to find a connection');
}
// Functions for dealing with media

View File

@ -0,0 +1,9 @@
// There doesn't seem to be a valid Promise.any polyfill in the current build config so I found one myself
export default {
any: (promises) => Promise.all(promises.map((promise) => promise.then((val) => {
throw val;
}, (reason) => reason))).then((reasons) => {
throw reasons;
}, (firstResolved) => firstResolved),
};

11
src/utils/xmlutils.js Normal file
View File

@ -0,0 +1,11 @@
import parser from 'fast-xml-parser';
const options = {
attributeNamePrefix: '',
ignoreAttributes: false,
arrayMode: true,
};
export default {
parseXML: (xml) => parser.parse(xml, options),
};