@roitium/expo-orpheus 0.1.1 → 0.2.1

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.
@@ -79,6 +79,10 @@ class ExpoOrpheusModule : Module() {
79
79
  if (d == C.TIME_UNSET) 0.0 else d.toDouble() / 1000.0
80
80
  }.runOnQueue(Queues.MAIN)
81
81
 
82
+ AsyncFunction("getBuffered") {
83
+ controller?.bufferedPosition?.toDouble()?.div(1000.0) ?: 0.0
84
+ }.runOnQueue(Queues.MAIN)
85
+
82
86
  AsyncFunction("getIsPlaying") {
83
87
  controller?.isPlaying ?: false
84
88
  }.runOnQueue(Queues.MAIN)
@@ -174,6 +178,16 @@ class ExpoOrpheusModule : Module() {
174
178
  controller?.shuffleModeEnabled = enabled
175
179
  }.runOnQueue(Queues.MAIN)
176
180
 
181
+ AsyncFunction("getRepeatMode") {
182
+ controller?.repeatMode
183
+ }.runOnQueue(Queues.MAIN)
184
+
185
+ AsyncFunction("removeTrack") { index: Int ->
186
+ if (index >= 0 && index < (controller?.mediaItemCount ?: 0)) {
187
+ controller?.removeMediaItem(index)
188
+ }
189
+ }
190
+
177
191
  AsyncFunction("getQueue") {
178
192
  val player = controller ?: return@AsyncFunction emptyList<TrackRecord>()
179
193
  val count = player.mediaItemCount
@@ -187,9 +201,7 @@ class ExpoOrpheusModule : Module() {
187
201
  return@AsyncFunction queue
188
202
  }.runOnQueue(Queues.MAIN)
189
203
 
190
- AsyncFunction("add") { tracks: List<TrackRecord> ->
191
- // 1. 【后台线程】执行:JSON 转换、Metadata 构建 (耗时操作在这里做,不卡 UI)
192
- // 这里不需要改,继续在当前线程跑
204
+ AsyncFunction("addToEnd") { tracks: List<TrackRecord>, startFromId: String? ->
193
205
  val mediaItems = tracks.mapNotNull { track ->
194
206
  try {
195
207
  val trackJson = gson.toJson(track)
@@ -208,7 +220,7 @@ class ExpoOrpheusModule : Module() {
208
220
 
209
221
  MediaItem.Builder()
210
222
  .setMediaId(track.id)
211
- .setUri(track.url ?: "")
223
+ .setUri(track.url)
212
224
  .setMediaMetadata(metadata)
213
225
  .build()
214
226
  } catch (e: Exception) {
@@ -216,26 +228,78 @@ class ExpoOrpheusModule : Module() {
216
228
  null
217
229
  }
218
230
  }
231
+ val player = controller ?: return@AsyncFunction
232
+ val initialSize = player.mediaItemCount
233
+ player.addMediaItems(mediaItems)
219
234
 
220
- val player = controller
221
- if (player != null) {
222
- // 获取 player 真正归属的 Looper
223
- val playerLooper = player.applicationLooper
235
+ if (!startFromId.isNullOrEmpty()) {
236
+ val relativeIndex = tracks.indexOfFirst { it.id == startFromId }
224
237
 
225
- if (Looper.myLooper() == playerLooper) {
226
- player.addMediaItems(mediaItems)
227
- if (player.playbackState == Player.STATE_IDLE) {
228
- player.prepare()
229
- }
230
- } else {
231
- Handler(playerLooper).post {
232
- player.addMediaItems(mediaItems)
233
- if (player.playbackState == Player.STATE_IDLE) {
234
- player.prepare()
235
- }
236
- }
238
+ if (relativeIndex != -1) {
239
+ val targetIndex = initialSize + relativeIndex
240
+
241
+ player.seekTo(targetIndex, C.TIME_UNSET)
242
+ player.prepare()
243
+ player.play()
244
+
245
+ return@AsyncFunction
246
+ }
247
+ }
248
+
249
+ if (player.playbackState == Player.STATE_IDLE) {
250
+ player.prepare()
251
+ }
252
+ }.runOnQueue(Queues.MAIN)
253
+
254
+ AsyncFunction("playNext") { track: TrackRecord ->
255
+ val player = controller ?: return@AsyncFunction
256
+
257
+ val trackJson = gson.toJson(track)
258
+ val extras = Bundle()
259
+ extras.putString("track_json", trackJson)
260
+ val artUri = if (!track.artwork.isNullOrEmpty()) track.artwork!!.toUri() else null
261
+
262
+ val metadata = MediaMetadata.Builder()
263
+ .setTitle(track.title)
264
+ .setArtist(track.artist)
265
+ .setArtworkUri(artUri)
266
+ .setExtras(extras)
267
+ .build()
268
+
269
+ val mediaItem = MediaItem.Builder()
270
+ .setMediaId(track.id)
271
+ .setUri(track.url)
272
+ .setMediaMetadata(metadata)
273
+ .build()
274
+
275
+ val currentId = player.currentMediaItem?.mediaId
276
+ val targetIndex = player.currentMediaItemIndex + 1
277
+
278
+ var existingIndex = -1
279
+ for (i in 0 until player.mediaItemCount) {
280
+ if (player.getMediaItemAt(i).mediaId == track.id) {
281
+ existingIndex = i
282
+ break
237
283
  }
238
284
  }
285
+
286
+ if (existingIndex != -1) {
287
+ if (existingIndex == player.currentMediaItemIndex) {
288
+ return@AsyncFunction
289
+ }
290
+ val safeTargetIndex = targetIndex.coerceAtMost(player.mediaItemCount)
291
+
292
+ player.moveMediaItem(existingIndex, safeTargetIndex)
293
+
294
+ } else {
295
+ val safeTargetIndex = targetIndex.coerceAtMost(player.mediaItemCount)
296
+
297
+ player.addMediaItem(safeTargetIndex, mediaItem)
298
+ }
299
+
300
+ if (player.playbackState == Player.STATE_IDLE) {
301
+ player.prepare()
302
+ }
239
303
  }.runOnQueue(Queues.MAIN)
240
304
  }
241
305
 
@@ -0,0 +1,68 @@
1
+ package expo.modules.orpheus
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import android.os.SystemClock
6
+ import androidx.media3.common.Player
7
+
8
+ class SleepTimerManager(private val player: Player) {
9
+ private val handler = Handler(Looper.getMainLooper())
10
+
11
+ private var targetTimeRaw: Long = 0
12
+ private var isTimerActive = false
13
+
14
+ private val checkRunnable = object : Runnable {
15
+ override fun run() {
16
+ if (!isTimerActive) return
17
+
18
+ val now = SystemClock.elapsedRealtime()
19
+ val timeLeft = targetTimeRaw - now
20
+
21
+ if (timeLeft <= 0) {
22
+ performStop()
23
+ } else {
24
+ handler.postDelayed(this, timeLeft)
25
+ }
26
+ }
27
+ }
28
+
29
+ /**
30
+ * 开启定时器
31
+ * @param duration 多少秒后停止
32
+ */
33
+ fun start(duration: Long) {
34
+ if (duration <= 0) {
35
+ cancel()
36
+ return
37
+ }
38
+
39
+ targetTimeRaw = SystemClock.elapsedRealtime() + (duration.times(1000L))
40
+ isTimerActive = true
41
+
42
+ handler.removeCallbacks(checkRunnable)
43
+ handler.postDelayed(checkRunnable, duration)
44
+ }
45
+
46
+ /**
47
+ * 取消定时器
48
+ */
49
+ fun cancel() {
50
+ isTimerActive = false
51
+ handler.removeCallbacks(checkRunnable)
52
+ targetTimeRaw = 0
53
+ }
54
+
55
+ /**
56
+ * 获取剩余时间(单位:秒)
57
+ */
58
+ fun getRemainingTime(): Long {
59
+ if (!isTimerActive) return -1L
60
+ val remaining = (targetTimeRaw - SystemClock.elapsedRealtime()).div(1000L)
61
+ return if (remaining > 0) remaining else 0
62
+ }
63
+
64
+ private fun performStop() {
65
+ isTimerActive = false
66
+ player.pause()
67
+ }
68
+ }
@@ -56,6 +56,10 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
56
56
  * 获取总时长(秒)
57
57
  */
58
58
  getDuration(): Promise<number>;
59
+ /**
60
+ * 获取缓冲进度(秒)
61
+ */
62
+ getBuffered(): Promise<number>;
59
63
  /**
60
64
  * 获取是否正在播放
61
65
  */
@@ -76,6 +80,7 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
76
80
  * 获取指定索引的 Track
77
81
  */
78
82
  getIndexTrack(index: number): Promise<Track | null>;
83
+ getRepeatMode(): Promise<RepeatMode>;
79
84
  setBilibiliCookie(cookie: string): void;
80
85
  play(): Promise<void>;
81
86
  pause(): Promise<void>;
@@ -91,7 +96,17 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
91
96
  setRepeatMode(mode: RepeatMode): Promise<void>;
92
97
  setShuffleMode(enabled: boolean): Promise<void>;
93
98
  getQueue(): Promise<Track[]>;
94
- add(tracks: Track[]): Promise<void>;
99
+ /**
100
+ * 添加到队列末尾,且不去重。
101
+ * @param tracks
102
+ */
103
+ addToEnd(tracks: Track[], startFromId?: string): Promise<void>;
104
+ /**
105
+ * 播放下一首
106
+ * @param track
107
+ */
108
+ playNext(track: Track): Promise<void>;
109
+ removeTrack(index: number): Promise<void>;
95
110
  }
96
111
  export declare const Orpheus: OrpheusModule;
97
112
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoOrpheusModule.d.ts","sourceRoot":"","sources":["../src/ExpoOrpheusModule.ts"],"names":[],"mappings":"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"}
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;AA6FD,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 onTrackTransition(event: {\n currentTrackId: string;\n previousTrackId?: string;\n reason: TransitionReason;\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 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 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 add(tracks: Track[]): Promise<void>;\n}\n\nexport const Orpheus = requireNativeModule<OrpheusModule>(\"Orpheus\");\n"]}
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;AAgHD,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 onTrackTransition(event: {\n currentTrackId: string;\n previousTrackId?: string;\n reason: TransitionReason;\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 */\n addToEnd(tracks: Track[], startFromId?: string): 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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"useProgress.d.ts","sourceRoot":"","sources":["../../src/hooks/useProgress.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW;;;;EAgE1B"}
1
+ {"version":3,"file":"useProgress.d.ts","sourceRoot":"","sources":["../../src/hooks/useProgress.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW;;;;EAmE1B"}
@@ -26,12 +26,16 @@ export function useProgress() {
26
26
  }
27
27
  };
28
28
  const manualSync = () => {
29
- Promise.all([Orpheus.getPosition(), Orpheus.getDuration()])
30
- .then(([pos, dur]) => {
29
+ Promise.all([
30
+ Orpheus.getPosition(),
31
+ Orpheus.getDuration(),
32
+ Orpheus.getBuffered(),
33
+ ])
34
+ .then(([pos, dur, buf]) => {
31
35
  setProgress((prev) => ({
32
- ...prev,
33
36
  position: pos,
34
37
  duration: dur,
38
+ buffered: buf,
35
39
  }));
36
40
  })
37
41
  .catch((e) => console.warn("同步最新进度失败", e));
@@ -39,7 +43,6 @@ export function useProgress() {
39
43
  useEffect(() => {
40
44
  manualSync();
41
45
  startListening();
42
- // === 监听 App 前后台切换 ===
43
46
  const subscription = AppState.addEventListener("change", (nextAppState) => {
44
47
  if (nextAppState === "active") {
45
48
  manualSync();
@@ -1 +1 @@
1
- {"version":3,"file":"useProgress.js","sourceRoot":"","sources":["../../src/hooks/useProgress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAkB,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAI/C,MAAM,UAAU,WAAW;IACzB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC;QACvC,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;KACZ,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,CAA6B,IAAI,CAAC,CAAC;IAE7D,MAAM,cAAc,GAAG,GAAG,EAAE;QAC1B,IAAI,WAAW,CAAC,OAAO;YAAE,OAAO;QAEhC,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE,EAAE;YACtE,WAAW,CAAC;gBACV,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACxB,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC7B,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;aACxD,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE;YACnB,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACrB,GAAG,IAAI;gBACP,QAAQ,EAAE,GAAG;gBACb,QAAQ,EAAE,GAAG;aACd,CAAC,CAAC,CAAC;QACN,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,EAAE,CAAC;QACb,cAAc,EAAE,CAAC;QAEjB,uBAAuB;QACvB,MAAM,YAAY,GAAG,QAAQ,CAAC,gBAAgB,CAC5C,QAAQ,EACR,CAAC,YAA4B,EAAE,EAAE;YAC/B,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;gBAC9B,UAAU,EAAE,CAAC;gBACb,cAAc,EAAE,CAAC;YACnB,CAAC;iBAAM,CAAC;gBACN,aAAa,EAAE,CAAC;YAClB,CAAC;QACH,CAAC,CACF,CAAC;QAEF,OAAO,GAAG,EAAE;YACV,aAAa,EAAE,CAAC;YAChB,YAAY,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import { useEffect, useState, useRef } from \"react\";\nimport { AppState, AppStateStatus } from \"react-native\";\nimport { Orpheus } from \"../ExpoOrpheusModule\";\n\ntype OrpheusSubscription = ReturnType<typeof Orpheus.addListener>;\n\nexport function useProgress() {\n const [progress, setProgress] = useState({\n position: 0,\n duration: 0,\n buffered: 0,\n });\n\n const listenerRef = useRef<null | OrpheusSubscription>(null);\n\n const startListening = () => {\n if (listenerRef.current) return;\n\n listenerRef.current = Orpheus.addListener(\"onPositionUpdate\", (event) => {\n setProgress({\n position: event.position,\n duration: event.duration,\n buffered: event.buffered,\n });\n });\n };\n\n const stopListening = () => {\n if (listenerRef.current) {\n listenerRef.current.remove();\n listenerRef.current = null;\n }\n };\n\n const manualSync = () => {\n Promise.all([Orpheus.getPosition(), Orpheus.getDuration()])\n .then(([pos, dur]) => {\n setProgress((prev) => ({\n ...prev,\n position: pos,\n duration: dur,\n }));\n })\n .catch((e) => console.warn(\"同步最新进度失败\", e));\n };\n\n useEffect(() => {\n manualSync();\n startListening();\n\n // === 监听 App 前后台切换 ===\n const subscription = AppState.addEventListener(\n \"change\",\n (nextAppState: AppStateStatus) => {\n if (nextAppState === \"active\") {\n manualSync();\n startListening();\n } else {\n stopListening();\n }\n }\n );\n\n return () => {\n stopListening();\n subscription.remove();\n };\n }, []);\n\n return progress;\n}\n"]}
1
+ {"version":3,"file":"useProgress.js","sourceRoot":"","sources":["../../src/hooks/useProgress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAkB,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAI/C,MAAM,UAAU,WAAW;IACzB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC;QACvC,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;KACZ,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,CAA6B,IAAI,CAAC,CAAC;IAE7D,MAAM,cAAc,GAAG,GAAG,EAAE;QAC1B,IAAI,WAAW,CAAC,OAAO;YAAE,OAAO;QAEhC,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE,EAAE;YACtE,WAAW,CAAC;gBACV,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACxB,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC7B,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,OAAO,CAAC,GAAG,CAAC;YACV,OAAO,CAAC,WAAW,EAAE;YACrB,OAAO,CAAC,WAAW,EAAE;YACrB,OAAO,CAAC,WAAW,EAAE;SACtB,CAAC;aACC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE;YACxB,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACrB,QAAQ,EAAE,GAAG;gBACb,QAAQ,EAAE,GAAG;gBACb,QAAQ,EAAE,GAAG;aACd,CAAC,CAAC,CAAC;QACN,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,EAAE,CAAC;QACb,cAAc,EAAE,CAAC;QAEjB,MAAM,YAAY,GAAG,QAAQ,CAAC,gBAAgB,CAC5C,QAAQ,EACR,CAAC,YAA4B,EAAE,EAAE;YAC/B,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;gBAC9B,UAAU,EAAE,CAAC;gBACb,cAAc,EAAE,CAAC;YACnB,CAAC;iBAAM,CAAC;gBACN,aAAa,EAAE,CAAC;YAClB,CAAC;QACH,CAAC,CACF,CAAC;QAEF,OAAO,GAAG,EAAE;YACV,aAAa,EAAE,CAAC;YAChB,YAAY,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import { useEffect, useState, useRef } from \"react\";\nimport { AppState, AppStateStatus } from \"react-native\";\nimport { Orpheus } from \"../ExpoOrpheusModule\";\n\ntype OrpheusSubscription = ReturnType<typeof Orpheus.addListener>;\n\nexport function useProgress() {\n const [progress, setProgress] = useState({\n position: 0,\n duration: 0,\n buffered: 0,\n });\n\n const listenerRef = useRef<null | OrpheusSubscription>(null);\n\n const startListening = () => {\n if (listenerRef.current) return;\n\n listenerRef.current = Orpheus.addListener(\"onPositionUpdate\", (event) => {\n setProgress({\n position: event.position,\n duration: event.duration,\n buffered: event.buffered,\n });\n });\n };\n\n const stopListening = () => {\n if (listenerRef.current) {\n listenerRef.current.remove();\n listenerRef.current = null;\n }\n };\n\n const manualSync = () => {\n Promise.all([\n Orpheus.getPosition(),\n Orpheus.getDuration(),\n Orpheus.getBuffered(),\n ])\n .then(([pos, dur, buf]) => {\n setProgress((prev) => ({\n position: pos,\n duration: dur,\n buffered: buf,\n }));\n })\n .catch((e) => console.warn(\"同步最新进度失败\", e));\n };\n\n useEffect(() => {\n manualSync();\n startListening();\n\n const subscription = AppState.addEventListener(\n \"change\",\n (nextAppState: AppStateStatus) => {\n if (nextAppState === \"active\") {\n manualSync();\n startListening();\n } else {\n stopListening();\n }\n }\n );\n\n return () => {\n stopListening();\n subscription.remove();\n };\n }, []);\n\n return progress;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roitium/expo-orpheus",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "A player for bbplayer",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -57,6 +57,11 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
57
57
  */
58
58
  getDuration(): Promise<number>;
59
59
 
60
+ /**
61
+ * 获取缓冲进度(秒)
62
+ */
63
+ getBuffered(): Promise<number>;
64
+
60
65
  /**
61
66
  * 获取是否正在播放
62
67
  */
@@ -82,6 +87,8 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
82
87
  */
83
88
  getIndexTrack(index: number): Promise<Track | null>;
84
89
 
90
+ getRepeatMode(): Promise<RepeatMode>;
91
+
85
92
  setBilibiliCookie(cookie: string): void;
86
93
 
87
94
  play(): Promise<void>;
@@ -108,7 +115,19 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
108
115
 
109
116
  getQueue(): Promise<Track[]>;
110
117
 
111
- add(tracks: Track[]): Promise<void>;
118
+ /**
119
+ * 添加到队列末尾,且不去重。
120
+ * @param tracks
121
+ */
122
+ addToEnd(tracks: Track[], startFromId?: string): Promise<void>;
123
+
124
+ /**
125
+ * 播放下一首
126
+ * @param track
127
+ */
128
+ playNext(track: Track): Promise<void>;
129
+
130
+ removeTrack(index: number): Promise<void>;
112
131
  }
113
132
 
114
133
  export const Orpheus = requireNativeModule<OrpheusModule>("Orpheus");
@@ -33,12 +33,16 @@ export function useProgress() {
33
33
  };
34
34
 
35
35
  const manualSync = () => {
36
- Promise.all([Orpheus.getPosition(), Orpheus.getDuration()])
37
- .then(([pos, dur]) => {
36
+ Promise.all([
37
+ Orpheus.getPosition(),
38
+ Orpheus.getDuration(),
39
+ Orpheus.getBuffered(),
40
+ ])
41
+ .then(([pos, dur, buf]) => {
38
42
  setProgress((prev) => ({
39
- ...prev,
40
43
  position: pos,
41
44
  duration: dur,
45
+ buffered: buf,
42
46
  }));
43
47
  })
44
48
  .catch((e) => console.warn("同步最新进度失败", e));
@@ -48,7 +52,6 @@ export function useProgress() {
48
52
  manualSync();
49
53
  startListening();
50
54
 
51
- // === 监听 App 前后台切换 ===
52
55
  const subscription = AppState.addEventListener(
53
56
  "change",
54
57
  (nextAppState: AppStateStatus) => {