Autoplay rework (WIP)

This commit is contained in:
Sam 2018-05-10 21:23:49 +10:00
parent d37bb26661
commit 0b96fa563a
12 changed files with 932 additions and 1029 deletions

View File

@ -205,6 +205,10 @@ export default {
window.EventBus.$on('NEW_TIMELINE', timeline => {
this.$store.dispatch('NEW_TIMELINE', timeline)
})
window.EventBus.$on('PLAYBACK_CHANGE', data => {
console.log('Playback change event', data)
this.$store.dispatch('PLAYBACK_CHANGE', data)
})
if (!window['localStorage'].getItem('plexuser')) {
console.log('Token doesnt exist', window['localStorage'].getItem('plexuser'))
this.$router.push('/signin')

View File

@ -133,7 +133,7 @@ export default {
return data.callback(playerdata)
}
if (data.command === '/player/playback/play') {
this.eventbus.$emit('player-press-play', function (res) {
this.eventbus.$emit('player-press-play', (res) => {
return data.callback(res)
})
return
@ -557,7 +557,6 @@ export default {
if (changeItem || !this.playingMetadata) {
this.playingMetadata = null
this.chosenServer.getMediaByRatingKey(this.chosenKey).then(result => {
console.log(result)
this.playingMetadata = result.MediaContainer.Metadata[0]
req()
})

View File

@ -1,95 +0,0 @@
<template>
<li v-if="object" class="mdc-list-item avatar ptuser mainUser"
style="height: 100%; margin-bottom: 1%; overflow-y:hidden">
<span class="mdc-list-item__start-detail" style="height:3em; width: 3em">
<img v-bind:src="object.avatarUrl" class="circle plex-gamboge-border" style="height:3em; width: 3em">
</span>
<span class="mdc-list-item__text" style="width: 89%; line-height: 1.2">
<span class="mdc-list-item__text__primary">
<div class="ptuser-username">{{ object.username }} </div>
<small class="ptuser-title"><i class="material-icons" v-if="playerState"
style="font-size:smaller">{{ playerState }}</i> {{ getTitle }}</small>
<i class="material-icons plex-gamboge-text ptuser-role right" v-bind:style="{ display: isHost }">
star
</i>
</span>
<span class="mdc-list-item__text__secondary" id="metaDropdownDiv">
<div class="progress" style="margin-bottom: 0;background-color: rgba(242, 243, 244, 0.11)">
<div class="determinate ptuser-percent" v-bind:style="{ width: percent }"></div>
</div>
<div>
<small style="float: left" class="ptuser-time">{{ getCurrent }}</small>
<small style="float: right" class="ptuser-maxTime">{{ getMax }}</small>
</div>
</span>
</span>
</li>
</template>
<script>
export default {
props: ['object'],
name: 'ptuser',
methods: {
getTimeFromMs (ms) {
var hours = ms / (1000 * 60 * 60)
var absoluteHours = Math.floor(hours)
var h = absoluteHours > 9 ? absoluteHours : '0' + absoluteHours
var minutes = (hours - absoluteHours) * 60
var absoluteMinutes = Math.floor(minutes)
var m = absoluteMinutes > 9 ? absoluteMinutes : '0' + absoluteMinutes
var seconds = (minutes - absoluteMinutes) * 60
var absoluteSeconds = Math.floor(seconds)
var s = absoluteSeconds > 9 ? absoluteSeconds : '0' + absoluteSeconds
return (h + ':' + m + ':' + s)
}
},
computed: {
isHost: function () {
if (this.object.role == 'host') {
return 'block'
}
return 'none'
},
percent: function () {
let perc = (parseInt(this.object.time) / parseInt(this.object.maxTime)) * 100
if (isNaN(perc)) {
perc = 0
}
return perc + '%'
},
getCurrent: function () {
if (isNaN(this.object.time)) {
return ''
}
return this.getTimeFromMs(this.object.time)
},
getMax: function () {
if (isNaN(this.object.maxTime)) {
return ''
}
return this.getTimeFromMs(this.object.maxTime)
},
getTitle: function () {
if (this.object.title && this.object.title.length > 0) {
return this.object.title
}
return 'Nothing'
},
playerState: function () {
if (this.object.playerState) {
if (this.object.playerState == 'stopped') {
return 'pause'
}
if (this.object.playerState == 'paused') {
return 'pause'
}
if (this.object.playerState == 'playing') {
return 'play_arrow'
}
}
return false
}
}
}
</script>

View File

@ -50,6 +50,7 @@ window.EventBus.$on('command', (data) => {
}
})
}
return data.callback(true)
})
Vue.mixin({

View File

@ -82,10 +82,8 @@
</template>
<script>
import ptuser from './components/application/ptuser.vue'
export default {
components: {
ptuser
},
data () {
return {

View File

@ -338,7 +338,8 @@ const actions = {
},
NEW_TIMELINE ({ commit, state, dispatch }, data) {
// return true
let [client, timeline, mediaContainer] = data
let timeline = data
let client = state.chosenClient
// console.log(state)
if (!state.chosenClient || (client.clientIdentifier !== state.chosenClient.clientIdentifier)) {
console.log('Invalid client')

View File

@ -219,4 +219,5 @@ export default {
console.log('Updating timeline for', client, 'with', timeline)
}
}

View File

@ -1,113 +1,111 @@
var axios = require('axios');
var parseXMLString = require('xml2js').parseString;
const EventEmitter = require('events');
var _PlexAuth = require('./PlexAuth.js');
var PlexAuth = new _PlexAuth();
var axios = require('axios')
var parseXMLString = require('xml2js').parseString
const EventEmitter = require('events')
var _PlexAuth = require('./PlexAuth.js')
var PlexAuth = new _PlexAuth()
module.exports = function PlexClient() {
this.commandId = 0;
this.name = null;
this.product = null;
this.productVersion = null;
this.platform = null;
this.platformVersion = null;
this.device = null;
this.clientIdentifier = null;
this.createdAt = null;
this.lastSeenAt = null;
this.provides = null;
this.owned = null;
this.publicAddressMatches = null;
this.presence = null;
this.plexConnections = null;
this.chosenConnection = null;
this.httpServer = null;
this.tempId = null;
this.events = new EventEmitter();
module.exports = function PlexClient () {
this.commandId = 0
this.name = null
this.product = null
this.productVersion = null
this.platform = null
this.platformVersion = null
this.device = null
this.clientIdentifier = null
this.createdAt = null
this.lastSeenAt = null
this.provides = null
this.owned = null
this.publicAddressMatches = null
this.presence = null
this.plexConnections = null
this.chosenConnection = null
this.httpServer = null
this.tempId = null
this.events = new EventEmitter()
this.userData = null;
this.userData = null
// Latest objects for reference in the future
this.lastRatingKey = null;
this.lastTimelineObject = null;
this.oldTimelineObject = null;
this.lastTimeline = null;
this.oldTimeline = null;
this.clientPlayingMetadata = null;
this.lastSubscribe = 0;
this.connectedstatus = 'fresh';
this.lastRatingKey = null
this.lastTimelineObject = null
this.oldTimelineObject = null
this.lastTimeline = null
this.oldTimeline = null
this.clientPlayingMetadata = null
this.lastSubscribe = 0
this.connectedstatus = 'fresh'
this.eventbus = window.EventBus; // We will use this to communicate with the SLPlayer
this.commit = null;
this.dispatch = null;
this.eventbus = window.EventBus // We will use this to communicate with the SLPlayer
this.commit = null
this.dispatch = null
let previousTimeline = {};
let previousTimeline = {}
this.setValue = function (key, value) {
this[key] = value;
this.commit('PLEX_CLIENT_SET_VALUE', [this, key, value]);
};
this[key] = value
this.commit('PLEX_CLIENT_SET_VALUE', [this, key, value])
}
this.generateGuid = function () {
function s4() {
function s4 () {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
.substring(1)
}
return s4() + s4() + '-' + s4();
};
this.uuid = this.generateGuid();
return s4() + s4() + '-' + s4()
}
this.uuid = this.generateGuid()
this.hitApi = function (command, params, connection) {
return new Promise(async (resolve, reject) => {
if (this.clientIdentifier == 'PTPLAYER9PLUS10') {
if (this.clientIdentifier === 'PTPLAYER9PLUS10') {
// We are using the SyncLounge Player
let data = {
command: command,
params: params,
callback: (resultData) => {
console.log('Result from player', resultData);
if (command === '/player/timeline/poll') {
this.updateTimelineObject(resultData);
this.updateTimelineObject(resultData)
}
resolve(resultData, 0, 200, 'PTPLAYER');
console.log('Heard back from PTPLAYER', resultData)
resolve(resultData)
}
};
this.eventbus.$emit('command', data);
}
this.eventbus.$emit('command', data)
} else {
const doRequest = () => {
if (!connection) {
connection = this.chosenConnection;
connection = this.chosenConnection
}
if (!connection) {
return reject('No connection specified');
return reject(new Error('No connection specified'))
}
var query = '';
var query = ''
Object.assign(params, {
type: 'video',
'X-Plex-Device-Name': 'SyncLounge'
});
})
// console.log(params)
for (let key in params) {
query += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&';
query += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&'
}
query = query + 'commandID=' + this.commandId;
if (connection.uri.charAt(connection.uri.length - 1) == '/') {
query = query + 'commandID=' + this.commandId
if (connection.uri.charAt(connection.uri.length - 1) === '/') {
// Remove a trailing / that some clients broadcast
connection.uri = connection.uri.slice(0, connection.uri.length - 1);
connection.uri = connection.uri.slice(0, connection.uri.length - 1)
}
var _url = connection.uri + command + '?' + query;
this.setValue('commandId', this.commandId + 1);
var options = PlexAuth.getClientApiOptions(_url, this.clientIdentifier, this.uuid, 5000);
var _url = connection.uri + command + '?' + query
this.setValue('commandId', this.commandId + 1)
var options = PlexAuth.getClientApiOptions(_url, this.clientIdentifier, this.uuid, 5000)
// console.log('sending api request', options)
if (window.localStorage.getItem('EXTAVAILABLE')) {
console.log('Extension is available');
chrome.runtime.sendMessage('mlmjjfdcbemagmnjahllphjnohbmhcnf', {
console.log('Extension is available')
window.chrome.runtime.sendMessage('mlmjjfdcbemagmnjahllphjnohbmhcnf', {
command: 'client',
data: {
url: _url,
@ -120,316 +118,225 @@ module.exports = function PlexClient() {
// console.log('SyncLoungePlus response', response)
if (response) {
parseXMLString(response, (err, result) => {
resolve(result);
if (command === '/player/timeline/poll') {
this.updateTimelineObject(result);
if (err) {
return reject(err)
}
return;
});
resolve(result)
if (command === '/player/timeline/poll') {
this.updateTimelineObject(result)
}
})
}
});
})
} else {
axios.get(connection.uri + command, {
params,
headers: options.headers
})
.then((response) => {
resolve(response);
resolve(response)
if (command === '/player/timeline/poll') {
this.updateTimelineObject(response);
this.updateTimelineObject(response)
}
})
.catch((error) => {
reject(error);
});
reject(error)
})
}
// request(options, (error, response, body) => {
// // console.log('response data', response)
// if (error) {
// return reject(error)
// } else {
// parseXMLString(body, function (err, result) {
// if (err || (response.statusCode != 200 && response.statusCode != 201)) {
// return reject(err)
// }
// return resolve(result)
// })
// }
// })
};
}
if ((new Date().getTime() - this.lastSubscribe) > 29000) {
// We need to subscribe first!
try {
// await this.subscribe(connection)
doRequest();
doRequest()
} catch (e) {
reject(e);
reject(e)
}
return;
} else {
doRequest();
return;
doRequest()
}
}
});
};
})
}
this.getTimeline = async function (callback) {
// Get the timeline object from the client
let data;
let data
try {
data = await this.hitApi('/player/timeline/poll', {
'wait': 0
}, this.chosenConnection);
data = await this.hitApi('/player/timeline/poll', { 'wait': 0 }, this.chosenConnection)
if (data) {
return this.updateTimelineObject(data);
return this.updateTimelineObject(data)
} else {
return false;
return false
}
} catch (e) {
// console.log(e)
return false;
return false
}
};
}
this.updateTimelineObject = function (result) {
// this.events.emit('new_timeline', result)
// console.log('New timeline', result)
// Check if we are the SLPlayer
if (this.clientIdentifier === 'PTPLAYER9PLUS10') {
// SLPLAYER
let tempObj = {
MediaContainer: {
timelines: [result]
}
};
result = tempObj;
// this.events.emit('new_timeline', result);
// var clonetimeline = this.lastTimelineObject;
// if (!this.oldTimelineObject) {
// if (!this.lastTimelineObject.ratingKey) {
// this.events.emit('playback_change', null);
// } else {
// this.events.emit('playback_change', this.lastTimelineObject.ratingKey);
// }
// this.setValue('oldTimelineObject', result);
// // this.oldTimelineObject = result
// return callback(result);
// }
// this.setValue('oldTimelineObject', clonetimeline);
// if (this.oldTimelineObject.ratingKey != this.lastTimelineObject.ratingKey) {
// if (!this.lastTimelineObject.ratingKey) {
// this.events.emit('playback_change', null);
// } else {
// this.events.emit('playback_change', this.lastTimelineObject.ratingKey);
// }
// }
// return true;
}
if (!result.MediaContainer.Timeline) {
// Not a valid timeline object
return false;
}
// Valid timeline data
// Standard player
let timelines = result.MediaContainer.Timeline;
let videoTimeline = {};
for (let i = 0; i < timelines.length; i++) {
let _timeline = timelines[i]['$'];
if (_timeline.type == 'video') {
videoTimeline = _timeline;
console.log('Does', videoTimeline.ratingKey + ' equal ' + previousTimeline.ratingKey);
if (videoTimeline.ratingKey !== previousTimeline.ratingKey) {
window.EventBus.$emit('PLAYBACK_CHANGE', [this, videoTimeline.ratingKey, result.MediaContainer]);
Timeline: [result]
}
}
// if ((_timeline.state && _timeline.state != 'stopped') || i == (timelines.length - 1)) {
// this.events.emit('new_timeline', timelines[i]['$'])
// var clonetimeline = this.lastTimelineObject
// this.lastTimelineObject = timelines[i]['$']
// this.setValue('lastTimelineObject', timelines[i]['$'])
// if (!this.oldTimelineObject) {
// // First time we've got data!
// if (!this.lastTimelineObject.ratingKey) {
// this.events.emit('playback_change', null)
// } else {
// this.events.emit('playback_change', this.lastTimelineObject.ratingKey)
// }
// this.setValue('oldTimelineObject', timelines[i]['$'])
// // this.oldTimelineObject = timelines[i]['$']
// return timelines[i]['$']
// }
// this.setValue('oldTimelineObject', clonetimeline)
// this.oldTimelineObject = clonetimeline
// if (this.oldTimelineObject.ratingKey != result.ratingKey) {
// if (!this.lastTimelineObject.ratingKey) {
// this.events.emit('playback_change', null)
// } else {
// this.events.emit('playback_change', result.ratingKey)
// }
// }
// return timelines[i]['$']
// }
result = tempObj
if (!previousTimeline.MediaContainer || result.MediaContainer.Timeline[0].ratingKey !== previousTimeline.MediaContainer.Timeline[0].ratingKey) {
window.EventBus.$emit('PLAYBACK_CHANGE', [this, result.MediaContainer.Timeline[0].ratingKey, result.MediaContainer.Timeline[0]])
}
previousTimeline = result
window.EventBus.$emit('NEW_TIMELINE', result.MediaContainer.Timeline[0])
return result
}
window.EventBus.$emit('NEW_TIMELINE', [this, videoTimeline, result.MediaContainer]);
previousTimeline = videoTimeline;
// Standard player
let timelines = result.MediaContainer.Timeline
let videoTimeline = {}
for (let i = 0; i < timelines.length; i++) {
let _timeline = timelines[i]['$']
if (_timeline.type === 'video') {
videoTimeline = _timeline
console.log('Does', videoTimeline.ratingKey + ' equal ' + previousTimeline.ratingKey)
if (videoTimeline.ratingKey !== previousTimeline.ratingKey) {
window.EventBus.$emit('PLAYBACK_CHANGE', [this, videoTimeline.ratingKey, result.MediaContainer])
}
window.EventBus.$emit('NEW_TIMELINE', videoTimeline)
}
}
window.EventBus.$emit('NEW_TIMELINE', [this, videoTimeline, result.MediaContainer])
previousTimeline = videoTimeline
// this.setValue('lastTimelineObject', videoTimeline)
return videoTimeline;
};
return videoTimeline
}
this.pressPlay = function (callback) {
// Press play on the client
this.hitApi('/player/playback/play', {
'wait': 0
}, this.chosenConnection).then((result, responseTime) => {
this.hitApi('/player/playback/play', { 'wait': 0 }, this.chosenConnection).then((result, responseTime) => {
if (result) {
//Valid response back from the client
return callback(result, responseTime);
// Valid response back from the client
return callback(result, responseTime)
} else {
return callback(null);
return callback(null)
}
});
};
})
}
this.pressPause = function (callback) {
// Press pause on the client
this.hitApi('/player/playback/pause', {
'wait': 0
}, this.chosenConnection).then((result, responseTime) => {
this.hitApi('/player/playback/pause', { 'wait': 0 }, this.chosenConnection).then((result, responseTime) => {
if (result) {
//Valid response back from the client
return callback(result, responseTime);
// Valid response back from the client
return callback(result, responseTime)
} else {
return callback(null);
return callback(null)
}
});
};
})
}
this.pressStop = function (callback) {
// Press pause on the client
this.hitApi('/player/playback/stop', {
'wait': 0
}, this.chosenConnection).then((result, responseTime) => {
this.hitApi('/player/playback/stop', { 'wait': 0 }, this.chosenConnection).then((result, responseTime) => {
if (result) {
//Valid response back from the client
return callback(result, responseTime);
// Valid response back from the client
return callback(result, responseTime)
} else {
return callback(null);
return callback(null)
}
});
};
})
}
this.seekTo = function (time, callback) {
// Seek to a time (in ms)
this.hitApi('/player/playback/seekTo', {
'wait': 0,
'offset': time
}, this.chosenConnection, function (result, responseTime) {
this.hitApi('/player/playback/seekTo', { 'wait': 0, 'offset': time }, this.chosenConnection, function (result, responseTime) {
if (result) {
//Valid response back from the client
return callback(result, responseTime);
// Valid response back from the client
return callback(result, responseTime)
} else {
return callback(null);
return callback(null)
}
});
};
})
}
this.getRatingKey = function (callback) {
// Get the ratingKey, aka the mediaId, of the item playing
this.hitApi('/player/timeline/poll', {
'wait': 0
}, this.chosenConnection).then((result) => {
this.hitApi('/player/timeline/poll', { 'wait': 0 }, this.chosenConnection).then((result) => {
if (result) {
//Valid response back from the client
var allTimelines = result.MediaContainer.Timeline;
// Valid response back from the client
var allTimelines = result.MediaContainer.Timeline
for (var i in allTimelines) {
var timeline = allTimelines[i]['$'];
//We only want the rating key of whatever is playing in the video timeline
if (timeline.type == 'video') {
return callback(timeline.ratingKey);
var timeline = allTimelines[i]['$']
// We only want the rating key of whatever is playing in the video timeline
if (timeline.type === 'video') {
return callback(timeline.ratingKey)
}
}
return callback(null);
return callback(null)
} else {
return callback(null);
return callback(null)
}
});
};
})
}
this.getServerId = function (callback) {
// Get the machineId of the server we're playing from'
this.hitApi('/player/timeline/poll', {
'wait': 0
}, this.chosenConnection).then((result) => {
this.hitApi('/player/timeline/poll', { 'wait': 0 }, this.chosenConnection).then((result) => {
if (result) {
//Valid response back from the client
var allTimelines = result.MediaContainer.Timeline;
// Valid response back from the client
var allTimelines = result.MediaContainer.Timeline
for (var i in allTimelines) {
var timeline = allTimelines[i]['$'];
//We only want the rating key of whatever is playing in the video timeline
if (timeline.type == 'video') {
return callback(timeline.machineIdentifier);
var timeline = allTimelines[i]['$']
// We only want the rating key of whatever is playing in the video timeline
if (timeline.type === 'video') {
return callback(timeline.machineIdentifier)
}
}
return callback(null);
return callback(null)
} else {
return callback(null);
return callback(null)
}
});
};
})
}
this.getPlayerState = function (callback) {
// Get the Player State (playing, paused or stopped)
this.hitApi('/player/timeline/poll', {
'wait': 0
}, this.chosenConnection).then((result) => {
this.hitApi('/player/timeline/poll', { 'wait': 0 }, this.chosenConnection).then((result) => {
if (result) {
//Valid response back from the client
var allTimelines = result.MediaContainer.Timeline;
// Valid response back from the client
var allTimelines = result.MediaContainer.Timeline
for (var i in allTimelines) {
var timeline = allTimelines[i]['$'];
//We only want the rating key of whatever is playing in the video timeline
if (timeline.type == 'video') {
return callback(timeline.state);
var timeline = allTimelines[i]['$']
// We only want the rating key of whatever is playing in the video timeline
if (timeline.type === 'video') {
return callback(timeline.state)
}
}
return callback(null);
return callback(null)
} else {
return callback(null);
return callback(null)
}
});
};
})
}
this.getPlayerTime = function (callback) {
// Get the current playback time in ms
this.hitApi('/player/timeline/poll', {
'wait': 0
}, this.chosenConnection).then((result, responseTime, code) => {
this.hitApi('/player/timeline/poll', { 'wait': 0 }, this.chosenConnection).then((result, responseTime, code) => {
if (result) {
//Valid response back from the client
var allTimelines = result.MediaContainer.Timeline;
// Valid response back from the client
var allTimelines = result.MediaContainer.Timeline
for (var i in allTimelines) {
var timeline = allTimelines[i]['$'];
//We only want the rating key of whatever is playing in the video timeline
if (timeline.type == 'video') {
return callback(timeline.time, responseTime);
var timeline = allTimelines[i]['$']
// We only want the rating key of whatever is playing in the video timeline
if (timeline.type === 'video') {
return callback(timeline.time, responseTime)
}
}
return callback(null, responseTime);
return callback(null, responseTime)
} else {
return callback(null, responseTime);
return callback(null, responseTime)
}
});
};
})
}
this.playMedia = async function (data) {
// Play a media item given a mediaId key and a server to play from
@ -438,60 +345,63 @@ module.exports = function PlexClient() {
// Server Ip, Server Port, Server Protocol, Path
// First we will mirror the item so the user has an idea of what we're about to play
return new Promise(async (resolve, reject) => {
console.log('Autoplaying from client', data)
const send = async () => {
let command = '/player/playback/playMedia'
let mediaId = '/library/metadata/' + data.ratingKey
let offset = data.offset || 0
let serverId = data.server.clientIdentifier
let address = data.server.chosenConnection.address
let port = data.server.chosenConnection.port
let protocol = data.server.chosenConnection.protocol
let path = data.server.chosenConnection.uri + mediaId
const send = async () => {
let command = '/player/playback/playMedia';
let mediaId = '/library/metadata/' + data.ratingKey;
let offset = data.offset || 0;
let serverId = data.server.clientIdentifier;
let address = data.server.chosenConnection.address;
let port = data.server.chosenConnection.port;
let protocol = data.server.chosenConnection.protocol;
let path = data.server.chosenConnection.uri + mediaId;
let params = {
'X-Plex-Client-Identifier': 'SyncLounge',
'key': mediaId,
'offset': offset,
'machineIdentifier': serverId,
'address': address,
'port': port,
'protocol': protocol,
'path': path,
'wait': 0,
'token': data.server.accessToken
}
let params = {
'X-Plex-Client-Identifier': 'SyncLounge',
'key': mediaId,
'offset': offset,
'machineIdentifier': serverId,
'address': address,
'port': port,
'protocol': protocol,
'path': path,
'wait': 0,
'token': data.server.accessToken
};
if (data.mediaIndex !== undefined || data.mediaIndex !== null) {
params.mediaIndex = data.mediaIndex
}
if (data.mediaIndex != undefined || data.mediaIndex != null) {
params.mediaIndex = data.mediaIndex;
// Now that we've built our params, it's time to hit the client api
console.log('Sending command')
await this.hitApi(command, params, this.chosenConnection)
console.log('PlayMedia DONE')
resolve(true)
}
// Now that we've built our params, it's time to hit the client api
return this.hitApi(command, params, this.chosenConnection);
};
if (this.clientIdentifier == 'PTPLAYER9PLUS10') {
return send();
} else {
await this.mirrorContent(data.ratingKey, data.server);
return send();
}
};
if (this.clientIdentifier === 'PTPLAYER9PLUS10') {
return send()
} else {
await this.mirrorContent(data.ratingKey, data.server)
return send()
}
})
}
this.mirrorContent = function (key, serverObject, callback) {
// Mirror 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 (0 for simplicity), server MachineId,
// Server Ip, Server Port, Server Protocol, Path
let command = '/player/mirror/details';
let mediaId = '/library/metadata/' + key;
let offset = 0;
let serverId = serverObject.clientIdentifier;
let address = serverObject.chosenConnection.address;
let port = serverObject.chosenConnection.port;
let protocol = serverObject.chosenConnection.protocol;
let path = serverObject.chosenConnection.uri + mediaId;
let command = '/player/mirror/details'
let mediaId = '/library/metadata/' + key
let serverId = serverObject.clientIdentifier
let address = serverObject.chosenConnection.address
let port = serverObject.chosenConnection.port
let protocol = serverObject.chosenConnection.protocol
let path = serverObject.chosenConnection.uri + mediaId
let params = {
'X-Plex-Client-Identifier': 'SyncLounge',
@ -503,95 +413,181 @@ module.exports = function PlexClient() {
'path': path,
'wait': 0,
'token': serverObject.accessToken
};
}
// Now that we've built our params, it's time to hit the client api
return this.hitApi(command, params, this.chosenConnection);
};
return this.hitApi(command, params, this.chosenConnection)
}
this.subscribe = function (connection, commit) {
return new Promise((resolve, reject) => {
const doRequest = () => {
// Already have a valid http server running, lets send the request
if (!connection) {
// It is possible to try to subscribe before we've found a working connection
connection = this.chosenConnection;
connection = this.chosenConnection
}
var command = '/player/timeline/subscribe';
var command = '/player/timeline/subscribe'
var params = {
'port': '8555',
'protocol': 'http',
'X-Plex-Device-Name': 'SyncLounge'
};
// Now that we've built our params, it's time to hit the client api
var query = '';
}
// Now that we've built our params, it's time to hit the client api
var query = ''
for (let key in params) {
query += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&';
query += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&'
}
query = query + 'commandID=' + this.commandId;
if (connection.uri.charAt(connection.uri.length - 1) == '/') {
//Remove a trailing / that some clients broadcast
connection.uri = connection.uri.slice(0, connection.uri.length - 1);
query = query + 'commandID=' + this.commandId
if (connection.uri.charAt(connection.uri.length - 1) === '/') {
// Remove a trailing / that some clients broadcast
connection.uri = connection.uri.slice(0, connection.uri.length - 1)
}
var _url = connection.uri + command + '?' + query;
var _url = connection.uri + command + '?' + query
// console.log('subscription url: ' + _url)
this.commandId = this.commandId + 1;
this.setValue('commandId', this.commandId + 1);
var options = PlexAuth.getClientApiOptions(_url, this.clientIdentifier, this.uuid, 5000);
console.log(options);
request(options, (error, response, body) => {
// console.log('subscription result', response)
this.setValue('lastSubscribe', new Date().getTime());
if (error) {
return reject(error);
} else {
return resolve(true);
this.commandId = this.commandId + 1
this.setValue('commandId', this.commandId + 1)
var options = PlexAuth.getClientApiOptions(_url, this.clientIdentifier, this.uuid, 5000)
console.log(options)
resolve()
// request(options, (error, response, body) => {
// // console.log('subscription result', response)
// this.setValue('lastSubscribe', new Date().getTime())
// if (error) {
// return reject(error)
// } else {
// return resolve(true)
// }
// })
}
doRequest()
})
}
// this.unsubscribe = function (callback) {
// // var that = this
// // doRequest()
// // function doRequest () {
// // // Already have a valid http server running, lets send the request
// // let tempId = 'SyncLoungeWeb'
// // var command = '/player/timeline/unsubscribe'
// // var params = {
// // 'port': '8555',
// // 'protocol': 'http',
// // 'X-Plex-Device-Name': 'SyncLounge'
// // }
// // // Now that we've built our params, it's time to hit the client api
// // var query = ''
// // for (let key in params) {
// // query += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&'
// // }
// // query = query + 'commandID=' + that.commandId
// // if (connection.uri.charAt(connection.uri.length - 1) == '/') {
// // // Remove a trailing / that some clients broadcast
// // connection.uri = connection.uri.slice(0, connection.uri.length - 1)
// // }
// // if (that.chosenConnection == null) {
// // // It is possible to try to subscribe before we've found a working connection
// // console.log('Chosen connection has not been set yet.')
// // return (callback(false))
// // }
// // var _url = that.chosenConnection.uri + command + '?' + query
// // // console.log('subscription url: ' + _url)
// // that.commandId = that.commandId + 1
// // var options = PlexAuth.getClientApiOptions(_url, that.clientIdentifier, that.uuid, 5000)
// // request(options, function (error, response, body) {
// // if (!error) {
// // return callback(true, that)
// // } else {
// // return callback(false, that)
// // }
// // })
// // }
// },
this.playContentAutomatically = function (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
let playables = []
console.log('Autoplay', client, hostData, servers, offset)
let serversArr = []
for (let i in servers) {
serversArr.push(servers[i])
}
await Promise.all(serversArr.map(async (server) => {
return new Promise(async (resolve, reject) => {
if (!server.chosenConnection) {
return resolve()
}
});
};
doRequest();
});
};
this.unsubscribe = function (callback) {
var that = this;
doRequest();
function doRequest() {
// Already have a valid http server running, lets send the request
let tempId = 'SyncLoungeWeb';
var command = '/player/timeline/unsubscribe';
var params = {
'port': '8555',
'protocol': 'http',
'X-Plex-Device-Name': 'SyncLounge'
};
//Now that we've built our params, it's time to hit the client api
var query = '';
for (key in params) {
query += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&';
}
query = query + 'commandID=' + that.commandId;
if (connection.uri.charAt(connection.uri.length - 1) == '/') {
//Remove a trailing / that some clients broadcast
connection.uri = connection.uri.slice(0, connection.uri.length - 1);
}
if (that.chosenConnection == null) {
// It is possible to try to subscribe before we've found a working connection
console.log('Chosen connection has not been set yet.');
return (callback(false));
}
var _url = that.chosenConnection.uri + command + '?' + query;
// console.log('subscription url: ' + _url)
that.commandId = that.commandId + 1;
var options = PlexAuth.getClientApiOptions(_url, that.clientIdentifier, that.uuid, 5000);
request(options, function (error, response, body) {
if (!error) {
return callback(true, that);
} else {
return callback(false, that);
let results = await server.search(hostData.rawTitle)
console.log(server.name, 'found', results.length, 'results')
for (var k = 0; k < results.length; k++) {
// Now we need to check the result
if (checkResult(results[k], hostData)) {
// Its a match!
playables.push({
'server': server,
'result': results[k]
})
}
}
resolve()
})
}))
const start = async (index) => {
// Now lets try and play our items one by one
if (playables.length === 0) {
return reject(new Error('Didnt find any playable items'))
}
});
}
};
};
var server = playables[index].server
var ratingKey = playables[index].result.ratingKey
let data = {
ratingKey: ratingKey,
mediaIndex: null,
server: server,
offset: offset || 0
}
if (client.clientIdentifier !== 'PTPLAYER9PLUS10') {
await client.subscribe()
}
let res = await this.playMedia(data).catch(() => {
start(parseInt(parseInt(index) + 1))
})
console.log('Autoplayer await result', res)
return resolve()
}
start(0)
function checkResult (data, hostData) {
console.log('Checking compatibility for this item', data, hostData)
// Do a series of checks to see if this result is OK
// Check if rawTitle matches
if (data.title !== hostData.rawTitle) {
// global.renderLog.info('wrong title')
return false
}
// Check if length is close enough
if (Math.abs(parseInt(data.duration) - parseInt(hostData.maxTime)) > 5000 || !data.duration) {
// global.renderLog.info('wrong time')
return false
}
if (data.type === 'movie') {
// We're good!
console.log('FOUND A PLAYABLE MOVIE')
return true
}
if (data.type === 'episode') {
// Check if the show name is the same
console.log('FOUND A PLAYABLE TV EPISODE')
return true
}
if (data.type === 'track') {
// We're good!
console.log('FOUND A PLAYABLE track')
return true
}
return false
}
})
}
}

