diff --git a/Server.js b/Server.js index ee9bc2fa..dd602512 100644 --- a/Server.js +++ b/Server.js @@ -16,6 +16,8 @@ class Server extends EventEmitter { this.connected = false this.stream = null + + this.isConnectingSocket = false } get token() { @@ -60,6 +62,10 @@ class Server extends EventEmitter { } async connect(url, token) { + if (this.connected) { + console.warn('[SOCKET] Connection already established for ' + this.url) + return true + } if (!url) { console.error('Invalid url to connect') return false @@ -151,38 +157,84 @@ class Server extends EventEmitter { } connectSocket() { - console.log('[SERVER] Connect Socket', this.url) + if (this.connected || this.socket) { + if (this.socket) console.error('[SOCKET] Socket already established', this.url) + else console.error('[SOCKET] Already connected to socket', this.url) + return + } - this.socket = io(this.url) + console.log('[SOCKET] Connect Socket', this.url) + + const socketOptions = { + transports: ['websocket'], + upgrade: false, + // reconnectionAttempts: 3 + } + this.socket = io(this.url, socketOptions) this.socket.on('connect', () => { - console.log('[Server] Socket Connected') + console.log('[SOCKET] Socket Connected ' + this.socket.id) // Authenticate socket with token this.socket.emit('auth', this.token) - this.connected = true this.emit('connected', true) this.store.commit('setSocketConnected', true) }) - this.socket.on('disconnect', () => { - console.log('[Server] Socket Disconnected') + this.socket.on('disconnect', (reason) => { + console.log('[SOCKET] Socket Disconnected: ' + reason) this.connected = false this.emit('connected', false) this.store.commit('setSocketConnected', false) + + // this.socket.removeAllListeners() + // if (this.socket.io && this.socket.io.removeAllListeners) { + // console.log(`[SOCKET] Removing ALL IO listeners`) + // this.socket.io.removeAllListeners() + // } }) this.socket.on('init', (data) => { - console.log('[Server] Initial socket data received', data) + console.log('[SOCKET] Initial socket data received', data) if (data.stream) { this.stream = data.stream this.store.commit('setStreamAudiobook', data.stream.audiobook) this.emit('initialStream', data.stream) } }) + this.socket.on('user_updated', (user) => { if (this.user && user.id === this.user.id) { this.setUser(user) } }) + + this.socket.on('current_user_audiobook_update', (payload) => { + this.emit('currentUserAudiobookUpdate', payload) + }) + + this.socket.onAny((evt, args) => { + console.log(`[SOCKET] ${this.socket.id}: ${evt} ${JSON.stringify(args)}`) + }) + + this.socket.on('connect_error', (err) => { + console.error('[SOCKET] connection failed', err) + this.emit('socketConnectionFailed', err) + }) + + this.socket.io.on("reconnect_attempt", (attempt) => { + console.log(`[SOCKET] Reconnect Attempt ${this.socket.id}: ${attempt}`) + }) + + this.socket.io.on("reconnect_error", (err) => { + console.log(`[SOCKET] Reconnect Error ${this.socket.id}: ${err}`) + }) + + this.socket.io.on("reconnect_failed", () => { + console.log(`[SOCKET] Reconnect Failed ${this.socket.id}`) + }) + + this.socket.io.on("reconnect", () => { + console.log(`[SOCKET] Reconnect Success ${this.socket.id}`) + }) } } diff --git a/android/app/build.gradle b/android/app/build.gradle index fc811d4b..29c6a0dc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.audiobookshelf.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 25 - versionName "0.9.9-beta" + versionCode 26 + versionName "0.9.10-beta" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/components/AudioPlayerMini.vue b/components/AudioPlayerMini.vue index 905ae743..2010f3b0 100644 --- a/components/AudioPlayerMini.vue +++ b/components/AudioPlayerMini.vue @@ -72,7 +72,9 @@ export default { bufferTrackWidth: 0, playedTrackWidth: 0, seekedTime: 0, - seekLoading: false + seekLoading: false, + onPlayingUpdateListener: null, + onMetadataListener: null } }, computed: { @@ -308,28 +310,29 @@ export default { terminateStream() { MyNativeAudio.terminateStream() }, + onPlayingUpdate(data) { + this.isPaused = !data.value + if (!this.isPaused) { + this.startPlayInterval() + } else { + this.stopPlayInterval() + } + }, + onMetadata(data) { + console.log('Native Audio On Metadata', JSON.stringify(data)) + this.totalDuration = Number((data.duration / 1000).toFixed(2)) + this.currentTime = Number((data.currentTime / 1000).toFixed(2)) + this.stateName = data.stateName + + if (this.stateName === 'ended' && this.isResetting) { + this.setFromObj() + } + + this.timeupdate() + }, init() { - MyNativeAudio.addListener('onPlayingUpdate', (data) => { - this.isPaused = !data.value - if (!this.isPaused) { - this.startPlayInterval() - } else { - this.stopPlayInterval() - } - }) - - MyNativeAudio.addListener('onMetadata', (data) => { - console.log('Native Audio On Metadata', JSON.stringify(data)) - this.totalDuration = Number((data.duration / 1000).toFixed(2)) - this.currentTime = Number((data.currentTime / 1000).toFixed(2)) - this.stateName = data.stateName - - if (this.stateName === 'ended' && this.isResetting) { - this.setFromObj() - } - - this.timeupdate() - }) + this.onPlayingUpdateListener = MyNativeAudio.addListener('onPlayingUpdate', this.onPlayingUpdate) + this.onMetadataListener = MyNativeAudio.addListener('onMetadata', this.onMetadata) if (this.$refs.track) { this.trackWidth = this.$refs.track.clientWidth @@ -342,6 +345,8 @@ export default { this.$nextTick(this.init) }, beforeDestroy() { + if (this.onPlayingUpdateListener) this.onPlayingUpdateListener.remove() + if (this.onMetadataListener) this.onMetadataListener.remove() clearInterval(this.playInterval) } } diff --git a/components/widgets/ConnectionIcon.vue b/components/widgets/ConnectionIcon.vue index 419dc5ee..900d3f9c 100644 --- a/components/widgets/ConnectionIcon.vue +++ b/components/widgets/ConnectionIcon.vue @@ -69,7 +69,6 @@ export default { if (!this.networkConnected) return - this.$server.on('connected', this.socketConnected) var localServerUrl = await this.$localStore.getServerUrl() var localUserToken = await this.$localStore.getToken() if (localServerUrl) { @@ -78,12 +77,16 @@ export default { // Server and Token are stored if (localUserToken) { this.processing = true + var isSocketAlreadyEstablished = this.$server.socket var success = await this.$server.connect(localServerUrl, localUserToken) if (!success && !this.$server.url) { this.processing = false this.serverUrl = null } else if (!success) { this.processing = false + } else if (isSocketAlreadyEstablished) { + // No need to wait for connect event + this.processing = false } } else { // Server only is stored @@ -97,7 +100,18 @@ export default { } }, mounted() { + if (!this.$server) { + console.error('Server not initalized in connection icon') + return + } + if (this.$server.connected) { + this.isConnected = true + } + this.$server.on('connected', this.socketConnected) this.init() + }, + beforeDestroy() { + if (this.$server) this.$server.off('connected', this.socketConnected) } } \ No newline at end of file diff --git a/layouts/default.vue b/layouts/default.vue index 6d35bca7..6bd4265f 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -32,18 +32,19 @@ export default { }, methods: { async connected(isConnected) { - if (this.$route.name === 'connect') { - if (isConnected) { - this.$router.push('/') - } - } - this.syncUserProgress() + if (isConnected) { + this.syncUserProgress() - // Load libraries - this.$store.dispatch('libraries/load') + // Load libraries + this.$store.dispatch('libraries/load') + } + }, + socketConnectionFailed(err) { + this.$toast.error('Socket connection error: ' + err.message) }, updateAudiobookProgressOnServer(audiobookProgress) { if (this.$server.socket) { + console.log(`[PROGRESSSYNC] Updating AB Progress on server ${JSON.stringify(audiobookProgress)}`) this.$server.socket.emit('progress_update', audiobookProgress) } }, @@ -54,22 +55,27 @@ export default { var localAudiobooks = this.$store.state.user.localUserAudiobooks var localHasUpdates = false + // console.log('[PROGRESSSYNC] Starting Sync USER', JSON.stringify(userAudiobooks)) + // console.log('[PROGRESSSYNC] Starting Sync LOCAL', JSON.stringify(localAudiobooks)) + var newestLocal = { ...localAudiobooks } for (const audiobookId in userAudiobooks) { - if (localAudiobooks[audiobookId]) { + if (!audiobookId || !userAudiobooks[audiobookId] || audiobookId === 'undefined') { + console.error(`[PROGRESSSYNC] Invalid audiobookId ${audiobookId} - ${JSON.stringify(userAudiobooks[audiobookId])}`) + } else if (localAudiobooks[audiobookId]) { if (localAudiobooks[audiobookId].lastUpdate > userAudiobooks[audiobookId].lastUpdate) { // Local progress is more recent than user progress this.updateAudiobookProgressOnServer(localAudiobooks[audiobookId]) } else if (localAudiobooks[audiobookId].lastUpdate < userAudiobooks[audiobookId].lastUpdate) { // Server is more recent than local newestLocal[audiobookId] = userAudiobooks[audiobookId] - console.log('SYNCUSERPROGRESS Server IS MORE RECENT for', audiobookId) + // console.log('[PROGRESSSYNC] Server IS MORE RECENT for', audiobookId, JSON.stringify(newestLocal[audiobookId])) localHasUpdates = true } } else { // Not on local yet - store on local newestLocal[audiobookId] = userAudiobooks[audiobookId] - console.log('SYNCUSERPROGRESS LOCAL Is NOT Stored YET for', audiobookId, JSON.stringify(newestLocal[audiobookId])) + // console.log('[PROGRESSSYNC] LOCAL Is NOT Stored YET for', audiobookId, JSON.stringify(newestLocal[audiobookId])) localHasUpdates = true } } @@ -82,10 +88,17 @@ export default { } if (localHasUpdates) { - console.log('Local audiobook progress has updates from server') + // console.log('[PROGRESSSYNC] Local audiobook progress has updates from server') this.$localStore.setAllAudiobookProgress(newestLocal) } }, + currentUserAudiobookUpdate({ id, data }) { + if (data) { + this.$localStore.updateUserAudiobookProgress(data) + } else { + this.$localStore.removeAudiobookProgress(id) + } + }, initialStream(stream) { if (this.$refs.streamContainer && this.$refs.streamContainer.audioPlayerReady) { this.$refs.streamContainer.streamOpen(stream) @@ -368,9 +381,12 @@ export default { }, mounted() { if (!this.$server) return console.error('No Server') + // console.log(`Default Mounted set SOCKET listeners ${this.$server.connected}`) this.$server.on('connected', this.connected) + this.$server.on('connectionFailed', this.socketConnectionFailed) this.$server.on('initialStream', this.initialStream) + this.$server.on('currentUserAudiobookUpdate', this.currentUserAudiobookUpdate) if (this.$store.state.isFirstLoad) { this.$store.commit('setIsFirstLoad', false) @@ -379,7 +395,6 @@ export default { this.initMediaStore() } - // For Testing Android Auto MyNativeAudio.addListener('onPrepareMedia', (data) => { var audiobookId = data.audiobookId var playWhenReady = data.playWhenReady @@ -403,8 +418,9 @@ export default { console.error('No Server beforeDestroy') return } - console.log('Default Before Destroy remove listeners') + this.$server.off('connected', this.connected) + this.$server.off('connectionFailed', this.socketConnectionFailed) this.$server.off('initialStream', this.initialStream) } } diff --git a/package.json b/package.json index f261b50c..0eaabb44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-app", - "version": "v0.9.9-beta", + "version": "v0.9.10-beta", "author": "advplyr", "scripts": { "dev": "nuxt --hostname localhost --port 1337",