expo-gliph-player 1.3.14 → 1.3.16
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/com/gliphplayer/GliphPlayerModule.kt +22 -15
- package/android/src/main/java/com/gliphplayer/GliphPlayerService.kt +69 -57
- package/ios/GliphAudioPlayer.swift +72 -13
- package/ios/GliphPlayerModule.mm +17 -3
- package/lib/module/GliphPlayer.js +258 -251
- package/lib/typescript/src/GliphPlayer.d.ts +35 -5
- package/package.json +1 -1
- package/src/GliphPlayer.ts +312 -305
- package/src/specs/NativeGliphPlayer.ts +86 -82
|
@@ -148,33 +148,40 @@ class GliphPlayerModule(
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
@ReactMethod
|
|
152
|
+
override fun skip(index: Double, initialPosition: Double, autoPlay: Boolean, promise: Promise) {
|
|
153
|
+
scope.launch {
|
|
154
|
+
try {
|
|
155
|
+
val pos = if (initialPosition < 0) -1.0 else initialPosition
|
|
156
|
+
playerService?.skip(index.toInt(), pos, autoPlay)
|
|
157
|
+
promise.resolve(null)
|
|
158
|
+
} catch (e: Exception) {
|
|
159
|
+
promise.reject("skip_error", e.message, e)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
158
162
|
}
|
|
159
|
-
}
|
|
160
163
|
|
|
161
164
|
@ReactMethod
|
|
162
|
-
override fun skipToNext(initialPosition: Double, promise: Promise) {
|
|
165
|
+
override fun skipToNext(initialPosition: Double, autoPlay: Boolean, promise: Promise) {
|
|
163
166
|
scope.launch {
|
|
164
167
|
try {
|
|
165
|
-
playerService?.skipToNext(if (initialPosition < 0) -1.0 else initialPosition)
|
|
168
|
+
playerService?.skipToNext(if (initialPosition < 0) -1.0 else initialPosition, autoPlay)
|
|
166
169
|
promise.resolve(null)
|
|
167
|
-
} catch (e: Exception) {
|
|
170
|
+
} catch (e: Exception) {
|
|
171
|
+
promise.reject("skip_error", e.message, e)
|
|
172
|
+
}
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
@ReactMethod
|
|
172
|
-
override fun skipToPrevious(initialPosition: Double, promise: Promise) {
|
|
177
|
+
override fun skipToPrevious(initialPosition: Double, autoPlay: Boolean, promise: Promise) {
|
|
173
178
|
scope.launch {
|
|
174
179
|
try {
|
|
175
|
-
playerService?.skipToPrevious(if (initialPosition < 0) -1.0 else initialPosition)
|
|
180
|
+
playerService?.skipToPrevious(if (initialPosition < 0) -1.0 else initialPosition, autoPlay)
|
|
176
181
|
promise.resolve(null)
|
|
177
|
-
} catch (e: Exception) {
|
|
182
|
+
} catch (e: Exception) {
|
|
183
|
+
promise.reject("skip_error", e.message, e)
|
|
184
|
+
}
|
|
178
185
|
}
|
|
179
186
|
}
|
|
180
187
|
|
|
@@ -357,4 +364,4 @@ class GliphPlayerModule(
|
|
|
357
364
|
}
|
|
358
365
|
|
|
359
366
|
override fun getName() = NAME
|
|
360
|
-
}
|
|
367
|
+
}
|
|
@@ -12,6 +12,7 @@ import android.os.IBinder
|
|
|
12
12
|
import androidx.media3.common.*
|
|
13
13
|
import androidx.media3.common.util.UnstableApi
|
|
14
14
|
import androidx.media3.exoplayer.ExoPlayer
|
|
15
|
+
import androidx.media3.exoplayer.DefaultLoadControl
|
|
15
16
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
|
16
17
|
import androidx.media3.session.*
|
|
17
18
|
import androidx.core.app.NotificationCompat
|
|
@@ -127,8 +128,8 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
127
128
|
when (intent?.action) {
|
|
128
129
|
ACTION_PLAY -> play()
|
|
129
130
|
ACTION_PAUSE -> pause()
|
|
130
|
-
ACTION_SKIP_NEXT -> skipToNext(-1.0)
|
|
131
|
-
ACTION_SKIP_PREVIOUS -> skipToPrevious(-1.0)
|
|
131
|
+
ACTION_SKIP_NEXT -> skipToNext(-1.0, true)
|
|
132
|
+
ACTION_SKIP_PREVIOUS -> skipToPrevious(-1.0, true)
|
|
132
133
|
ACTION_STOP -> {
|
|
133
134
|
stop()
|
|
134
135
|
stopSelf()
|
|
@@ -163,29 +164,39 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
163
164
|
// ── Init ────────────────────────────────────────────────────────────────────
|
|
164
165
|
|
|
165
166
|
private fun initPlayer(opts: ReadableMap? = null) {
|
|
166
|
-
val
|
|
167
|
-
val
|
|
168
|
-
val
|
|
169
|
-
val
|
|
170
|
-
|
|
171
|
-
val
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
167
|
+
val playBufferSec = getDouble(opts, "playBuffer", 2.5)
|
|
168
|
+
val minBufferSec = getDouble(opts, "minBuffer", 15.0)
|
|
169
|
+
val maxBufferSec = getDouble(opts, "maxBuffer", 50.0)
|
|
170
|
+
val backBufferSec = getDouble(opts, "backBuffer", 0.0)
|
|
171
|
+
|
|
172
|
+
val playBufferMs = (playBufferSec * 1000).toInt() // 2500ms
|
|
173
|
+
var minBufferMs = (minBufferSec * 1000).toInt() // 15000ms
|
|
174
|
+
val maxBufferMs = (maxBufferSec * 1000).toInt() // 50000ms
|
|
175
|
+
val backBufferMs = (backBufferSec * 1000).toInt() // 0ms
|
|
176
|
+
|
|
177
|
+
if (minBufferMs < playBufferMs) {
|
|
178
|
+
Log.w("GliphPlayer", "minBufferMs ($minBufferMs) < playBufferMs ($playBufferMs), adjusting...")
|
|
179
|
+
minBufferMs = playBufferMs
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
val loadControl = DefaultLoadControl.Builder()
|
|
183
|
+
.setBufferDurationsMs(minBufferMs, maxBufferMs, playBufferMs, playBufferMs)
|
|
184
|
+
.setBackBuffer(backBufferMs, false)
|
|
185
|
+
.build()
|
|
175
186
|
|
|
176
187
|
player = ExoPlayer.Builder(this)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
.setMediaSourceFactory(DefaultMediaSourceFactory(this))
|
|
189
|
+
.setLoadControl(loadControl)
|
|
190
|
+
.setHandleAudioBecomingNoisy(true)
|
|
191
|
+
.setWakeMode(C.WAKE_MODE_NETWORK)
|
|
192
|
+
.setAudioAttributes(
|
|
193
|
+
AudioAttributes.Builder()
|
|
194
|
+
.setUsage(C.USAGE_MEDIA)
|
|
195
|
+
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
|
|
196
|
+
.build(),
|
|
197
|
+
true
|
|
198
|
+
)
|
|
199
|
+
.build()
|
|
189
200
|
|
|
190
201
|
player.addListener(playerListener)
|
|
191
202
|
|
|
@@ -384,7 +395,7 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
384
395
|
insertBeforeIndex
|
|
385
396
|
}
|
|
386
397
|
|
|
387
|
-
val wasEmpty = queue.isEmpty()
|
|
398
|
+
val wasEmpty = queue.isEmpty()
|
|
388
399
|
val mediaItems = mutableListOf<MediaItem>()
|
|
389
400
|
|
|
390
401
|
for (i in 0 until tracks.size()) {
|
|
@@ -400,7 +411,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
400
411
|
player.addMediaItems(insertBeforeIndex, mediaItems)
|
|
401
412
|
}
|
|
402
413
|
|
|
403
|
-
// ✅ Если очередь была пуста - отправляем событие
|
|
404
414
|
if (wasEmpty && queue.isNotEmpty()) {
|
|
405
415
|
emitActiveTrackChanged()
|
|
406
416
|
}
|
|
@@ -432,42 +442,57 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
432
442
|
}
|
|
433
443
|
}
|
|
434
444
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
val lastIndex = player.currentMediaItemIndex
|
|
438
|
-
player.seekTo(index, if (initialPosition >= 0) (initialPosition * 1000).toLong() else C.TIME_UNSET)
|
|
439
|
-
player.play()
|
|
445
|
+
fun skip(index: Int, initialPosition: Double, autoPlay: Boolean = true) {
|
|
446
|
+
val lastIndex = player.currentMediaItemIndex
|
|
440
447
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
448
|
+
if (index < 0 || index >= queue.size) {
|
|
449
|
+
Log.e("GliphPlayer", "skip: index $index out of bounds")
|
|
450
|
+
return
|
|
451
|
+
}
|
|
445
452
|
|
|
446
|
-
|
|
447
|
-
|
|
453
|
+
player.seekTo(index, if (initialPosition >= 0) (initialPosition * 1000).toLong() else C.TIME_UNSET)
|
|
454
|
+
|
|
455
|
+
if (autoPlay) {
|
|
456
|
+
player.playWhenReady = true
|
|
457
|
+
|
|
458
|
+
if (player.playbackState == Player.STATE_IDLE) {
|
|
459
|
+
player.prepare()
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
player.play()
|
|
463
|
+
emitPlaybackState("playing")
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
emitActiveTrackChanged(lastIndex)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
fun skipToNext(initialPosition: Double, autoPlay: Boolean = true) {
|
|
448
470
|
if (player.hasNextMediaItem()) {
|
|
449
471
|
val lastIndex = player.currentMediaItemIndex
|
|
450
472
|
player.seekToNextMediaItem()
|
|
451
473
|
if (initialPosition >= 0) player.seekTo((initialPosition * 1000).toLong())
|
|
452
|
-
player.play()
|
|
453
474
|
|
|
454
|
-
|
|
475
|
+
if (autoPlay) {
|
|
476
|
+
player.play()
|
|
477
|
+
}
|
|
478
|
+
|
|
455
479
|
emitActiveTrackChanged(lastIndex)
|
|
456
|
-
emitPlaybackState("playing")
|
|
480
|
+
emitPlaybackState(if (autoPlay) "playing" else "paused")
|
|
457
481
|
}
|
|
458
482
|
}
|
|
459
483
|
|
|
460
|
-
|
|
461
|
-
fun skipToPrevious(initialPosition: Double) {
|
|
484
|
+
fun skipToPrevious(initialPosition: Double, autoPlay: Boolean = true) {
|
|
462
485
|
if (player.hasPreviousMediaItem()) {
|
|
463
486
|
val lastIndex = player.currentMediaItemIndex
|
|
464
487
|
player.seekToPreviousMediaItem()
|
|
465
488
|
if (initialPosition >= 0) player.seekTo((initialPosition * 1000).toLong())
|
|
466
|
-
player.play()
|
|
467
489
|
|
|
468
|
-
|
|
490
|
+
if (autoPlay) {
|
|
491
|
+
player.play()
|
|
492
|
+
}
|
|
493
|
+
|
|
469
494
|
emitActiveTrackChanged(lastIndex)
|
|
470
|
-
emitPlaybackState("playing")
|
|
495
|
+
emitPlaybackState(if (autoPlay) "playing" else "paused")
|
|
471
496
|
}
|
|
472
497
|
}
|
|
473
498
|
|
|
@@ -480,17 +505,14 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
480
505
|
|
|
481
506
|
// ── Playback control ────────────────────────────────────────────────────────
|
|
482
507
|
|
|
483
|
-
// ✅ ИСПРАВЛЕНО: Добавлены события
|
|
484
508
|
fun play() {
|
|
485
509
|
if (player.playbackState == Player.STATE_IDLE) {
|
|
486
510
|
player.prepare()
|
|
487
511
|
}
|
|
488
512
|
player.play()
|
|
489
513
|
|
|
490
|
-
// ✅ Отправляем состояние
|
|
491
514
|
emitPlaybackState("playing")
|
|
492
515
|
|
|
493
|
-
// ✅ Если есть активный трек - отправляем его
|
|
494
516
|
if (player.currentMediaItemIndex >= 0 && player.currentMediaItemIndex < queue.size) {
|
|
495
517
|
emitActiveTrackChanged()
|
|
496
518
|
}
|
|
@@ -498,34 +520,28 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
498
520
|
updateMediaNotification()
|
|
499
521
|
}
|
|
500
522
|
|
|
501
|
-
// ✅ ИСПРАВЛЕНО: Добавлено событие
|
|
502
523
|
fun pause() {
|
|
503
524
|
player.pause()
|
|
504
525
|
|
|
505
|
-
// ✅ Отправляем состояние
|
|
506
526
|
emitPlaybackState("paused")
|
|
507
527
|
|
|
508
528
|
updateMediaNotification()
|
|
509
529
|
}
|
|
510
530
|
|
|
511
|
-
// ✅ ИСПРАВЛЕНО: Добавлено событие
|
|
512
531
|
fun stop() {
|
|
513
532
|
player.stop()
|
|
514
533
|
|
|
515
|
-
// ✅ Отправляем состояние
|
|
516
534
|
emitPlaybackState("stopped")
|
|
517
535
|
|
|
518
536
|
updateMediaNotification()
|
|
519
537
|
}
|
|
520
538
|
|
|
521
|
-
// ✅ ИСПРАВЛЕНО: Добавлены события
|
|
522
539
|
fun reset() {
|
|
523
540
|
val lastIndex = player.currentMediaItemIndex
|
|
524
541
|
player.stop()
|
|
525
542
|
player.clearMediaItems()
|
|
526
543
|
queue.clear()
|
|
527
544
|
|
|
528
|
-
// ✅ Отправляем событие об очистке
|
|
529
545
|
val map = Arguments.createMap()
|
|
530
546
|
map.putInt("index", -1)
|
|
531
547
|
map.putNull("track")
|
|
@@ -766,12 +782,10 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
766
782
|
updateMediaNotification()
|
|
767
783
|
|
|
768
784
|
if (playbackState == Player.STATE_ENDED) {
|
|
769
|
-
// ✅ Отправляем track-ended
|
|
770
785
|
val map = Arguments.createMap()
|
|
771
786
|
map.putInt("index", player.currentMediaItemIndex)
|
|
772
787
|
eventEmitter?.invoke("playback-track-ended", map)
|
|
773
788
|
|
|
774
|
-
// ✅ Отправляем queue-ended
|
|
775
789
|
val queueMap = Arguments.createMap()
|
|
776
790
|
queueMap.putInt("index", player.currentMediaItemIndex)
|
|
777
791
|
queueMap.putDouble("position", player.currentPosition / 1000.0)
|
|
@@ -785,7 +799,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
785
799
|
}
|
|
786
800
|
|
|
787
801
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
|
788
|
-
// ✅ Отправляем track-ended для предыдущего трека
|
|
789
802
|
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO ||
|
|
790
803
|
reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) {
|
|
791
804
|
val endedMap = Arguments.createMap()
|
|
@@ -811,7 +824,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
811
824
|
map.putNull("lastTrack")
|
|
812
825
|
}
|
|
813
826
|
|
|
814
|
-
// ✅ Отправляем active-track-changed
|
|
815
827
|
eventEmitter?.invoke("playback-active-track-changed", map)
|
|
816
828
|
lastIndex = newIndex
|
|
817
829
|
updateMediaNotification()
|
|
@@ -20,12 +20,16 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
20
20
|
private var playerItems: [AVPlayerItem] = []
|
|
21
21
|
private var queue: [[String: Any]] = []
|
|
22
22
|
private var currentIndex: Int = -1
|
|
23
|
-
private var repeatMode: Int = 0
|
|
23
|
+
private var repeatMode: Int = 0 // 0=off, 1=track, 2=queue
|
|
24
24
|
private var isSetup = false
|
|
25
25
|
private var progressTimer: Timer?
|
|
26
26
|
private var progressInterval: TimeInterval = 1.0
|
|
27
27
|
private var options: [String: Any] = [:]
|
|
28
28
|
|
|
29
|
+
// ✅ AVPlayerLooper для повтора трека
|
|
30
|
+
private var playerLooper: AVPlayerLooper?
|
|
31
|
+
private var templateItem: AVPlayerItem?
|
|
32
|
+
|
|
29
33
|
private let eventEmitter: EventEmitter
|
|
30
34
|
private var timeObserver: Any?
|
|
31
35
|
private var playerObservations: [NSKeyValueObservation] = []
|
|
@@ -162,6 +166,7 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
162
166
|
@objc public func skipToIndex(
|
|
163
167
|
_ index: Int,
|
|
164
168
|
initialPosition: Double,
|
|
169
|
+
autoPlay: Bool = true,
|
|
165
170
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
166
171
|
reject: @escaping RCTPromiseRejectBlock
|
|
167
172
|
) {
|
|
@@ -176,7 +181,10 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
176
181
|
if initialPosition >= 0 {
|
|
177
182
|
player?.seek(to: CMTime(seconds: initialPosition, preferredTimescale: 1000))
|
|
178
183
|
}
|
|
179
|
-
|
|
184
|
+
|
|
185
|
+
if autoPlay {
|
|
186
|
+
player?.play()
|
|
187
|
+
}
|
|
180
188
|
|
|
181
189
|
emitActiveTrackChanged(lastIndex: lastIndex)
|
|
182
190
|
|
|
@@ -185,6 +193,7 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
185
193
|
|
|
186
194
|
@objc public func skipToNext(
|
|
187
195
|
initialPosition: Double,
|
|
196
|
+
autoPlay: Bool = true,
|
|
188
197
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
189
198
|
reject: @escaping RCTPromiseRejectBlock
|
|
190
199
|
) {
|
|
@@ -192,11 +201,12 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
192
201
|
guard next < queue.count else {
|
|
193
202
|
reject("skip_error", "No next track", nil); return
|
|
194
203
|
}
|
|
195
|
-
skipToIndex(next, initialPosition: initialPosition, resolve: resolve, reject: reject)
|
|
204
|
+
skipToIndex(next, initialPosition: initialPosition, autoPlay: autoPlay, resolve: resolve, reject: reject)
|
|
196
205
|
}
|
|
197
206
|
|
|
198
207
|
@objc public func skipToPrevious(
|
|
199
208
|
initialPosition: Double,
|
|
209
|
+
autoPlay: Bool = true,
|
|
200
210
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
201
211
|
reject: @escaping RCTPromiseRejectBlock
|
|
202
212
|
) {
|
|
@@ -204,7 +214,7 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
204
214
|
guard prev >= 0 else {
|
|
205
215
|
reject("skip_error", "No previous track", nil); return
|
|
206
216
|
}
|
|
207
|
-
skipToIndex(prev, initialPosition: initialPosition, resolve: resolve, reject: reject)
|
|
217
|
+
skipToIndex(prev, initialPosition: initialPosition, autoPlay: autoPlay, resolve: resolve, reject: reject)
|
|
208
218
|
}
|
|
209
219
|
|
|
210
220
|
@objc public func moveFrom(
|
|
@@ -254,6 +264,12 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
254
264
|
|
|
255
265
|
@objc public func reset(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
256
266
|
player?.pause()
|
|
267
|
+
|
|
268
|
+
// ✅ Уничтожаем looper
|
|
269
|
+
playerLooper?.disableLooping()
|
|
270
|
+
playerLooper = nil
|
|
271
|
+
templateItem = nil
|
|
272
|
+
|
|
257
273
|
player?.removeAllItems()
|
|
258
274
|
queue.removeAll()
|
|
259
275
|
playerItems.removeAll()
|
|
@@ -298,8 +314,23 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
298
314
|
|
|
299
315
|
@objc public func getRate() -> Float { return player?.rate ?? 1.0 }
|
|
300
316
|
|
|
317
|
+
// ✅ ИСПРАВЛЕНО: пересоздаём очередь при смене режима
|
|
301
318
|
@objc public func setRepeatMode(_ mode: Int, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
319
|
+
let oldMode = repeatMode
|
|
302
320
|
repeatMode = mode
|
|
321
|
+
|
|
322
|
+
// ✅ Пересоздаём очередь с учётом нового режима
|
|
323
|
+
if oldMode != mode {
|
|
324
|
+
let wasPlaying = player?.timeControlStatus == .playing
|
|
325
|
+
rebuildPlayerQueue()
|
|
326
|
+
if wasPlaying {
|
|
327
|
+
player?.play()
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ✅ Отправляем событие
|
|
332
|
+
eventEmitter("playback-repeat-mode-changed", ["mode": mode])
|
|
333
|
+
|
|
303
334
|
resolve(nil)
|
|
304
335
|
}
|
|
305
336
|
|
|
@@ -414,12 +445,40 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
414
445
|
])
|
|
415
446
|
}
|
|
416
447
|
|
|
448
|
+
// ✅ ИСПРАВЛЕНО: добавлен AVPlayerLooper для повторения трека
|
|
417
449
|
private func rebuildPlayerQueue() {
|
|
418
450
|
guard let player = player else { return }
|
|
451
|
+
|
|
452
|
+
// ✅ Уничтожаем старый looper
|
|
453
|
+
playerLooper?.disableLooping()
|
|
454
|
+
playerLooper = nil
|
|
455
|
+
templateItem = nil
|
|
456
|
+
|
|
419
457
|
player.removeAllItems()
|
|
420
458
|
playerItems.removeAll()
|
|
421
459
|
|
|
422
460
|
let startIndex = max(0, currentIndex)
|
|
461
|
+
|
|
462
|
+
// ✅ РЕЖИМ ПОВТОРА ТРЕКА (RepeatMode.Track = 1)
|
|
463
|
+
if repeatMode == 1 && startIndex < queue.count {
|
|
464
|
+
let track = queue[startIndex]
|
|
465
|
+
guard let urlString = track["url"] as? String,
|
|
466
|
+
let url = URL(string: urlString) else { return }
|
|
467
|
+
|
|
468
|
+
// ✅ Создаём template item
|
|
469
|
+
let item = AVPlayerItem(url: url)
|
|
470
|
+
templateItem = item
|
|
471
|
+
playerItems.append(item)
|
|
472
|
+
|
|
473
|
+
// ✅ Создаём looper для бесконечного повтора
|
|
474
|
+
playerLooper = AVPlayerLooper(player: player, templateItem: item)
|
|
475
|
+
|
|
476
|
+
currentIndex = startIndex
|
|
477
|
+
updateNowPlayingInfo()
|
|
478
|
+
return
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ✅ ОБЫЧНАЯ ОЧЕРЕДЬ (RepeatMode.Off / RepeatMode.Queue)
|
|
423
482
|
for i in startIndex..<queue.count {
|
|
424
483
|
let track = queue[i]
|
|
425
484
|
guard let urlString = track["url"] as? String,
|
|
@@ -528,7 +587,7 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
528
587
|
guard let self = self else { return .commandFailed }
|
|
529
588
|
let next = self.currentIndex + 1
|
|
530
589
|
if next < self.queue.count {
|
|
531
|
-
self.skipToIndex(next, initialPosition: -1, resolve: { _ in }, reject: { _, _, _ in })
|
|
590
|
+
self.skipToIndex(next, initialPosition: -1, autoPlay: true, resolve: { _ in }, reject: { _, _, _ in })
|
|
532
591
|
}
|
|
533
592
|
self.eventEmitter("remote-next", nil)
|
|
534
593
|
return .success
|
|
@@ -540,7 +599,7 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
540
599
|
guard let self = self else { return .commandFailed }
|
|
541
600
|
let prev = self.currentIndex - 1
|
|
542
601
|
if prev >= 0 {
|
|
543
|
-
self.skipToIndex(prev, initialPosition: -1, resolve: { _ in }, reject: { _, _, _ in })
|
|
602
|
+
self.skipToIndex(prev, initialPosition: -1, autoPlay: true, resolve: { _ in }, reject: { _, _, _ in })
|
|
544
603
|
}
|
|
545
604
|
self.eventEmitter("remote-previous", nil)
|
|
546
605
|
return .success
|
|
@@ -626,25 +685,24 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
626
685
|
}
|
|
627
686
|
}
|
|
628
687
|
|
|
688
|
+
// ✅ ИСПРАВЛЕНО: AVPlayerLooper сам обрабатывает повтор трека
|
|
629
689
|
@objc private func playerItemDidFinish(_ notification: Notification) {
|
|
630
690
|
guard let finishedItem = notification.object as? AVPlayerItem,
|
|
631
691
|
finishedItem === player?.currentItem else { return }
|
|
632
692
|
|
|
693
|
+
// ✅ Если режим повтора трека - AVPlayerLooper сам обрабатывает, выходим
|
|
694
|
+
if repeatMode == 1 {
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
|
|
633
698
|
let nextIndex = currentIndex + 1
|
|
634
699
|
let currentPosition = CMTimeGetSeconds(player?.currentTime() ?? .zero)
|
|
635
700
|
|
|
636
|
-
// ✅ 1. Отправляем событие окончания ТЕКУЩЕГО трека
|
|
637
701
|
eventEmitter("playback-track-ended", [
|
|
638
702
|
"index": currentIndex,
|
|
639
703
|
"position": currentPosition
|
|
640
704
|
])
|
|
641
705
|
|
|
642
|
-
if repeatMode == 1 {
|
|
643
|
-
player?.seek(to: .zero)
|
|
644
|
-
player?.play()
|
|
645
|
-
return
|
|
646
|
-
}
|
|
647
|
-
|
|
648
706
|
if nextIndex < queue.count {
|
|
649
707
|
currentIndex = nextIndex
|
|
650
708
|
updateNowPlayingInfo()
|
|
@@ -655,6 +713,7 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
655
713
|
"lastPosition": 0.0,
|
|
656
714
|
])
|
|
657
715
|
} else if repeatMode == 2 {
|
|
716
|
+
// ✅ ПОВТОР ВСЕЙ ОЧЕРЕДИ
|
|
658
717
|
currentIndex = 0
|
|
659
718
|
rebuildPlayerQueue()
|
|
660
719
|
player?.play()
|
package/ios/GliphPlayerModule.mm
CHANGED
|
@@ -41,6 +41,7 @@ RCT_EXPORT_MODULE(RNGliphPlayer)
|
|
|
41
41
|
@"playback-queue-ended",
|
|
42
42
|
@"playback-progress-updated",
|
|
43
43
|
@"playback-metadata-received",
|
|
44
|
+
@"playback-repeat-mode-changed",
|
|
44
45
|
@"remote-play",
|
|
45
46
|
@"remote-pause",
|
|
46
47
|
@"remote-stop",
|
|
@@ -96,21 +97,34 @@ RCT_EXPORT_METHOD(removeUpcomingTracks:(RCTPromiseResolveBlock)resolve
|
|
|
96
97
|
|
|
97
98
|
RCT_EXPORT_METHOD(skip:(double)index
|
|
98
99
|
initialPosition:(double)initialPosition
|
|
100
|
+
autoPlay:(BOOL)autoPlay
|
|
99
101
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
100
102
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
101
|
-
[_player skipToIndex:(NSInteger)index
|
|
103
|
+
[_player skipToIndex:(NSInteger)index
|
|
104
|
+
initialPosition:initialPosition
|
|
105
|
+
autoPlay:autoPlay
|
|
106
|
+
resolve:resolve
|
|
107
|
+
reject:reject];
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
RCT_EXPORT_METHOD(skipToNext:(double)initialPosition
|
|
111
|
+
autoPlay:(BOOL)autoPlay
|
|
105
112
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
106
113
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
107
|
-
[_player skipToNextWithInitialPosition:initialPosition
|
|
114
|
+
[_player skipToNextWithInitialPosition:initialPosition
|
|
115
|
+
autoPlay:autoPlay
|
|
116
|
+
resolve:resolve
|
|
117
|
+
reject:reject];
|
|
108
118
|
}
|
|
109
119
|
|
|
110
120
|
RCT_EXPORT_METHOD(skipToPrevious:(double)initialPosition
|
|
121
|
+
autoPlay:(BOOL)autoPlay
|
|
111
122
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
112
123
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
113
|
-
[_player skipToPreviousWithInitialPosition:initialPosition
|
|
124
|
+
[_player skipToPreviousWithInitialPosition:initialPosition
|
|
125
|
+
autoPlay:autoPlay
|
|
126
|
+
resolve:resolve
|
|
127
|
+
reject:reject];
|
|
114
128
|
}
|
|
115
129
|
|
|
116
130
|
RCT_EXPORT_METHOD(move:(double)fromIndex
|