@roitium/expo-orpheus 0.2.2 → 0.2.4
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 +25 -0
- package/android/src/main/java/expo/modules/orpheus/ExpoOrpheusModule.kt +42 -16
- package/android/src/main/java/expo/modules/orpheus/OrpheusService.kt +8 -1
- package/build/ExpoOrpheusModule.d.ts +10 -4
- package/build/ExpoOrpheusModule.d.ts.map +1 -1
- package/build/ExpoOrpheusModule.js.map +1 -1
- package/build/hooks/useCurrentTrack.js +1 -1
- package/build/hooks/useCurrentTrack.js.map +1 -1
- package/build/hooks/useShuffleMode.js +1 -1
- package/build/hooks/useShuffleMode.js.map +1 -1
- package/package.json +1 -1
- package/src/ExpoOrpheusModule.ts +12 -5
- package/src/hooks/useCurrentTrack.ts +1 -1
- package/src/hooks/useShuffleMode.ts +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package expo.modules.orpheus
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.media3.common.util.UnstableApi
|
|
5
|
+
import androidx.media3.database.StandaloneDatabaseProvider
|
|
6
|
+
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
|
|
7
|
+
import androidx.media3.datasource.cache.SimpleCache
|
|
8
|
+
import java.io.File
|
|
9
|
+
|
|
10
|
+
@UnstableApi
|
|
11
|
+
object DownloadCache {
|
|
12
|
+
private var cache: SimpleCache? = null
|
|
13
|
+
|
|
14
|
+
@UnstableApi
|
|
15
|
+
fun get(context: Context): SimpleCache {
|
|
16
|
+
if (cache == null) {
|
|
17
|
+
val cacheDir = File(context.cacheDir, "media_cache")
|
|
18
|
+
val evictor = LeastRecentlyUsedCacheEvictor(256 * 1024 * 1024)
|
|
19
|
+
val databaseProvider = StandaloneDatabaseProvider(context)
|
|
20
|
+
|
|
21
|
+
cache = SimpleCache(cacheDir, evictor, databaseProvider)
|
|
22
|
+
}
|
|
23
|
+
return cache!!
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -20,10 +20,6 @@ import expo.modules.kotlin.modules.Module
|
|
|
20
20
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
21
21
|
|
|
22
22
|
class ExpoOrpheusModule : Module() {
|
|
23
|
-
companion object {
|
|
24
|
-
val TAG = "Orpheus"
|
|
25
|
-
}
|
|
26
|
-
|
|
27
23
|
private var controllerFuture: ListenableFuture<MediaController>? = null
|
|
28
24
|
|
|
29
25
|
private var controller: MediaController? = null
|
|
@@ -33,6 +29,11 @@ class ExpoOrpheusModule : Module() {
|
|
|
33
29
|
// 记录上一首歌曲的 ID,用于在切歌时发送给 JS
|
|
34
30
|
private var lastMediaId: String? = null
|
|
35
31
|
|
|
32
|
+
private var currentTrackDuration: Long = 0L
|
|
33
|
+
|
|
34
|
+
// 用来暂存上一首歌曲切歌瞬间的进度
|
|
35
|
+
private var lastTrackFinalPosition: Long = 0L
|
|
36
|
+
|
|
36
37
|
val gson = Gson()
|
|
37
38
|
|
|
38
39
|
override fun definition() = ModuleDefinition {
|
|
@@ -40,10 +41,11 @@ class ExpoOrpheusModule : Module() {
|
|
|
40
41
|
|
|
41
42
|
Events(
|
|
42
43
|
"onPlaybackStateChanged",
|
|
43
|
-
"onTrackTransition",
|
|
44
44
|
"onPlayerError",
|
|
45
45
|
"onPositionUpdate",
|
|
46
|
-
"onIsPlayingChanged"
|
|
46
|
+
"onIsPlayingChanged",
|
|
47
|
+
"onTrackFinished",
|
|
48
|
+
"onTrackStarted"
|
|
47
49
|
)
|
|
48
50
|
|
|
49
51
|
OnCreate {
|
|
@@ -201,7 +203,7 @@ class ExpoOrpheusModule : Module() {
|
|
|
201
203
|
return@AsyncFunction queue
|
|
202
204
|
}.runOnQueue(Queues.MAIN)
|
|
203
205
|
|
|
204
|
-
AsyncFunction("addToEnd") { tracks: List<TrackRecord>, startFromId: String? ->
|
|
206
|
+
AsyncFunction("addToEnd") { tracks: List<TrackRecord>, startFromId: String?, clearQueue: Boolean? ->
|
|
205
207
|
val mediaItems = tracks.mapNotNull { track ->
|
|
206
208
|
try {
|
|
207
209
|
val trackJson = gson.toJson(track)
|
|
@@ -229,6 +231,9 @@ class ExpoOrpheusModule : Module() {
|
|
|
229
231
|
}
|
|
230
232
|
}
|
|
231
233
|
val player = controller ?: return@AsyncFunction
|
|
234
|
+
if (clearQueue == true) {
|
|
235
|
+
player.clearMediaItems()
|
|
236
|
+
}
|
|
232
237
|
val initialSize = player.mediaItemCount
|
|
233
238
|
player.addMediaItems(mediaItems)
|
|
234
239
|
|
|
@@ -271,8 +276,6 @@ class ExpoOrpheusModule : Module() {
|
|
|
271
276
|
.setUri(track.url)
|
|
272
277
|
.setMediaMetadata(metadata)
|
|
273
278
|
.build()
|
|
274
|
-
|
|
275
|
-
val currentId = player.currentMediaItem?.mediaId
|
|
276
279
|
val targetIndex = player.currentMediaItemIndex + 1
|
|
277
280
|
|
|
278
281
|
var existingIndex = -1
|
|
@@ -310,19 +313,37 @@ class ExpoOrpheusModule : Module() {
|
|
|
310
313
|
* 核心:处理切歌、播放结束逻辑
|
|
311
314
|
*/
|
|
312
315
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
|
313
|
-
val
|
|
314
|
-
Player.MEDIA_ITEM_TRANSITION_REASON_AUTO
|
|
316
|
+
val newId = mediaItem?.mediaId ?: ""
|
|
315
317
|
|
|
316
318
|
sendEvent(
|
|
317
|
-
"
|
|
318
|
-
"
|
|
319
|
-
"previousTrackId" to lastMediaId, // 上一首歌是什么
|
|
319
|
+
"onTrackStarted", mapOf(
|
|
320
|
+
"trackId" to newId,
|
|
320
321
|
"reason" to reason
|
|
321
322
|
)
|
|
322
323
|
)
|
|
323
324
|
|
|
324
|
-
|
|
325
|
-
|
|
325
|
+
lastMediaId = newId
|
|
326
|
+
currentTrackDuration = 0L
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
override fun onPositionDiscontinuity(
|
|
330
|
+
oldPosition: Player.PositionInfo,
|
|
331
|
+
newPosition: Player.PositionInfo,
|
|
332
|
+
reason: Int
|
|
333
|
+
) {
|
|
334
|
+
Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT
|
|
335
|
+
if (oldPosition.mediaItemIndex != newPosition.mediaItemIndex) {
|
|
336
|
+
val lastMediaItem =
|
|
337
|
+
controller?.getMediaItemAt(oldPosition.mediaItemIndex) ?: return
|
|
338
|
+
|
|
339
|
+
sendEvent(
|
|
340
|
+
"onTrackFinished", mapOf(
|
|
341
|
+
"trackId" to lastMediaItem.mediaId,
|
|
342
|
+
"finalPosition" to oldPosition.positionMs / 1000.0,
|
|
343
|
+
"duration" to currentTrackDuration / 1000.0,
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
}
|
|
326
347
|
}
|
|
327
348
|
|
|
328
349
|
/**
|
|
@@ -336,6 +357,11 @@ class ExpoOrpheusModule : Module() {
|
|
|
336
357
|
)
|
|
337
358
|
)
|
|
338
359
|
|
|
360
|
+
if (state == Player.STATE_READY) {
|
|
361
|
+
val d = controller?.duration
|
|
362
|
+
if (d != C.TIME_UNSET && d != null) currentTrackDuration = d
|
|
363
|
+
}
|
|
364
|
+
|
|
339
365
|
updateProgressRunnerState()
|
|
340
366
|
}
|
|
341
367
|
|
|
@@ -8,6 +8,7 @@ import androidx.media3.common.util.UnstableApi
|
|
|
8
8
|
import androidx.media3.datasource.DataSpec
|
|
9
9
|
import androidx.media3.datasource.DefaultHttpDataSource
|
|
10
10
|
import androidx.media3.datasource.ResolvingDataSource
|
|
11
|
+
import androidx.media3.datasource.cache.CacheDataSource
|
|
11
12
|
import androidx.media3.exoplayer.ExoPlayer
|
|
12
13
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
|
13
14
|
import androidx.media3.session.MediaLibraryService
|
|
@@ -29,8 +30,13 @@ class OrpheusService : MediaLibraryService() {
|
|
|
29
30
|
.setUserAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36")
|
|
30
31
|
.setAllowCrossProtocolRedirects(true)
|
|
31
32
|
|
|
33
|
+
val cacheDataSourceFactory = CacheDataSource.Factory()
|
|
34
|
+
.setCache(DownloadCache.get(this))
|
|
35
|
+
.setUpstreamDataSourceFactory(httpDataSourceFactory)
|
|
36
|
+
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
|
|
37
|
+
|
|
32
38
|
val resolvingDataSourceFactory = ResolvingDataSource.Factory(
|
|
33
|
-
|
|
39
|
+
cacheDataSourceFactory,
|
|
34
40
|
object : ResolvingDataSource.Resolver {
|
|
35
41
|
// TODO: maybe we need to add a cache?
|
|
36
42
|
override fun resolveDataSpec(dataSpec: DataSpec): DataSpec {
|
|
@@ -64,6 +70,7 @@ class OrpheusService : MediaLibraryService() {
|
|
|
64
70
|
return dataSpec.buildUpon()
|
|
65
71
|
.setUri(realUrl.toUri())
|
|
66
72
|
.setHttpRequestHeaders(headers)
|
|
73
|
+
.setKey(uri.toString())
|
|
67
74
|
.build()
|
|
68
75
|
} catch (e: Exception) {
|
|
69
76
|
throw IOException("Resolve Url Failed: ${e.message}", e)
|
|
@@ -29,11 +29,15 @@ export type OrpheusEvents = {
|
|
|
29
29
|
onPlaybackStateChanged(event: {
|
|
30
30
|
state: PlaybackState;
|
|
31
31
|
}): void;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
previousTrackId?: string;
|
|
32
|
+
onTrackStarted(event: {
|
|
33
|
+
trackId: string;
|
|
35
34
|
reason: TransitionReason;
|
|
36
35
|
}): void;
|
|
36
|
+
onTrackFinished(event: {
|
|
37
|
+
trackId: string;
|
|
38
|
+
finalPosition: number;
|
|
39
|
+
duration: number;
|
|
40
|
+
}): void;
|
|
37
41
|
onPlayerError(event: {
|
|
38
42
|
code: string;
|
|
39
43
|
message: string;
|
|
@@ -99,8 +103,10 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
|
99
103
|
/**
|
|
100
104
|
* 添加到队列末尾,且不去重。
|
|
101
105
|
* @param tracks
|
|
106
|
+
* @param startFromId 可选,添加后立即播放该 ID 的曲目
|
|
107
|
+
* @param clearQueue 可选,是否清空当前队列
|
|
102
108
|
*/
|
|
103
|
-
addToEnd(tracks: Track[], startFromId?: string): Promise<void>;
|
|
109
|
+
addToEnd(tracks: Track[], startFromId?: string, clearQueue?: boolean): Promise<void>;
|
|
104
110
|
/**
|
|
105
111
|
* 播放下一首
|
|
106
112
|
* @param track
|
|
@@ -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,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,sBAAsB,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,aAAa,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,
|
|
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;AAuHD,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 [key: string]: any;\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};\n\ndeclare class OrpheusModule extends NativeModule<OrpheusEvents> {\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 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\nexport const Orpheus = requireNativeModule<OrpheusModule>(\"Orpheus\");\n"]}
|
|
@@ -24,7 +24,7 @@ export function useCurrentTrack() {
|
|
|
24
24
|
setIndex(currentIndex);
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
|
-
const sub = Orpheus.addListener("
|
|
27
|
+
const sub = Orpheus.addListener("onTrackStarted", async () => {
|
|
28
28
|
const { currentTrack, currentIndex } = await fetchTrack();
|
|
29
29
|
if (isMounted) {
|
|
30
30
|
setTrack(currentTrack);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCurrentTrack.js","sourceRoot":"","sources":["../../src/hooks/useCurrentTrack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAS,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,UAAU,eAAe;IAC7B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAS,CAAC,CAAC,CAAC,CAAC;IAE/C,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;QAC5B,IAAI,CAAC;YACH,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACrD,OAAO,CAAC,eAAe,EAAE;gBACzB,OAAO,CAAC,eAAe,EAAE;aAC1B,CAAC,CAAC;YACH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;QACxC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,+BAA+B,EAAE,CAAC,CAAC,CAAC;YACjD,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC;QAClD,CAAC;IACH,CAAC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,IAAI,CAAC;QAErB,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,EAAE;YACnD,IAAI,SAAS,EAAE,CAAC;gBACd,QAAQ,CAAC,YAAY,CAAC,CAAC;gBACvB,QAAQ,CAAC,YAAY,CAAC,CAAC;YACzB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"useCurrentTrack.js","sourceRoot":"","sources":["../../src/hooks/useCurrentTrack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAS,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,UAAU,eAAe;IAC7B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAS,CAAC,CAAC,CAAC,CAAC;IAE/C,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;QAC5B,IAAI,CAAC;YACH,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACrD,OAAO,CAAC,eAAe,EAAE;gBACzB,OAAO,CAAC,eAAe,EAAE;aAC1B,CAAC,CAAC;YACH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;QACxC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,+BAA+B,EAAE,CAAC,CAAC,CAAC;YACjD,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC;QAClD,CAAC;IACH,CAAC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,IAAI,CAAC;QAErB,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,EAAE;YACnD,IAAI,SAAS,EAAE,CAAC;gBACd,QAAQ,CAAC,YAAY,CAAC,CAAC;gBACvB,QAAQ,CAAC,YAAY,CAAC,CAAC;YACzB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;YAC3D,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,MAAM,UAAU,EAAE,CAAC;YAC1D,IAAI,SAAS,EAAE,CAAC;gBACd,QAAQ,CAAC,YAAY,CAAC,CAAC;gBACvB,QAAQ,CAAC,YAAY,CAAC,CAAC;YACzB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,KAAK,CAAC;YAClB,GAAG,CAAC,MAAM,EAAE,CAAC;QACf,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC","sourcesContent":["import { useState, useEffect } from \"react\";\nimport { Track, Orpheus } from \"../ExpoOrpheusModule\";\n\nexport function useCurrentTrack() {\n const [track, setTrack] = useState<Track | null>(null);\n const [index, setIndex] = useState<number>(-1);\n\n const fetchTrack = async () => {\n try {\n const [currentTrack, currentIndex] = await Promise.all([\n Orpheus.getCurrentTrack(),\n Orpheus.getCurrentIndex(),\n ]);\n return { currentTrack, currentIndex };\n } catch (e) {\n console.warn(\"Failed to fetch current track\", e);\n return { currentTrack: null, currentIndex: -1 };\n }\n };\n\n useEffect(() => {\n let isMounted = true;\n\n fetchTrack().then(({ currentTrack, currentIndex }) => {\n if (isMounted) {\n setTrack(currentTrack);\n setIndex(currentIndex);\n }\n });\n\n const sub = Orpheus.addListener(\"onTrackStarted\", async () => {\n const { currentTrack, currentIndex } = await fetchTrack();\n if (isMounted) {\n setTrack(currentTrack);\n setIndex(currentIndex);\n }\n });\n\n return () => {\n isMounted = false;\n sub.remove();\n };\n }, []);\n\n return { track, index };\n}\n"]}
|
|
@@ -8,7 +8,7 @@ export function useShuffleMode() {
|
|
|
8
8
|
};
|
|
9
9
|
useEffect(() => {
|
|
10
10
|
refresh();
|
|
11
|
-
const sub = Orpheus.addListener("
|
|
11
|
+
const sub = Orpheus.addListener("onTrackStarted", refresh);
|
|
12
12
|
return () => sub.remove();
|
|
13
13
|
}, []);
|
|
14
14
|
const toggleShuffle = async () => {
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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"]}
|
package/package.json
CHANGED
package/src/ExpoOrpheusModule.ts
CHANGED
|
@@ -32,10 +32,11 @@ export interface Track {
|
|
|
32
32
|
|
|
33
33
|
export type OrpheusEvents = {
|
|
34
34
|
onPlaybackStateChanged(event: { state: PlaybackState }): void;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
onTrackStarted(event: { trackId: string; reason: TransitionReason }): void;
|
|
36
|
+
onTrackFinished(event: {
|
|
37
|
+
trackId: string;
|
|
38
|
+
finalPosition: number;
|
|
39
|
+
duration: number;
|
|
39
40
|
}): void;
|
|
40
41
|
onPlayerError(event: { code: string; message: string }): void;
|
|
41
42
|
onPositionUpdate(event: {
|
|
@@ -118,8 +119,14 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
|
118
119
|
/**
|
|
119
120
|
* 添加到队列末尾,且不去重。
|
|
120
121
|
* @param tracks
|
|
122
|
+
* @param startFromId 可选,添加后立即播放该 ID 的曲目
|
|
123
|
+
* @param clearQueue 可选,是否清空当前队列
|
|
121
124
|
*/
|
|
122
|
-
addToEnd(
|
|
125
|
+
addToEnd(
|
|
126
|
+
tracks: Track[],
|
|
127
|
+
startFromId?: string,
|
|
128
|
+
clearQueue?: boolean
|
|
129
|
+
): Promise<void>;
|
|
123
130
|
|
|
124
131
|
/**
|
|
125
132
|
* 播放下一首
|
|
@@ -28,7 +28,7 @@ export function useCurrentTrack() {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
const sub = Orpheus.addListener("
|
|
31
|
+
const sub = Orpheus.addListener("onTrackStarted", async () => {
|
|
32
32
|
const { currentTrack, currentIndex } = await fetchTrack();
|
|
33
33
|
if (isMounted) {
|
|
34
34
|
setTrack(currentTrack);
|