Update:Metered network connections to send progress syncs every 60s instead of every 5s and show metered/unmetered in connection status icon #238

This commit is contained in:
advplyr 2022-07-21 19:18:32 -05:00
parent fd134097a1
commit 4c678836fb
6 changed files with 71 additions and 9 deletions

View file

@ -19,6 +19,7 @@ data class MediaProgressSyncData(
class MediaProgressSyncer(val playerNotificationService:PlayerNotificationService, private val apiHandler: ApiHandler) { class MediaProgressSyncer(val playerNotificationService:PlayerNotificationService, private val apiHandler: ApiHandler) {
private val tag = "MediaProgressSync" private val tag = "MediaProgressSync"
private val METERED_CONNECTION_SYNC_INTERVAL = 60000
private var listeningTimerTask: TimerTask? = null private var listeningTimerTask: TimerTask? = null
var listeningTimerRunning:Boolean = false var listeningTimerRunning:Boolean = false
@ -57,8 +58,11 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
listeningTimerTask = Timer("ListeningTimer", false).schedule(0L, 5000L) { listeningTimerTask = Timer("ListeningTimer", false).schedule(0L, 5000L) {
Handler(Looper.getMainLooper()).post() { Handler(Looper.getMainLooper()).post() {
if (playerNotificationService.currentPlayer.isPlaying) { 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() val currentTime = playerNotificationService.getCurrentTimeSeconds()
sync(currentTime) { sync(shouldSyncServer, currentTime) {
Log.d(tag, "Sync complete") Log.d(tag, "Sync complete")
} }
} }
@ -71,7 +75,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
Log.d(tag, "stop: Stopping listening for $currentDisplayTitle") Log.d(tag, "stop: Stopping listening for $currentDisplayTitle")
val currentTime = playerNotificationService.getCurrentTimeSeconds() val currentTime = playerNotificationService.getCurrentTimeSeconds()
sync(currentTime) { sync(true, currentTime) {
reset() reset()
cb() cb()
} }
@ -82,7 +86,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
Log.d(tag, "pause: Pausing progress syncer for $currentDisplayTitle") Log.d(tag, "pause: Pausing progress syncer for $currentDisplayTitle")
val currentTime = playerNotificationService.getCurrentTimeSeconds() val currentTime = playerNotificationService.getCurrentTimeSeconds()
sync(currentTime) { sync(true, currentTime) {
listeningTimerTask?.cancel() listeningTimerTask?.cancel()
listeningTimerTask = null listeningTimerTask = null
listeningTimerRunning = false 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 val diffSinceLastSync = System.currentTimeMillis() - lastSyncTime
if (diffSinceLastSync < 1000L) { if (diffSinceLastSync < 1000L) {
return cb() return cb()
} }
val listeningTimeToAdd = diffSinceLastSync / 1000L val listeningTimeToAdd = diffSinceLastSync / 1000L
lastSyncTime = System.currentTimeMillis()
val syncData = MediaProgressSyncData(listeningTimeToAdd,currentPlaybackDuration,currentTime) val syncData = MediaProgressSyncData(listeningTimeToAdd,currentPlaybackDuration,currentTime)
@ -124,10 +127,11 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
currentPlaybackSession?.let { currentPlaybackSession?.let {
DeviceManager.dbManager.saveLocalPlaybackSession(it) DeviceManager.dbManager.saveLocalPlaybackSession(it)
saveLocalProgress(it) saveLocalProgress(it)
lastSyncTime = System.currentTimeMillis()
// Local library item is linked to a server library item // 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 // 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 -> apiHandler.sendLocalProgressSync(it) { syncSuccess ->
Log.d( Log.d(
tag, tag,
@ -151,12 +155,13 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
cb() cb()
} }
} }
} else { } else if (shouldSyncServer) {
apiHandler.sendProgressSync(currentSessionId, syncData) { apiHandler.sendProgressSync(currentSessionId, syncData) {
if (it) { if (it) {
Log.d(tag, "Progress sync data sent to server $currentDisplayTitle for time $currentTime") Log.d(tag, "Progress sync data sent to server $currentDisplayTitle for time $currentTime")
failedSyncs = 0 failedSyncs = 0
playerNotificationService.alertSyncSuccess() playerNotificationService.alertSyncSuccess()
lastSyncTime = System.currentTimeMillis()
} else { } else {
failedSyncs++ failedSyncs++
if (failedSyncs == 2) { if (failedSyncs == 2) {
@ -167,6 +172,8 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
} }
cb() cb()
} }
} else {
cb()
} }
} }

View file

@ -6,6 +6,10 @@ import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.hardware.Sensor import android.hardware.Sensor
import android.hardware.SensorManager 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.os.*
import android.support.v4.media.MediaBrowserCompat import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat import android.support.v4.media.MediaDescriptionCompat
@ -45,6 +49,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
companion object { companion object {
var isStarted = false var isStarted = false
var isClosed = false var isClosed = false
var isUnmeteredNetwork = false
} }
interface ClientEventEmitter { interface ClientEventEmitter {
@ -59,6 +64,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
fun onMediaPlayerChanged(mediaPlayer:String) fun onMediaPlayerChanged(mediaPlayer:String)
fun onProgressSyncFailing() fun onProgressSyncFailing()
fun onProgressSyncSuccess() fun onProgressSyncSuccess()
fun onNetworkMeteredChanged(isUnmetered:Boolean)
} }
private val tag = "PlayerService" private val tag = "PlayerService"
@ -164,6 +170,15 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
super.onCreate() super.onCreate()
ctx = this 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) DbManager.initialize(ctx)
// Initialize API // 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)
}
}
} }

View file

@ -84,6 +84,10 @@ class AbsAudioPlayer : Plugin() {
override fun onProgressSyncSuccess() { override fun onProgressSyncSuccess() {
emit("onProgressSyncSuccess", "") emit("onProgressSyncSuccess", "")
} }
override fun onNetworkMeteredChanged(isUnmetered:Boolean) {
emit("onNetworkMeteredChanged", isUnmetered)
}
}) })
} }
mainActivity.pluginCallback = foregroundServiceReady mainActivity.pluginCallback = foregroundServiceReady

