Skip to content

Commit 4803b7a

Browse files
authored
Merge pull request #109 from Mudit200408/develop
Implement Seekbar sync for now playing
2 parents db03d64 + d7f97fd commit 4803b7a

7 files changed

Lines changed: 85 additions & 19 deletions

File tree

app/src/main/java/com/sameerasw/airsync/domain/model/DeviceStatus.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ data class AudioInfo(
1313
val isMuted: Boolean,
1414
val albumArt: String? = null,
1515
val albumArtLite: String? = null,
16+
val durationMs: Long = 0L,
17+
val positionMs: Long = 0L,
18+
val positionTimestampMs: Long = 0L,
19+
val isBuffering: Boolean = false,
1620
// New: like status for current media ("liked", "not_liked", or "none")
1721
val likeStatus: String = "none"
1822
)
@@ -23,6 +27,10 @@ data class MediaInfo(
2327
val artist: String,
2428
val albumArt: String? = null,
2529
val albumArtLite: String? = null,
30+
val durationMs: Long = 0L,
31+
val positionMs: Long = 0L,
32+
val positionTimestampMs: Long = 0L,
33+
val isBuffering: Boolean = false,
2634
// New: like status for current media ("liked", "not_liked", or "none")
2735
val likeStatus: String = "none"
28-
)
36+
)

app/src/main/java/com/sameerasw/airsync/service/MediaNotificationListener.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class MediaNotificationListener : NotificationListenerService() {
8686
fun getMediaInfo(context: Context): MediaInfo {
8787
// Respect global toggle; if disabled, return empty media
8888
if (!isNowPlayingEnabled) {
89-
return MediaInfo(false, "", "", null, "none")
89+
return MediaInfo(false, "", "", null, null, 0L, 0L, 0L, false, "none")
9090
}
9191
return try {
9292
val mediaSessionManager =
@@ -119,6 +119,14 @@ class MediaNotificationListener : NotificationListenerService() {
119119
val title = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE) ?: ""
120120
val artist = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST) ?: ""
121121
val isPlaying = playbackState?.state == PlaybackState.STATE_PLAYING
122+
val durationMs = metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION) ?: 0L
123+
val positionMs = playbackState?.position ?: 0L
124+
val positionTimestampMs = System.currentTimeMillis()
125+
val isBuffering = when (playbackState?.state) {
126+
PlaybackState.STATE_BUFFERING,
127+
PlaybackState.STATE_CONNECTING -> true
128+
else -> false
129+
}
122130

123131
val albumArtBitmap =
124132
metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
@@ -166,6 +174,10 @@ class MediaNotificationListener : NotificationListenerService() {
166174
artist = artist,
167175
albumArt = albumArtBase64,
168176
albumArtLite = albumArtLiteBase64,
177+
durationMs = durationMs,
178+
positionMs = positionMs,
179+
positionTimestampMs = positionTimestampMs,
180+
isBuffering = isBuffering,
169181
likeStatus = likeStatus
170182
)
171183
}
@@ -181,10 +193,10 @@ class MediaNotificationListener : NotificationListenerService() {
181193
}
182194

183195
// Log.d(TAG, "No media info found")
184-
MediaInfo(false, "", "", null, "none")
196+
MediaInfo(false, "", "", null, null, 0L, 0L, 0L, false, "none")
185197
} catch (e: Exception) {
186198
Log.e(TAG, "Error getting media info: ${e.message}")
187-
MediaInfo(false, "", "", null, "none")
199+
MediaInfo(false, "", "", null, null, 0L, 0L, 0L, false, "none")
188200
}
189201
}
190202

app/src/main/java/com/sameerasw/airsync/utils/DeviceInfoUtil.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ object DeviceInfoUtil {
152152
isMuted = isMuted,
153153
albumArt = null,
154154
albumArtLite = null,
155+
durationMs = 0L,
156+
positionMs = 0L,
157+
positionTimestampMs = 0L,
158+
isBuffering = false,
155159
likeStatus = "none"
156160
)
157161
}
@@ -168,11 +172,15 @@ object DeviceInfoUtil {
168172
isMuted = isMuted,
169173
albumArt = mediaInfo.albumArt,
170174
albumArtLite = mediaInfo.albumArtLite,
175+
durationMs = mediaInfo.durationMs,
176+
positionMs = mediaInfo.positionMs,
177+
positionTimestampMs = mediaInfo.positionTimestampMs,
178+
isBuffering = mediaInfo.isBuffering,
171179
likeStatus = mediaInfo.likeStatus
172180
)
173181
} catch (e: Exception) {
174182
Log.e("DeviceInfoUtil", "Error getting audio info: ${e.message}")
175-
AudioInfo(false, "", "", 0, true, null, "none")
183+
AudioInfo(false, "", "", 0, true, null, null, 0L, 0L, 0L, false, "none")
176184
}
177185
}
178186

@@ -191,6 +199,10 @@ object DeviceInfoUtil {
191199
isMuted = audioInfo.isMuted,
192200
albumArt = audioInfo.albumArt,
193201
albumArtLite = audioInfo.albumArtLite,
202+
duration = audioInfo.durationMs,
203+
position = audioInfo.positionMs,
204+
positionTimestamp = audioInfo.positionTimestampMs,
205+
isBuffering = audioInfo.isBuffering,
194206
likeStatus = audioInfo.likeStatus
195207
)
196208
}
@@ -215,4 +227,4 @@ object DeviceInfoUtil {
215227
val powerManager = context.getSystemService(Context.POWER_SERVICE) as? android.os.PowerManager
216228
return powerManager?.isPowerSaveMode == true
217229
}
218-
}
230+
}

