From b87adc9d6c0727d3db424b33f5e6a53fa9deae34 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Fri, 17 Apr 2020 14:34:34 +0300 Subject: [PATCH] Add scheduled playback progress report --- src/components/playback/playbackmanager.js | 19 +--- src/libraries/apiclient/apiclientcore.js | 100 +++++++++++++++++---- 2 files changed, 82 insertions(+), 37 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index b5340f0bfb..29bc94bf14 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -3280,7 +3280,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla function onPlaybackVolumeChange(e) { var player = this; - sendProgressUpdateDelayed(player, 'volumechange'); + sendProgressUpdate(player, 'volumechange'); } function onRepeatModeChange(e) { @@ -3375,16 +3375,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla pluginManager.ofType('mediaplayer').map(initMediaPlayer); - /** Delay timer for sendProgressUpdate */ - var sendProgressUpdateTimer; - - /** Delay time in ms for sendProgressUpdate */ - var sendProgressUpdateDelay = 700; - function sendProgressUpdate(player, progressEventName, reportPlaylist) { - clearTimeout(sendProgressUpdateTimer); - sendProgressUpdateTimer = null; - if (!player) { throw new Error('player cannot be null'); } @@ -3409,14 +3400,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } } - function sendProgressUpdateDelayed(player, progressEventName, reportPlaylist) { - if (!sendProgressUpdateTimer) { - sendProgressUpdateTimer = setTimeout(function () { - sendProgressUpdate(player, progressEventName, reportPlaylist); - }, sendProgressUpdateDelay); - } - } - function getLiveStreamMediaInfo(player, streamInfo, mediaSource, liveStreamId, serverId) { console.debug('getLiveStreamMediaInfo'); diff --git a/src/libraries/apiclient/apiclientcore.js b/src/libraries/apiclient/apiclientcore.js index 557a4e1033..499c22b4e0 100644 --- a/src/libraries/apiclient/apiclientcore.js +++ b/src/libraries/apiclient/apiclientcore.js @@ -1,6 +1,12 @@ define(["events", "appStorage"], function(events, appStorage) { "use strict"; + /** Report rate limits in ms for different events */ + const reportRateLimits = { + "timeupdate": 10000, + "volumechange": 3000 + }; + function redetectBitrate(instance) { stopBitrateDetection(instance), instance.accessToken() && !1 !== instance.enableAutomaticBitrateDetection && setTimeout(redetectBitrateInternal.bind(instance), 6e3) } @@ -231,6 +237,11 @@ define(["events", "appStorage"], function(events, appStorage) { } return 0 } + + function cancelReportPlaybackProgressPromise(instance) { + if (typeof instance.reportPlaybackProgressCancel === "function") instance.reportPlaybackProgressCancel(); + } + ApiClient.prototype.appName = function() { return this._appName }, ApiClient.prototype.setRequestHeaders = function(headers) { @@ -1417,6 +1428,7 @@ define(["events", "appStorage"], function(events, appStorage) { }, ApiClient.prototype.reportPlaybackStart = function(options) { if (!options) throw new Error("null options"); this.lastPlaybackProgressReport = 0, this.lastPlaybackProgressReportTicks = null, stopBitrateDetection(this); + cancelReportPlaybackProgressPromise(this); var url = this.getUrl("Sessions/Playing"); return this.ajax({ type: "POST", @@ -1426,25 +1438,74 @@ define(["events", "appStorage"], function(events, appStorage) { }) }, ApiClient.prototype.reportPlaybackProgress = function(options) { if (!options) throw new Error("null options"); - var newPositionTicks = options.PositionTicks; - if ("timeupdate" === (options.EventName || "timeupdate")) { - var now = (new Date).getTime(), - msSinceLastReport = now - (this.lastPlaybackProgressReport || 0); - if (msSinceLastReport <= 1e4) { - if (!newPositionTicks) return Promise.resolve(); - var expectedReportTicks = 1e4 * msSinceLastReport + (this.lastPlaybackProgressReportTicks || 0); - if (Math.abs((newPositionTicks || 0) - expectedReportTicks) < 5e7) return Promise.resolve() - } - this.lastPlaybackProgressReport = now - } else this.lastPlaybackProgressReport = 0; - this.lastPlaybackProgressReportTicks = newPositionTicks; - var url = this.getUrl("Sessions/Playing/Progress"); - return this.ajax({ - type: "POST", - data: JSON.stringify(options), - contentType: "application/json", - url: url - }) + + const eventName = options.EventName || "timeupdate"; + let reportRateLimitTime = reportRateLimits[eventName] || 0; + + const now = (new Date).getTime(); + const msSinceLastReport = now - (this.lastPlaybackProgressReport || 0); + const newPositionTicks = options.PositionTicks; + + if (msSinceLastReport < reportRateLimitTime && eventName === "timeupdate" && newPositionTicks) { + const expectedReportTicks = 1e4 * msSinceLastReport + (this.lastPlaybackProgressReportTicks || 0); + if (Math.abs((newPositionTicks || 0) - expectedReportTicks) >= 5e7) reportRateLimitTime = 0; + } + + if (reportRateLimitTime < (this.reportPlaybackProgressTimeout !== undefined ? this.reportPlaybackProgressTimeout : 1e6)) { + cancelReportPlaybackProgressPromise(this); + } + + this.lastPlaybackProgressOptions = options; + + if (this.reportPlaybackProgressPromise) return Promise.resolve(); + + let instance = this; + let promise; + let cancelled = false; + + let resetPromise = function () { + if (instance.reportPlaybackProgressPromise !== promise) return; + + delete instance.lastPlaybackProgressOptions; + delete instance.reportPlaybackProgressTimeout; + delete instance.reportPlaybackProgressPromise; + delete instance.reportPlaybackProgressCancel; + }; + + let sendReport = function (options) { + resetPromise(); + + if (!options) throw new Error("null options"); + + instance.lastPlaybackProgressReport = (new Date).getTime(); + instance.lastPlaybackProgressReportTicks = options.PositionTicks; + + const url = instance.getUrl("Sessions/Playing/Progress"); + return instance.ajax({ + type: "POST", + data: JSON.stringify(options), + contentType: "application/json", + url: url + }); + }; + + let delay = Math.max(0, reportRateLimitTime - msSinceLastReport); + + promise = new Promise((resolve, reject) => setTimeout(resolve, delay)).then(() => { + if (cancelled) return Promise.resolve(); + return sendReport(instance.lastPlaybackProgressOptions); + }).finally(() => { + resetPromise(); + }); + + this.reportPlaybackProgressTimeout = reportRateLimitTime; + this.reportPlaybackProgressPromise = promise; + this.reportPlaybackProgressCancel = function () { + cancelled = true; + resetPromise(); + }; + + return promise; }, ApiClient.prototype.reportOfflineActions = function(actions) { if (!actions) throw new Error("null actions"); var url = this.getUrl("Sync/OfflineActions"); @@ -1489,6 +1550,7 @@ define(["events", "appStorage"], function(events, appStorage) { }, ApiClient.prototype.reportPlaybackStopped = function(options) { if (!options) throw new Error("null options"); this.lastPlaybackProgressReport = 0, this.lastPlaybackProgressReportTicks = null, redetectBitrate(this); + cancelReportPlaybackProgressPromise(this); var url = this.getUrl("Sessions/Playing/Stopped"); return this.ajax({ type: "POST",