Merge branch 'server_con_fix' into vuecli
This commit is contained in:
commit
30e161b7f7
|
@ -1,3 +1,4 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie <= 8
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
// Prefer secure connections first.
|
||||
const secureConnections = this.plexConnections.filter((connection) => connection.protocol === 'https');
|
||||
|
||||
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 */
|
||||
),
|
||||
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
|
||||
|
|
|
@ -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),
|
||||
};
|
|
@ -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),
|
||||
};
|
Loading…
Reference in New Issue