app/src/main/java/com/sameerasw/airsync/utils/JsonUtil.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,15 @@ object JsonUtil {
157157
isMuted: Boolean,
158158
albumArt: String?,
159159
albumArtLite: String? = null,
160+
duration: Long = 0L,
161+
position: Long = 0L,
162+
positionTimestamp: Long = 0L,
163+
isBuffering: Boolean = false,
160164
likeStatus: String
161165
): String {
162166
val albumArtJson = if (albumArt != null) ",\"albumArt\":\"$albumArt\"" else ""
163167
val albumArtLiteJson = if (albumArtLite != null) ",\"albumArtLite\":\"$albumArtLite\"" else ""
164-
return """{"type":"status","data":{"battery":{"level":$batteryLevel,"isCharging":$isCharging},"isPaired":$isPaired,"music":{"isPlaying":$isPlaying,"title":"$title","artist":"$artist","volume":$volume,"isMuted":$isMuted$albumArtJson$albumArtLiteJson,"likeStatus":"$likeStatus"}}}"""
168+
return """{"type":"status","data":{"battery":{"level":$batteryLevel,"isCharging":$isCharging},"isPaired":$isPaired,"music":{"isPlaying":$isPlaying,"title":"$title","artist":"$artist","volume":$volume,"isMuted":$isMuted$albumArtJson$albumArtLiteJson,"duration":$duration,"position":$position,"positionTimestamp":$positionTimestamp,"isBuffering":$isBuffering,"likeStatus":"$likeStatus"}}}"""
165169
}
166170

167171
/**
@@ -266,4 +270,4 @@ object JsonUtil {
266270
)
267271
}"}}"""
268272
}
269-
}
273+
}

app/src/main/java/com/sameerasw/airsync/utils/MediaControlUtil.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,26 @@ object MediaControlUtil {
100100
}
101101
}
102102

103+
/**
104+
* Seek active media playback to an absolute position in milliseconds.
105+
*/
106+
fun seekTo(context: Context, positionMs: Long): Boolean {
107+
return try {
108+
val controller = getActiveMediaController(context)
109+
if (controller != null) {
110+
controller.transportControls.seekTo(positionMs.coerceAtLeast(0L))
111+
Log.d(TAG, "Seeked media to ${positionMs}ms")
112+
true
113+
} else {
114+
Log.w(TAG, "No active media controller for seek")
115+
false
116+
}
117+
} catch (e: Exception) {
118+
Log.e(TAG, "Error in seekTo: ${e.message}")
119+
false
120+
}
121+
}
122+
103123
/**
104124
* Toggle like status by invoking the Like/Unlike action in the active media notification.
105125
*/

app/src/main/java/com/sameerasw/airsync/utils/SyncManager.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ object SyncManager {
9595
last.artist != currentAudio.artist ||
9696
last.volume != currentAudio.volume ||
9797
last.isMuted != currentAudio.isMuted ||
98+
last.isBuffering != currentAudio.isBuffering ||
99+
last.durationMs != currentAudio.durationMs ||
100+
kotlin.math.abs(last.positionMs - currentAudio.positionMs) >= 5_000L ||
98101
last.likeStatus != currentAudio.likeStatus
99102
) {
100103
shouldSync = true
@@ -618,4 +621,4 @@ object SyncManager {
618621
lastBatteryInfo = null
619622
lastVolume = -1
620623
}
621-
}
624+
}

app/src/main/java/com/sameerasw/airsync/utils/WebSocketMessageHandler.kt

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ object WebSocketMessageHandler {
212212
}
213213

214214
/**
215-
* Handles media control commands (play/pause, next, previous, like).
215+
* Handles media control commands (play/pause, seek, next, previous, like).
216216
* Sends a response back to Mac and updates local media state after a short delay.
217217
*/
218218
private fun handleMediaControl(context: Context, data: JSONObject?) {
@@ -243,6 +243,13 @@ object WebSocketMessageHandler {
243243
message = if (success) "Playback paused" else "Failed to pause playback"
244244
}
245245

246+
"seekTo" -> {
247+
val positionMs = data.optLong("positionMs", -1L)
248+
success = positionMs >= 0L && MediaControlUtil.seekTo(context, positionMs)
249+
message =
250+
if (success) "Seeked to ${positionMs}ms" else "Failed to seek playback"
251+
}
252+
246253
"next" -> {
247254
// Suppress automatic media updates before executing skip command
248255
SyncManager.suppressMediaUpdatesForSkip()
@@ -289,14 +296,15 @@ object WebSocketMessageHandler {
289296

290297
// Send updated media state after successful control
291298
if (success) {
292-
// For track skip actions (next/previous), add a delay to allow media player to update
293-
CoroutineScope(Dispatchers.IO).launch {
294-
val delayMs = when (action) {
295-
"next", "previous" -> 1200L
296-
else -> 400L // smaller delay for like/others
297-
}
298-
delay(delayMs)
299-
SyncManager.onMediaStateChanged(context)
299+
// For track skip actions (next/previous), add a delay to allow media player to update
300+
CoroutineScope(Dispatchers.IO).launch {
301+
val delayMs = when (action) {
302+
"seekTo" -> 650L
303+
"next", "previous" -> 1200L
304+
else -> 400L // smaller delay for like/others
305+
}
306+
delay(delayMs)
307+
SyncManager.onMediaStateChanged(context)
300308
}
301309
}
302310
} catch (e: Exception) {
@@ -915,4 +923,3 @@ object WebSocketMessageHandler {
915923
}
916924
}
917925
}
918-

0 commit comments

Comments
 (0)