Files
NovaSonic/android/app/src/main/kotlin/com/novasonic/app/BridgeMessages.kt
T
HoneyandClaude Sonnet 4.6 0e17d2e5db feat: native Android audio via Media3 ExoPlayer + full NSBridge JS adapter
- Replace legacy MediaBrowserServiceCompat/MediaSessionCompat with
  Media3 MediaSessionService (MusicService.kt) + ExoPlayer set to
  WAKE_MODE_NETWORK — holds WakeLock + WifiLock through Doze so audio
  plays uninterrupted on locked screen
- Media3 manages the notification; no manual NotificationCompat.Builder
  → no disappear/reappear flicker when skipping tracks
- Full bidirectional bridge:
    JS → native: window.NSBridge.* (WebBridge.kt, 13 methods)
    Native → JS: window.NSPlayer.* callbacks (onReady, onTrackChanged,
      onPositionUpdate, onPlaybackStateChanged, onQueueEnded, onError)
- player.js: progressive enhancement — all playback routes through
  NSBridge when running inside the wrapper, falls back to S.audio on
  plain PWA/browser; removes all window.Android.* calls
- main.js: guard _onForeground mediaSession playbackState setter behind
  !window.NSBridge so native Media3 session is never stomped
- ProGuard rules updated to keep WebBridge @JavascriptInterface methods
  and Media3 classes
- bridge-test.html: manual integration test page for every bridge method

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 01:14:28 +02:00

54 lines
1.6 KiB
Kotlin

package com.novasonic.app
import android.net.Uri
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import org.json.JSONArray
import org.json.JSONObject
data class Track(
val id: String,
val url: String,
val title: String,
val artist: String,
val album: String?,
val artworkUrl: String?,
val durationMs: Long
) {
fun toMediaItem(): MediaItem {
val metadata = MediaMetadata.Builder()
.setTitle(title)
.setArtist(artist)
.setAlbumTitle(album)
.apply {
if (!artworkUrl.isNullOrEmpty()) setArtworkUri(Uri.parse(artworkUrl))
if (durationMs > 0) setDurationMs(durationMs)
}
.build()
return MediaItem.Builder()
.setMediaId(id)
.setUri(url)
.setMediaMetadata(metadata)
.build()
}
companion object {
fun fromJson(obj: JSONObject) = Track(
id = obj.optString("id", ""),
url = obj.optString("url", ""),
title = obj.optString("title", ""),
artist = obj.optString("artist", ""),
album = obj.optString("album").takeIf { it.isNotEmpty() },
artworkUrl = obj.optString("artworkUrl").takeIf { it.isNotEmpty() },
durationMs = obj.optLong("durationMs", 0)
)
}
}
fun parseTrackList(json: String): List<Track> = try {
val arr = JSONArray(json)
List(arr.length()) { Track.fromJson(arr.getJSONObject(it)) }
} catch (_: Exception) {
emptyList()
}