@roitium/expo-orpheus 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/android/src/main/java/expo/modules/orpheus/DownloadCache.kt +13 -0
- package/android/src/main/java/expo/modules/orpheus/ExpoOrpheusModule.kt +11 -11
- package/android/src/main/java/expo/modules/orpheus/OrpheusMusicService.kt +19 -30
- package/android/src/main/java/expo/modules/orpheus/utils/DownloadUtil.kt +10 -18
- package/android/src/main/java/expo/modules/orpheus/utils/{Storage.kt → GeneralStorage.kt} +1 -1
- package/android/src/main/java/expo/modules/orpheus/utils/LoudnessStorage.kt +40 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@ package expo.modules.orpheus
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import androidx.media3.common.util.UnstableApi
|
|
5
5
|
import androidx.media3.database.StandaloneDatabaseProvider
|
|
6
|
+
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
|
|
6
7
|
import androidx.media3.datasource.cache.NoOpCacheEvictor
|
|
7
8
|
import androidx.media3.datasource.cache.SimpleCache
|
|
8
9
|
import java.io.File
|
|
@@ -10,6 +11,7 @@ import java.io.File
|
|
|
10
11
|
@UnstableApi
|
|
11
12
|
object DownloadCache {
|
|
12
13
|
private var stableCache: SimpleCache? = null
|
|
14
|
+
private var lruCache: SimpleCache? = null
|
|
13
15
|
|
|
14
16
|
@Synchronized
|
|
15
17
|
fun getStableCache(context: Context): SimpleCache {
|
|
@@ -21,4 +23,15 @@ object DownloadCache {
|
|
|
21
23
|
}
|
|
22
24
|
return stableCache!!
|
|
23
25
|
}
|
|
26
|
+
|
|
27
|
+
@Synchronized
|
|
28
|
+
fun getLruCache(context: Context): SimpleCache {
|
|
29
|
+
if (lruCache == null) {
|
|
30
|
+
val cacheDir = File(context.cacheDir, "media_cache_lru")
|
|
31
|
+
val evictor = LeastRecentlyUsedCacheEvictor(256 * 1024 * 1024)
|
|
32
|
+
val databaseProvider = StandaloneDatabaseProvider(context)
|
|
33
|
+
lruCache = SimpleCache(cacheDir, evictor, databaseProvider)
|
|
34
|
+
}
|
|
35
|
+
return lruCache!!
|
|
36
|
+
}
|
|
24
37
|
}
|
|
@@ -25,7 +25,8 @@ import expo.modules.kotlin.modules.Module
|
|
|
25
25
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
26
26
|
import expo.modules.orpheus.models.TrackRecord
|
|
27
27
|
import expo.modules.orpheus.utils.DownloadUtil
|
|
28
|
-
import expo.modules.orpheus.utils.
|
|
28
|
+
import expo.modules.orpheus.utils.GeneralStorage
|
|
29
|
+
import expo.modules.orpheus.utils.LoudnessStorage
|
|
29
30
|
import expo.modules.orpheus.utils.toMediaItem
|
|
30
31
|
|
|
31
32
|
@UnstableApi
|
|
@@ -156,12 +157,12 @@ class ExpoOrpheusModule : Module() {
|
|
|
156
157
|
|
|
157
158
|
override fun onRepeatModeChanged(repeatMode: Int) {
|
|
158
159
|
super.onRepeatModeChanged(repeatMode)
|
|
159
|
-
|
|
160
|
+
GeneralStorage.saveRepeatMode(repeatMode)
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
|
163
164
|
super.onShuffleModeEnabledChanged(shuffleModeEnabled)
|
|
164
|
-
|
|
165
|
+
GeneralStorage.saveShuffleMode(shuffleModeEnabled)
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
168
|
|
|
@@ -181,7 +182,8 @@ class ExpoOrpheusModule : Module() {
|
|
|
181
182
|
|
|
182
183
|
OnCreate {
|
|
183
184
|
val context = appContext.reactContext ?: return@OnCreate
|
|
184
|
-
|
|
185
|
+
GeneralStorage.initialize(context)
|
|
186
|
+
LoudnessStorage.initialize(context)
|
|
185
187
|
val sessionToken = SessionToken(
|
|
186
188
|
context,
|
|
187
189
|
ComponentName(context, OrpheusMusicService::class.java)
|
|
@@ -217,11 +219,11 @@ class ExpoOrpheusModule : Module() {
|
|
|
217
219
|
}
|
|
218
220
|
|
|
219
221
|
Constant("restorePlaybackPositionEnabled") {
|
|
220
|
-
|
|
222
|
+
GeneralStorage.isRestoreEnabled()
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
Constant("loudnessNormalizationEnabled") {
|
|
224
|
-
|
|
226
|
+
GeneralStorage.isLoudnessNormalizationEnabled()
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
Function("setBilibiliCookie") { cookie: String ->
|
|
@@ -229,11 +231,11 @@ class ExpoOrpheusModule : Module() {
|
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
Function("setLoudnessNormalizationEnabled") { enabled: Boolean ->
|
|
232
|
-
|
|
234
|
+
GeneralStorage.setLoudnessNormalizationEnabled(enabled)
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
Function("setRestorePlaybackPositionEnabled") { enabled: Boolean ->
|
|
236
|
-
|
|
238
|
+
GeneralStorage.setRestoreEnabled(enabled)
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
AsyncFunction("getPosition") {
|
|
@@ -302,7 +304,6 @@ class ExpoOrpheusModule : Module() {
|
|
|
302
304
|
checkPlayer()
|
|
303
305
|
player?.clearMediaItems()
|
|
304
306
|
durationCache.clear()
|
|
305
|
-
DownloadUtil.itemVolumeMap.clear()
|
|
306
307
|
}.runOnQueue(Queues.MAIN)
|
|
307
308
|
|
|
308
309
|
AsyncFunction("skipTo") { index: Int ->
|
|
@@ -396,7 +397,6 @@ class ExpoOrpheusModule : Module() {
|
|
|
396
397
|
if (clearQueue == true) {
|
|
397
398
|
p.clearMediaItems()
|
|
398
399
|
durationCache.clear()
|
|
399
|
-
DownloadUtil.itemVolumeMap.clear()
|
|
400
400
|
}
|
|
401
401
|
val initialSize = p.mediaItemCount
|
|
402
402
|
p.addMediaItems(mediaItems)
|
|
@@ -714,7 +714,7 @@ class ExpoOrpheusModule : Module() {
|
|
|
714
714
|
private fun saveCurrentPosition() {
|
|
715
715
|
val p = player ?: return
|
|
716
716
|
if (p.playbackState != Player.STATE_IDLE) {
|
|
717
|
-
|
|
717
|
+
GeneralStorage.savePosition(
|
|
718
718
|
p.currentMediaItemIndex,
|
|
719
719
|
p.currentPosition
|
|
720
720
|
)
|
|
@@ -2,6 +2,7 @@ package expo.modules.orpheus
|
|
|
2
2
|
|
|
3
3
|
import android.app.PendingIntent
|
|
4
4
|
import android.content.Intent
|
|
5
|
+
import android.util.Log
|
|
5
6
|
import androidx.annotation.OptIn
|
|
6
7
|
import androidx.core.net.toUri
|
|
7
8
|
import androidx.media3.common.AudioAttributes
|
|
@@ -15,10 +16,10 @@ import androidx.media3.session.MediaLibraryService
|
|
|
15
16
|
import androidx.media3.session.MediaSession
|
|
16
17
|
import com.google.common.util.concurrent.Futures
|
|
17
18
|
import com.google.common.util.concurrent.ListenableFuture
|
|
18
|
-
import expo.modules.orpheus.bilibili.VolumeData
|
|
19
19
|
import expo.modules.orpheus.utils.DownloadUtil
|
|
20
20
|
import expo.modules.orpheus.utils.SleepTimeController
|
|
21
|
-
import expo.modules.orpheus.utils.
|
|
21
|
+
import expo.modules.orpheus.utils.GeneralStorage
|
|
22
|
+
import expo.modules.orpheus.utils.LoudnessStorage
|
|
22
23
|
import expo.modules.orpheus.utils.calculateLoudnessGain
|
|
23
24
|
import expo.modules.orpheus.utils.fadeInTo
|
|
24
25
|
import kotlinx.coroutines.Job
|
|
@@ -61,7 +62,8 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
61
62
|
super.onCreate()
|
|
62
63
|
instance = this
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
GeneralStorage.initialize(this)
|
|
66
|
+
LoudnessStorage.initialize(this)
|
|
65
67
|
|
|
66
68
|
|
|
67
69
|
val dataSourceFactory = DownloadUtil.getPlayerDataSourceFactory(this)
|
|
@@ -111,18 +113,8 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
111
113
|
.setSessionActivity(contentIntent)
|
|
112
114
|
.build()
|
|
113
115
|
|
|
114
|
-
restorePlayerState(
|
|
116
|
+
restorePlayerState(GeneralStorage.isRestoreEnabled())
|
|
115
117
|
sleepTimerManager = SleepTimeController(player!!)
|
|
116
|
-
|
|
117
|
-
// 当有新的响度数据时,如果是当前这首歌的就直接应用,否则是预加载,等待 onMediaItemTransition 处理
|
|
118
|
-
scope.launch {
|
|
119
|
-
DownloadUtil.volumeResolvedEvent.collect { (uri, volumeData) ->
|
|
120
|
-
val currentUri = player?.currentMediaItem?.localConfiguration?.uri?.toString()
|
|
121
|
-
if (currentUri == uri) {
|
|
122
|
-
applyVolumeForCurrentItem(volumeData)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
118
|
}
|
|
127
119
|
|
|
128
120
|
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? {
|
|
@@ -187,15 +179,15 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
187
179
|
private fun restorePlayerState(restorePosition: Boolean) {
|
|
188
180
|
val player = player ?: return
|
|
189
181
|
|
|
190
|
-
val restoredItems =
|
|
182
|
+
val restoredItems = GeneralStorage.restoreQueue()
|
|
191
183
|
|
|
192
184
|
if (restoredItems.isNotEmpty()) {
|
|
193
185
|
player.setMediaItems(restoredItems)
|
|
194
186
|
|
|
195
|
-
val savedIndex =
|
|
196
|
-
val savedPosition =
|
|
197
|
-
val savedShuffleMode =
|
|
198
|
-
val savedRepeatMode =
|
|
187
|
+
val savedIndex = GeneralStorage.getSavedIndex()
|
|
188
|
+
val savedPosition = GeneralStorage.getSavedPosition()
|
|
189
|
+
val savedShuffleMode = GeneralStorage.getShuffleMode()
|
|
190
|
+
val savedRepeatMode = GeneralStorage.getRepeatMode()
|
|
199
191
|
|
|
200
192
|
if (savedIndex >= 0 && savedIndex < restoredItems.size) {
|
|
201
193
|
player.seekTo(savedIndex, if (restorePosition) savedPosition else C.TIME_UNSET)
|
|
@@ -221,10 +213,8 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
221
213
|
saveCurrentQueue()
|
|
222
214
|
val uri = mediaItem?.localConfiguration?.uri?.toString() ?: return
|
|
223
215
|
|
|
224
|
-
val volumeData =
|
|
225
|
-
|
|
226
|
-
applyVolumeForCurrentItem(volumeData)
|
|
227
|
-
}
|
|
216
|
+
val volumeData = LoudnessStorage.getLoudnessData(uri)
|
|
217
|
+
applyVolumeForCurrentItem(volumeData)
|
|
228
218
|
}
|
|
229
219
|
|
|
230
220
|
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
|
|
@@ -237,21 +227,20 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
237
227
|
val player = player ?: return
|
|
238
228
|
val queue = List(player.mediaItemCount) { i -> player.getMediaItemAt(i) }
|
|
239
229
|
if (queue.isNotEmpty()) {
|
|
240
|
-
|
|
230
|
+
GeneralStorage.saveQueue(queue)
|
|
241
231
|
}
|
|
242
232
|
}
|
|
243
233
|
|
|
244
234
|
@OptIn(UnstableApi::class)
|
|
245
|
-
private fun applyVolumeForCurrentItem(
|
|
235
|
+
private fun applyVolumeForCurrentItem(measuredI: Double) {
|
|
236
|
+
Log.d("LoudnessNormalization", "measuredI: $measuredI")
|
|
246
237
|
val player = player ?: return
|
|
247
238
|
volumeFadeJob?.cancel()
|
|
248
|
-
val isLoudnessNormalizationEnabled =
|
|
239
|
+
val isLoudnessNormalizationEnabled = GeneralStorage.isLoudnessNormalizationEnabled()
|
|
249
240
|
if (!isLoudnessNormalizationEnabled) return
|
|
250
241
|
val gain = run {
|
|
251
|
-
val
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (measured == 0.0) 1.0f else calculateLoudnessGain(measured, target)
|
|
242
|
+
val target = -14.0 // bilibili 的这个值似乎是固定的
|
|
243
|
+
if (measuredI == 0.0) 1.0f else calculateLoudnessGain(measuredI, target)
|
|
255
244
|
}
|
|
256
245
|
|
|
257
246
|
val targetVol = 1.0f * gain
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package expo.modules.orpheus.utils
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
4
5
|
import androidx.core.net.toUri
|
|
5
6
|
import androidx.media3.common.util.UnstableApi
|
|
6
7
|
import androidx.media3.database.StandaloneDatabaseProvider
|
|
@@ -35,15 +36,6 @@ object DownloadUtil {
|
|
|
35
36
|
|
|
36
37
|
private var downloadNotificationHelper: DownloadNotificationHelper? = null
|
|
37
38
|
|
|
38
|
-
var itemVolumeMap: MutableMap<String, VolumeData> = mutableMapOf()
|
|
39
|
-
|
|
40
|
-
private val _volumeResolvedEvent = MutableSharedFlow<Pair<String, VolumeData>>(
|
|
41
|
-
replay = 0,
|
|
42
|
-
extraBufferCapacity = 1,
|
|
43
|
-
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
|
44
|
-
)
|
|
45
|
-
val volumeResolvedEvent = _volumeResolvedEvent.asSharedFlow()
|
|
46
|
-
|
|
47
39
|
@Synchronized
|
|
48
40
|
fun getDownloadManager(context: Context): DownloadManager {
|
|
49
41
|
if (downloadManager == null) {
|
|
@@ -68,10 +60,16 @@ object DownloadUtil {
|
|
|
68
60
|
val upstreamFactory = getUpstreamFactory()
|
|
69
61
|
|
|
70
62
|
val downloadCache = DownloadCache.getStableCache(context)
|
|
63
|
+
val lruCache = DownloadCache.getLruCache(context)
|
|
64
|
+
|
|
65
|
+
val cacheFactory = CacheDataSource.Factory()
|
|
66
|
+
.setCache(lruCache)
|
|
67
|
+
.setUpstreamDataSourceFactory(upstreamFactory)
|
|
68
|
+
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
|
|
71
69
|
|
|
72
70
|
val downloadFactory = CacheDataSource.Factory()
|
|
73
71
|
.setCache(downloadCache)
|
|
74
|
-
.setUpstreamDataSourceFactory(
|
|
72
|
+
.setUpstreamDataSourceFactory(cacheFactory)
|
|
75
73
|
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
|
|
76
74
|
.setCacheWriteDataSinkFactory(null)
|
|
77
75
|
|
|
@@ -109,10 +107,6 @@ object DownloadUtil {
|
|
|
109
107
|
return downloadNotificationHelper!!
|
|
110
108
|
}
|
|
111
109
|
|
|
112
|
-
suspend fun emitVolumeEvent(uri: String, data: VolumeData) {
|
|
113
|
-
_volumeResolvedEvent.emit(uri to data)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
110
|
private class BilibiliResolver :
|
|
117
111
|
ResolvingDataSource.Resolver {
|
|
118
112
|
override fun resolveDataSpec(dataSpec: DataSpec): DataSpec {
|
|
@@ -132,10 +126,8 @@ object DownloadUtil {
|
|
|
132
126
|
)
|
|
133
127
|
// 在这里保存响度均衡数据,并且直接发一个事件,在 OrpheusMusicService 监听
|
|
134
128
|
if (volume !== null) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
emitVolumeEvent(dataSpec.uri.toString(), volume)
|
|
138
|
-
}
|
|
129
|
+
Log.d("LoudnessNormalization", "uri: ${dataSpec.uri}, measuredI: ${volume.measuredI}")
|
|
130
|
+
LoudnessStorage.setLoudnessData(dataSpec.uri.toString(), volume.measuredI)
|
|
139
131
|
}
|
|
140
132
|
|
|
141
133
|
val headers = HashMap<String, String>()
|
|
@@ -11,7 +11,7 @@ import com.tencent.mmkv.MMKV
|
|
|
11
11
|
import expo.modules.orpheus.models.TrackRecord
|
|
12
12
|
|
|
13
13
|
@OptIn(UnstableApi::class)
|
|
14
|
-
object
|
|
14
|
+
object GeneralStorage {
|
|
15
15
|
private var kv: MMKV? = null
|
|
16
16
|
private val gson = Gson()
|
|
17
17
|
private const val KEY_RESTORE_POSITION_ENABLED = "config_restore_position_enabled"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package expo.modules.orpheus.utils
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import com.tencent.mmkv.MMKV
|
|
6
|
+
|
|
7
|
+
object LoudnessStorage {
|
|
8
|
+
private var kv: MMKV? = null
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@Synchronized
|
|
12
|
+
fun initialize(context: Context) {
|
|
13
|
+
if (kv == null) {
|
|
14
|
+
MMKV.initialize(context)
|
|
15
|
+
kv = MMKV.mmkvWithID("loudness_normalization_store")
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private val safeKv: MMKV
|
|
20
|
+
get() = kv ?: throw IllegalStateException("LoudnessStorage not initialized")
|
|
21
|
+
|
|
22
|
+
fun setLoudnessData(key: String, measuredI: Double) {
|
|
23
|
+
try {
|
|
24
|
+
Log.d("LoudnessNormalization", "setLoudnessData: $key, $measuredI")
|
|
25
|
+
safeKv.encode(key, measuredI)
|
|
26
|
+
} catch (e: Exception) {
|
|
27
|
+
Log.e("LoudnessStorage", "Failed to set loudness data", e)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fun getLoudnessData(key: String): Double {
|
|
32
|
+
try {
|
|
33
|
+
Log.d("LoudnessNormalization", "getLoudnessData: $key")
|
|
34
|
+
return safeKv.decodeDouble(key)
|
|
35
|
+
} catch (e: Exception) {
|
|
36
|
+
Log.e("LoudnessStorage", "Failed to get loudness data", e)
|
|
37
|
+
return 0.0
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|