View File

@ -1,7 +1,7 @@
module.exports = function PlexConnection() {
this.protocol = null;
this.address = null;
this.port = null;
this.uri = null;
this.local = null;
};
module.exports = function PlexConnection () {
this.protocol = null
this.address = null
this.port = null
this.uri = null
this.local = null
}

View File

@ -1,240 +1,239 @@
var request = require('request');
var safeParse = require('safe-json-parse/callback');
var _PlexAuth = require('./PlexAuth.js');
var PlexAuth = new _PlexAuth();
var request = require('request')
var safeParse = require('safe-json-parse/callback')
var _PlexAuth = require('./PlexAuth.js')
var PlexAuth = new _PlexAuth()
module.exports = function PlexServer() {
this.name;
this.product;
this.productVersion;
this.platform;
this.platformVersion;
this.device;
this.clientIdentifier;
this.createdAt;
this.lastSeenAt;
this.provides;
this.owned;
this.httpsRequired;
this.ownerId;
this.home;
this.accessToken;
this.sourceTitle;
this.synced;
this.relay;
this.publicAddressMatches;
this.presence;
this.plexConnections;
this.chosenConnection = null;
module.exports = function PlexServer () {
this.name = ''
this.product = ''
this.productVersion = ''
this.platform = ''
this.platformVersion = ''
this.device = ''
this.clientIdentifier = ''
this.createdAt = ''
this.lastSeenAt = ''
this.provides = ''
this.owned = ''
this.httpsRequired = ''
this.ownerId = ''
this.home = ''
this.accessToken = ''
this.sourceTitle = ''
this.synced = ''
this.relay = ''
this.publicAddressMatches = ''
this.presence = ''
this.plexConnections = ''
this.chosenConnection = null
this.commit;
this.commit = null
this.setValue = function (key, value) {
this[key] = value;
this.commit('PLEX_SERVER_SET_VALUE', [this, key, value]);
};
this[key] = value
this.commit('PLEX_SERVER_SET_VALUE', [this, key, value])
}
// Functions
this.hitApi = function (command, params) {
return new Promise(async(resolve, reject) => {
return new Promise(async (resolve, reject) => {
try {
let query = '';
//console.log('Query params: ' + JSON.stringify(params))
let query = ''
// console.log('Query params: ' + JSON.stringify(params))
for (let key in params) {
query += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&';
query += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&'
}
if (!this.chosenConnection) {
let result = await this.findConnection();
let result = await this.findConnection()
if (!result) {
return reject('Failed to find a connection');
return reject(new Error('Failed to find a connection'))
}
}
var _url = this.chosenConnection.uri + command + '?' + query;
var options = PlexAuth.getApiOptions(_url, this.accessToken, 15000, 'GET');
var _url = this.chosenConnection.uri + command + '?' + query
var options = PlexAuth.getApiOptions(_url, this.accessToken, 15000, 'GET')
request(options, (error, response, body) => {
if (!error) {
let parsed = JSON.parse(body);
console.log('Metadata request result', parsed);
this.handleMetadata(parsed);
return resolve(parsed);
} else return reject(false, error);
});
let parsed = JSON.parse(body)
console.log('Metadata request result', parsed)
this.handleMetadata(parsed)
return resolve(parsed)
} else return reject(error)
})
} catch (e) {
console.log(e);
reject(e);
console.log(e)
reject(e)
}
});
};
})
}
this.hitApiTestConnection = async function (command, connection) {
return new Promise(async(resolve, reject) => {
var _url = connection.uri + command;
var options = PlexAuth.getApiOptions(_url, this.accessToken, 7500, 'GET');
return new Promise(async (resolve, reject) => {
var _url = connection.uri + command
var options = PlexAuth.getApiOptions(_url, this.accessToken, 7500, 'GET')
request(options, function (error, response, body) {
if (!error) {
safeParse(body, function (err, json) {
if (err) {
return reject(err);
return reject(err)
}
return resolve(json);
});
return resolve(json)
})
} else {
return reject(null);
return reject(error)
}
});
});
};
})
})
}
this.setChosenConnection = function (con) {
this.chosenConnection = con;
return;
};
this.chosenConnection = con
}
this.findConnection = function () {
// 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;
let resolved = false
return new Promise(async(resolve, reject) => {
await Promise.all(this.plexConnections.map(async(connection, index) => {
return new Promise(async(_resolve, _reject) => {
return new Promise(async (resolve, reject) => {
await Promise.all(this.plexConnections.map(async (connection, index) => {
/*eslint-disable */
return new Promise(async (_resolve, _reject) => {
try {
let result = await this.hitApiTestConnection('', connection);
let result = await this.hitApiTestConnection('', connection)
if (result) {
resolved = true;
//console.log('Succesfully connected to', server, 'via', connection)
this.setValue('chosenConnection', connection);
resolve(true);
resolved = true
// console.log('Succesfully connected to', server, 'via', connection)
this.setValue('chosenConnection', connection)
resolve(true)
}
_resolve(false);
_resolve(false)
} catch (e) {
_resolve(false);
_resolve(false)
}
});
}));
})
/* eslint-enable */
}))
if (!resolved) {
reject('Unable to find a connection');
reject(new Error('Unable to find a connection'))
}
});
};
})
}
//Functions for dealing with media
// Functions for dealing with media
this.search = async function (searchTerm) {
//This function hits the PMS using the /search endpoint and returns what the server returns if valid
let result = await this.hitApi('/search', {
query: searchTerm
});
let validResults = [];
if (result && result.MediaContainer) {
if (result.MediaContainer.Metadata) {
for (let i = 0; i < result.MediaContainer.Metadata.length; i++) {
validResults.push(result.MediaContainer.Metadata[i]);
// This function hits the PMS using the /search endpoint and returns what the server returns if valid
return new Promise(async (resolve, reject) => {
let result = await this.hitApi('/search', { query: searchTerm })
let validResults = []
if (result && result.MediaContainer) {
if (result.MediaContainer.Metadata) {
for (let i = 0; i < result.MediaContainer.Metadata.length; i++) {
validResults.push(result.MediaContainer.Metadata[i])
}
}
}
return validResults;
}
return null;
};
return resolve(validResults)
})
}
this.getMediaByRatingKey = async function (ratingKey) {
//This function hits the PMS and returns the item at the ratingKey
// This function hits the PMS and returns the item at the ratingKey
try {
let data = await this.hitApi('/library/metadata/' + ratingKey, {});
let data = await this.hitApi('/library/metadata/' + ratingKey, {})
if (data && data.MediaContainer.librarySectionID) {
this.commit('SET_LIBRARYCACHE', [data.MediaContainer.librarySectionID, this.clientIdentifier, data.MediaContainer.librarySectionTitle]);
this.commit('SET_LIBRARYCACHE', [data.MediaContainer.librarySectionID, this.clientIdentifier, data.MediaContainer.librarySectionTitle])
}
return data;
return data
} catch (e) {
console.log(e);
return false;
console.log(e)
return false
}
// return this.handleMetadata(data)
};
}
this.markWatchedByRatingKey = function (ratingKey) {
return this.hitApi('/:/scrobble', {
identifier: 'com.plexapp.plugins.library',
key: ratingKey
});
};
})
}
this.getUrlForLibraryLoc = function (location, width, height, blur) {
if (!(blur > 0)) {
blur = 0;
blur = 0
}
return this.chosenConnection.uri + '/photo/:/transcode?url=' + location + '&X-Plex-Token=' + this.accessToken + '&height=' + Math.floor(height) + '&width=' + Math.floor(width) + '&blur=' + blur;
};
return this.chosenConnection.uri + '/photo/:/transcode?url=' + location + '&X-Plex-Token=' + this.accessToken + '&height=' + Math.floor(height) + '&width=' + Math.floor(width) + '&blur=' + blur
}
this.getRandomItem = async function () {
try {
let data = await this.getAllLibraries();
let data = await this.getAllLibraries()
if (!data || !data.MediaContainer || !data.MediaContainer.Directory) {
return false;
return false
}
let libraries = data.MediaContainer.Directory;
let library = libraries[Math.floor(Math.random() * libraries.length)];
let libraries = data.MediaContainer.Directory
let library = libraries[Math.floor(Math.random() * libraries.length)]
let result = await this.getLibraryContents(library.key, 0, 50);
let result = await this.getLibraryContents(library.key, 0, 50)
if (!result) {
return false;
return false
}
let items = result.MediaContainer.Metadata;
let item = items[Math.floor(Math.random() * items.length)];
return item;
let items = result.MediaContainer.Metadata
let item = items[Math.floor(Math.random() * items.length)]
return item
} catch (e) {
console.log(e);
throw new Error(e);
console.log(e)
throw new Error(e)
}
};
}
this.getAllLibraries = async function () {
try {
let data = await this.hitApi('/library/sections', {});
console.log('Library data', data);
let data = await this.hitApi('/library/sections', {})
console.log('Library data', data)
if (data && data.MediaContainer) {
data.MediaContainer.Directory.forEach((library) => {
this.commit('SET_LIBRARYCACHE', [library.key, this.clientIdentifier, library.title]);
});
this.commit('SET_LIBRARYCACHE', [library.key, this.clientIdentifier, library.title])
})
}
return data;
return data
} catch (e) {
return false;
return false
}
};
}
this.getLibraryContents = async function (key, start, size) {
try {
let data = await this.hitApi('/library/sections/' + key + '/all', {
'X-Plex-Container-Start': start,
'X-Plex-Container-Size': size,
'excludeAllLeaves': 1
});
})
for (let i = 0; i < data.MediaContainer.Metadata.length; i++) {
data.MediaContainer.Metadata[i].librarySectionID = key;
// this.commit('SET_ITEMCACHE', [data.MediaContainer.Metadata[i].ratingKey,
// data.MediaContainer.Metadata[i]])
data.MediaContainer.Metadata[i].librarySectionID = key
// this.commit('SET_ITEMCACHE', [data.MediaContainer.Metadata[i].ratingKey,
// data.MediaContainer.Metadata[i]])
}
return data;
return data
} catch (e) {
console.log(e);
return false;
console.log(e)
return false
}
};
}
this.getRelated = function (key, count) {
return this.hitApi('/hubs/metadata/' + key + '/related', {
'count': count || 10
});
};
})
}
this.getRecentlyAddedAll = function (start, size) {
return this.hitApi('/library/recentlyAdded', {});
};
return this.hitApi('/library/recentlyAdded', {})
}
this.getOnDeck = function (start, size) {
return this.hitApi('/library/onDeck', {
'X-Plex-Container-Start': start,
'X-Plex-Container-Size': size,
});
};
'X-Plex-Container-Size': size
})
}
this.getRelated = function (ratingKey, size) {
ratingKey = ratingKey.replace('/library/metadata/', '');
ratingKey = ratingKey.replace('/library/metadata/', '')
return this.hitApi('/hubs/metadata/' + ratingKey + '/related', {
excludeFields: 'summary',
count: 12
});
};
})
}
this.getSeriesData = function (ratingKey) {
return this.hitApi('/library/metadata/' + ratingKey, {
includeConcerts: 1,
@ -244,8 +243,8 @@ module.exports = function PlexServer() {
asyncCheckFiles: 1,
asyncRefreshAnalysis: 1,
asyncRefreshLocalMediaAgent: 1
});
};
})
}
this.getSeriesChildren = async function (ratingKey, start, size, excludeAllLeaves, library) {
try {
@ -253,38 +252,38 @@ module.exports = function PlexServer() {
'X-Plex-Container-Start': start,
'X-Plex-Container-Size': size,
excludeAllLeaves: excludeAllLeaves
});
})
if (library) {
for (let i = 0; i < data.MediaContainer.Metadata.length; i++) {
data.MediaContainer.Metadata[i].librarySectionID = library;
// this.commit('SET_ITEMCACHE', [data.MediaContainer.Metadata[i].ratingKey,
// data.MediaContainer.Metadata[i]])
data.MediaContainer.Metadata[i].librarySectionID = library
// this.commit('SET_ITEMCACHE', [data.MediaContainer.Metadata[i].ratingKey,
// data.MediaContainer.Metadata[i]])
}
}
return data;
return data
} catch (e) {
console.log(e);
return false;
console.log(e)
return false
}
};
}
this.handleMetadata = function (result) {
if (result) {
if (result.MediaContainer && result.MediaContainer.Metadata && result.MediaContainer.Metadata.length > 0) {
for (let i = 0; i < result.MediaContainer.Metadata.length; i++) {
result.MediaContainer.Metadata[i].machineIdentifier = this.clientIdentifier;
result.MediaContainer.Metadata[i].machineIdentifier = this.clientIdentifier
if (result.MediaContainer.Metadata[i].ratingKey) {
this.commit('SET_ITEMCACHE', [result.MediaContainer.Metadata[i].ratingKey,
result.MediaContainer.Metadata[i]
]);
])
}
}
} else {
if (result.MediaContainer.ratingKey) {
this.commit('SET_ITEMCACHE', [result.MediaContainer.ratingKey, result.MediaContainer]);
this.commit('SET_ITEMCACHE', [result.MediaContainer.ratingKey, result.MediaContainer])
}
}
return result.MediaContainer.Metadata;
return result.MediaContainer.Metadata
}
};
};
}
}

