1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00
jellyfin-web/src/components/syncPlay/timeSyncManager.js

206 lines
5.8 KiB
JavaScript
Raw Normal View History

2020-04-16 16:05:04 +02:00
/**
* Module that manages time syncing with server.
* @module components/syncPlay/timeSyncManager
2020-04-16 16:05:04 +02:00
*/
import events from 'events';
import connectionManager from 'connectionManager';
/**
* Time estimation
*/
const NumberOfTrackedMeasurements = 8;
const PollingIntervalGreedy = 1000; // milliseconds
const PollingIntervalLowProfile = 60000; // milliseconds
const GreedyPingCount = 3;
/**
* Class that stores measurement data.
*/
class Measurement {
/**
* Creates a new measurement.
2020-05-05 12:01:43 +02:00
* @param {Date} requestSent Client's timestamp of the request transmission
* @param {Date} requestReceived Server's timestamp of the request reception
* @param {Date} responseSent Server's timestamp of the response transmission
* @param {Date} responseReceived Client's timestamp of the response reception
2020-04-16 16:05:04 +02:00
*/
2020-05-05 12:01:43 +02:00
constructor(requestSent, requestReceived, responseSent, responseReceived) {
this.requestSent = requestSent.getTime();
this.requestReceived = requestReceived.getTime();
this.responseSent = responseSent.getTime();
this.responseReceived = responseReceived.getTime();
2020-04-16 16:05:04 +02:00
}
/**
* Time offset from server.
*/
getOffset () {
2020-05-05 12:01:43 +02:00
return ((this.requestReceived - this.requestSent) + (this.responseSent - this.responseReceived)) / 2;
2020-04-16 16:05:04 +02:00
}
/**
* Get round-trip delay.
*/
getDelay () {
2020-05-05 12:01:43 +02:00
return (this.responseReceived - this.requestSent) - (this.responseSent - this.requestReceived);
2020-04-16 16:05:04 +02:00
}
/**
* Get ping time.
*/
getPing () {
return this.getDelay() / 2;
}
}
/**
* Class that manages time syncing with server.
*/
class TimeSyncManager {
constructor() {
this.pingStop = true;
this.pollingInterval = PollingIntervalGreedy;
this.poller = null;
this.pings = 0; // number of pings
this.measurement = null; // current time sync
this.measurements = [];
}
/**
* Gets status of time sync.
* @returns {boolean} _true_ if a measurement has been done, _false_ otherwise.
*/
isReady() {
2020-05-05 12:01:43 +02:00
return !!this.measurement;
2020-04-16 16:05:04 +02:00
}
/**
* Gets time offset with server.
* @returns {number} The time offset.
*/
getTimeOffset () {
return this.measurement ? this.measurement.getOffset() : 0;
}
/**
* Gets ping time to server.
* @returns {number} The ping time.
*/
getPing () {
return this.measurement ? this.measurement.getPing() : 0;
}
/**
* Updates time offset between server and client.
* @param {Measurement} measurement The new measurement.
*/
updateTimeOffset(measurement) {
this.measurements.push(measurement);
if (this.measurements.length > NumberOfTrackedMeasurements) {
this.measurements.shift();
}
// Pick measurement with minimum delay
const sortedMeasurements = this.measurements.slice(0);
sortedMeasurements.sort((a, b) => a.getDelay() - b.getDelay());
this.measurement = sortedMeasurements[0];
}
/**
* Schedules a ping request to the server. Triggers time offset update.
*/
requestPing() {
if (!this.poller) {
this.poller = setTimeout(() => {
this.poller = null;
const apiClient = connectionManager.currentApiClient();
2020-05-05 12:01:43 +02:00
const requestSent = new Date();
2020-04-16 16:05:04 +02:00
apiClient.getServerTime().then((response) => {
2020-05-05 12:01:43 +02:00
const responseReceived = new Date();
2020-04-16 16:05:04 +02:00
response.json().then((data) => {
2020-05-05 12:01:43 +02:00
const requestReceived = new Date(data.RequestReceptionTime);
const responseSent = new Date(data.ResponseTransmissionTime);
2020-04-16 16:05:04 +02:00
2020-05-05 12:01:43 +02:00
const measurement = new Measurement(requestSent, requestReceived, responseSent, responseReceived);
2020-04-16 16:05:04 +02:00
this.updateTimeOffset(measurement);
// Avoid overloading server
if (this.pings >= GreedyPingCount) {
this.pollingInterval = PollingIntervalLowProfile;
} else {
this.pings++;
}
2020-05-05 12:01:43 +02:00
events.trigger(this, 'update', [null, this.getTimeOffset(), this.getPing()]);
2020-04-16 16:05:04 +02:00
});
}).catch((error) => {
console.error(error);
2020-05-05 12:01:43 +02:00
events.trigger(this, 'update', [error, null, null]);
2020-04-16 16:05:04 +02:00
}).finally(() => {
this.requestPing();
});
}, this.pollingInterval);
}
}
/**
* Drops accumulated measurements.
*/
resetMeasurements () {
this.measurement = null;
this.measurements = [];
}
/**
* Starts the time poller.
*/
startPing() {
this.requestPing();
}
/**
* Stops the time poller.
*/
stopPing() {
if (this.poller) {
clearTimeout(this.poller);
this.poller = null;
}
}
/**
* Resets poller into greedy mode.
*/
forceUpdate() {
this.stopPing();
this.pollingInterval = PollingIntervalGreedy;
this.pings = 0;
this.startPing();
}
/**
* Converts server time to local time.
* @param {Date} server The time to convert.
* @returns {Date} Local time.
*/
serverDateToLocal(server) {
// server - local = offset
2020-04-17 19:41:02 +02:00
return new Date(server.getTime() - this.getTimeOffset());
2020-04-16 16:05:04 +02:00
}
/**
* Converts local time to server time.
* @param {Date} local The time to convert.
* @returns {Date} Server time.
*/
localDateToServer(local) {
// server - local = offset
2020-04-17 19:41:02 +02:00
return new Date(local.getTime() + this.getTimeOffset());
2020-04-16 16:05:04 +02:00
}
}
/** TimeSyncManager singleton. */
export default new TimeSyncManager();