View file

@ -79,6 +79,12 @@ class ApiHandler(var ctx:Context) {
return false 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) { fun makeRequest(request:Request, httpClient:OkHttpClient?, cb: (JSObject) -> Unit) {
val client = httpClient ?: defaultClient val client = httpClient ?: defaultClient
client.newCall(request).enqueue(object : Callback { client.newCall(request).enqueue(object : Callback {

View file

@ -24,6 +24,9 @@ export default {
networkConnectionType() { networkConnectionType() {
return this.$store.state.networkConnectionType return this.$store.state.networkConnectionType
}, },
isNetworkUnmetered() {
return this.$store.state.isNetworkUnmetered
},
isCellular() { isCellular() {
return this.networkConnectionType === 'cellular' return this.networkConnectionType === 'cellular'
}, },
@ -43,6 +46,7 @@ export default {
iconClass() { iconClass() {
if (!this.networkConnected) return 'text-error' if (!this.networkConnected) return 'text-error'
else if (!this.socketConnected) return 'text-warning' else if (!this.socketConnected) return 'text-warning'
else if (!this.isNetworkUnmetered) return 'text-yellow-400'
else if (this.isCellular) return 'text-gray-200' else if (this.isCellular) return 'text-gray-200'
else return 'text-success' else return 'text-success'
} }
@ -50,14 +54,15 @@ export default {
methods: { methods: {
showAlertDialog() { showAlertDialog() {
var msg = '' var msg = ''
var meteredString = this.isNetworkUnmetered ? 'unmetered' : 'metered'
if (!this.networkConnected) { if (!this.networkConnected) {
msg = 'No internet' msg = 'No internet'
} else if (!this.socketConnected) { } else if (!this.socketConnected) {
msg = 'Socket not connected' msg = 'Socket not connected'
} else if (this.isCellular) { } else if (this.isCellular) {
msg = 'Socket connected over cellular' msg = `Socket connected over ${meteredString} cellular`
} else { } else {
msg = 'Socket connected over wifi' msg = `Socket connected over ${meteredString} wifi`
} }
Dialog.alert({ Dialog.alert({
title: 'Connection Status', title: 'Connection Status',

View file

@ -1,4 +1,5 @@
import { Network } from '@capacitor/network' import { Network } from '@capacitor/network'
import { AbsAudioPlayer } from '@/plugins/capacitor'
export const state = () => ({ export const state = () => ({
deviceData: null, deviceData: null,
@ -12,6 +13,7 @@ export const state = () => ({
socketConnected: false, socketConnected: false,
networkConnected: false, networkConnected: false,
networkConnectionType: null, networkConnectionType: null,
isNetworkUnmetered: true,
isFirstLoad: true, isFirstLoad: true,
hasStoragePermission: false, hasStoragePermission: false,
selectedLibraryItem: null, selectedLibraryItem: null,
@ -62,6 +64,12 @@ export const actions = {
console.log('Network status changed', status.connected, status.connectionType) console.log('Network status changed', status.connected, status.connectionType)
commit('setNetworkStatus', status) 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.networkConnected = val.connected
state.networkConnectionType = val.connectionType state.networkConnectionType = val.connectionType
}, },
setIsNetworkUnmetered(state, val) {
state.isNetworkUnmetered = val
},
openReader(state, libraryItem) { openReader(state, libraryItem) {
state.selectedLibraryItem = libraryItem state.selectedLibraryItem = libraryItem
state.showReader = true state.showReader = true