View File

@ -10,385 +10,377 @@ var _PlexAuth = require('./PlexAuth.js')
var PlexAuth = new _PlexAuth()
module.exports = function () {
this.signedin = false
this.plextvdata = {}
this.gotDevices = false
this.signedin = false
this.plextvdata = {}
this.gotDevices = false
this.user = {}
this.username = null
this.all_devices = []
this.servers = []
this.clients = []
this.checkedClients = []
this.user = {}
this.username = null
this.all_devices = []
this.servers = []
this.clients = []
this.checkedClients = []
this.chosenClient = null
this.chosenServer = null
this.chosenClient = null
this.chosenServer = null
this.httpServer = null
this.httpServerPort = 1
this.httpServer = null
this.httpServerPort = 1
// Functions
this.getPort = function () {
this.httpServerPort = 8082
}
this.loginUserPass = function (_username, _password, callback) {
var that = this
this.doStandardLogin(_username, _password, function (result) {
if (result === 200 || result === 201) {
// Login successful!
that.signedin = true
return (callback(true))
}
return (callback(false))
})
}
this.loginToken = function (token, callback) {
var that = this
this.doTokenLogin(token, function (result) {
if (result === 200 || result === 201) {
// Login successful!
that.signedin = true
return (callback(true))
}
return (callback(false))
})
}
//Functions
this.getPort = function () {
this.httpServerPort = 8082
}
this.loginUserPass = function (_username, _password, callback) {
var that = this
this.doStandardLogin(_username, _password, function (result) {
if (result == 200 || result == 201) {
//Login successful!
that.signedin = true
return (callback(true))
}
return (callback(false))
})
}
this.loginToken = function (token, callback) {
var that = this
this.doTokenLogin(token, function (result) {
if (result == 200 || result == 201) {
//Login successful!
that.signedin = true
return (callback(true))
}
return (callback(false))
})
}
this.getDevices = function (callback) {
// Retrieve all clients from the plex.tv/api/resources endpoint
this.getDevices = function (callback) {
//Retrieve all clients from the plex.tv/api/resources endpoint
return new Promise((resolve, reject) => {
if (this.user == null) {
console.log('Must be logged in to retrieve devices!')
return (callback(false))
}
this.gotDevices = false
return new Promise((resolve, reject) => {
if (this.user == null) {
console.log('Must be logged in to retrieve devices!')
return (callback(false))
}
this.gotDevices = false
this.servers = []
this.clients = []
console.log('Retrieving devices for ' + this.user.username)
var options = PlexAuth.getApiOptions('https://plex.tv/api/resources?includeHttps=1', this.user.authToken, 5000, 'GET')
request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
// Valid response
parseXMLString(body, async (err, result) => {
this.servers = []
this.clients = []
console.log('Retrieving devices for ' + this.user.username)
var options = PlexAuth.getApiOptions('https://plex.tv/api/resources?includeHttps=1', this.user.authToken, 5000, 'GET')
request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
//Valid response
parseXMLString(body, async (err, result) => {
this.servers = []
this.clients = []
for (var index in result.MediaContainer.Device) {
//Handle the individual device
let device = result.MediaContainer.Device[index]['$']
//Each device can have multiple network connections
//Any of them can be viable routes to interacting with the device
let connections = result.MediaContainer.Device[index]['Connection']
let tempConnectionsArray = []
//Create a temporary array of object:PlexConnection
for (var i in connections) {
let connection = connections[i]['$']
//Exclude local IPs starting with 169.254
if (!connection.address.startsWith('169.254')) {
let tempConnection = new PlexConnection()
for (var key in connection) {
tempConnection[key] = connection[key]
}
tempConnectionsArray.push(tempConnection)
}
}
this.all_devices.push(device)
if (device.provides.indexOf('player') != -1) {
//This is a Client
//Create a new PlexClient object
var tempClient = new PlexClient()
for (var key in device) {
tempClient[key] = device[key]
}
tempClient.plexConnections = tempConnectionsArray
tempClient.subscribePort = this.httpServerPort
tempClient.userData = this.user
this.clients.push(tempClient)
} else {
//This is a Server
//Create a new PlexServer object
let tempServer = new PlexServer()
for (var key in device) {
tempServer[key] = device[key]
}
tempServer.plexConnections = tempConnectionsArray
if (tempServer['accessToken'] == null) {
tempServer['accessToken'] = this.user.authToken
}
//that.servers.push(tempServer)
tempServer.findConnection().then((result) => {
if (result) {
this.servers.push(tempServer)
}
})
}
}
let ptplayer = new PlexClient()
ptplayer.provides = 'player'
ptplayer.clientIdentifier = 'PTPLAYER9PLUS10'
ptplayer.platform = 'Web'
ptplayer.device = 'Web'
ptplayer.product = 'SyncLounge'
ptplayer.name = 'SyncLounge Player (BETA)'
ptplayer.lastSeenAt = Math.round((new Date).getTime() / 1000)
this.clients.push(ptplayer)
this.clients.sort(function (a, b) {
return parseInt(b.lastSeenAt) - parseInt(a.lastSeenAt)
})
// Setup our PTPlayer
console.log('Succesfully retrieved all Plex Devices')
this.gotDevices = true
return resolve(true)
})
} else {
//Invalid response
this.gotDevices = true
return reject(false)
for (var index in result.MediaContainer.Device) {
// Handle the individual device
let device = result.MediaContainer.Device[index]['$']
// Each device can have multiple network connections
// Any of them can be viable routes to interacting with the device
let connections = result.MediaContainer.Device[index]['Connection']
let tempConnectionsArray = []
// Create a temporary array of object:PlexConnection
for (var i in connections) {
let connection = connections[i]['$']
// Exclude local IPs starting with 169.254
if (!connection.address.startsWith('169.254')) {
let tempConnection = new PlexConnection()
for (var key in connection) {
tempConnection[key] = connection[key]
}
tempConnectionsArray.push(tempConnection)
}
})
})
}
this.doTokenLogin = function (token, callback) {
var that = this
//Login via a token, this is the normal login path after
// the initial setup
var options = PlexAuth.getApiOptions('https://plex.tv/users/sign_in.json', token, 5000, 'POST')
var that = this
request(options, function (error, response, body) {
if (!error && (response.statusCode == 200 || response.statusCode == 201)) {
safeParse(body, function (err, json) {
if (err) {
that.signedin = false
return (callback(false, response, body))
}
that.signedin = true
that.user = json.user
return (callback(true, response, body))
}
this.all_devices.push(device)
if (device.provides.indexOf('player') != -1) {
// This is a Client
// Create a new PlexClient object
var tempClient = new PlexClient()
for (var key in device) {
tempClient[key] = device[key]
}
tempClient.plexConnections = tempConnectionsArray
tempClient.subscribePort = this.httpServerPort
tempClient.userData = this.user
this.clients.push(tempClient)
} else {
// This is a Server
// Create a new PlexServer object
let tempServer = new PlexServer()
for (var key in device) {
tempServer[key] = device[key]
}
tempServer.plexConnections = tempConnectionsArray
if (tempServer['accessToken'] == null) {
tempServer['accessToken'] = this.user.authToken
}
// that.servers.push(tempServer)
tempServer.findConnection().then((result) => {
if (result) {
this.servers.push(tempServer)
}
})
} else {
var code = response.statusCode
if (code == 401) {
that.signedin = false
return (callback(false, response, body))
}
that.signedin = false
return (callback(false, response, body))
}
}
})
let ptplayer = new PlexClient()
ptplayer.provides = 'player'
ptplayer.clientIdentifier = 'PTPLAYER9PLUS10'
ptplayer.platform = 'Web'
ptplayer.device = 'Web'
ptplayer.product = 'SyncLounge'
ptplayer.name = 'SyncLounge Player (BETA)'
ptplayer.lastSeenAt = Math.round((new Date()).getTime() / 1000)
}
this.doStandardLogin = function (username, password, _callback) {
//Sign in to Plex.tv via plex.tv/users/sign_in.json via POST
var base64encoded = new Buffer(username + ':' + password).toString('base64')
var options = {
url: 'https://plex.tv/users/sign_in.json',
headers: {
'Authorization': 'Basic ' + base64encoded,
'X-Plex-Client-Identifier': global.constants.X_PLEX_CLIENT_IDENTIFIER
},
method: 'POST'
this.clients.push(ptplayer)
this.clients.sort(function (a, b) {
return parseInt(b.lastSeenAt) - parseInt(a.lastSeenAt)
})
// Setup our PTPlayer
console.log('Succesfully retrieved all Plex Devices')
this.gotDevices = true
return resolve(true)
})
} else {
// Invalid response
this.gotDevices = true
return reject(false)
}
var that = this
request(options, function (error, response, body) {
if (!error && (response.statusCode == 200 || response.statusCode == 201)) {
safeParse(body, function (err, json) {
that.user = json.user
that.signedin = true
return (_callback(response.statusCode))
})
} else {
var code = response.statusCode
if (code == 401) {
that.signedin = false
return (_callback(response.statusCode))
}
that.signedin = false
return (_callback(response.statusCode))
}
})
})
}
this.doTokenLogin = function (token, callback) {
var that = this
// Login via a token, this is the normal login path after
// the initial setup
var options = PlexAuth.getApiOptions('https://plex.tv/users/sign_in.json', token, 5000, 'POST')
var that = this
request(options, function (error, response, body) {
if (!error && (response.statusCode == 200 || response.statusCode == 201)) {
safeParse(body, function (err, json) {
if (err) {
that.signedin = false
return (callback(false, response, body))
}
that.signedin = true
that.user = json.user
return (callback(true, response, body))
})
} else {
var code = response.statusCode
if (code == 401) {
that.signedin = false
return (callback(false, response, body))
}
that.signedin = false
return (callback(false, response, body))
}
})
}
this.doStandardLogin = function (username, password, _callback) {
// Sign in to Plex.tv via plex.tv/users/sign_in.json via POST
var base64encoded = new Buffer(username + ':' + password).toString('base64')
var options = {
url: 'https://plex.tv/users/sign_in.json',
headers: {
'Authorization': 'Basic ' + base64encoded,
'X-Plex-Client-Identifier': global.constants.X_PLEX_CLIENT_IDENTIFIER
},
method: 'POST'
}
this.getClientById = function (clientId, callback) {
for (var i in this.clients) {
var client = this.clients[i]
if (client.clientIdentifier == clientId) {
return callback(client)
var that = this
request(options, function (error, response, body) {
if (!error && (response.statusCode == 200 || response.statusCode == 201)) {
safeParse(body, function (err, json) {
that.user = json.user
that.signedin = true
return (_callback(response.statusCode))
})
} else {
var code = response.statusCode
if (code == 401) {
that.signedin = false
return (_callback(response.statusCode))
}
that.signedin = false
return (_callback(response.statusCode))
}
})
}
this.getClientById = function (clientId, callback) {
for (var i in this.clients) {
var client = this.clients[i]
if (client.clientIdentifier == clientId) {
return callback(client)
}
}
return callback(null)
}
this.getServerById = function (clientId) {
for (var i in this.servers) {
var server = this.servers[i]
if (server.clientIdentifier == clientId) {
return server
}
}
return null
}
this.getRandomThumb = function (callback) {
let ticker = (failures) => {
setTimeout(() => {
if (failures == 10) {
return callback(false)
}
let validServers = this.servers.filter((server) => {
if (server.chosenConnection) {
return true
}
return false
})
if (validServers.length > 1) {
let randomServer = validServers[Math.floor(Math.random() * validServers.length)]
randomServer.getRandomItem((result) => {
console.log('Random item result', result)
if (!result) {
return callback(false)
}
return callback(randomServer.getUrlForLibraryLoc(result.thumb, 900, 900, 8))
})
} else {
ticker(failures + 1)
}
return callback(null)
}, 100)
}
this.getServerById = function (clientId) {
for (var i in this.servers) {
var server = this.servers[i]
if (server.clientIdentifier == clientId) {
return server
}
}
return null
}
this.getRandomThumb = function (callback) {
let ticker = (failures) => {
setTimeout( () => {
if (failures == 10){
return callback(false)
}
let validServers = this.servers.filter( (server) => {
if (server.chosenConnection){
return true
}
return false
})
if (validServers.length > 1){
let randomServer = validServers[Math.floor(Math.random()*validServers.length)]
randomServer.getRandomItem((result) => {
console.log('Random item result',result)
if (!result){
return callback(false)
}
return callback(randomServer.getUrlForLibraryLoc(result.thumb,900 ,900 ,8))
})
} else {
ticker(failures+1)
}
},100)
}
ticker(0)
}
this.playContentAutomatically = function (client, hostData, blockedServers, offset, callback) {
ticker(0)
}
this.playContentAutomatically = function (client, hostData, blockedServers, offset, callback) {
// Automatically play content on the client searching all servers based on the title
var that = this
var that = this
// First lets find all of our playable items
let playables = []
let j = 0
// First lets find all of our playable items
let playables = []
let j = 0
let validServers = this.servers.length
if (blockedServers){
for (let i = 0; i < blockedServers.length; i++ ){
if (this.servers[blockedServers[i]]){
validServers--
}
}
let validServers = this.servers.length
if (blockedServers) {
for (let i = 0; i < blockedServers.length; i++) {
if (this.servers[blockedServers[i]]) {
validServers--
}
if (validServers == 0){
return callback(false)
}
for (let i = 0; i < this.servers.length; i++) {
var server = this.servers[i]
let blocked = false
if (blockedServers){
for (let i = 0; i < blockedServers.length; i++ ){
if (blockedServers[i] == server.clientIdentifier){
console.log('Server: ' + server.name + ' is blocked - not searching')
blocked = true
}
}
}
if (blocked){
continue
}
server.search(hostData.rawTitle, function (results, _server) {
j++
console.log('Heard back from ' + j + ' servers')
if (results != null) {
for (var k = 0; k < results.length; k++) {
//Now we need to check the result
if (checkResult(results[k], hostData)) {
//Its a match!
playables.push({
'server': _server,
'result': results[k]
})
}
}
}
if (j == validServers) {
console.log('Found ' + playables.length + ' playable items')
start(playables, 0)
}
})
}
function start (playables, index) {
// Now lets try and play our items one by one
if (playables.length == 0) {
console.log('We didnt find any playables.')
return callback(false)
}
console.log('Going to try playing the next playable: Index ' + index)
var server = playables[index].server
console.log('Attempting to play from ')
console.log(server)
var ratingKey = playables[index].result.ratingKey
let data = {
ratingKey: ratingKey,
mediaIndex: null,
server: server,
offset: offset || 0,
callback: function(playResult, code){
console.log('Play result: ' + code)
if (code == 200) {
return callback(true)
} else {
return start(playables, parseInt(parseInt(index) + 1))
}
}
}
if (client.clientIdentifier == 'PTPLAYER9PLUS10') {
client.playMedia(data)
} else {
// Standard Plex Player
client.subscribe(function (result) {
client.playMedia(data)
})
}
}
function checkResult (data, hostData) {
console.log('Checking compatibility for this item')
console.log(data)
console.log(hostData)
//Do a series of checks to see if this result is OK
//Check if rawTitle matches
if (data.title != hostData.rawTitle) {
//global.renderLog.info('wrong title')
return false
}
//Check if length is close enough
if (Math.abs(parseInt(data.duration) - parseInt(hostData.maxTime)) > 5000 || !data.duration) {
//global.renderLog.info('wrong time')
return false
}
if (data.type == 'movie') {
//We're good!
console.log('FOUND A PLAYABLE MOVIE')
return true
}
if (data.type == 'episode') {
//Check if the show name is the same
console.log('FOUND A PLAYABLE TV EPISODE')
return true
}
if (data.type == 'track') {
//We're good!
console.log('FOUND A PLAYABLE track')
return true
}
return false
}
},
this.createHttpServer = function () {
}
}
this.getPort()
if (validServers == 0) {
return callback(false)
}
for (let i = 0; i < this.servers.length; i++) {
var server = this.servers[i]
let blocked = false
if (blockedServers) {
for (let i = 0; i < blockedServers.length; i++) {
if (blockedServers[i] == server.clientIdentifier) {
console.log('Server: ' + server.name + ' is blocked - not searching')
blocked = true
}
}
}
if (blocked) {
continue
}
server.search(hostData.rawTitle, function (results, _server) {
j++
console.log('Heard back from ' + j + ' servers')
if (results != null) {
for (var k = 0; k < results.length; k++) {
// Now we need to check the result
if (checkResult(results[k], hostData)) {
// Its a match!
playables.push({
'server': _server,
'result': results[k]
})
}
}
}
if (j == validServers) {
console.log('Found ' + playables.length + ' playable items')
start(playables, 0)
}
})
}
function start (playables, index) {
// Now lets try and play our items one by one
if (playables.length == 0) {
console.log('We didnt find any playables.')
return callback(false)
}
console.log('Going to try playing the next playable: Index ' + index)
var server = playables[index].server
console.log('Attempting to play from ')
console.log(server)
var ratingKey = playables[index].result.ratingKey
let data = {
ratingKey: ratingKey,
mediaIndex: null,
server: server,
offset: offset || 0,
callback: function (playResult, code) {
console.log('Play result: ' + code)
if (code == 200) {
return callback(true)
} else {
return start(playables, parseInt(parseInt(index) + 1))
}
}
}
if (client.clientIdentifier == 'PTPLAYER9PLUS10') {
client.playMedia(data)
} else {
// Standard Plex Player
client.subscribe(function (result) {
client.playMedia(data)
})
}
}
function checkResult (data, hostData) {
console.log('Checking compatibility for this item')
console.log(data)
console.log(hostData)
// Do a series of checks to see if this result is OK
// Check if rawTitle matches
if (data.title != hostData.rawTitle) {
// global.renderLog.info('wrong title')
return false
}
// Check if length is close enough
if (Math.abs(parseInt(data.duration) - parseInt(hostData.maxTime)) > 5000 || !data.duration) {
// global.renderLog.info('wrong time')
return false
}
if (data.type == 'movie') {
// We're good!
console.log('FOUND A PLAYABLE MOVIE')
return true
}
if (data.type == 'episode') {
// Check if the show name is the same
console.log('FOUND A PLAYABLE TV EPISODE')
return true
}
if (data.type == 'track') {
// We're good!
console.log('FOUND A PLAYABLE track')
return true
}
return false
}
},
this.createHttpServer = function () {
}
this.getPort()
}

View File

@ -233,7 +233,7 @@ export default {
type: 'alert'
})
})
state._socket.on('host-update', (data) => {
state._socket.on('host-update', async (data) => {
/* This is data from the host, we should react to this data by potentially changing
what we're playing or seeking to get back in sync with the host.
@ -254,7 +254,11 @@ export default {
}
if (!rootState.chosenClient.lastTimelineObject) {
console.log('Dont have our first timeline data yet.')
return
if (rootState.chosenClient.clientIdentifier === 'PTPLAYER9PLUS10') {
rootState.chosenClient.lastTimelineObject = {}
} else {
return
}
}
// Check previous timeline data age
let timelineAge = new Date().getTime() - rootState.chosenClient.lastTimelineObject.recievedAt
@ -266,7 +270,7 @@ export default {
decisionMaker(timelineAge)
}
function decisionMaker (timelineAge) {
async function decisionMaker (timelineAge) {
let ourTimeline = rootState.chosenClient.lastTimelineObject
let hostTimeline = data
@ -295,39 +299,42 @@ export default {
state.decisionBlocked = true
let blockedServers = rootState.BLOCKEDSERVERS
let validServers = rootState.plex.servers.length
let servers = Object.assign({}, rootState.plex.servers)
if (blockedServers) {
for (let i = 0; i < blockedServers.length; i++) {
if (rootState.plex.servers[blockedServers[i]]) {
validServers--
delete servers[i]
}
}
}
sendNotification('Searching ' + validServers + ' Plex Servers for "' + hostTimeline.rawTitle + '"')
rootState.plex.playContentAutomatically(rootState.chosenClient, hostTimeline, blockedServers, hostTimeline.time, function (result) {
console.log('Auto play result: ' + result)
if (!result) {
sendNotification('Failed to find a compatible copy of ' + hostTimeline.rawTitle)
}
state.decisionBlocked = false
sendNotification('Searching Plex Servers for "' + hostTimeline.rawTitle + '"')
let result = await rootState.chosenClient.playContentAutomatically(rootState.chosenClient, hostTimeline, servers, hostTimeline.time).catch((e) => {
sendNotification('Failed to find a compatible copy of ' + hostTimeline.rawTitle)
setTimeout(function () {
rootState.blockAutoPlay = false
}, 15000)
state.decisionBlocked = false
})
console.log('Auto play result: ' + result)
state.decisionBlocked = false
await new Promise((resolve, reject) => {
setTimeout(() => resolve(), 10000)
})
rootState.blockAutoPlay = false
return
}
let difference = Math.abs((parseInt(ourTimeline.time) + parseInt(timelineAge)) - parseInt(hostTimeline.time))
if (hostTimeline.playerState === 'playing' && ourTimeline.state === 'paused') {
sendNotification('The host pressed play')
sendNotification('Resuming..')
rootState.chosenClient.pressPlay(() => {
checkForSeek()
})
return
}
if (hostTimeline.playerState === 'paused' && ourTimeline.state === 'playing') {
sendNotification('The host pressed pause')
sendNotification('Pausing..')
rootState.chosenClient.pressPause(() => {
checkForSeek()
})