Much progress on sync logic but there will need to be so much testing
This commit is contained in:
parent
dd53064ae7
commit
0eef02afa1
|
@ -146,57 +146,6 @@ class PlexClient {
|
|||
}
|
||||
return this.seekTo(time);
|
||||
}
|
||||
|
||||
async sync(hostTime, SYNCFLEXIBILITY, SYNCMODE, POLLINTERVAL) {
|
||||
const hostTimeline = hostTime;
|
||||
if (this.clientIdentifier === 'PTPLAYER9PLUS10') {
|
||||
await this.getTimeline();
|
||||
}
|
||||
const lastCommandTime = Math.abs(this.lastSyncCommand - new Date().getTime());
|
||||
if (this.lastSyncCommand && this.clientIdentifier !== 'PTPLAYER9PLUS10' && lastCommandTime < POLLINTERVAL) {
|
||||
throw new Error('too soon for another sync command');
|
||||
}
|
||||
const lagTime = Math.abs(hostTimeline.recievedAt - new Date().getTime());
|
||||
if (lagTime) {
|
||||
hostTimeline.time += lagTime;
|
||||
}
|
||||
const timelineAge = new Date().getTime() - this.lastTimelineObject.recievedAt;
|
||||
const ourTime = parseInt(this.lastTimelineObject.time, 10) + parseInt(timelineAge, 10);
|
||||
const difference = Math.abs((parseInt(ourTime, 10)) - parseInt(hostTimeline.time, 10));
|
||||
// console.log('Difference with host is', difference);
|
||||
const bothPaused = hostTimeline.playerState === 'paused' && this.lastTimelineObject.state === 'paused';
|
||||
|
||||
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
|
||||
if (SYNCMODE === 'cleanseek' || hostTimeline.playerState === 'paused') {
|
||||
return this.cleanSeek(hostTimeline.time);
|
||||
}
|
||||
if (SYNCMODE === 'skipahead') {
|
||||
return this.skipAhead(hostTimeline.time, 10000);
|
||||
}
|
||||
// Fall back to skipahead
|
||||
return this.skipAhead(hostTimeline.time, 10000);
|
||||
}
|
||||
// Calc the average delay of the last 10 host timeline updates
|
||||
// We do this to avoid any issues with random lag spikes
|
||||
this.differenceCache.unshift(difference);
|
||||
if (this.differenceCache.length > 5) {
|
||||
this.differenceCache.pop();
|
||||
}
|
||||
let total = 0;
|
||||
for (let i = 0; i < this.differenceCache.length; i += 1) {
|
||||
total += this.differenceCache[i];
|
||||
}
|
||||
const avg = total / this.differenceCache.length;
|
||||
if (this.clientIdentifier === 'PTPLAYER9PLUS10' && avg > 1500) {
|
||||
console.log('Soft syncing because difference is', difference);
|
||||
return this.cleanSeek(hostTimeline.time, true);
|
||||
}
|
||||
return 'No sync needed';
|
||||
}
|
||||
}
|
||||
|
||||
export default PlexClient;
|
||||
|
|
|
@ -79,12 +79,28 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
FETCH_CHOSEN_CLIENT_TIMELINE: async ({ getters }) => {
|
||||
const { data } = await getters.GET_CHOSEN_PLEX_CLIENT_AXIOS.get('/player/timeline/poll', {
|
||||
SEND_CLIENT_REQUEST: async ({ getters, commit }, { path, params }) => {
|
||||
const { data } = await getters.GET_CHOSEN_PLEX_CLIENT_AXIOS.get(path, {
|
||||
params: {
|
||||
commandId: getters.GET_COMMAND_ID,
|
||||
type: 'video',
|
||||
...params,
|
||||
},
|
||||
transformResponse: xmlutils.parseXML,
|
||||
});
|
||||
|
||||
// TODO: maybe potentially increment it even if it fails
|
||||
commit('INCREMENT_COMMAND_ID');
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
FETCH_CHOSEN_CLIENT_TIMELINE: async ({ dispatch }) => {
|
||||
const data = await dispatch('SEND_CLIENT_REQUEST', {
|
||||
url: '/player/timeline/poll',
|
||||
params: {
|
||||
wait: 0,
|
||||
},
|
||||
transformResponse: xmlutils.parseXML,
|
||||
});
|
||||
|
||||
const videoTimeline = data.MediaContainer[0].Timeline.find((timeline) => timeline.type === 'video');
|
||||
|
@ -94,6 +110,7 @@ export default {
|
|||
time: parseInt(videoTimeline.time, 10),
|
||||
duration: parseInt(videoTimeline.duration, 10),
|
||||
receivedAt: Date.now(),
|
||||
commandID: parseInt(data.MediaContainer[0].commandID, 10),
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -163,4 +180,79 @@ export default {
|
|||
machineIdentifier: getters.GET_ACTIVE_SERVER_ID,
|
||||
};
|
||||
},
|
||||
|
||||
SYNC: async ({
|
||||
getters, dispatch, commit, rootGetters,
|
||||
}) => {
|
||||
if (getters.GET_CHOSEN_CLIENT_ID !== 'PTPLAYER9PLUS10'
|
||||
&& (!getters.GET_PREVIOUS_SYNC_TIMELINE_COMMAND_ID
|
||||
|| getters.GET_PLEX_CLIENT_TIMELINE.commandID
|
||||
<= getters.GET_PREVIOUS_SYNC_TIMELINE_COMMAND_ID)) {
|
||||
// TODO: examine if I should throw error or handle it another way
|
||||
throw new Error('Already synced with this timeline. Need to wait for new one to sync again');
|
||||
}
|
||||
|
||||
const adjustedHostTime = rootGetters['synclounge/GET_HOST_PLAYER_TIME_ADJUSTED']();
|
||||
|
||||
// TODO: only do this if we are playign (but maybe we just did a play command>...)
|
||||
|
||||
// TODO: see if i need await
|
||||
const playerPollData = dispatch('FETCH_TIMELINE_POLL_DATA_CACHE');
|
||||
|
||||
// TODO: also assuming 0 rtt between us and player (is this wise)
|
||||
const timelineAge = playerPollData.recievedAt
|
||||
? Date.now() - playerPollData.recievedAt
|
||||
: 0;
|
||||
|
||||
const adjustedTime = playerPollData.playerState === 'playing'
|
||||
? playerPollData + timelineAge
|
||||
: playerPollData.time;
|
||||
|
||||
const difference = Math.abs(adjustedHostTime - adjustedTime);
|
||||
|
||||
const bothPaused = rootGetters['synclounge/GET_HOST_TIMELINE'].playerState === 'paused'
|
||||
&& playerPollData.playerState === 'paused';
|
||||
|
||||
if (difference > rootGetters['settings/GET_SYNCFLEXIBILITY'] || (bothPaused && difference > 10)) {
|
||||
// We need to seek!
|
||||
// Decide what seeking method we want to use
|
||||
|
||||
if (rootGetters['settings/GET_SYNCMODE'] === 'cleanseek'
|
||||
|| rootGetters['synclounge/GET_HOST_TIMELINE'].playerState === 'paused') {
|
||||
return this.cleanSeek(adjustedHostTime);
|
||||
}
|
||||
|
||||
if (rootGetters['settings/GET_SYNCMODE'] === 'skipahead') {
|
||||
return this.skipAhead(adjustedHostTime, 10000);
|
||||
}
|
||||
|
||||
// Fall back to skipahead
|
||||
return this.skipAhead(adjustedHostTime, 10000);
|
||||
}
|
||||
|
||||
// Calc the average delay of the last 10 host timeline updates
|
||||
// We do this to avoid any issues with random lag spikes
|
||||
this.differenceCache.unshift(difference);
|
||||
if (this.differenceCache.length > 5) {
|
||||
this.differenceCache.pop();
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
for (let i = 0; i < this.differenceCache.length; i += 1) {
|
||||
total += this.differenceCache[i];
|
||||
}
|
||||
|
||||
const avg = total / this.differenceCache.length;
|
||||
if (this.clientIdentifier === 'PTPLAYER9PLUS10' && avg > 1500) {
|
||||
console.log('Soft syncing because difference is', difference);
|
||||
return this.cleanSeek(adjustedHostTime, true);
|
||||
}
|
||||
|
||||
// TODO: make sure all them hit here and fix the id lol
|
||||
// todo: also store the command id above because it might have changed during the awaits
|
||||
// TODO: also update this when pausing or playign so we don't have werid stuff
|
||||
commit('SET_PREVIOUS_SYNC_TIMELINE_COMMAND_ID', null);
|
||||
|
||||
return 'No sync needed';
|
||||
},
|
||||
};
|
||||
|
|
|
@ -21,8 +21,12 @@ export default {
|
|||
|
||||
return axios.create({
|
||||
baseURL: client.chosenConnection.uri,
|
||||
// TODO: examine this timeout...
|
||||
timeout: 5000,
|
||||
headers: rootGetters['plex/GET_PLEX_BASE_PARAMS'](client.accessToken),
|
||||
headers: {
|
||||
...rootGetters['plex/GET_PLEX_BASE_PARAMS'](client.accessToken),
|
||||
'X-Plex-Target-Client-Identifier': clientIdentifier,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -54,4 +58,8 @@ export default {
|
|||
playerState: getters.GET_PLEX_CLIENT_TIMELINE.state,
|
||||
})
|
||||
: null),
|
||||
|
||||
GET_COMMAND_ID: (state) => state.commandId,
|
||||
|
||||
GET_PREVIOUS_SYNC_TIMELINE_COMMAND_ID: (state) => state.previousSyncTimelineCommandId,
|
||||
};
|
||||
|
|
|
@ -20,4 +20,12 @@ export default {
|
|||
SET_ACTIVE_SERVER_ID: (state, id) => {
|
||||
state.activeServerId = id;
|
||||
},
|
||||
|
||||
INCREMENT_COMMAND_ID: (state) => {
|
||||
state.commandId += 1;
|
||||
},
|
||||
|
||||
SET_PREVIOUS_SYNC_TIMELINE_COMMAND_ID: (state, commandId) => {
|
||||
state.previousSyncTimelineCommandId = commandId;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -22,6 +22,11 @@ const state = () => ({
|
|||
|
||||
// Timeline storage only for plex clients. For slplayer, we query its state directly
|
||||
plexClientTimeline: null,
|
||||
commandId: 0,
|
||||
|
||||
// Tracks the commandId of the timeline that was used to synchronize last, so it doesn't try and synchronize
|
||||
// multiple times with the same data and instead waits for a fresh one
|
||||
previousSyncTimelineCommandId: null,
|
||||
});
|
||||
|
||||
export default state;
|
||||
|
|
|
@ -222,6 +222,7 @@ export default {
|
|||
},
|
||||
|
||||
NORMAL_SEEK: async ({ getters, commit, rootGetters }, seekToMs) => {
|
||||
// TODO: check the logic here to make sense if the seek time is in the past ...
|
||||
if ((Math.abs(seekToMs - getPlayerCurrentTimeMs(getters)) < 3000
|
||||
&& rootGetters.GET_HOST_PLAYER_STATE === 'playing')) {
|
||||
let cancelled = false;
|
||||
|
|
|
@ -196,7 +196,10 @@ export default {
|
|||
data: {
|
||||
...data,
|
||||
commandId: getters.GET_POLL_NUMBER,
|
||||
latency: getters.GET_SRTT,
|
||||
// RTT / 2 because this is just the time it takes for a message to get to the server,
|
||||
// not a complete round trip. The receiver should add this latency as well as 1/2 their srtt
|
||||
// to the server when calculating delays
|
||||
latency: getters.GET_SRTT / 2,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -8,11 +8,10 @@ export default {
|
|||
commit('CLEAR_MESSAGES');
|
||||
|
||||
commit('SET_USERS', currentUsers);
|
||||
// commit('SET_ME', _data.username);
|
||||
commit('SET_PARTYPAUSING', partyPausing);
|
||||
commit('SET_ROOM', _data.room);
|
||||
|
||||
sendNotification(`Joined room: ${_data.room}`);
|
||||
dispatch('DISPLAY_NOTIFICATION', `Joined room: ${_data.room}`, { root: true });
|
||||
// Add this item to our recently-connected list
|
||||
dispatch(
|
||||
'settings/ADD_RECENT_ROOM',
|
||||
|
@ -85,7 +84,7 @@ export default {
|
|||
commit('SET_PARTYPAUSING', value);
|
||||
},
|
||||
|
||||
HANDLE_PARTY_PAUSING_PAUSE: ({ commit, rootGetters }, { isPause, user }) => {
|
||||
HANDLE_PARTY_PAUSING_PAUSE: ({ commit, dispatch, rootGetters }, { isPause, user }) => {
|
||||
const messageText = `${user.username} pressed ${isPause ? 'pause' : 'play'}`;
|
||||
commit('ADD_MESSAGE', {
|
||||
msg: messageText,
|
||||
|
@ -93,7 +92,7 @@ export default {
|
|||
type: 'alert',
|
||||
});
|
||||
|
||||
sendNotification(messageText);
|
||||
dispatch('DISPLAY_NOTIFICATION', messageText, { root: true });
|
||||
if (rootGetters.GET_CHOSEN_CLIENT) {
|
||||
if (isPause) {
|
||||
rootGetters.GET_CHOSEN_CLIENT.pressPause();
|
||||
|
@ -143,6 +142,8 @@ export default {
|
|||
commit('SET_HOST_TIMELINE', {
|
||||
...timeline,
|
||||
recievedAt: Date.now(),
|
||||
// TODO: think about whether I need this or
|
||||
srttSnapsnotAtReception: getters.GET_SRTT,
|
||||
});
|
||||
|
||||
return dispatch('SYNCHRONIZE');
|
||||
|
@ -170,18 +171,13 @@ export default {
|
|||
|
||||
// console.log('Timeline age is', timelineAge);
|
||||
try {
|
||||
// if ((timelineAge > 1000 && rootState.chosenClient.clientIdentifier !== 'PTPLAYER9PLUS10') || rootState.chosenClient.clientIdentifier === 'PTPLAYER9PLUS10') {
|
||||
// await rootState.chosenClient.getTimeline()
|
||||
// await decisionMaker(0)
|
||||
// } else {
|
||||
await decisionMaker();
|
||||
// }
|
||||
await dispatch('DECISION_MAKER');
|
||||
} catch (e) {
|
||||
console.log('Error caught in sync logic', e);
|
||||
}
|
||||
},
|
||||
|
||||
DECISION_MAKE: async ({
|
||||
DECISION_MAKER: async ({
|
||||
getters, dispatch, rootGetters,
|
||||
}) => {
|
||||
// TODO: potentailly don't do anythign if we have no timeline data yet
|
||||
|
@ -192,7 +188,9 @@ export default {
|
|||
dispatch('DISPLAY_NOTIFICATION', 'The host pressed stop', { root: true });
|
||||
await rootGetters.GET_CHOSEN_CLIENT.pressStop();
|
||||
return;
|
||||
} if (rootGetters['settings/GET_AUTOPLAY']
|
||||
}
|
||||
|
||||
if (rootGetters['settings/GET_AUTOPLAY']
|
||||
&& (getters.GET_HOST_TIMELINE.ratingKey !== getters.GET_HOST_LAST_RATING_KEY
|
||||
|| timeline.playerState === 'stopped')) {
|
||||
// If we have autoplay enabled and the host rating key has changed or if we aren't playign anything
|
||||
|
@ -208,6 +206,7 @@ export default {
|
|||
if (getters.GET_HOST_TIMELINE.playerState === 'playing' && timeline.state === 'paused') {
|
||||
dispatch('DISPLAY_NOTIFICATION', 'Resuming..', { root: true });
|
||||
await rootGetters.GET_CHOSEN_CLIENT.pressPlay();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((getters.GET_HOST_TIMELINE.playerState === 'paused'
|
||||
|
@ -215,19 +214,22 @@ export default {
|
|||
&& timeline.state === 'playing') {
|
||||
dispatch('DISPLAY_NOTIFICATION', 'Pausing..', { root: true });
|
||||
await rootGetters.GET_CHOSEN_CLIENT.pressPause();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: since we have awaited,
|
||||
|
||||
// TODO: potentially update the player state if we paused or played so we know in the sync
|
||||
|
||||
if (getters.GET_HOST_TIMELINE.playerState === 'playing') {
|
||||
// Add on the delay between us and the SLServer plus the delay between the server and the host
|
||||
hostUpdateData.time = hostUpdateData.time + (getters.GET_SRTT || 0)
|
||||
+ (hostTimeline.latency || 0);
|
||||
}
|
||||
|
||||
await dispatch('plexclients/SYNC', null, { root: true });
|
||||
await rootGetters.GET_CHOSEN_CLIENT.sync(
|
||||
hostUpdateData,
|
||||
rootGetters['settings/GET_SYNCFLEXIBILITY'],
|
||||
rootGetters['settings/GET_SYNCMODE'],
|
||||
rootGetters['settings/GET_CLIENTPOLLINTERVAL'],
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -252,8 +254,9 @@ export default {
|
|||
commit('SET_HOST_LAST_RATING_KEY', getters.GET_HOST_TIMELINE.ratingKey);
|
||||
},
|
||||
|
||||
HANDLE_DISCONNECT: ({ commit }, disconnectData) => {
|
||||
sendNotification('Disconnected from the SyncLounge server');
|
||||
HANDLE_DISCONNECT: ({ commit, dispatch }, disconnectData) => {
|
||||
dispatch('DISPLAY_NOTIFICATION', 'Disconnected from the SyncLounge server', { root: true });
|
||||
|
||||
console.log('Disconnect data', disconnectData);
|
||||
if (disconnectData === 'io client disconnect') {
|
||||
console.log('We disconnected from the server');
|
||||
|
|
|
@ -75,8 +75,11 @@ export default {
|
|||
GET_HOST_PLAYER_TIME_ADJUSTED: (state, getters) => () => {
|
||||
const hostAge = Date.now() - getters.GET_HOST_TIMELINE.recievedAt;
|
||||
|
||||
// TODO: please veyr much examine the latency and maybe see if the server should calc
|
||||
return getters.GET_HOST_TIMELINE.playerState === 'playing'
|
||||
? getters.GET_HOST_TIMELINE.time + hostAge
|
||||
+ (getters.GET_HOST_TIMELINE.latency || 0)
|
||||
+ (getters.GET_HOST_TIMELINE.srttSnapsnotAtReception || 0) / 2
|
||||
: getters.GET_HOST_TIMELINE.time;
|
||||
},
|
||||
|
||||
|
@ -102,6 +105,6 @@ export default {
|
|||
|
||||
GET_SERVER: (state) => state.server,
|
||||
|
||||
// Used to detect if the host changes
|
||||
// Used to detect if the host changes
|
||||
GET_HOST_LAST_RATING_KEY: (state) => state.hostLastRatingKey,
|
||||
};
|
||||
|
|
|
@ -70,4 +70,5 @@ export default {
|
|||
SET_HOST_TIMELINE: (state, timeline) => {
|
||||
state.hostTimeline = timeline;
|
||||
},
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue