@roitium/expo-orpheus 0.4.6 → 0.5.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/android/src/main/java/expo/modules/orpheus/DownloadCache.kt +0 -13
- package/android/src/main/java/expo/modules/orpheus/ExpoOrpheusModule.kt +59 -6
- package/android/src/main/java/expo/modules/orpheus/OrpheusMusicService.kt +65 -7
- package/android/src/main/java/expo/modules/orpheus/bilibili/BilibiliRepository.kt +7 -6
- package/android/src/main/java/expo/modules/orpheus/models/TrackRecord.kt +0 -11
- package/android/src/main/java/expo/modules/orpheus/utils/DownloadUtil.kt +31 -9
- package/android/src/main/java/expo/modules/orpheus/utils/{MediaItemStorer.kt → Storage.kt} +22 -1
- package/android/src/main/java/expo/modules/orpheus/utils/TrackRecordExtension.kt +0 -5
- package/android/src/main/java/expo/modules/orpheus/utils/Volume.kt +57 -0
- package/build/ExpoOrpheusModule.d.ts +2 -0
- package/build/ExpoOrpheusModule.d.ts.map +1 -1
- package/build/ExpoOrpheusModule.js.map +1 -1
- package/build/hooks/index.d.ts +0 -1
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +0 -1
- package/build/hooks/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ExpoOrpheusModule.ts +2 -0
- package/src/hooks/index.ts +0 -1
- package/build/hooks/useShuffleMode.d.ts +0 -2
- package/build/hooks/useShuffleMode.d.ts.map +0 -1
- package/build/hooks/useShuffleMode.js +0 -22
- package/build/hooks/useShuffleMode.js.map +0 -1
- package/src/hooks/useShuffleMode.ts +0 -26
|
@@ -3,27 +3,14 @@ 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
|
|
7
6
|
import androidx.media3.datasource.cache.NoOpCacheEvictor
|
|
8
7
|
import androidx.media3.datasource.cache.SimpleCache
|
|
9
8
|
import java.io.File
|
|
10
9
|
|
|
11
10
|
@UnstableApi
|
|
12
11
|
object DownloadCache {
|
|
13
|
-
private var lruCache: SimpleCache? = null
|
|
14
12
|
private var stableCache: SimpleCache? = null
|
|
15
13
|
|
|
16
|
-
@Synchronized
|
|
17
|
-
fun getLruCache(context: Context): SimpleCache {
|
|
18
|
-
if (lruCache == null) {
|
|
19
|
-
val cacheDir = File(context.cacheDir, "media_cache")
|
|
20
|
-
val evictor = LeastRecentlyUsedCacheEvictor(256 * 1024 * 1024)
|
|
21
|
-
val databaseProvider = StandaloneDatabaseProvider(context)
|
|
22
|
-
lruCache = SimpleCache(cacheDir, evictor, databaseProvider)
|
|
23
|
-
}
|
|
24
|
-
return lruCache!!
|
|
25
|
-
}
|
|
26
|
-
|
|
27
14
|
@Synchronized
|
|
28
15
|
fun getStableCache(context: Context): SimpleCache {
|
|
29
16
|
if (stableCache == null) {
|
|
@@ -30,7 +30,7 @@ import expo.modules.kotlin.modules.Module
|
|
|
30
30
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
31
31
|
import expo.modules.orpheus.models.TrackRecord
|
|
32
32
|
import expo.modules.orpheus.utils.DownloadUtil
|
|
33
|
-
import expo.modules.orpheus.utils.
|
|
33
|
+
import expo.modules.orpheus.utils.Storage
|
|
34
34
|
import expo.modules.orpheus.utils.toMediaItem
|
|
35
35
|
|
|
36
36
|
@UnstableApi
|
|
@@ -67,7 +67,7 @@ class ExpoOrpheusModule : Module() {
|
|
|
67
67
|
|
|
68
68
|
OnCreate {
|
|
69
69
|
val context = appContext.reactContext ?: return@OnCreate
|
|
70
|
-
|
|
70
|
+
Storage.initialize(context)
|
|
71
71
|
val sessionToken = SessionToken(
|
|
72
72
|
context,
|
|
73
73
|
ComponentName(context, OrpheusMusicService::class.java)
|
|
@@ -97,16 +97,23 @@ class ExpoOrpheusModule : Module() {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
Constant("restorePlaybackPositionEnabled") {
|
|
100
|
-
|
|
100
|
+
Storage.isRestoreEnabled()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Constant("loudnessNormalizationEnabled") {
|
|
104
|
+
Storage.isLoudnessNormalizationEnabled()
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
Function("setBilibiliCookie") { cookie: String ->
|
|
104
108
|
OrpheusConfig.bilibiliCookie = cookie
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
Function("setLoudnessNormalizationEnabled") { enabled: Boolean ->
|
|
112
|
+
Storage.setLoudnessNormalizationEnabled(enabled)
|
|
113
|
+
}
|
|
107
114
|
|
|
108
115
|
Function("setRestorePlaybackPositionEnabled") { enabled: Boolean ->
|
|
109
|
-
|
|
116
|
+
Storage.setRestoreEnabled(enabled)
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
AsyncFunction("getPosition") {
|
|
@@ -175,6 +182,7 @@ class ExpoOrpheusModule : Module() {
|
|
|
175
182
|
checkController()
|
|
176
183
|
controller?.clearMediaItems()
|
|
177
184
|
durationCache.clear()
|
|
185
|
+
DownloadUtil.itemVolumeMap.clear()
|
|
178
186
|
}.runOnQueue(Queues.MAIN)
|
|
179
187
|
|
|
180
188
|
AsyncFunction("skipTo") { index: Int ->
|
|
@@ -229,7 +237,7 @@ class ExpoOrpheusModule : Module() {
|
|
|
229
237
|
if (index >= 0 && index < (controller?.mediaItemCount ?: 0)) {
|
|
230
238
|
controller?.removeMediaItem(index)
|
|
231
239
|
}
|
|
232
|
-
}
|
|
240
|
+
}.runOnQueue(Queues.MAIN)
|
|
233
241
|
|
|
234
242
|
AsyncFunction("getQueue") {
|
|
235
243
|
checkController()
|
|
@@ -293,6 +301,7 @@ class ExpoOrpheusModule : Module() {
|
|
|
293
301
|
if (clearQueue == true) {
|
|
294
302
|
player.clearMediaItems()
|
|
295
303
|
durationCache.clear()
|
|
304
|
+
DownloadUtil.itemVolumeMap.clear()
|
|
296
305
|
}
|
|
297
306
|
val initialSize = player.mediaItemCount
|
|
298
307
|
player.addMediaItems(mediaItems)
|
|
@@ -316,6 +325,40 @@ class ExpoOrpheusModule : Module() {
|
|
|
316
325
|
}
|
|
317
326
|
}.runOnQueue(Queues.MAIN)
|
|
318
327
|
|
|
328
|
+
AsyncFunction("playNext") { track: TrackRecord ->
|
|
329
|
+
checkController()
|
|
330
|
+
val player = controller ?: return@AsyncFunction
|
|
331
|
+
|
|
332
|
+
val mediaItem = track.toMediaItem(gson)
|
|
333
|
+
val targetIndex = player.currentMediaItemIndex + 1
|
|
334
|
+
|
|
335
|
+
var existingIndex = -1
|
|
336
|
+
for (i in 0 until player.mediaItemCount) {
|
|
337
|
+
if (player.getMediaItemAt(i).mediaId == track.id) {
|
|
338
|
+
existingIndex = i
|
|
339
|
+
break
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (existingIndex != -1) {
|
|
344
|
+
if (existingIndex == player.currentMediaItemIndex) {
|
|
345
|
+
return@AsyncFunction
|
|
346
|
+
}
|
|
347
|
+
val safeTargetIndex = targetIndex.coerceAtMost(player.mediaItemCount)
|
|
348
|
+
|
|
349
|
+
player.moveMediaItem(existingIndex, safeTargetIndex)
|
|
350
|
+
|
|
351
|
+
} else {
|
|
352
|
+
val safeTargetIndex = targetIndex.coerceAtMost(player.mediaItemCount)
|
|
353
|
+
|
|
354
|
+
player.addMediaItem(safeTargetIndex, mediaItem)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (player.playbackState == Player.STATE_IDLE) {
|
|
358
|
+
player.prepare()
|
|
359
|
+
}
|
|
360
|
+
}.runOnQueue(Queues.MAIN)
|
|
361
|
+
|
|
319
362
|
AsyncFunction("downloadTrack") { track: TrackRecord ->
|
|
320
363
|
val context = appContext.reactContext ?: return@AsyncFunction
|
|
321
364
|
val downloadRequest = DownloadRequest.Builder(track.id, track.url.toUri())
|
|
@@ -616,6 +659,16 @@ class ExpoOrpheusModule : Module() {
|
|
|
616
659
|
)
|
|
617
660
|
)
|
|
618
661
|
}
|
|
662
|
+
|
|
663
|
+
override fun onRepeatModeChanged(repeatMode: Int) {
|
|
664
|
+
super.onRepeatModeChanged(repeatMode)
|
|
665
|
+
Storage.saveRepeatMode(repeatMode)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
|
669
|
+
super.onShuffleModeEnabledChanged(shuffleModeEnabled)
|
|
670
|
+
Storage.saveShuffleMode(shuffleModeEnabled)
|
|
671
|
+
}
|
|
619
672
|
})
|
|
620
673
|
}
|
|
621
674
|
|
|
@@ -686,7 +739,7 @@ class ExpoOrpheusModule : Module() {
|
|
|
686
739
|
private fun saveCurrentPosition() {
|
|
687
740
|
val player = controller ?: return
|
|
688
741
|
if (player.playbackState != Player.STATE_IDLE) {
|
|
689
|
-
|
|
742
|
+
Storage.savePosition(
|
|
690
743
|
player.currentMediaItemIndex,
|
|
691
744
|
player.currentPosition
|
|
692
745
|
)
|
|
@@ -19,21 +19,31 @@ import androidx.media3.session.SessionCommand
|
|
|
19
19
|
import androidx.media3.session.SessionResult
|
|
20
20
|
import com.google.common.util.concurrent.Futures
|
|
21
21
|
import com.google.common.util.concurrent.ListenableFuture
|
|
22
|
+
import expo.modules.orpheus.bilibili.VolumeData
|
|
22
23
|
import expo.modules.orpheus.utils.DownloadUtil
|
|
23
|
-
import expo.modules.orpheus.utils.MediaItemStorer
|
|
24
24
|
import expo.modules.orpheus.utils.SleepTimeController
|
|
25
|
+
import expo.modules.orpheus.utils.Storage
|
|
26
|
+
import expo.modules.orpheus.utils.calculateLoudnessGain
|
|
27
|
+
import expo.modules.orpheus.utils.fadeInTo
|
|
28
|
+
import kotlinx.coroutines.Job
|
|
29
|
+
import kotlinx.coroutines.MainScope
|
|
30
|
+
import kotlinx.coroutines.cancel
|
|
31
|
+
import kotlinx.coroutines.launch
|
|
32
|
+
import kotlin.math.abs
|
|
25
33
|
|
|
26
34
|
class OrpheusMusicService : MediaLibraryService() {
|
|
27
35
|
|
|
28
36
|
private var player: ExoPlayer? = null
|
|
29
37
|
private var mediaSession: MediaLibrarySession? = null
|
|
30
38
|
private var sleepTimerManager: SleepTimeController? = null
|
|
39
|
+
private var volumeFadeJob: Job? = null
|
|
40
|
+
private var scope = MainScope()
|
|
31
41
|
|
|
32
42
|
@OptIn(UnstableApi::class)
|
|
33
43
|
override fun onCreate() {
|
|
34
44
|
super.onCreate()
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
Storage.initialize(this)
|
|
37
47
|
|
|
38
48
|
|
|
39
49
|
val dataSourceFactory = DownloadUtil.getPlayerDataSourceFactory(this)
|
|
@@ -83,8 +93,18 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
83
93
|
.setSessionActivity(contentIntent)
|
|
84
94
|
.build()
|
|
85
95
|
|
|
86
|
-
restorePlayerState(
|
|
96
|
+
restorePlayerState(Storage.isRestoreEnabled())
|
|
87
97
|
sleepTimerManager = SleepTimeController(player!!)
|
|
98
|
+
|
|
99
|
+
// 当有新的响度数据时,如果是当前这首歌的就直接应用,否则是预加载,等待 onMediaItemTransition 处理
|
|
100
|
+
scope.launch {
|
|
101
|
+
DownloadUtil.volumeResolvedEvent.collect { (uri, volumeData) ->
|
|
102
|
+
val currentUri = player?.currentMediaItem?.localConfiguration?.uri?.toString()
|
|
103
|
+
if (currentUri == uri) {
|
|
104
|
+
applyVolumeForCurrentItem(volumeData)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
88
108
|
}
|
|
89
109
|
|
|
90
110
|
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? {
|
|
@@ -92,6 +112,8 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
92
112
|
}
|
|
93
113
|
|
|
94
114
|
override fun onDestroy() {
|
|
115
|
+
scope.cancel()
|
|
116
|
+
|
|
95
117
|
mediaSession?.run {
|
|
96
118
|
player.release()
|
|
97
119
|
release()
|
|
@@ -187,13 +209,15 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
187
209
|
private fun restorePlayerState(restorePosition: Boolean) {
|
|
188
210
|
val player = player ?: return
|
|
189
211
|
|
|
190
|
-
val restoredItems =
|
|
212
|
+
val restoredItems = Storage.restoreQueue()
|
|
191
213
|
|
|
192
214
|
if (restoredItems.isNotEmpty()) {
|
|
193
215
|
player.setMediaItems(restoredItems)
|
|
194
216
|
|
|
195
|
-
val savedIndex =
|
|
196
|
-
val savedPosition =
|
|
217
|
+
val savedIndex = Storage.getSavedIndex()
|
|
218
|
+
val savedPosition = Storage.getSavedPosition()
|
|
219
|
+
val savedShuffleMode = Storage.getShuffleMode()
|
|
220
|
+
val savedRepeatMode = Storage.getRepeatMode()
|
|
197
221
|
|
|
198
222
|
if (savedIndex >= 0 && savedIndex < restoredItems.size) {
|
|
199
223
|
player.seekTo(savedIndex, if (restorePosition) savedPosition else C.TIME_UNSET)
|
|
@@ -201,6 +225,9 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
201
225
|
player.seekTo(0, 0L)
|
|
202
226
|
}
|
|
203
227
|
|
|
228
|
+
player.shuffleModeEnabled = savedShuffleMode
|
|
229
|
+
player.repeatMode = savedRepeatMode
|
|
230
|
+
|
|
204
231
|
player.playWhenReady = false
|
|
205
232
|
player.prepare()
|
|
206
233
|
}
|
|
@@ -208,11 +235,19 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
208
235
|
|
|
209
236
|
private fun setupListeners() {
|
|
210
237
|
player?.addListener(object : Player.Listener {
|
|
238
|
+
|
|
239
|
+
@OptIn(UnstableApi::class)
|
|
211
240
|
override fun onMediaItemTransition(
|
|
212
241
|
mediaItem: androidx.media3.common.MediaItem?,
|
|
213
242
|
reason: Int
|
|
214
243
|
) {
|
|
215
244
|
saveCurrentQueue()
|
|
245
|
+
val uri = mediaItem?.localConfiguration?.uri?.toString() ?: return
|
|
246
|
+
|
|
247
|
+
val volumeData = DownloadUtil.itemVolumeMap[uri]
|
|
248
|
+
if (volumeData != null) {
|
|
249
|
+
applyVolumeForCurrentItem(volumeData)
|
|
250
|
+
}
|
|
216
251
|
}
|
|
217
252
|
|
|
218
253
|
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
|
|
@@ -225,7 +260,30 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
225
260
|
val player = player ?: return
|
|
226
261
|
val queue = List(player.mediaItemCount) { i -> player.getMediaItemAt(i) }
|
|
227
262
|
if (queue.isNotEmpty()) {
|
|
228
|
-
|
|
263
|
+
Storage.saveQueue(queue)
|
|
229
264
|
}
|
|
230
265
|
}
|
|
266
|
+
|
|
267
|
+
@OptIn(UnstableApi::class)
|
|
268
|
+
private fun applyVolumeForCurrentItem(volumeData: VolumeData) {
|
|
269
|
+
val player = player ?: return
|
|
270
|
+
volumeFadeJob?.cancel()
|
|
271
|
+
val isLoudnessNormalizationEnabled = Storage.isLoudnessNormalizationEnabled()
|
|
272
|
+
if (!isLoudnessNormalizationEnabled) return
|
|
273
|
+
val gain = run {
|
|
274
|
+
val measured = volumeData.measuredI
|
|
275
|
+
val target = volumeData.targetI
|
|
276
|
+
|
|
277
|
+
if (measured == 0.0) 1.0f else calculateLoudnessGain(measured, target)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
val targetVol = 1.0f * gain
|
|
281
|
+
val currentVolume = player.volume
|
|
282
|
+
|
|
283
|
+
if (abs(currentVolume - targetVol) < 0.001f) {
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
volumeFadeJob = player.fadeInTo(targetVol, 600L, scope)
|
|
288
|
+
}
|
|
231
289
|
}
|
|
@@ -58,7 +58,7 @@ object BilibiliRepository {
|
|
|
58
58
|
enableDolby: Boolean,
|
|
59
59
|
enableHiRes: Boolean,
|
|
60
60
|
cookie: String?
|
|
61
|
-
): String {
|
|
61
|
+
): Pair<String, VolumeData?> {
|
|
62
62
|
var cidInternal = cid
|
|
63
63
|
val (imgKey, subKey) = getWbiKeys()
|
|
64
64
|
if (cidInternal === null) {
|
|
@@ -94,22 +94,23 @@ object BilibiliRepository {
|
|
|
94
94
|
val data = apiResponse.data
|
|
95
95
|
val dash = data.dash
|
|
96
96
|
val durl = data.durl
|
|
97
|
+
val volume = data.volume
|
|
97
98
|
|
|
98
99
|
if (dash == null) {
|
|
99
100
|
if (durl.isNullOrEmpty()) {
|
|
100
101
|
throw IOException("AudioStreamError: 请求到的流数据不包含 dash 或 durl 任一字段")
|
|
101
102
|
}
|
|
102
|
-
return durl[0].url
|
|
103
|
+
return durl[0].url to volume
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
if (enableDolby && dash.dolby?.audio?.isNotEmpty() == true) {
|
|
106
107
|
Log.d(TAG, "select dolby source")
|
|
107
|
-
return dash.dolby.audio[0].baseUrl
|
|
108
|
+
return dash.dolby.audio[0].baseUrl to volume
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
if (enableHiRes && dash.flac?.audio != null) {
|
|
111
112
|
Log.d(TAG, "select hires source")
|
|
112
|
-
return dash.flac.audio.baseUrl
|
|
113
|
+
return dash.flac.audio.baseUrl to volume
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
if (dash.audio.isNullOrEmpty()) {
|
|
@@ -119,10 +120,10 @@ object BilibiliRepository {
|
|
|
119
120
|
val targetAudio = dash.audio.find { it.id == audioQuality }
|
|
120
121
|
|
|
121
122
|
if (targetAudio != null) {
|
|
122
|
-
return targetAudio.baseUrl
|
|
123
|
+
return targetAudio.baseUrl to volume
|
|
123
124
|
} else {
|
|
124
125
|
val highestQualityAudio = dash.audio[0]
|
|
125
|
-
return highestQualityAudio.baseUrl
|
|
126
|
+
return highestQualityAudio.baseUrl to volume
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
|
|
@@ -3,14 +3,6 @@ package expo.modules.orpheus.models
|
|
|
3
3
|
import expo.modules.kotlin.records.Field
|
|
4
4
|
import expo.modules.kotlin.records.Record
|
|
5
5
|
|
|
6
|
-
class LoudnessRecord : Record {
|
|
7
|
-
@Field
|
|
8
|
-
var measured_i: Double = 0.0
|
|
9
|
-
|
|
10
|
-
@Field
|
|
11
|
-
var target_i: Double = 0.0
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
class TrackRecord : Record {
|
|
15
7
|
@Field
|
|
16
8
|
var id: String = ""
|
|
@@ -30,7 +22,4 @@ class TrackRecord : Record {
|
|
|
30
22
|
// unit: second
|
|
31
23
|
@Field
|
|
32
24
|
var duration: Double? = null
|
|
33
|
-
|
|
34
|
-
@Field
|
|
35
|
-
var loudness: LoudnessRecord? = null
|
|
36
25
|
}
|
|
@@ -16,6 +16,13 @@ import expo.modules.orpheus.DownloadCache
|
|
|
16
16
|
import expo.modules.orpheus.OrpheusConfig
|
|
17
17
|
import expo.modules.orpheus.OrpheusDownloadService
|
|
18
18
|
import expo.modules.orpheus.bilibili.BilibiliRepository
|
|
19
|
+
import expo.modules.orpheus.bilibili.VolumeData
|
|
20
|
+
import kotlinx.coroutines.CoroutineScope
|
|
21
|
+
import kotlinx.coroutines.Dispatchers
|
|
22
|
+
import kotlinx.coroutines.channels.BufferOverflow
|
|
23
|
+
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
24
|
+
import kotlinx.coroutines.flow.asSharedFlow
|
|
25
|
+
import kotlinx.coroutines.launch
|
|
19
26
|
import java.io.IOException
|
|
20
27
|
import java.util.concurrent.Executors
|
|
21
28
|
|
|
@@ -28,6 +35,15 @@ object DownloadUtil {
|
|
|
28
35
|
|
|
29
36
|
private var downloadNotificationHelper: DownloadNotificationHelper? = null
|
|
30
37
|
|
|
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
|
+
|
|
31
47
|
@Synchronized
|
|
32
48
|
fun getDownloadManager(context: Context): DownloadManager {
|
|
33
49
|
if (downloadManager == null) {
|
|
@@ -51,17 +67,11 @@ object DownloadUtil {
|
|
|
51
67
|
if (playerDataSourceFactory == null) {
|
|
52
68
|
val upstreamFactory = getUpstreamFactory()
|
|
53
69
|
|
|
54
|
-
val lruCache = DownloadCache.getLruCache(context)
|
|
55
70
|
val downloadCache = DownloadCache.getStableCache(context)
|
|
56
71
|
|
|
57
|
-
val lruFactory = CacheDataSource.Factory()
|
|
58
|
-
.setCache(lruCache)
|
|
59
|
-
.setUpstreamDataSourceFactory(upstreamFactory)
|
|
60
|
-
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
|
|
61
|
-
|
|
62
72
|
val downloadFactory = CacheDataSource.Factory()
|
|
63
73
|
.setCache(downloadCache)
|
|
64
|
-
.setUpstreamDataSourceFactory(
|
|
74
|
+
.setUpstreamDataSourceFactory(upstreamFactory)
|
|
65
75
|
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
|
|
66
76
|
.setCacheWriteDataSinkFactory(null)
|
|
67
77
|
|
|
@@ -99,7 +109,12 @@ object DownloadUtil {
|
|
|
99
109
|
return downloadNotificationHelper!!
|
|
100
110
|
}
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
suspend fun emitVolumeEvent(uri: String, data: VolumeData) {
|
|
113
|
+
_volumeResolvedEvent.emit(uri to data)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private class BilibiliResolver :
|
|
117
|
+
ResolvingDataSource.Resolver {
|
|
103
118
|
override fun resolveDataSpec(dataSpec: DataSpec): DataSpec {
|
|
104
119
|
val uri = dataSpec.uri
|
|
105
120
|
if (uri.scheme == "orpheus" && uri.host == "bilibili") {
|
|
@@ -107,7 +122,7 @@ object DownloadUtil {
|
|
|
107
122
|
val bvid = uri.getQueryParameter("bvid")
|
|
108
123
|
val cid = uri.getQueryParameter("cid")?.toLongOrNull()
|
|
109
124
|
val quality = uri.getQueryParameter("quality")?.toIntOrNull() ?: 30280
|
|
110
|
-
val realUrl = BilibiliRepository.resolveAudioUrl(
|
|
125
|
+
val (realUrl, volume) = BilibiliRepository.resolveAudioUrl(
|
|
111
126
|
bvid = bvid!!,
|
|
112
127
|
cid = cid,
|
|
113
128
|
audioQuality = quality,
|
|
@@ -115,6 +130,13 @@ object DownloadUtil {
|
|
|
115
130
|
enableHiRes = uri.getQueryParameter("hires") == "1",
|
|
116
131
|
cookie = OrpheusConfig.bilibiliCookie
|
|
117
132
|
)
|
|
133
|
+
// 在这里保存响度均衡数据,并且直接发一个事件,在 OrpheusMusicService 监听
|
|
134
|
+
if (volume !== null) {
|
|
135
|
+
itemVolumeMap[dataSpec.uri.toString()] = volume
|
|
136
|
+
CoroutineScope(Dispatchers.IO).launch {
|
|
137
|
+
emitVolumeEvent(dataSpec.uri.toString(), volume)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
118
140
|
|
|
119
141
|
val headers = HashMap<String, String>()
|
|
120
142
|
headers["Referer"] = "https://www.bilibili.com/"
|
|
@@ -11,13 +11,17 @@ import com.tencent.mmkv.MMKV
|
|
|
11
11
|
import expo.modules.orpheus.models.TrackRecord
|
|
12
12
|
|
|
13
13
|
@OptIn(UnstableApi::class)
|
|
14
|
-
object
|
|
14
|
+
object Storage {
|
|
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"
|
|
18
|
+
|
|
19
|
+
private const val KEY_LOUDNESS_NORMALIZATION_ENABLED = "config_loudness_normalization_enabled"
|
|
18
20
|
private const val KEY_SAVED_QUEUE = "saved_queue_json_list"
|
|
19
21
|
private const val KEY_SAVED_INDEX = "saved_index"
|
|
20
22
|
private const val KEY_SAVED_POSITION = "saved_position"
|
|
23
|
+
private const val KEY_SAVED_REPEAT_MODE = "saved_repeat_mode"
|
|
24
|
+
private const val KEY_SAVED_SHUFFLE_MODE = "saved_shuffle_mode"
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
@Synchronized
|
|
@@ -39,10 +43,22 @@ object MediaItemStorer {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
fun setLoudnessNormalizationEnabled(enabled: Boolean) {
|
|
47
|
+
try {
|
|
48
|
+
safeKv.encode(KEY_LOUDNESS_NORMALIZATION_ENABLED, enabled)
|
|
49
|
+
} catch (e: Exception) {
|
|
50
|
+
Log.e("MediaItemStorer", "Failed to set loudness normalization enabled", e)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
fun isRestoreEnabled(): Boolean {
|
|
43
55
|
return safeKv.decodeBool(KEY_RESTORE_POSITION_ENABLED, false)
|
|
44
56
|
}
|
|
45
57
|
|
|
58
|
+
fun isLoudnessNormalizationEnabled(): Boolean {
|
|
59
|
+
return safeKv.decodeBool(KEY_LOUDNESS_NORMALIZATION_ENABLED, true)
|
|
60
|
+
}
|
|
61
|
+
|
|
46
62
|
@OptIn(UnstableApi::class)
|
|
47
63
|
fun saveQueue(mediaItems: List<MediaItem>) {
|
|
48
64
|
try {
|
|
@@ -89,6 +105,11 @@ object MediaItemStorer {
|
|
|
89
105
|
safeKv.encode(KEY_SAVED_POSITION, position)
|
|
90
106
|
}
|
|
91
107
|
|
|
108
|
+
fun saveRepeatMode(repeatMode: Int) = safeKv.encode(KEY_SAVED_REPEAT_MODE, repeatMode)
|
|
109
|
+
fun saveShuffleMode(shuffleMode: Boolean) = safeKv.encode(KEY_SAVED_SHUFFLE_MODE, shuffleMode)
|
|
110
|
+
|
|
92
111
|
fun getSavedIndex() = kv?.decodeInt(KEY_SAVED_INDEX, 0) ?: 0
|
|
93
112
|
fun getSavedPosition() = kv?.decodeLong(KEY_SAVED_POSITION, 0L) ?: 0L
|
|
113
|
+
fun getRepeatMode() = kv?.decodeInt(KEY_SAVED_REPEAT_MODE, 0) ?: 0
|
|
114
|
+
fun getShuffleMode() = kv?.decodeBool(KEY_SAVED_SHUFFLE_MODE, false) ?: false
|
|
94
115
|
}
|
|
@@ -13,11 +13,6 @@ fun TrackRecord.toMediaItem(gson: Gson): MediaItem {
|
|
|
13
13
|
val extras = Bundle()
|
|
14
14
|
extras.putString("track_json", trackJson)
|
|
15
15
|
|
|
16
|
-
this.loudness?.let {
|
|
17
|
-
extras.putDouble("loudness_measured_i", it.measured_i)
|
|
18
|
-
extras.putDouble("loudness_target_i", it.target_i)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
16
|
val artUri = if (!this.artwork.isNullOrEmpty()) this.artwork?.toUri() else null
|
|
22
17
|
|
|
23
18
|
val metadata = MediaMetadata.Builder()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package expo.modules.orpheus.utils
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import kotlinx.coroutines.CoroutineScope
|
|
5
|
+
import kotlinx.coroutines.Job
|
|
6
|
+
import kotlinx.coroutines.delay
|
|
7
|
+
import kotlinx.coroutines.launch
|
|
8
|
+
import kotlin.math.pow
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 响度均衡计算
|
|
12
|
+
* @param measuredI 实测响度
|
|
13
|
+
* @param targetI 目标响度
|
|
14
|
+
* @return gain
|
|
15
|
+
*/
|
|
16
|
+
fun calculateLoudnessGain(measuredI: Double, targetI: Double = -14.0): Float {
|
|
17
|
+
|
|
18
|
+
if (measuredI == 0.0) {
|
|
19
|
+
return 1.0f
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
val gainDb = targetI - measuredI
|
|
23
|
+
val linearFactor = 10.0.pow(gainDb / 20.0).toFloat()
|
|
24
|
+
|
|
25
|
+
val finalResult = linearFactor.coerceIn(0.0f, 1.0f)
|
|
26
|
+
|
|
27
|
+
return finalResult
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Volume Fade In
|
|
32
|
+
* @param targetVolume 最终要达到的音量
|
|
33
|
+
* @param durationMs 淡入持续时间
|
|
34
|
+
* @param scope 协程作用域
|
|
35
|
+
*/
|
|
36
|
+
fun androidx.media3.common.Player.fadeInTo(
|
|
37
|
+
targetVolume: Float,
|
|
38
|
+
durationMs: Long = 600L,
|
|
39
|
+
scope: CoroutineScope
|
|
40
|
+
): Job {
|
|
41
|
+
this.volume = 0f
|
|
42
|
+
|
|
43
|
+
return scope.launch {
|
|
44
|
+
val stepInterval = 16L
|
|
45
|
+
val steps = (durationMs / stepInterval).toInt()
|
|
46
|
+
val volumeStep = targetVolume / steps
|
|
47
|
+
|
|
48
|
+
for (i in 1..steps) {
|
|
49
|
+
val newVol = volumeStep * i
|
|
50
|
+
val finalVol = newVol.coerceAtMost(targetVolume)
|
|
51
|
+
Log.d("Loudness", "finalVol $finalVol")
|
|
52
|
+
volume = finalVol
|
|
53
|
+
delay(stepInterval)
|
|
54
|
+
}
|
|
55
|
+
volume = targetVolume
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -57,6 +57,7 @@ export type OrpheusEvents = {
|
|
|
57
57
|
};
|
|
58
58
|
declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
59
59
|
restorePlaybackPositionEnabled: boolean;
|
|
60
|
+
loudnessNormalizationEnabled: boolean;
|
|
60
61
|
/**
|
|
61
62
|
* 获取当前进度(秒)
|
|
62
63
|
*/
|
|
@@ -92,6 +93,7 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
|
92
93
|
getRepeatMode(): Promise<RepeatMode>;
|
|
93
94
|
setBilibiliCookie(cookie: string): void;
|
|
94
95
|
setRestorePlaybackPositionEnabled(enabled: boolean): void;
|
|
96
|
+
setLoudnessNormalizationEnabled(enabled: boolean): void;
|
|
95
97
|
play(): Promise<void>;
|
|
96
98
|
pause(): Promise<void>;
|
|
97
99
|
clear(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoOrpheusModule.d.ts","sourceRoot":"","sources":["../src/ExpoOrpheusModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtE,oBAAY,aAAa;IACvB,IAAI,IAAI;IACR,SAAS,IAAI;IACb,KAAK,IAAI;IACT,KAAK,IAAI;CACV;AAED,oBAAY,UAAU;IACpB,GAAG,IAAI;IACP,KAAK,IAAI;IACT,KAAK,IAAI;CACV;AAED,oBAAY,gBAAgB;IAC1B,MAAM,IAAI;IACV,IAAI,IAAI;IACR,IAAI,IAAI;IACR,gBAAgB,IAAI;CACrB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QACT,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAA;CACF;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,sBAAsB,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,aAAa,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,cAAc,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,gBAAgB,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E,eAAe,CAAC,KAAK,EAAE;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,EAAE,MAAM,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,IAAI,CAAC;IACT,aAAa,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,gBAAgB,CAAC,KAAK,EAAE;QACtB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,IAAI,CAAC;IACT,kBAAkB,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACrD,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;CAC9C,CAAC;AAEF,OAAO,OAAO,aAAc,SAAQ,YAAY,CAAC,aAAa,CAAC;IAE7D,8BAA8B,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoOrpheusModule.d.ts","sourceRoot":"","sources":["../src/ExpoOrpheusModule.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoOrpheusModule.js","sourceRoot":"","sources":["../src/ExpoOrpheusModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAgB,MAAM,mBAAmB,CAAC;AAEtE,MAAM,CAAN,IAAY,aAKX;AALD,WAAY,aAAa;IACvB,iDAAQ,CAAA;IACR,2DAAa,CAAA;IACb,mDAAS,CAAA;IACT,mDAAS,CAAA;AACX,CAAC,EALW,aAAa,KAAb,aAAa,QAKxB;AAED,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACpB,yCAAO,CAAA;IACP,6CAAS,CAAA;IACT,6CAAS,CAAA;AACX,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAED,MAAM,CAAN,IAAY,gBAKX;AALD,WAAY,gBAAgB;IAC1B,2DAAU,CAAA;IACV,uDAAQ,CAAA;IACR,uDAAQ,CAAA;IACR,+EAAoB,CAAA;AACtB,CAAC,EALW,gBAAgB,KAAhB,gBAAgB,QAK3B;
|
|
1
|
+
{"version":3,"file":"ExpoOrpheusModule.js","sourceRoot":"","sources":["../src/ExpoOrpheusModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAgB,MAAM,mBAAmB,CAAC;AAEtE,MAAM,CAAN,IAAY,aAKX;AALD,WAAY,aAAa;IACvB,iDAAQ,CAAA;IACR,2DAAa,CAAA;IACb,mDAAS,CAAA;IACT,mDAAS,CAAA;AACX,CAAC,EALW,aAAa,KAAb,aAAa,QAKxB;AAED,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACpB,yCAAO,CAAA;IACP,6CAAS,CAAA;IACT,6CAAS,CAAA;AACX,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAED,MAAM,CAAN,IAAY,gBAKX;AALD,WAAY,gBAAgB;IAC1B,2DAAU,CAAA;IACV,uDAAQ,CAAA;IACR,uDAAQ,CAAA;IACR,+EAAoB,CAAA;AACtB,CAAC,EALW,gBAAgB,KAAhB,gBAAgB,QAK3B;AAwLD,MAAM,CAAN,IAAY,aAQX;AARD,WAAY,aAAa;IACvB,qDAAU,CAAA;IACV,uDAAW,CAAA;IACX,+DAAe,CAAA;IACf,2DAAa,CAAA;IACb,qDAAU,CAAA;IACV,yDAAY,CAAA;IACZ,6DAAc,CAAA;AAChB,CAAC,EARW,aAAa,KAAb,aAAa,QAQxB;AAWD,MAAM,CAAC,MAAM,OAAO,GAAG,mBAAmB,CAAgB,SAAS,CAAC,CAAC","sourcesContent":["import { requireNativeModule, NativeModule } from \"expo-modules-core\";\n\nexport enum PlaybackState {\n IDLE = 1,\n BUFFERING = 2,\n READY = 3,\n ENDED = 4,\n}\n\nexport enum RepeatMode {\n OFF = 0,\n TRACK = 1,\n QUEUE = 2,\n}\n\nexport enum TransitionReason {\n REPEAT = 0,\n AUTO = 1,\n SEEK = 2,\n PLAYLIST_CHANGED = 3,\n}\n\nexport interface Track {\n id: string;\n url: string;\n title?: string;\n artist?: string;\n artwork?: string;\n duration?: number;\n loudness?: {\n measured_i: number;\n target_i: number;\n }\n}\n\nexport type OrpheusEvents = {\n onPlaybackStateChanged(event: { state: PlaybackState }): void;\n onTrackStarted(event: { trackId: string; reason: TransitionReason }): void;\n onTrackFinished(event: {\n trackId: string;\n finalPosition: number;\n duration: number;\n }): void;\n onPlayerError(event: { code: string; message: string }): void;\n onPositionUpdate(event: {\n position: number;\n duration: number;\n buffered: number;\n }): void;\n onIsPlayingChanged(event: { status: boolean }): void;\n onDownloadUpdated(event: DownloadTask): void;\n};\n\ndeclare class OrpheusModule extends NativeModule<OrpheusEvents> {\n \n restorePlaybackPositionEnabled: boolean;\n loudnessNormalizationEnabled: boolean;\n\n /**\n * 获取当前进度(秒)\n */\n getPosition(): Promise<number>;\n\n /**\n * 获取总时长(秒)\n */\n getDuration(): Promise<number>;\n\n /**\n * 获取缓冲进度(秒)\n */\n getBuffered(): Promise<number>;\n\n /**\n * 获取是否正在播放\n */\n getIsPlaying(): Promise<boolean>;\n\n /**\n * 获取当前播放索引\n */\n getCurrentIndex(): Promise<number>;\n\n /**\n * 获取当前播放的 Track 对象\n */\n getCurrentTrack(): Promise<Track | null>;\n\n /**\n * 获取随机模式状态\n */\n getShuffleMode(): Promise<boolean>;\n\n /**\n * 获取指定索引的 Track\n */\n getIndexTrack(index: number): Promise<Track | null>;\n\n getRepeatMode(): Promise<RepeatMode>;\n\n setBilibiliCookie(cookie: string): void;\n \n setRestorePlaybackPositionEnabled(enabled: boolean): void;\n setLoudnessNormalizationEnabled(enabled: boolean): void;\n\n play(): Promise<void>;\n\n pause(): Promise<void>;\n\n clear(): Promise<void>;\n\n skipTo(index: number): Promise<void>;\n\n skipToNext(): Promise<void>;\n\n skipToPrevious(): Promise<void>;\n\n /**\n * 跳转进度\n * @param seconds 秒数\n */\n seekTo(seconds: number): Promise<void>;\n\n setRepeatMode(mode: RepeatMode): Promise<void>;\n\n setShuffleMode(enabled: boolean): Promise<void>;\n\n getQueue(): Promise<Track[]>;\n\n /**\n * 添加到队列末尾,且不去重。\n * @param tracks\n * @param startFromId 可选,添加后立即播放该 ID 的曲目\n * @param clearQueue 可选,是否清空当前队列\n */\n addToEnd(\n tracks: Track[],\n startFromId?: string,\n clearQueue?: boolean\n ): Promise<void>;\n\n /**\n * 播放下一首\n * @param track\n */\n playNext(track: Track): Promise<void>;\n\n removeTrack(index: number): Promise<void>;\n\n /**\n * 设置睡眠定时器\n * @param durationMs 单位毫秒\n */\n setSleepTimer(durationMs: number): Promise<void>;\n\n /**\n * 获取睡眠定时器结束时间\n * @returns 单位毫秒,如果没有设置则返回 null\n */\n getSleepTimerEndTime(): Promise<number | null>;\n\n cancelSleepTimer(): Promise<void>;\n\n /**\n * 下载单首歌曲\n */\n downloadTrack(track: Track): Promise<void>;\n\n /**\n * 移除下载任务\n */\n removeDownload(id: string): Promise<void>;\n\n /**\n * 批量下载歌曲\n */\n multiDownload(tracks: Track[]): Promise<void>;\n\n /**\n * 移除所有下载任务(包括已完成的及源文件)\n */\n removeAllDownloads(): Promise<void>;\n\n /**\n * 获取所有下载任务\n */\n getDownloads(): Promise<DownloadTask[]>;\n\n /**\n * 批量返回指定 ID 的下载状态\n */\n getDownloadStatusByIds(ids: string[]): Promise<Record<string, DownloadState>>;\n\n /**\n * 清除未完成的下载任务\n */\n clearUncompletedDownloadTasks(): Promise<void>;\n\n /**\n * 获取所有未完成的下载任务\n */\n getUncompletedDownloadTasks(): Promise<DownloadTask[]>;\n}\n\nexport enum DownloadState {\n QUEUED = 0,\n STOPPED = 1,\n DOWNLOADING = 2,\n COMPLETED = 3,\n FAILED = 4,\n REMOVING = 5,\n RESTARTING = 7,\n}\n\nexport interface DownloadTask {\n id: string;\n state: DownloadState;\n percentDownloaded: number;\n bytesDownloaded: number;\n contentLength: number;\n track?: Track;\n}\n\nexport const Orpheus = requireNativeModule<OrpheusModule>(\"Orpheus\");\n"]}
|
package/build/hooks/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"}
|
package/build/hooks/index.js
CHANGED
package/build/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC","sourcesContent":["export * from \"./useProgress\";\nexport * from \"./usePlaybackState\";\nexport * from \"./useIsPlaying\";\nexport * from \"./useCurrentTrack\";\nexport * from \"./useOrpheus\";\n"]}
|
package/package.json
CHANGED
package/src/ExpoOrpheusModule.ts
CHANGED
|
@@ -54,6 +54,7 @@ export type OrpheusEvents = {
|
|
|
54
54
|
declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
55
55
|
|
|
56
56
|
restorePlaybackPositionEnabled: boolean;
|
|
57
|
+
loudnessNormalizationEnabled: boolean;
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
60
|
* 获取当前进度(秒)
|
|
@@ -100,6 +101,7 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
|
100
101
|
setBilibiliCookie(cookie: string): void;
|
|
101
102
|
|
|
102
103
|
setRestorePlaybackPositionEnabled(enabled: boolean): void;
|
|
104
|
+
setLoudnessNormalizationEnabled(enabled: boolean): void;
|
|
103
105
|
|
|
104
106
|
play(): Promise<void>;
|
|
105
107
|
|
package/src/hooks/index.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useShuffleMode.d.ts","sourceRoot":"","sources":["../../src/hooks/useShuffleMode.ts"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,4CAsB7B"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import { Orpheus } from "../ExpoOrpheusModule";
|
|
3
|
-
export function useShuffleMode() {
|
|
4
|
-
const [shuffleMode, setShuffleMode] = useState(false);
|
|
5
|
-
const refresh = async () => {
|
|
6
|
-
const val = await Orpheus.getShuffleMode();
|
|
7
|
-
setShuffleMode(val);
|
|
8
|
-
};
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
refresh();
|
|
11
|
-
const sub = Orpheus.addListener("onTrackStarted", refresh);
|
|
12
|
-
return () => sub.remove();
|
|
13
|
-
}, []);
|
|
14
|
-
const toggleShuffle = async () => {
|
|
15
|
-
const newVal = !shuffleMode;
|
|
16
|
-
setShuffleMode(newVal);
|
|
17
|
-
await Orpheus.setShuffleMode(newVal);
|
|
18
|
-
refresh();
|
|
19
|
-
};
|
|
20
|
-
return [shuffleMode, toggleShuffle];
|
|
21
|
-
}
|
|
22
|
-
//# sourceMappingURL=useShuffleMode.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useShuffleMode.js","sourceRoot":"","sources":["../../src/hooks/useShuffleMode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,MAAM,UAAU,cAAc;IAC5B,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEtD,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3C,cAAc,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,EAAE,CAAC;QACV,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QAC3D,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,aAAa,GAAG,KAAK,IAAI,EAAE;QAC/B,MAAM,MAAM,GAAG,CAAC,WAAW,CAAC;QAC5B,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,MAAM,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IAEF,OAAO,CAAC,WAAW,EAAE,aAAa,CAAU,CAAC;AAC/C,CAAC","sourcesContent":["import { useState, useEffect } from \"react\";\nimport { Orpheus } from \"../ExpoOrpheusModule\";\n\nexport function useShuffleMode() {\n const [shuffleMode, setShuffleMode] = useState(false);\n\n const refresh = async () => {\n const val = await Orpheus.getShuffleMode();\n setShuffleMode(val);\n };\n\n useEffect(() => {\n refresh();\n const sub = Orpheus.addListener(\"onTrackStarted\", refresh);\n return () => sub.remove();\n }, []);\n\n const toggleShuffle = async () => {\n const newVal = !shuffleMode;\n setShuffleMode(newVal);\n await Orpheus.setShuffleMode(newVal);\n refresh();\n };\n\n return [shuffleMode, toggleShuffle] as const;\n}\n"]}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import { Orpheus } from "../ExpoOrpheusModule";
|
|
3
|
-
|
|
4
|
-
export function useShuffleMode() {
|
|
5
|
-
const [shuffleMode, setShuffleMode] = useState(false);
|
|
6
|
-
|
|
7
|
-
const refresh = async () => {
|
|
8
|
-
const val = await Orpheus.getShuffleMode();
|
|
9
|
-
setShuffleMode(val);
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
refresh();
|
|
14
|
-
const sub = Orpheus.addListener("onTrackStarted", refresh);
|
|
15
|
-
return () => sub.remove();
|
|
16
|
-
}, []);
|
|
17
|
-
|
|
18
|
-
const toggleShuffle = async () => {
|
|
19
|
-
const newVal = !shuffleMode;
|
|
20
|
-
setShuffleMode(newVal);
|
|
21
|
-
await Orpheus.setShuffleMode(newVal);
|
|
22
|
-
refresh();
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
return [shuffleMode, toggleShuffle] as const;
|
|
26
|
-
}
|