Fix code issues
This commit is contained in:
parent
a2ba96ab82
commit
11f6217bb2
17 changed files with 340 additions and 242 deletions
|
@ -45,7 +45,7 @@ const options = {
|
|||
query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg']
|
||||
},
|
||||
copy: {
|
||||
query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.wav']
|
||||
query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.mp3']
|
||||
},
|
||||
injectBundle: {
|
||||
query: 'src/index.html'
|
||||
|
|
BIN
src/assets/audio/silence.mp3
Normal file
BIN
src/assets/audio/silence.mp3
Normal file
Binary file not shown.
Binary file not shown.
|
@ -520,8 +520,8 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp
|
|||
var list = [];
|
||||
var audio = document.createElement('audio');
|
||||
|
||||
if (typeof audio.playbackRate === "number") {
|
||||
list.push("PlaybackRate");
|
||||
if (typeof audio.playbackRate === 'number') {
|
||||
list.push('PlaybackRate');
|
||||
}
|
||||
|
||||
return list;
|
||||
|
|
|
@ -1442,8 +1442,8 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
list.push('AirPlay');
|
||||
}
|
||||
|
||||
if (typeof video.playbackRate === "number") {
|
||||
list.push("PlaybackRate");
|
||||
if (typeof video.playbackRate === 'number') {
|
||||
list.push('PlaybackRate');
|
||||
}
|
||||
|
||||
list.push('SetBrightness');
|
||||
|
|
|
@ -54,6 +54,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
if (!serverId) {
|
||||
// Not a server item
|
||||
// We can expand on this later and possibly report them
|
||||
events.trigger(playbackManagerInstance, 'reportplayback', [false]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -77,7 +78,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
}
|
||||
|
||||
var apiClient = connectionManager.getApiClient(serverId);
|
||||
apiClient[method](info);
|
||||
var reportPlaybackPromise = apiClient[method](info);
|
||||
// Notify that report has been sent
|
||||
reportPlaybackPromise.then(() => {
|
||||
events.trigger(playbackManagerInstance, 'reportplayback', [true]);
|
||||
});
|
||||
}
|
||||
|
||||
function getPlaylistSync(playbackManagerInstance, player) {
|
||||
|
@ -3777,18 +3782,14 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
}
|
||||
};
|
||||
|
||||
PlaybackManager.prototype.setPlaybackRate = function (value, player) {
|
||||
player = player || this._currentPlayer;
|
||||
|
||||
if (player) {
|
||||
PlaybackManager.prototype.setPlaybackRate = function (value, player = this._currentPlayer) {
|
||||
if (player && player.setPlaybackRate) {
|
||||
player.setPlaybackRate(value);
|
||||
}
|
||||
};
|
||||
|
||||
PlaybackManager.prototype.getPlaybackRate = function (player) {
|
||||
player = player || this._currentPlayer;
|
||||
|
||||
if (player) {
|
||||
PlaybackManager.prototype.getPlaybackRate = function (player = this._currentPlayer) {
|
||||
if (player && player.getPlaybackRate) {
|
||||
return player.getPlaybackRate();
|
||||
}
|
||||
|
||||
|
|
|
@ -332,17 +332,17 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncplay
|
|||
var stats = syncplayManager.getStats();
|
||||
|
||||
syncStats.push({
|
||||
label: globalize.translate("LabelSyncplayTimeOffset"),
|
||||
value: stats.TimeOffset + "ms"
|
||||
label: globalize.translate('LabelSyncplayTimeOffset'),
|
||||
value: stats.TimeOffset + globalize.translate('MillisecondsUnit')
|
||||
});
|
||||
|
||||
syncStats.push({
|
||||
label: globalize.translate("LabelSyncplayPlaybackDiff"),
|
||||
value: stats.PlaybackDiff + "ms"
|
||||
label: globalize.translate('LabelSyncplayPlaybackDiff'),
|
||||
value: stats.PlaybackDiff + globalize.translate('MillisecondsUnit')
|
||||
});
|
||||
|
||||
syncStats.push({
|
||||
label: globalize.translate("LabelSyncplaySyncMethod"),
|
||||
label: globalize.translate('LabelSyncplaySyncMethod'),
|
||||
value: stats.SyncMethod
|
||||
});
|
||||
|
||||
|
|
|
@ -187,9 +187,9 @@ define(['connectionManager', 'playbackManager', 'syncplayManager', 'events', 'in
|
|||
events.trigger(serverNotifications, 'UserDataChanged', [apiClient, msg.Data.UserDataList[i]]);
|
||||
}
|
||||
}
|
||||
} else if (msg.MessageType === "SyncplayCommand") {
|
||||
} else if (msg.MessageType === 'SyncplayCommand') {
|
||||
syncplayManager.processCommand(msg.Data, apiClient);
|
||||
} else if (msg.MessageType === "SyncplayGroupUpdate") {
|
||||
} else if (msg.MessageType === 'SyncplayGroupUpdate') {
|
||||
syncplayManager.processGroupUpdate(msg.Data, apiClient);
|
||||
} else {
|
||||
events.trigger(serverNotifications, msg.MessageType, [apiClient, msg.Data]);
|
||||
|
|
|
@ -3,7 +3,6 @@ import connectionManager from 'connectionManager';
|
|||
import playbackManager from 'playbackManager';
|
||||
import syncplayManager from 'syncplayManager';
|
||||
import loading from 'loading';
|
||||
import datetime from 'datetime';
|
||||
import toast from 'toast';
|
||||
import actionsheet from 'actionsheet';
|
||||
import globalize from 'globalize';
|
||||
|
@ -18,13 +17,6 @@ function getActivePlayerId () {
|
|||
return info ? info.id : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to avoid console logs about uncaught promises
|
||||
*/
|
||||
function emptyCallback () {
|
||||
// avoid console logs about uncaught promises
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when user needs to join a group.
|
||||
* @param {HTMLElement} button - Element where to place the menu.
|
||||
|
@ -32,47 +24,43 @@ function emptyCallback () {
|
|||
* @param {Object} apiClient - ApiClient.
|
||||
*/
|
||||
function showNewJoinGroupSelection (button, user, apiClient) {
|
||||
let sessionId = getActivePlayerId();
|
||||
sessionId = sessionId ? sessionId : "none";
|
||||
const inSession = sessionId !== "none";
|
||||
const sessionId = getActivePlayerId() || 'none';
|
||||
const inSession = sessionId !== 'none';
|
||||
const policy = user.localUser ? user.localUser.Policy : {};
|
||||
let playingItemId;
|
||||
try {
|
||||
const playState = playbackManager.getPlayerState();
|
||||
playingItemId = playState.NowPlayingItem.Id;
|
||||
console.debug('Item', playingItemId, 'is currently playing.');
|
||||
} catch (error) {
|
||||
playingItemId = "";
|
||||
playingItemId = '';
|
||||
console.debug('No item is currently playing.');
|
||||
}
|
||||
|
||||
apiClient.sendSyncplayCommand(sessionId, "ListGroups").then(function (response) {
|
||||
apiClient.sendSyncplayCommand(sessionId, 'ListGroups').then(function (response) {
|
||||
response.json().then(function (groups) {
|
||||
var menuItems = groups.map(function (group) {
|
||||
// TODO: update running time if group is playing?
|
||||
var name = datetime.getDisplayRunningTime(group.PositionTicks);
|
||||
if (!inSession) {
|
||||
name = group.PlayingItemName;
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
icon: "group",
|
||||
name: group.PlayingItemName,
|
||||
icon: 'group',
|
||||
id: group.GroupId,
|
||||
selected: false,
|
||||
secondaryText: group.Participants.join(", ")
|
||||
secondaryText: group.Participants.join(', ')
|
||||
};
|
||||
});
|
||||
|
||||
if (inSession && policy.SyncplayAccess === "CreateAndJoinGroups") {
|
||||
if (inSession && policy.SyncplayAccess === 'CreateAndJoinGroups') {
|
||||
menuItems.push({
|
||||
name: globalize.translate('LabelSyncplayNewGroup'),
|
||||
icon: "add",
|
||||
id: "new-group",
|
||||
icon: 'add',
|
||||
id: 'new-group',
|
||||
selected: true,
|
||||
secondaryText: globalize.translate('LabelSyncplayNewGroupDescription')
|
||||
});
|
||||
}
|
||||
|
||||
if (menuItems.length === 0) {
|
||||
if (inSession && policy.SyncplayAccess === "JoinGroups") {
|
||||
if (inSession && policy.SyncplayAccess === 'JoinGroups') {
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncplayCreateGroupDenied')
|
||||
});
|
||||
|
@ -94,15 +82,17 @@ function showNewJoinGroupSelection (button, user, apiClient) {
|
|||
};
|
||||
|
||||
actionsheet.show(menuOptions).then(function (id) {
|
||||
if (id == "new-group") {
|
||||
apiClient.sendSyncplayCommand(sessionId, "NewGroup");
|
||||
if (id == 'new-group') {
|
||||
apiClient.sendSyncplayCommand(sessionId, 'NewGroup');
|
||||
} else {
|
||||
apiClient.sendSyncplayCommand(sessionId, "JoinGroup", {
|
||||
apiClient.sendSyncplayCommand(sessionId, 'JoinGroup', {
|
||||
GroupId: id,
|
||||
PlayingItemId: playingItemId
|
||||
});
|
||||
}
|
||||
}, emptyCallback);
|
||||
}).catch((error) => {
|
||||
console.error('Syncplay: unexpected error listing groups:', error);
|
||||
});
|
||||
|
||||
loading.hide();
|
||||
});
|
||||
|
@ -110,7 +100,7 @@ function showNewJoinGroupSelection (button, user, apiClient) {
|
|||
console.error(error);
|
||||
loading.hide();
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncplayNoGroupsAvailable')
|
||||
text: globalize.translate('MessageSyncplayErrorAccessingGroups')
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -126,17 +116,16 @@ function showLeaveGroupSelection (button, user, apiClient) {
|
|||
if (!sessionId) {
|
||||
syncplayManager.signalError();
|
||||
toast({
|
||||
// TODO: translate
|
||||
text: "Syncplay error occured."
|
||||
text: globalize.translate('MessageSyncplayErrorNoActivePlayer')
|
||||
});
|
||||
showNewJoinGroupSelection(button, user, apiClient);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const menuItems = [{
|
||||
name: globalize.translate('LabelSyncplayLeaveGroup'),
|
||||
icon: "meeting_room",
|
||||
id: "leave-group",
|
||||
icon: 'meeting_room',
|
||||
id: 'leave-group',
|
||||
selected: true,
|
||||
secondaryText: globalize.translate('LabelSyncplayLeaveGroupDescription')
|
||||
}];
|
||||
|
@ -150,17 +139,19 @@ function showLeaveGroupSelection (button, user, apiClient) {
|
|||
};
|
||||
|
||||
actionsheet.show(menuOptions).then(function (id) {
|
||||
if (id == "leave-group") {
|
||||
apiClient.sendSyncplayCommand(sessionId, "LeaveGroup");
|
||||
if (id == 'leave-group') {
|
||||
apiClient.sendSyncplayCommand(sessionId, 'LeaveGroup');
|
||||
}
|
||||
}, emptyCallback);
|
||||
}).catch((error) => {
|
||||
console.error('Syncplay: unexpected error showing group menu:', error);
|
||||
});
|
||||
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
// Register to Syncplay events
|
||||
let syncplayEnabled = false;
|
||||
events.on(syncplayManager, 'SyncplayEnabled', function (e, enabled) {
|
||||
events.on(syncplayManager, 'enabled', function (e, enabled) {
|
||||
syncplayEnabled = enabled;
|
||||
});
|
||||
|
||||
|
@ -173,11 +164,11 @@ export function show (button) {
|
|||
|
||||
// TODO: should feature be disabled if playback permission is missing?
|
||||
playbackPermissionManager.check().then(() => {
|
||||
console.debug("Playback is allowed.");
|
||||
console.debug('Playback is allowed.');
|
||||
}).catch((error) => {
|
||||
console.error("Playback not allowed!", error);
|
||||
console.error('Playback not allowed!', error);
|
||||
toast({
|
||||
text: globalize.translate("MessageSyncplayPlaybackPermissionRequired")
|
||||
text: globalize.translate('MessageSyncplayPlaybackPermissionRequired')
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ function createTestMediaElement () {
|
|||
document.body.appendChild(elem);
|
||||
|
||||
elem.volume = 1; // Volume should not be zero to trigger proper permissions
|
||||
elem.src = "assets/audio/silence.wav"; // Silent sound
|
||||
elem.src = 'assets/audio/silence.mp3'; // Silent sound
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable indent */
|
||||
|
||||
/**
|
||||
* Module that manages the Syncplay feature.
|
||||
* @module components/syncplay/syncplayManager
|
||||
|
@ -13,15 +11,25 @@ import toast from 'toast';
|
|||
import globalize from 'globalize';
|
||||
|
||||
/**
|
||||
* Waits for an event to be triggered on an object.
|
||||
* Waits for an event to be triggered on an object. An optional timeout can specified after which the promise is rejected.
|
||||
* @param {Object} emitter Object on which to listen for events.
|
||||
* @param {string} eventType Event name to listen for.
|
||||
* @param {number} timeout Time in milliseconds before rejecting promise if event does not trigger.
|
||||
* @returns {Promise} A promise that resolves when the event is triggered.
|
||||
*/
|
||||
function waitForEvent(emitter, eventType) {
|
||||
return new Promise((resolve) => {
|
||||
var callback = () => {
|
||||
function waitForEventOnce(emitter, eventType, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let rejectTimeout;
|
||||
if (timeout) {
|
||||
rejectTimeout = setTimeout(() => {
|
||||
reject('Timed out.');
|
||||
}, timeout);
|
||||
}
|
||||
const callback = () => {
|
||||
events.off(emitter, eventType, callback);
|
||||
if (rejectTimeout) {
|
||||
clearTimeout(rejectTimeout);
|
||||
}
|
||||
resolve(arguments);
|
||||
};
|
||||
events.on(emitter, eventType, callback);
|
||||
|
@ -48,11 +56,10 @@ const MaxAttemptsSpeedToSync = 3; // attempts before disabling SpeedToSync
|
|||
const MaxAttemptsSync = 5; // attempts before disabling syncing at all
|
||||
|
||||
/**
|
||||
* Time estimation
|
||||
* Other constants
|
||||
*/
|
||||
const PingIntervalTimeoutGreedy = 1000; // milliseconds
|
||||
const PingIntervalTimeoutLowProfile = 60000; // milliseconds
|
||||
const GreedyPingCount = 3;
|
||||
const WaitForEventDefaultTimeout = 30000; // milliseconds
|
||||
const WaitForPlayerEventTimeout = 500; // milliseconds
|
||||
|
||||
/**
|
||||
* Class that manages the Syncplay feature.
|
||||
|
@ -62,7 +69,7 @@ class SyncplayManager {
|
|||
this.playbackRateSupported = false;
|
||||
this.syncEnabled = false;
|
||||
this.playbackDiffMillis = 0; // used for stats
|
||||
this.syncMethod = "None"; // used for stats
|
||||
this.syncMethod = 'None'; // used for stats
|
||||
this.syncAttempts = 0;
|
||||
this.lastSyncTime = new Date();
|
||||
this.syncWatcherTimeout = null; // interval that watches playback time and syncs it
|
||||
|
@ -71,6 +78,7 @@ class SyncplayManager {
|
|||
this.minBufferingThresholdMillis = 1000;
|
||||
|
||||
this.currentPlayer = null;
|
||||
this.localPlayerPlaybackRate = 1.0; // used to restore user PlaybackRate
|
||||
|
||||
this.syncplayEnabledAt = null; // Server time of when Syncplay has been enabled
|
||||
this.syncplayReady = false; // Syncplay is ready after first ping to server
|
||||
|
@ -85,23 +93,36 @@ class SyncplayManager {
|
|||
this.roundTripDuration = 0;
|
||||
this.notifySyncplayReady = false;
|
||||
|
||||
events.on(playbackManager, "playerchange", () => {
|
||||
events.on(playbackManager, 'playbackstart', (player, state) => {
|
||||
this.onPlaybackStart(player, state);
|
||||
});
|
||||
|
||||
events.on(playbackManager, 'playbackstop', (stopInfo) => {
|
||||
this.onPlaybackStop(stopInfo);
|
||||
});
|
||||
|
||||
events.on(playbackManager, 'playerchange', () => {
|
||||
this.onPlayerChange();
|
||||
});
|
||||
|
||||
this.bindToPlayer(playbackManager.getCurrentPlayer());
|
||||
|
||||
events.on(this, "TimeUpdate", (event) => {
|
||||
events.on(this, 'timeupdate', (event) => {
|
||||
this.syncPlaybackTime();
|
||||
});
|
||||
|
||||
events.on(timeSyncManager, "Update", (event, timeOffset, ping) => {
|
||||
events.on(timeSyncManager, 'update', (event, error, timeOffset, ping) => {
|
||||
if (error) {
|
||||
console.debug('Syncplay, time update issue', error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.timeOffsetWithServer = timeOffset;
|
||||
this.roundTripDuration = ping * 2;
|
||||
|
||||
if (this.notifySyncplayReady) {
|
||||
this.syncplayReady = true;
|
||||
events.trigger(this, "SyncplayReady");
|
||||
events.trigger(this, 'ready');
|
||||
this.notifySyncplayReady = false;
|
||||
}
|
||||
|
||||
|
@ -113,33 +134,55 @@ class SyncplayManager {
|
|||
if (!sessionId) {
|
||||
this.signalError();
|
||||
toast({
|
||||
// TODO: translate
|
||||
text: "Syncplay error occured."
|
||||
text: globalize.translate('MessageSyncplayErrorMissingSession')
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
apiClient.sendSyncplayCommand(sessionId, "UpdatePing", {
|
||||
apiClient.sendSyncplayCommand(sessionId, 'UpdatePing', {
|
||||
Ping: ping
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when playback starts.
|
||||
*/
|
||||
onPlaybackStart (player, state) {
|
||||
events.trigger(this, 'playbackstart', [player, state]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when playback stops.
|
||||
*/
|
||||
onPlaybackStop (stopInfo) {
|
||||
events.trigger(this, 'playbackstop', [stopInfo]);
|
||||
if (this.isSyncplayEnabled()) {
|
||||
this.disableSyncplay(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the player changes.
|
||||
*/
|
||||
onPlayerChange () {
|
||||
this.bindToPlayer(playbackManager.getCurrentPlayer());
|
||||
events.trigger(this, "PlayerChange", [this.currentPlayer]);
|
||||
events.trigger(this, 'playerchange', [this.currentPlayer]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on playback state changes.
|
||||
* @param {Object} e The playback state change event.
|
||||
* Called when playback unpauses.
|
||||
*/
|
||||
onPlayPauseStateChanged (e) {
|
||||
events.trigger(this, "PlayPauseStateChange", [this.currentPlayer]);
|
||||
onPlayerUnpause () {
|
||||
events.trigger(this, 'unpause', [this.currentPlayer]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when playback pauses.
|
||||
*/
|
||||
onPlayerPause() {
|
||||
events.trigger(this, 'pause', [this.currentPlayer]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,7 +192,7 @@ class SyncplayManager {
|
|||
onTimeUpdate (e) {
|
||||
// NOTICE: this event is unreliable, at least in Safari
|
||||
// which just stops firing the event after a while.
|
||||
events.trigger(this, "TimeUpdate", [e]);
|
||||
events.trigger(this, 'timeupdate', [e]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,7 +201,7 @@ class SyncplayManager {
|
|||
onPlaying () {
|
||||
// TODO: implement group wait
|
||||
this.lastPlaybackWaiting = null;
|
||||
events.trigger(this, "PlayerPlaying");
|
||||
events.trigger(this, 'playing');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,7 +212,7 @@ class SyncplayManager {
|
|||
if (!this.lastPlaybackWaiting) {
|
||||
this.lastPlaybackWaiting = new Date();
|
||||
}
|
||||
events.trigger(this, "PlayerWaiting");
|
||||
events.trigger(this, 'waiting');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,14 +235,17 @@ class SyncplayManager {
|
|||
if (!player) return;
|
||||
}
|
||||
|
||||
// TODO: remove this extra functions
|
||||
// FIXME: the following are needed because the 'events' module
|
||||
// is changing the scope when executing the callbacks.
|
||||
// For instance, calling 'onPlayerUnpause' from the wrong scope breaks things because 'this'
|
||||
// points to 'player' (the event emitter) instead of pointing to the SyncplayManager singleton.
|
||||
const self = this;
|
||||
this._onPlayPauseStateChanged = () => {
|
||||
self.onPlayPauseStateChanged();
|
||||
this._onPlayerUnpause = () => {
|
||||
self.onPlayerUnpause();
|
||||
};
|
||||
|
||||
this._onPlayPauseStateChanged = (e) => {
|
||||
self.onPlayPauseStateChanged(e);
|
||||
this._onPlayerPause = () => {
|
||||
self.onPlayerPause();
|
||||
};
|
||||
|
||||
this._onTimeUpdate = (e) => {
|
||||
|
@ -214,12 +260,17 @@ class SyncplayManager {
|
|||
self.onWaiting();
|
||||
};
|
||||
|
||||
events.on(player, "pause", this._onPlayPauseStateChanged);
|
||||
events.on(player, "unpause", this._onPlayPauseStateChanged);
|
||||
events.on(player, "timeupdate", this._onTimeUpdate);
|
||||
events.on(player, "playing", this._onPlaying);
|
||||
events.on(player, "waiting", this._onWaiting);
|
||||
this.playbackRateSupported = player.supports("PlaybackRate");
|
||||
events.on(player, 'unpause', this._onPlayerUnpause);
|
||||
events.on(player, 'pause', this._onPlayerPause);
|
||||
events.on(player, 'timeupdate', this._onTimeUpdate);
|
||||
events.on(player, 'playing', this._onPlaying);
|
||||
events.on(player, 'waiting', this._onWaiting);
|
||||
this.playbackRateSupported = player.supports('PlaybackRate');
|
||||
|
||||
// Save player current PlaybackRate value
|
||||
if (this.playbackRateSupported) {
|
||||
this.localPlayerPlaybackRate = player.getPlaybackRate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -228,13 +279,15 @@ class SyncplayManager {
|
|||
releaseCurrentPlayer () {
|
||||
var player = this.currentPlayer;
|
||||
if (player) {
|
||||
events.off(player, "pause", this._onPlayPauseStateChanged);
|
||||
events.off(player, "unpause", this._onPlayPauseStateChanged);
|
||||
events.off(player, "timeupdate", this._onTimeUpdate);
|
||||
events.off(player, "playing", this._onPlaying);
|
||||
events.off(player, "waiting", this._onWaiting);
|
||||
events.off(player, 'unpause', this._onPlayerUnpause);
|
||||
events.off(player, 'pause', this._onPlayerPause);
|
||||
events.off(player, 'timeupdate', this._onTimeUpdate);
|
||||
events.off(player, 'playing', this._onPlaying);
|
||||
events.off(player, 'waiting', this._onWaiting);
|
||||
// Restore player original PlaybackRate value
|
||||
if (this.playbackRateSupported) {
|
||||
player.setPlaybackRate(1);
|
||||
player.setPlaybackRate(this.localPlayerPlaybackRate);
|
||||
this.localPlayerPlaybackRate = 1.0;
|
||||
}
|
||||
this.currentPlayer = null;
|
||||
this.playbackRateSupported = false;
|
||||
|
@ -262,8 +315,7 @@ class SyncplayManager {
|
|||
});
|
||||
break;
|
||||
case 'GroupJoined':
|
||||
const enabledAt = new Date(cmd.Data);
|
||||
this.enableSyncplay(apiClient, enabledAt, true);
|
||||
this.enableSyncplay(apiClient, new Date(cmd.Data), true);
|
||||
break;
|
||||
case 'NotInGroup':
|
||||
case 'GroupLeft':
|
||||
|
@ -274,28 +326,28 @@ class SyncplayManager {
|
|||
text: globalize.translate('MessageSyncplayGroupWait', cmd.Data)
|
||||
});
|
||||
break;
|
||||
case 'GroupNotJoined':
|
||||
case 'GroupDoesNotExist':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncplayGroupNotJoined', cmd.Data)
|
||||
text: globalize.translate('MessageSyncplayGroupDoesNotExist')
|
||||
});
|
||||
break;
|
||||
case 'CreateGroupDenied':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncplayCreateGroupDenied', cmd.Data)
|
||||
text: globalize.translate('MessageSyncplayCreateGroupDenied')
|
||||
});
|
||||
break;
|
||||
case 'JoinGroupDenied':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncplayJoinGroupDenied', cmd.Data)
|
||||
text: globalize.translate('MessageSyncplayJoinGroupDenied')
|
||||
});
|
||||
break;
|
||||
case 'LibraryAccessDenied':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncplayLibraryAccessDenied', cmd.Data)
|
||||
text: globalize.translate('MessageSyncplayLibraryAccessDenied')
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.error('processSyncplayGroupUpdate does not recognize: ' + cmd.Type);
|
||||
console.error('processSyncplayGroupUpdate: command is not recognised: ' + cmd.Type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -309,12 +361,12 @@ class SyncplayManager {
|
|||
if (cmd === null) return;
|
||||
|
||||
if (!this.isSyncplayEnabled()) {
|
||||
console.debug("Syncplay processCommand: ignoring command", cmd);
|
||||
console.debug('Syncplay processCommand: SyncPlay not enabled, ignoring command', cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.syncplayReady) {
|
||||
console.debug("Syncplay processCommand: queued command", cmd);
|
||||
console.debug('Syncplay processCommand: SyncPlay not ready, queued command', cmd);
|
||||
this.queuedCommand = cmd;
|
||||
return;
|
||||
}
|
||||
|
@ -323,7 +375,7 @@ class SyncplayManager {
|
|||
cmd.EmittedAt = new Date(cmd.EmitttedAt);
|
||||
|
||||
if (cmd.EmitttedAt < this.syncplayEnabledAt) {
|
||||
console.debug("Syncplay processCommand: ignoring old command", cmd);
|
||||
console.debug('Syncplay processCommand: ignoring old command', cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -333,12 +385,12 @@ class SyncplayManager {
|
|||
this.lastCommand.PositionTicks === cmd.PositionTicks &&
|
||||
this.Command === cmd.Command
|
||||
) {
|
||||
console.debug("Syncplay processCommand: ignoring duplicate command", cmd);
|
||||
console.debug('Syncplay processCommand: ignoring duplicate command', cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastCommand = cmd;
|
||||
console.log("Syncplay will", cmd.Command, "at", cmd.When, "PositionTicks", cmd.PositionTicks);
|
||||
console.log('Syncplay will', cmd.Command, 'at', cmd.When, 'PositionTicks', cmd.PositionTicks);
|
||||
|
||||
switch (cmd.Command) {
|
||||
case 'Play':
|
||||
|
@ -351,7 +403,7 @@ class SyncplayManager {
|
|||
this.scheduleSeek(cmd.When, cmd.PositionTicks);
|
||||
break;
|
||||
default:
|
||||
console.error('processSyncplayCommand does not recognize: ' + cmd.Type);
|
||||
console.error('processCommand: command is not recognised: ' + cmd.Type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -363,7 +415,7 @@ class SyncplayManager {
|
|||
* @param {Object} sessionData Info about the content to load.
|
||||
*/
|
||||
prepareSession (apiClient, groupId, sessionData) {
|
||||
var serverId = apiClient.serverInfo().Id;
|
||||
const serverId = apiClient.serverInfo().Id;
|
||||
playbackManager.play({
|
||||
ids: sessionData.ItemIds,
|
||||
startPositionTicks: sessionData.StartPositionTicks,
|
||||
|
@ -373,14 +425,12 @@ class SyncplayManager {
|
|||
startIndex: sessionData.StartIndex,
|
||||
serverId: serverId
|
||||
}).then(() => {
|
||||
waitForEvent(this, "PlayerChange").then(() => {
|
||||
playbackManager.pause();
|
||||
waitForEventOnce(this, 'playbackstart', WaitForEventDefaultTimeout).then(() => {
|
||||
var sessionId = getActivePlayerId();
|
||||
if (!sessionId) {
|
||||
console.error("Missing sessionId!");
|
||||
console.error('Missing sessionId!');
|
||||
toast({
|
||||
// TODO: translate
|
||||
text: "Failed to enable Syncplay! Missing session id."
|
||||
text: globalize.translate('MessageSyncplayErrorMissingSession')
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -390,21 +440,38 @@ class SyncplayManager {
|
|||
const playState = playbackManager.getPlayerState();
|
||||
playingItemId = playState.NowPlayingItem.Id;
|
||||
} catch (error) {
|
||||
playingItemId = "";
|
||||
playingItemId = '';
|
||||
}
|
||||
// Sometimes JoinGroup fails, maybe because server hasn't been updated yet
|
||||
setTimeout(() => {
|
||||
apiClient.sendSyncplayCommand(sessionId, "JoinGroup", {
|
||||
// Make sure the server has received the player state
|
||||
waitForEventOnce(playbackManager, 'reportplayback', WaitForEventDefaultTimeout).then((success) => {
|
||||
this.localPause();
|
||||
if (!success) {
|
||||
console.warning('Error reporting playback state to server. Joining group will fail.');
|
||||
}
|
||||
apiClient.sendSyncplayCommand(sessionId, 'JoinGroup', {
|
||||
GroupId: groupId,
|
||||
PlayingItemId: playingItemId
|
||||
});
|
||||
}, 500);
|
||||
}).catch(() => {
|
||||
console.error('Timed out while waiting for `reportplayback` event!');
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncplayErrorMedia')
|
||||
});
|
||||
return;
|
||||
});
|
||||
}).catch(() => {
|
||||
console.error('Timed out while waiting for `playbackstart` event!');
|
||||
if (!this.isSyncplayEnabled()) {
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncplayErrorMedia')
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
toast({
|
||||
// TODO: translate
|
||||
text: "Failed to enable Syncplay! Media error."
|
||||
text: globalize.translate('MessageSyncplayErrorMedia')
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -418,12 +485,13 @@ class SyncplayManager {
|
|||
enableSyncplay (apiClient, enabledAt, showMessage = false) {
|
||||
this.syncplayEnabledAt = enabledAt;
|
||||
this.injectPlaybackManager();
|
||||
events.trigger(this, "SyncplayEnabled", [true]);
|
||||
events.trigger(this, 'enabled', [true]);
|
||||
|
||||
waitForEvent(this, "SyncplayReady").then(() => {
|
||||
waitForEventOnce(this, 'ready').then(() => {
|
||||
this.processCommand(this.queuedCommand, apiClient);
|
||||
this.queuedCommand = null;
|
||||
});
|
||||
|
||||
this.syncplayReady = false;
|
||||
this.notifySyncplayReady = true;
|
||||
|
||||
|
@ -446,7 +514,7 @@ class SyncplayManager {
|
|||
this.lastCommand = null;
|
||||
this.queuedCommand = null;
|
||||
this.syncEnabled = false;
|
||||
events.trigger(this, "SyncplayEnabled", [false]);
|
||||
events.trigger(this, 'enabled', [false]);
|
||||
this.restorePlaybackManager();
|
||||
|
||||
if (showMessage) {
|
||||
|
@ -461,7 +529,7 @@ class SyncplayManager {
|
|||
* @returns {boolean} _true_ if user joined a group, _false_ otherwise.
|
||||
*/
|
||||
isSyncplayEnabled () {
|
||||
return this.syncplayEnabledAt !== null ? true : false;
|
||||
return this.syncplayEnabledAt !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -471,15 +539,15 @@ class SyncplayManager {
|
|||
*/
|
||||
schedulePlay (playAtTime, positionTicks) {
|
||||
this.clearScheduledCommand();
|
||||
var currentTime = new Date();
|
||||
var playAtTimeLocal = timeSyncManager.serverDateToLocal(playAtTime);
|
||||
const currentTime = new Date();
|
||||
const playAtTimeLocal = timeSyncManager.serverDateToLocal(playAtTime);
|
||||
|
||||
if (playAtTimeLocal > currentTime) {
|
||||
var playTimeout = playAtTimeLocal - currentTime;
|
||||
playbackManager.syncplay_seek(positionTicks);
|
||||
const playTimeout = playAtTimeLocal - currentTime;
|
||||
this.localSeek(positionTicks);
|
||||
|
||||
this.scheduledCommand = setTimeout(() => {
|
||||
playbackManager.syncplay_unpause();
|
||||
this.localUnpause();
|
||||
|
||||
this.syncTimeout = setTimeout(() => {
|
||||
this.syncEnabled = true;
|
||||
|
@ -487,12 +555,14 @@ class SyncplayManager {
|
|||
|
||||
}, playTimeout);
|
||||
|
||||
// console.debug("Syncplay schedulePlay:", playTimeout);
|
||||
console.debug('Scheduled play in', playTimeout / 1000.0, 'seconds.');
|
||||
} else {
|
||||
// Group playback already started
|
||||
var serverPositionTicks = positionTicks + (currentTime - playAtTimeLocal) * 10000;
|
||||
playbackManager.syncplay_unpause();
|
||||
playbackManager.syncplay_seek(serverPositionTicks);
|
||||
const serverPositionTicks = positionTicks + (currentTime - playAtTimeLocal) * 10000;
|
||||
waitForEventOnce(this, 'unpause').then(() => {
|
||||
this.localSeek(serverPositionTicks);
|
||||
});
|
||||
this.localUnpause();
|
||||
|
||||
this.syncTimeout = setTimeout(() => {
|
||||
this.syncEnabled = true;
|
||||
|
@ -507,24 +577,26 @@ class SyncplayManager {
|
|||
*/
|
||||
schedulePause (pauseAtTime, positionTicks) {
|
||||
this.clearScheduledCommand();
|
||||
var currentTime = new Date();
|
||||
var pauseAtTimeLocal = timeSyncManager.serverDateToLocal(pauseAtTime);
|
||||
const currentTime = new Date();
|
||||
const pauseAtTimeLocal = timeSyncManager.serverDateToLocal(pauseAtTime);
|
||||
|
||||
const callback = () => {
|
||||
waitForEventOnce(this, 'pause', WaitForPlayerEventTimeout).then(() => {
|
||||
this.localSeek(positionTicks);
|
||||
}).catch(() => {
|
||||
// Player was already paused, seeking
|
||||
this.localSeek(positionTicks);
|
||||
});
|
||||
this.localPause();
|
||||
};
|
||||
|
||||
if (pauseAtTimeLocal > currentTime) {
|
||||
var pauseTimeout = pauseAtTimeLocal - currentTime;
|
||||
const pauseTimeout = pauseAtTimeLocal - currentTime;
|
||||
this.scheduledCommand = setTimeout(callback, pauseTimeout);
|
||||
|
||||
this.scheduledCommand = setTimeout(() => {
|
||||
playbackManager.syncplay_pause();
|
||||
setTimeout(() => {
|
||||
playbackManager.syncplay_seek(positionTicks);
|
||||
}, 800);
|
||||
|
||||
}, pauseTimeout);
|
||||
console.debug('Scheduled pause in', pauseTimeout / 1000.0, 'seconds.');
|
||||
} else {
|
||||
playbackManager.syncplay_pause();
|
||||
setTimeout(() => {
|
||||
playbackManager.syncplay_seek(positionTicks);
|
||||
}, 800);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -559,9 +631,9 @@ class SyncplayManager {
|
|||
if (playbackManager.syncplayEnabled) return;
|
||||
|
||||
// TODO: make this less hacky
|
||||
playbackManager.syncplay_unpause = playbackManager.unpause;
|
||||
playbackManager.syncplay_pause = playbackManager.pause;
|
||||
playbackManager.syncplay_seek = playbackManager.seek;
|
||||
playbackManager._localUnpause = playbackManager.unpause;
|
||||
playbackManager._localPause = playbackManager.pause;
|
||||
playbackManager._localSeek = playbackManager.seek;
|
||||
|
||||
playbackManager.unpause = this.playRequest;
|
||||
playbackManager.pause = this.pauseRequest;
|
||||
|
@ -576,9 +648,9 @@ class SyncplayManager {
|
|||
if (this.isSyncplayEnabled()) return;
|
||||
if (!playbackManager.syncplayEnabled) return;
|
||||
|
||||
playbackManager.unpause = playbackManager.syncplay_unpause;
|
||||
playbackManager.pause = playbackManager.syncplay_pause;
|
||||
playbackManager.seek = playbackManager.syncplay_seek;
|
||||
playbackManager.unpause = playbackManager._localUnpause;
|
||||
playbackManager.pause = playbackManager._localPause;
|
||||
playbackManager.seek = playbackManager._localSeek;
|
||||
playbackManager.syncplayEnabled = false;
|
||||
}
|
||||
|
||||
|
@ -588,7 +660,7 @@ class SyncplayManager {
|
|||
playRequest (player) {
|
||||
var apiClient = connectionManager.currentApiClient();
|
||||
var sessionId = getActivePlayerId();
|
||||
apiClient.sendSyncplayCommand(sessionId, "PlayRequest");
|
||||
apiClient.sendSyncplayCommand(sessionId, 'PlayRequest');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -597,9 +669,9 @@ class SyncplayManager {
|
|||
pauseRequest (player) {
|
||||
var apiClient = connectionManager.currentApiClient();
|
||||
var sessionId = getActivePlayerId();
|
||||
apiClient.sendSyncplayCommand(sessionId, "PauseRequest");
|
||||
apiClient.sendSyncplayCommand(sessionId, 'PauseRequest');
|
||||
// Pause locally as well, to give the user some little control
|
||||
playbackManager.syncplay_pause();
|
||||
playbackManager._localUnpause(player);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -608,11 +680,44 @@ class SyncplayManager {
|
|||
seekRequest (PositionTicks, player) {
|
||||
var apiClient = connectionManager.currentApiClient();
|
||||
var sessionId = getActivePlayerId();
|
||||
apiClient.sendSyncplayCommand(sessionId, "SeekRequest", {
|
||||
apiClient.sendSyncplayCommand(sessionId, 'SeekRequest', {
|
||||
PositionTicks: PositionTicks
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls original PlaybackManager's unpause method.
|
||||
*/
|
||||
localUnpause(player) {
|
||||
if (playbackManager.syncplayEnabled) {
|
||||
playbackManager._localUnpause(player);
|
||||
} else {
|
||||
playbackManager.unpause(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls original PlaybackManager's pause method.
|
||||
*/
|
||||
localPause(player) {
|
||||
if (playbackManager.syncplayEnabled) {
|
||||
playbackManager._localPause(player);
|
||||
} else {
|
||||
playbackManager.pause(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls original PlaybackManager's seek method.
|
||||
*/
|
||||
localSeek(PositionTicks, player) {
|
||||
if (playbackManager.syncplayEnabled) {
|
||||
playbackManager._localSeek(PositionTicks, player);
|
||||
} else {
|
||||
playbackManager.seek(PositionTicks, player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to sync playback time with estimated server time.
|
||||
*
|
||||
|
@ -637,18 +742,15 @@ class SyncplayManager {
|
|||
|
||||
const playAtTime = this.lastCommand.When;
|
||||
|
||||
const CurrentPositionTicks = playbackManager.currentTime();
|
||||
const currentPositionTicks = playbackManager.currentTime();
|
||||
// Estimate PositionTicks on server
|
||||
const ServerPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) + this.timeOffsetWithServer) * 10000;
|
||||
const serverPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) + this.timeOffsetWithServer) * 10000;
|
||||
// Measure delay that needs to be recovered
|
||||
// diff might be caused by the player internally starting the playback
|
||||
const diff = ServerPositionTicks - CurrentPositionTicks;
|
||||
const diffMillis = diff / 10000;
|
||||
const diffMillis = (serverPositionTicks - currentPositionTicks) / 10000.0;
|
||||
|
||||
this.playbackDiffMillis = diffMillis;
|
||||
|
||||
// console.debug("Syncplay onTimeUpdate", diffMillis, CurrentPositionTicks, ServerPositionTicks);
|
||||
|
||||
if (this.syncEnabled) {
|
||||
const absDiffMillis = Math.abs(diffMillis);
|
||||
// TODO: SpeedToSync sounds bad on songs
|
||||
|
@ -664,7 +766,7 @@ class SyncplayManager {
|
|||
this.currentPlayer.setPlaybackRate(speed);
|
||||
this.syncEnabled = false;
|
||||
this.syncAttempts++;
|
||||
this.showSyncIcon("SpeedToSync (x" + speed + ")");
|
||||
this.showSyncIcon('SpeedToSync (x' + speed + ')');
|
||||
|
||||
this.syncTimeout = setTimeout(() => {
|
||||
this.currentPlayer.setPlaybackRate(1);
|
||||
|
@ -675,13 +777,13 @@ class SyncplayManager {
|
|||
// Disable SkipToSync if it keeps failing
|
||||
if (this.syncAttempts > MaxAttemptsSync) {
|
||||
this.syncEnabled = false;
|
||||
this.showSyncIcon("Sync disabled (too many attempts)");
|
||||
this.showSyncIcon('Sync disabled (too many attempts)');
|
||||
}
|
||||
// SkipToSync method
|
||||
playbackManager.syncplay_seek(ServerPositionTicks);
|
||||
this.localSeek(serverPositionTicks);
|
||||
this.syncEnabled = false;
|
||||
this.syncAttempts++;
|
||||
this.showSyncIcon("SkipToSync (" + this.syncAttempts + ")");
|
||||
this.showSyncIcon('SkipToSync (' + this.syncAttempts + ')');
|
||||
|
||||
this.syncTimeout = setTimeout(() => {
|
||||
this.syncEnabled = true;
|
||||
|
@ -690,7 +792,7 @@ class SyncplayManager {
|
|||
} else {
|
||||
// Playback is synced
|
||||
if (this.syncAttempts > 0) {
|
||||
// console.debug("Playback has been synced after", this.syncAttempts, "attempts.");
|
||||
console.debug('Playback has been synced after', this.syncAttempts, 'attempts.');
|
||||
}
|
||||
this.syncAttempts = 0;
|
||||
}
|
||||
|
@ -706,7 +808,7 @@ class SyncplayManager {
|
|||
TimeOffset: this.timeOffsetWithServer,
|
||||
PlaybackDiff: this.playbackDiffMillis,
|
||||
SyncMethod: this.syncMethod
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -714,15 +816,15 @@ class SyncplayManager {
|
|||
*/
|
||||
showSyncIcon (syncMethod) {
|
||||
this.syncMethod = syncMethod;
|
||||
events.trigger(this, "SyncplayError", [true]);
|
||||
events.trigger(this, 'syncing', [true, this.syncMethod]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to clear the Syncplay status icon.
|
||||
*/
|
||||
clearSyncIcon () {
|
||||
this.syncMethod = "None";
|
||||
events.trigger(this, "SyncplayError", [false]);
|
||||
this.syncMethod = 'None';
|
||||
events.trigger(this, 'syncing', [false, this.syncMethod]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable indent */
|
||||
|
||||
/**
|
||||
* Module that manages time syncing with server.
|
||||
* @module components/syncplay/timeSyncManager
|
||||
|
@ -22,30 +20,30 @@ const GreedyPingCount = 3;
|
|||
class Measurement {
|
||||
/**
|
||||
* Creates a new measurement.
|
||||
* @param {Date} t0 Client's timestamp of the request transmission
|
||||
* @param {Date} t1 Server's timestamp of the request reception
|
||||
* @param {Date} t2 Server's timestamp of the response transmission
|
||||
* @param {Date} t3 Client's timestamp of the response reception
|
||||
* @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
|
||||
*/
|
||||
constructor(t0, t1, t2, t3) {
|
||||
this.t0 = t0.getTime();
|
||||
this.t1 = t1.getTime();
|
||||
this.t2 = t2.getTime();
|
||||
this.t3 = t3.getTime();
|
||||
constructor(requestSent, requestReceived, responseSent, responseReceived) {
|
||||
this.requestSent = requestSent.getTime();
|
||||
this.requestReceived = requestReceived.getTime();
|
||||
this.responseSent = responseSent.getTime();
|
||||
this.responseReceived = responseReceived.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Time offset from server.
|
||||
*/
|
||||
getOffset () {
|
||||
return ((this.t1 - this.t0) + (this.t2 - this.t3)) / 2;
|
||||
return ((this.requestReceived - this.requestSent) + (this.responseSent - this.responseReceived)) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get round-trip delay.
|
||||
*/
|
||||
getDelay () {
|
||||
return (this.t3 - this.t0) - (this.t2 - this.t1);
|
||||
return (this.responseReceived - this.requestSent) - (this.responseSent - this.requestReceived);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,7 +74,7 @@ class TimeSyncManager {
|
|||
* @returns {boolean} _true_ if a measurement has been done, _false_ otherwise.
|
||||
*/
|
||||
isReady() {
|
||||
return this.measurement ? true : false;
|
||||
return !!this.measurement;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,14 +117,14 @@ class TimeSyncManager {
|
|||
this.poller = setTimeout(() => {
|
||||
this.poller = null;
|
||||
const apiClient = connectionManager.currentApiClient();
|
||||
const t0 = new Date(); // pingStartTime
|
||||
const requestSent = new Date();
|
||||
apiClient.getServerTime().then((response) => {
|
||||
const t3 = new Date(); // pingEndTime
|
||||
const responseReceived = new Date();
|
||||
response.json().then((data) => {
|
||||
const t1 = new Date(data.RequestReceptionTime); // request received
|
||||
const t2 = new Date(data.ResponseTransmissionTime); // response sent
|
||||
const requestReceived = new Date(data.RequestReceptionTime);
|
||||
const responseSent = new Date(data.ResponseTransmissionTime);
|
||||
|
||||
const measurement = new Measurement(t0, t1, t2, t3);
|
||||
const measurement = new Measurement(requestSent, requestReceived, responseSent, responseReceived);
|
||||
this.updateTimeOffset(measurement);
|
||||
|
||||
// Avoid overloading server
|
||||
|
@ -136,11 +134,11 @@ class TimeSyncManager {
|
|||
this.pings++;
|
||||
}
|
||||
|
||||
events.trigger(this, "Update", [this.getTimeOffset(), this.getPing()]);
|
||||
events.trigger(this, 'update', [null, this.getTimeOffset(), this.getPing()]);
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
events.trigger(this, "Error", [error]);
|
||||
events.trigger(this, 'update', [error, null, null]);
|
||||
}).finally(() => {
|
||||
this.requestPing();
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'syncplayManager', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, syncplayManager, browser, globalize, imageHelper) {
|
||||
define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'syncplayManager', 'groupSelectionMenu', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, syncplayManager, groupSelectionMenu, browser, globalize, imageHelper) {
|
||||
'use strict';
|
||||
|
||||
function renderHeader() {
|
||||
|
@ -89,12 +89,13 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
|
|||
|
||||
var policy = user.Policy ? user.Policy : user.localUser.Policy;
|
||||
|
||||
if (headerSyncButton && policy && policy.SyncplayAccess !== "None") {
|
||||
headerSyncButton.classList.remove("hide");
|
||||
if (headerSyncButton && policy && policy.SyncplayAccess !== 'None') {
|
||||
headerSyncButton.classList.remove('hide');
|
||||
}
|
||||
} else {
|
||||
headerHomeButton.classList.add('hide');
|
||||
headerCastButton.classList.add('hide');
|
||||
headerSyncButton.classList.add('hide');
|
||||
|
||||
if (headerSearchButton) {
|
||||
headerSearchButton.classList.add('hide');
|
||||
|
@ -188,27 +189,26 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
|
|||
|
||||
function onSyncButtonClicked() {
|
||||
var btn = this;
|
||||
|
||||
require(["groupSelectionMenu"], function (groupSelectionMenu) {
|
||||
groupSelectionMenu.show(btn);
|
||||
});
|
||||
}
|
||||
|
||||
function updateSyncplayIcon(event, enabled) {
|
||||
var icon = headerSyncButton.querySelector("i");
|
||||
function onSyncplayEnabled(event, enabled) {
|
||||
var icon = headerSyncButton.querySelector('span');
|
||||
icon.classList.remove('sync', 'sync_disabled', 'sync_problem');
|
||||
if (enabled) {
|
||||
icon.innerHTML = "sync";
|
||||
icon.classList.add('sync');
|
||||
} else {
|
||||
icon.innerHTML = "sync_disabled";
|
||||
icon.classList.add('sync_disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function updateSyncplayErrorIcon(event, show_error) {
|
||||
var icon = headerSyncButton.querySelector("i");
|
||||
if (show_error) {
|
||||
icon.innerHTML = "sync_problem";
|
||||
function onSyncplaySyncing(event, is_syncing, syncMethod) {
|
||||
var icon = headerSyncButton.querySelector('span');
|
||||
icon.classList.remove('sync', 'sync_disabled', 'sync_problem');
|
||||
if (is_syncing) {
|
||||
icon.classList.add('sync_problem');
|
||||
} else {
|
||||
icon.innerHTML = "sync";
|
||||
icon.classList.add('sync');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -967,8 +967,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
|
|||
updateUserInHeader();
|
||||
});
|
||||
events.on(playbackManager, 'playerchange', updateCastIcon);
|
||||
events.on(syncplayManager, 'SyncplayEnabled', updateSyncplayIcon);
|
||||
events.on(syncplayManager, 'SyncplayError', updateSyncplayErrorIcon);
|
||||
events.on(syncplayManager, 'enabled', onSyncplayEnabled);
|
||||
events.on(syncplayManager, 'syncing', onSyncplaySyncing);
|
||||
loadNavDrawer();
|
||||
return LibraryMenu;
|
||||
});
|
||||
|
|
|
@ -316,7 +316,7 @@ var AppInfo = {};
|
|||
|
||||
function returnDefault(obj) {
|
||||
if (obj.default === null) {
|
||||
throw new Error("Object has no default!");
|
||||
throw new Error('Object has no default!');
|
||||
}
|
||||
return obj.default;
|
||||
}
|
||||
|
@ -825,7 +825,8 @@ var AppInfo = {};
|
|||
define('homescreenSettings', [componentsPath + '/homescreensettings/homescreensettings'], returnFirstDependency);
|
||||
define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager);
|
||||
define('timeSyncManager', [componentsPath + '/syncplay/timeSyncManager'], returnDefault);
|
||||
define('syncplayManager', [componentsPath + '/syncplay/syncplaymanager'], returnDefault);
|
||||
define('groupSelectionMenu', [componentsPath + '/syncplay/groupSelectionMenu'], returnFirstDependency);
|
||||
define('syncplayManager', [componentsPath + '/syncplay/syncplayManager'], returnDefault);
|
||||
define('playbackPermissionManager', [componentsPath + '/syncplay/playbackPermissionManager'], returnDefault);
|
||||
define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager);
|
||||
define('homeSections', [componentsPath + '/homesections/homesections'], returnFirstDependency);
|
||||
|
|
|
@ -855,7 +855,8 @@
|
|||
"LabelSubtitlePlaybackMode": "Subtitle mode:",
|
||||
"LabelSubtitles": "Subtitles",
|
||||
"LabelSupportedMediaTypes": "Supported Media Types:",
|
||||
"LabelSyncplayTimeOffset": "Time offset with server:",
|
||||
"LabelSyncplayTimeOffset": "Time offset with the server:",
|
||||
"MillisecondsUnit": "ms",
|
||||
"LabelSyncplayPlaybackDiff": "Playback time difference:",
|
||||
"LabelSyncplaySyncMethod": "Sync method:",
|
||||
"LabelSyncplayNewGroup": "New group",
|
||||
|
@ -1031,15 +1032,19 @@
|
|||
"MessageYouHaveVersionInstalled": "You currently have version {0} installed.",
|
||||
"MessageSyncplayEnabled": "Syncplay enabled.",
|
||||
"MessageSyncplayDisabled": "Syncplay disabled.",
|
||||
"MessageSyncplayUserJoined": "<b>{0}</b> joined group.",
|
||||
"MessageSyncplayUserLeft": "<b>{0}</b> left group.",
|
||||
"MessageSyncplayUserJoined": "<b>{0}</b> has joined the group.",
|
||||
"MessageSyncplayUserLeft": "<b>{0}</b> has left the group.",
|
||||
"MessageSyncplayGroupWait": "<b>{0}</b> is buffering...",
|
||||
"MessageSyncplayNoGroupsAvailable": "No groups available. Start playing something first.",
|
||||
"MessageSyncplayPlaybackPermissionRequired": "Playback permission required.",
|
||||
"MessageSyncplayGroupNotJoined": "Failed to join requested group.",
|
||||
"MessageSyncplayGroupDoesNotExist": "Failed to join group because it does not exist.",
|
||||
"MessageSyncplayCreateGroupDenied": "Permission required to create a group.",
|
||||
"MessageSyncplayJoinGroupDenied": "Permission required to use Syncplay.",
|
||||
"MessageSyncplayLibraryAccessDenied": "Access to this content is restricted.",
|
||||
"MessageSyncplayErrorAccessingGroups": "An error occurred while accessing groups list.",
|
||||
"MessageSyncplayErrorNoActivePlayer": "No active player found. Syncplay has been disabled.",
|
||||
"MessageSyncplayErrorMissingSession": "Failed to enable Syncplay! Missing session.",
|
||||
"MessageSyncplayErrorMedia": "Failed to enable Syncplay! Media error.",
|
||||
"Metadata": "Metadata",
|
||||
"MetadataManager": "Metadata Manager",
|
||||
"MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.",
|
||||
|
|
|
@ -46,8 +46,8 @@ module.exports = merge(common, {
|
|||
]
|
||||
},
|
||||
{
|
||||
test: /\.(wav)$/i,
|
||||
use: ["file-loader"]
|
||||
test: /\.(mp3)$/i,
|
||||
use: ['file-loader']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -39,8 +39,8 @@ module.exports = merge(common, {
|
|||
]
|
||||
},
|
||||
{
|
||||
test: /\.(wav)$/i,
|
||||
use: ["file-loader"]
|
||||
test: /\.(mp3)$/i,
|
||||
use: ['file-loader']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue