From 4c678836fbaf09e0b8964e71ac95f22aeee863ba Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 21 Jul 2022 19:18:32 -0500 Subject: [PATCH] Update:Metered network connections to send progress syncs every 60s instead of every 5s and show metered/unmetered in connection status icon #238 --- .../app/player/MediaProgressSyncer.kt | 21 +++++++++----- .../app/player/PlayerNotificationService.kt | 29 +++++++++++++++++++ .../app/plugins/AbsAudioPlayer.kt | 4 +++ .../audiobookshelf/app/server/ApiHandler.kt | 6 ++++ components/widgets/ConnectionIndicator.vue | 9 ++++-- store/index.js | 11 +++++++ 6 files changed, 71 insertions(+), 9 deletions(-) diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/MediaProgressSyncer.kt b/android/app/src/main/java/com/audiobookshelf/app/player/MediaProgressSyncer.kt index 2f774a1d..ae2e78b3 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/MediaProgressSyncer.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/MediaProgressSyncer.kt @@ -19,6 +19,7 @@ data class MediaProgressSyncData( class MediaProgressSyncer(val playerNotificationService:PlayerNotificationService, private val apiHandler: ApiHandler) { private val tag = "MediaProgressSync" + private val METERED_CONNECTION_SYNC_INTERVAL = 60000 private var listeningTimerTask: TimerTask? = null var listeningTimerRunning:Boolean = false @@ -57,8 +58,11 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic listeningTimerTask = Timer("ListeningTimer", false).schedule(0L, 5000L) { Handler(Looper.getMainLooper()).post() { if (playerNotificationService.currentPlayer.isPlaying) { + // Only sync with server on unmetered connection every 5s OR sync with server if last sync time is >= 60s + val shouldSyncServer = PlayerNotificationService.isUnmeteredNetwork || System.currentTimeMillis() - lastSyncTime >= METERED_CONNECTION_SYNC_INTERVAL + val currentTime = playerNotificationService.getCurrentTimeSeconds() - sync(currentTime) { + sync(shouldSyncServer, currentTime) { Log.d(tag, "Sync complete") } } @@ -71,7 +75,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic Log.d(tag, "stop: Stopping listening for $currentDisplayTitle") val currentTime = playerNotificationService.getCurrentTimeSeconds() - sync(currentTime) { + sync(true, currentTime) { reset() cb() } @@ -82,7 +86,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic Log.d(tag, "pause: Pausing progress syncer for $currentDisplayTitle") val currentTime = playerNotificationService.getCurrentTimeSeconds() - sync(currentTime) { + sync(true, currentTime) { listeningTimerTask?.cancel() listeningTimerTask = null listeningTimerRunning = false @@ -102,13 +106,12 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic } } - fun sync(currentTime:Double, cb: () -> Unit) { + fun sync(shouldSyncServer:Boolean, currentTime:Double, cb: () -> Unit) { val diffSinceLastSync = System.currentTimeMillis() - lastSyncTime if (diffSinceLastSync < 1000L) { return cb() } val listeningTimeToAdd = diffSinceLastSync / 1000L - lastSyncTime = System.currentTimeMillis() val syncData = MediaProgressSyncData(listeningTimeToAdd,currentPlaybackDuration,currentTime) @@ -124,10 +127,11 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic currentPlaybackSession?.let { DeviceManager.dbManager.saveLocalPlaybackSession(it) saveLocalProgress(it) + lastSyncTime = System.currentTimeMillis() // Local library item is linked to a server library item // Send sync to server also if connected to this server and local item belongs to this server - if (!it.libraryItemId.isNullOrEmpty() && it.serverConnectionConfigId != null && DeviceManager.serverConnectionConfig?.id == it.serverConnectionConfigId) { + if (shouldSyncServer && !it.libraryItemId.isNullOrEmpty() && it.serverConnectionConfigId != null && DeviceManager.serverConnectionConfig?.id == it.serverConnectionConfigId) { apiHandler.sendLocalProgressSync(it) { syncSuccess -> Log.d( tag, @@ -151,12 +155,13 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic cb() } } - } else { + } else if (shouldSyncServer) { apiHandler.sendProgressSync(currentSessionId, syncData) { if (it) { Log.d(tag, "Progress sync data sent to server $currentDisplayTitle for time $currentTime") failedSyncs = 0 playerNotificationService.alertSyncSuccess() + lastSyncTime = System.currentTimeMillis() } else { failedSyncs++ if (failedSyncs == 2) { @@ -167,6 +172,8 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic } cb() } + } else { + cb() } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index df082706..68cdd8ec 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -6,6 +6,10 @@ import android.content.Intent import android.graphics.Color import android.hardware.Sensor import android.hardware.SensorManager +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest import android.os.* import android.support.v4.media.MediaBrowserCompat import android.support.v4.media.MediaDescriptionCompat @@ -45,6 +49,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { companion object { var isStarted = false var isClosed = false + var isUnmeteredNetwork = false } interface ClientEventEmitter { @@ -59,6 +64,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { fun onMediaPlayerChanged(mediaPlayer:String) fun onProgressSyncFailing() fun onProgressSyncSuccess() + fun onNetworkMeteredChanged(isUnmetered:Boolean) } private val tag = "PlayerService" @@ -164,6 +170,15 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { super.onCreate() ctx = this + // To listen for network change from metered to unmetered + val networkRequest = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .build() + val connectivityManager = getSystemService(ConnectivityManager::class.java) as ConnectivityManager + connectivityManager.registerNetworkCallback(networkRequest, networkCallback) + DbManager.initialize(ctx) // Initialize API @@ -914,5 +929,19 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { } } } + + private val networkCallback = object : ConnectivityManager.NetworkCallback() { + // Network capabilities have changed for the network + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + super.onCapabilitiesChanged(network, networkCapabilities) + val unmetered = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + Log.i(tag, "Network capabilities changed is unmetered = $unmetered") + isUnmeteredNetwork = unmetered + clientEventEmitter?.onNetworkMeteredChanged(unmetered) + } + } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt index 2a36a584..8d9a62c0 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt @@ -84,6 +84,10 @@ class AbsAudioPlayer : Plugin() { override fun onProgressSyncSuccess() { emit("onProgressSyncSuccess", "") } + + override fun onNetworkMeteredChanged(isUnmetered:Boolean) { + emit("onNetworkMeteredChanged", isUnmetered) + } }) } mainActivity.pluginCallback = foregroundServiceReady diff --git a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt index aebcede3..b55910f5 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt @@ -79,6 +79,12 @@ class ApiHandler(var ctx:Context) { return false } + fun isUsingCellularData(): Boolean { + val connectivityManager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + return capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true + } + fun makeRequest(request:Request, httpClient:OkHttpClient?, cb: (JSObject) -> Unit) { val client = httpClient ?: defaultClient client.newCall(request).enqueue(object : Callback { diff --git a/components/widgets/ConnectionIndicator.vue b/components/widgets/ConnectionIndicator.vue index 90aa4d5c..e58e01e1 100644 --- a/components/widgets/ConnectionIndicator.vue +++ b/components/widgets/ConnectionIndicator.vue @@ -24,6 +24,9 @@ export default { networkConnectionType() { return this.$store.state.networkConnectionType }, + isNetworkUnmetered() { + return this.$store.state.isNetworkUnmetered + }, isCellular() { return this.networkConnectionType === 'cellular' }, @@ -43,6 +46,7 @@ export default { iconClass() { if (!this.networkConnected) return 'text-error' else if (!this.socketConnected) return 'text-warning' + else if (!this.isNetworkUnmetered) return 'text-yellow-400' else if (this.isCellular) return 'text-gray-200' else return 'text-success' } @@ -50,14 +54,15 @@ export default { methods: { showAlertDialog() { var msg = '' + var meteredString = this.isNetworkUnmetered ? 'unmetered' : 'metered' if (!this.networkConnected) { msg = 'No internet' } else if (!this.socketConnected) { msg = 'Socket not connected' } else if (this.isCellular) { - msg = 'Socket connected over cellular' + msg = `Socket connected over ${meteredString} cellular` } else { - msg = 'Socket connected over wifi' + msg = `Socket connected over ${meteredString} wifi` } Dialog.alert({ title: 'Connection Status', diff --git a/store/index.js b/store/index.js index f0274c44..fa9e1197 100644 --- a/store/index.js +++ b/store/index.js @@ -1,4 +1,5 @@ import { Network } from '@capacitor/network' +import { AbsAudioPlayer } from '@/plugins/capacitor' export const state = () => ({ deviceData: null, @@ -12,6 +13,7 @@ export const state = () => ({ socketConnected: false, networkConnected: false, networkConnectionType: null, + isNetworkUnmetered: true, isFirstLoad: true, hasStoragePermission: false, selectedLibraryItem: null, @@ -62,6 +64,12 @@ export const actions = { console.log('Network status changed', status.connected, status.connectionType) commit('setNetworkStatus', status) }) + + AbsAudioPlayer.addListener('onNetworkMeteredChanged', (payload) => { + const isUnmetered = payload.value + console.log('On network metered changed', isUnmetered) + commit('setIsNetworkUnmetered', isUnmetered) + }) } } @@ -114,6 +122,9 @@ export const mutations = { state.networkConnected = val.connected state.networkConnectionType = val.connectionType }, + setIsNetworkUnmetered(state, val) { + state.isNetworkUnmetered = val + }, openReader(state, libraryItem) { state.selectedLibraryItem = libraryItem state.showReader = true