@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 CHANGED
@@ -15,7 +15,7 @@ Orpheus 通过特殊的 uri 识别来自 bilibili 的资源,格式为 `orpheus
15
15
  Orpheus 内部有两层缓存:
16
16
 
17
17
  1. 用户手动下载的缓存
18
- 2. ~~ 边下边播:LRU 缓存,256mb ~~ (移除了这一层缓存,方便响度均衡实现)
18
+ 2. 边下边播:LRU 缓存,256mb
19
19
 
20
20
  ## 下载系统
21
21
 
@@ -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.Storage
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
- Storage.saveRepeatMode(repeatMode)
160
+ GeneralStorage.saveRepeatMode(repeatMode)
160
161
  }
161
162
 
162
163
  override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
163
164
  super.onShuffleModeEnabledChanged(shuffleModeEnabled)
164
- Storage.saveShuffleMode(shuffleModeEnabled)
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
- Storage.initialize(context)
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
- Storage.isRestoreEnabled()
222
+ GeneralStorage.isRestoreEnabled()
221
223
  }
222
224
 
223
225
  Constant("loudnessNormalizationEnabled") {
224
- Storage.isLoudnessNormalizationEnabled()
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
- Storage.setLoudnessNormalizationEnabled(enabled)
234
+ GeneralStorage.setLoudnessNormalizationEnabled(enabled)
233
235
  }
234
236
 
235
237
  Function("setRestorePlaybackPositionEnabled") { enabled: Boolean ->
236
- Storage.setRestoreEnabled(enabled)
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
- Storage.savePosition(
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.Storage
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
- Storage.initialize(this)
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(Storage.isRestoreEnabled())
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 = Storage.restoreQueue()
182
+ val restoredItems = GeneralStorage.restoreQueue()
191
183
 
192
184
  if (restoredItems.isNotEmpty()) {
193
185
  player.setMediaItems(restoredItems)
194
186
 
195
- val savedIndex = Storage.getSavedIndex()
196
- val savedPosition = Storage.getSavedPosition()
197
- val savedShuffleMode = Storage.getShuffleMode()
198
- val savedRepeatMode = Storage.getRepeatMode()
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 = DownloadUtil.itemVolumeMap[uri]
225
- if (volumeData != null) {
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
- Storage.saveQueue(queue)
230
+ GeneralStorage.saveQueue(queue)
241
231
  }
242
232
  }
243
233
 
244
234
  @OptIn(UnstableApi::class)
245
- private fun applyVolumeForCurrentItem(volumeData: VolumeData) {
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 = Storage.isLoudnessNormalizationEnabled()
239
+ val isLoudnessNormalizationEnabled = GeneralStorage.isLoudnessNormalizationEnabled()
249
240
  if (!isLoudnessNormalizationEnabled) return
250
241
  val gain = run {
251
- val measured = volumeData.measuredI
252
- val target = volumeData.targetI
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(upstreamFactory)
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
- itemVolumeMap[dataSpec.uri.toString()] = volume
136
- CoroutineScope(Dispatchers.IO).launch {
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 Storage {
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roitium/expo-orpheus",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "A player for bbplayer",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",