From f8b8032ef6e84e808e9ff861ad50846fbd97c237 Mon Sep 17 00:00:00 2001 From: et0h Date: Sat, 23 May 2020 16:55:22 +0100 Subject: [PATCH] Re-work handling of sync on join and playlist change (#280) --- syncplay/client.py | 66 ++++++++++++++++++++++++++++------------- syncplay/constants.py | 1 + syncplay/players/mpc.py | 2 ++ syncplay/protocols.py | 13 +++++++- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 184e7d4..3ba8c24 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -76,9 +76,12 @@ class SyncplayClient(object): self.serverFeatures = {} self.lastRewindTime = None + self.lastUpdatedFileTime = None self.lastAdvanceTime = None self.lastConnectTime = None self.lastSetRoomTime = None + self.hadFirstPlaylistIndex = False + self.hadFirstStateUpdate = False self.lastLeftTime = 0 self.lastPausedOnLeaveTime = None self.lastLeftUser = "" @@ -186,21 +189,23 @@ class SyncplayClient(object): pauseChange = self.getPlayerPaused() != paused and self.getGlobalPaused() != paused _playerDiff = abs(self.getPlayerPosition() - position) _globalDiff = abs(self.getGlobalPosition() - position) - seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD and not self.recentlySetRoom() + seeked = _playerDiff > constants.SEEK_THRESHOLD and _globalDiff > constants.SEEK_THRESHOLD return pauseChange, seeked def rewindFile(self): - self.setPosition(-1) + self.setPosition(0) self.establishRewindDoubleCheck() def establishRewindDoubleCheck(self): - reactor.callLater(0.5, self.doubleCheckRewindFile,) - reactor.callLater(1, self.doubleCheckRewindFile,) - reactor.callLater(1.5, self.doubleCheckRewindFile,) + if constants.DOUBLE_CHECK_REWIND: + reactor.callLater(0.5, self.doubleCheckRewindFile,) + reactor.callLater(1, self.doubleCheckRewindFile,) + reactor.callLater(1.5, self.doubleCheckRewindFile,) + return def doubleCheckRewindFile(self): if self.getStoredPlayerPosition() > 5: - self.setPosition(-1) + self.setPosition(0) self.ui.showDebugMessage("Rewinded after double-check") def isPlayingMusic(self): @@ -251,7 +256,7 @@ class SyncplayClient(object): def _recentlyAdvanced(self): lastAdvandedDiff = time.time() - self.lastAdvanceTime if self.lastAdvanceTime else None - if lastAdvandedDiff is not None and lastAdvandedDiff < constants.LAST_PAUSED_DIFF_THRESHOLD: + if lastAdvandedDiff is not None and lastAdvandedDiff < constants.AUTOPLAY_DELAY + 5: return True def recentlyConnected(self): @@ -259,15 +264,17 @@ class SyncplayClient(object): if connectDiff is None or connectDiff < constants.LAST_PAUSED_DIFF_THRESHOLD: return True - def recentlySetRoom(self): - setroomDiff = time.time() - self.lastSetRoomTime if self.lastSetRoomTime else None - if setroomDiff is None or setroomDiff < constants.LAST_PAUSED_DIFF_THRESHOLD: - return True + def recentlyRewound(self, recentRewindThreshold = 5.0): + lastRewindTime = self.lastRewindTime + if lastRewindTime and self.lastUpdatedFileTime and self.lastUpdatedFileTime > lastRewindTime: + lastRewindTime = self.lastRewindTime - 4.5 + return lastRewindTime is not None and abs(time.time() - lastRewindTime) < recentRewindThreshold def _toggleReady(self, pauseChange, paused): if not self.userlist.currentUser.canControl(): self._player.setPaused(self._globalPaused) - self.toggleReady(manuallyInitiated=True) + if not self.recentlyRewound() and not ((self._globalPaused == True) and not self._recentlyAdvanced()): + self.toggleReady(manuallyInitiated=True) self._playerPaused = self._globalPaused pauseChange = False if self.userlist.currentUser.isReady(): @@ -278,6 +285,10 @@ class SyncplayClient(object): self.ui.showDebugMessage("Readiness toggle ignored due to seamless music override") self._player.setPaused(paused) self._playerPaused = paused + elif (self.recentlyRewound() and (self._globalPaused == True) and not self._recentlyAdvanced()): + self._player.setPaused(self._globalPaused) + self._playerPaused = self._globalPaused + pauseChange = False elif not paused and not self.instaplayConditionsMet(): paused = True self._player.setPaused(paused) @@ -489,6 +500,7 @@ class SyncplayClient(object): return self._globalPaused def updateFile(self, filename, duration, path): + self.lastUpdatedFileTime = time.time() newPath = "" if utils.isURL(path): filename = path @@ -550,6 +562,7 @@ class SyncplayClient(object): self.playlist.openedFile() self._player.openFile(filePath, resetPosition) if resetPosition: + self.rewindFile() self.establishRewindDoubleCheck() self.lastRewindTime = time.time() self.autoplayCheck() @@ -911,12 +924,12 @@ class SyncplayClient(object): def autoplayConditionsMet(self): if self.seamlessMusicOveride(): self.setPaused(False) - recentlyReset = (self.lastRewindTime is not None and abs(time.time() - self.lastRewindTime) < 10) and self._playerPosition < 3 + recentlyAdvanced = self._recentlyAdvanced() return ( - self._playerPaused and (self.autoPlay or recentlyReset) and + self._playerPaused and (self.autoPlay or recentlyAdvanced) and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported() and self.userlist.areAllUsersInRoomReady(requireSameFilenames=self._config["autoplayRequireSameFilenames"]) - and ((self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold) or recentlyReset) + and ((self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold) or recentlyAdvanced) ) def autoplayTimerIsRunning(self): @@ -1618,6 +1631,7 @@ class UiManager(object): class SyncplayPlaylist(): def __init__(self, client): + self.queuedIndex = None self._client = client self._ui = self._client.ui self._previousPlaylist = None @@ -1643,11 +1657,15 @@ class SyncplayPlaylist(): try: index = self._playlist.index(filename) if index != self._playlistIndex: - self.changeToPlaylistIndex(index) + self.changeToPlaylistIndex(index, resetPosition=True) + else: + if filename == self.queuedIndexFilename: + return + self._client.rewindFile() except ValueError: pass - def changeToPlaylistIndex(self, index, username=None): + def changeToPlaylistIndex(self, index, username=None, resetPosition=False): if self._playlist is None or len(self._playlist) == 0: return if index is None: @@ -1674,10 +1692,12 @@ class SyncplayPlaylist(): self._playlistIndex = index if username is None: if self._client.isConnectedAndInARoom() and self._client.sharedPlaylistIsEnabled(): + if resetPosition: + self._client.rewindFile() self._client.setPlaylistIndex(index) - else: + elif index is not None: self._ui.showMessage(getMessage("playlist-selection-changed-notification").format(username)) - self.switchToNewPlaylistIndex(index) + self.switchToNewPlaylistIndex(index, resetPosition=resetPosition) def canSwitchToNextPlaylistIndex(self): if self._thereIsNextPlaylistIndex() and self._client.sharedPlaylistIsEnabled(): @@ -1696,7 +1716,12 @@ class SyncplayPlaylist(): return False @needsSharedPlaylistsEnabled - def switchToNewPlaylistIndex(self, index, resetPosition=False): + def switchToNewPlaylistIndex(self, index, resetPosition = False): + try: + self.queuedIndexFilename = self._playlist[index] + except: + self.queuedIndexFilename = None + self._ui.showDebugMessage("Failed to find index {} in plauylist".format(index)) self._lastPlaylistIndexChange = time.time() if self._client.playerIsNotReady(): self._client.addPlayerReadyCallback(lambda x: self.switchToNewPlaylistIndex(index, resetPosition)) @@ -1773,6 +1798,7 @@ class SyncplayPlaylist(): def changePlaylist(self, files, username=None, resetIndex=False): + self.queuedIndexFilename = None if self._playlist == files: if self._playlistIndex != 0 and resetIndex: self.changeToPlaylistIndex(0) diff --git a/syncplay/constants.py b/syncplay/constants.py index 3c35acc..a40e9ac 100755 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -109,6 +109,7 @@ FOLDER_SEARCH_TIMEOUT = 20.0 # Secs - How long to wait until searches in folder FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 30.0 # Secs - Frequency of updating cache # Usually there's no need to adjust these +DOUBLE_CHECK_REWIND = False LAST_PAUSED_DIFF_THRESHOLD = 2 FILENAME_STRIP_REGEX = "[-~_\.\[\](): ]" CONTROL_PASSWORD_STRIP_REGEX = "[^a-zA-Z0-9\-]" diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index 88d684c..68f7e9b 100755 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -406,6 +406,8 @@ class MPCHCAPIPlayer(BasePlayer): def openFile(self, filePath, resetPosition=False): self._mpcApi.openFile(filePath) + if resetPosition: + self.setPosition(0) def displayMessage( self, message, diff --git a/syncplay/protocols.py b/syncplay/protocols.py index d13d07f..aef41da 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -73,12 +73,16 @@ class SyncClientProtocol(JSONCommandProtocol): self.clientIgnoringOnTheFly = 0 self.serverIgnoringOnTheFly = 0 self.logged = False + self.hadFirstPlaylistIndex = False + self.hadFirstStateUpdate = False self._pingService = PingService() def showDebugMessage(self, line): self._client.ui.showDebugMessage(line) def connectionMade(self): + self.hadFirstPlaylistIndex = False + self.hadFirstStateUpdate = False self._client.initProtocol(self) if self._client._clientSupportsTLS: if self._client._serverSupportsTLS: @@ -183,7 +187,12 @@ class SyncClientProtocol(JSONCommandProtocol): manuallyInitiated = values["manuallyInitiated"] if "manuallyInitiated" in values else True self._client.setReady(user, isReady, manuallyInitiated) elif command == "playlistIndex": - self._client.playlist.changeToPlaylistIndex(values['index'], values['user']) + user = values['user'] + resetPosition = True + if not self.hadFirstPlaylistIndex: + self.hadFirstPlaylistIndex = True + resetPosition = False + self._client.playlist.changeToPlaylistIndex(values['index'], user, resetPosition=resetPosition) elif command == "playlistChange": self._client.playlist.changePlaylist(values['files'], values['user']) elif command == "features": @@ -245,6 +254,8 @@ class SyncClientProtocol(JSONCommandProtocol): def handleState(self, state): position, paused, doSeek, setBy = None, None, None, None messageAge = 0 + if not self.hadFirstStateUpdate: + self.hadFirstStateUpdate = True if "ignoringOnTheFly" in state: ignore = state["ignoringOnTheFly"] if "server" in ignore: