expo-gliph-player 1.3.16 → 1.3.18
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/ios/GliphAudioPlayer.swift +51 -49
- package/ios/GliphPlayerModule.mm +0 -2
- package/package.json +1 -1
|
@@ -2,47 +2,37 @@ import AVFoundation
|
|
|
2
2
|
import MediaPlayer
|
|
3
3
|
import UIKit
|
|
4
4
|
|
|
5
|
-
// MARK: - Type aliases
|
|
6
|
-
|
|
7
5
|
public typealias EventEmitter = (String, [String: Any]?) -> Void
|
|
8
6
|
|
|
9
|
-
// React Native's promise block types
|
|
10
7
|
public typealias RCTPromiseResolveBlock = (Any?) -> Void
|
|
11
8
|
public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
12
9
|
|
|
13
|
-
// MARK: - GliphAudioPlayer
|
|
14
|
-
|
|
15
10
|
@objc public class GliphAudioPlayer: NSObject {
|
|
16
11
|
|
|
17
|
-
// ── Properties ──────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
12
|
private var player: AVQueuePlayer?
|
|
20
13
|
private var playerItems: [AVPlayerItem] = []
|
|
21
14
|
private var queue: [[String: Any]] = []
|
|
22
15
|
private var currentIndex: Int = -1
|
|
23
|
-
private var repeatMode: Int = 0
|
|
16
|
+
private var repeatMode: Int = 0
|
|
24
17
|
private var isSetup = false
|
|
25
18
|
private var progressTimer: Timer?
|
|
26
19
|
private var progressInterval: TimeInterval = 1.0
|
|
27
20
|
private var options: [String: Any] = [:]
|
|
28
21
|
|
|
29
|
-
// ✅ AVPlayerLooper для повтора трека
|
|
30
22
|
private var playerLooper: AVPlayerLooper?
|
|
31
23
|
private var templateItem: AVPlayerItem?
|
|
24
|
+
private var lastLooperTime: Double = 0
|
|
25
|
+
private var looperTimeObserver: Any?
|
|
32
26
|
|
|
33
27
|
private let eventEmitter: EventEmitter
|
|
34
28
|
private var timeObserver: Any?
|
|
35
29
|
private var playerObservations: [NSKeyValueObservation] = []
|
|
36
30
|
|
|
37
|
-
// ── Init ────────────────────────────────────────────────────────────────────
|
|
38
|
-
|
|
39
31
|
@objc public init(eventEmitter: @escaping EventEmitter) {
|
|
40
32
|
self.eventEmitter = eventEmitter
|
|
41
33
|
super.init()
|
|
42
34
|
}
|
|
43
35
|
|
|
44
|
-
// ── Thread helper ───────────────────────────────────────────────────────────
|
|
45
|
-
|
|
46
36
|
private func runOnMain(_ block: () -> Void) {
|
|
47
37
|
if Thread.isMainThread {
|
|
48
38
|
block()
|
|
@@ -51,8 +41,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
51
41
|
}
|
|
52
42
|
}
|
|
53
43
|
|
|
54
|
-
// ── Setup ───────────────────────────────────────────────────────────────────
|
|
55
|
-
|
|
56
44
|
@objc public func setupPlayer(
|
|
57
45
|
options: [String: Any],
|
|
58
46
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
@@ -99,6 +87,16 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
99
87
|
@objc public func destroy() {
|
|
100
88
|
stopProgressTimer()
|
|
101
89
|
removeObservers()
|
|
90
|
+
|
|
91
|
+
if let obs = looperTimeObserver {
|
|
92
|
+
player?.removeTimeObserver(obs)
|
|
93
|
+
looperTimeObserver = nil
|
|
94
|
+
}
|
|
95
|
+
playerLooper?.disableLooping()
|
|
96
|
+
playerLooper = nil
|
|
97
|
+
templateItem = nil
|
|
98
|
+
lastLooperTime = 0
|
|
99
|
+
|
|
102
100
|
player?.pause()
|
|
103
101
|
player?.removeAllItems()
|
|
104
102
|
player = nil
|
|
@@ -114,8 +112,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
114
112
|
|
|
115
113
|
@objc public func isReady() -> Bool { return isSetup }
|
|
116
114
|
|
|
117
|
-
// ── Queue management ────────────────────────────────────────────────────────
|
|
118
|
-
|
|
119
115
|
@objc public func addTracks(
|
|
120
116
|
_ tracks: [[String: Any]],
|
|
121
117
|
insertBeforeIndex: Int,
|
|
@@ -233,8 +229,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
233
229
|
resolve(nil)
|
|
234
230
|
}
|
|
235
231
|
|
|
236
|
-
// ── Playback control ────────────────────────────────────────────────────────
|
|
237
|
-
|
|
238
232
|
@objc public func play(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
239
233
|
player?.play()
|
|
240
234
|
startProgressTimer()
|
|
@@ -265,10 +259,14 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
265
259
|
@objc public func reset(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
266
260
|
player?.pause()
|
|
267
261
|
|
|
268
|
-
|
|
262
|
+
if let obs = looperTimeObserver {
|
|
263
|
+
player?.removeTimeObserver(obs)
|
|
264
|
+
looperTimeObserver = nil
|
|
265
|
+
}
|
|
269
266
|
playerLooper?.disableLooping()
|
|
270
267
|
playerLooper = nil
|
|
271
268
|
templateItem = nil
|
|
269
|
+
lastLooperTime = 0
|
|
272
270
|
|
|
273
271
|
player?.removeAllItems()
|
|
274
272
|
queue.removeAll()
|
|
@@ -314,21 +312,29 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
314
312
|
|
|
315
313
|
@objc public func getRate() -> Float { return player?.rate ?? 1.0 }
|
|
316
314
|
|
|
317
|
-
// ✅ ИСПРАВЛЕНО: пересоздаём очередь при смене режима
|
|
318
315
|
@objc public func setRepeatMode(_ mode: Int, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
319
316
|
let oldMode = repeatMode
|
|
320
317
|
repeatMode = mode
|
|
321
318
|
|
|
322
|
-
|
|
323
|
-
|
|
319
|
+
let oldNeedsLooper = oldMode == 1
|
|
320
|
+
let newNeedsLooper = mode == 1
|
|
321
|
+
|
|
322
|
+
if oldNeedsLooper != newNeedsLooper {
|
|
324
323
|
let wasPlaying = player?.timeControlStatus == .playing
|
|
324
|
+
let savedPosition = CMTimeGetSeconds(player?.currentTime() ?? .zero)
|
|
325
|
+
let savedIndex = currentIndex
|
|
326
|
+
|
|
325
327
|
rebuildPlayerQueue()
|
|
328
|
+
|
|
329
|
+
currentIndex = savedIndex
|
|
330
|
+
if savedPosition > 0 {
|
|
331
|
+
player?.seek(to: CMTime(seconds: savedPosition, preferredTimescale: 1000))
|
|
332
|
+
}
|
|
326
333
|
if wasPlaying {
|
|
327
334
|
player?.play()
|
|
328
335
|
}
|
|
329
336
|
}
|
|
330
337
|
|
|
331
|
-
// ✅ Отправляем событие
|
|
332
338
|
eventEmitter("playback-repeat-mode-changed", ["mode": mode])
|
|
333
339
|
|
|
334
340
|
resolve(nil)
|
|
@@ -336,8 +342,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
336
342
|
|
|
337
343
|
@objc public func getRepeatMode() -> Int { return repeatMode }
|
|
338
344
|
|
|
339
|
-
// ── Queue getters ───────────────────────────────────────────────────────────
|
|
340
|
-
|
|
341
345
|
@objc public func getQueue() -> [[String: Any]] { return queue }
|
|
342
346
|
|
|
343
347
|
@objc public func getActiveTrackIndex() -> NSNumber? {
|
|
@@ -357,8 +361,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
357
361
|
|
|
358
362
|
@objc public func getQueueSize() -> Int { return queue.count }
|
|
359
363
|
|
|
360
|
-
// ── State / progress ────────────────────────────────────────────────────────
|
|
361
|
-
|
|
362
364
|
@objc public func getPlaybackState() -> [String: Any] {
|
|
363
365
|
return ["state": currentStateString()]
|
|
364
366
|
}
|
|
@@ -380,8 +382,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
380
382
|
]
|
|
381
383
|
}
|
|
382
384
|
|
|
383
|
-
// ── Metadata ────────────────────────────────────────────────────────────────
|
|
384
|
-
|
|
385
385
|
@objc public func updateMetadataForTrack(
|
|
386
386
|
_ index: Int,
|
|
387
387
|
metadata: [String: Any],
|
|
@@ -431,8 +431,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
431
431
|
resolve(nil)
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
-
// ── Private helpers ─────────────────────────────────────────────────────────
|
|
435
|
-
|
|
436
434
|
private func emitActiveTrackChanged(lastIndex: Int = -1) {
|
|
437
435
|
let lastPos = CMTimeGetSeconds(player?.currentTime() ?? .zero)
|
|
438
436
|
let track = (currentIndex >= 0 && currentIndex < queue.count) ? queue[currentIndex] : nil
|
|
@@ -445,40 +443,55 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
445
443
|
])
|
|
446
444
|
}
|
|
447
445
|
|
|
448
|
-
// ✅ ИСПРАВЛЕНО: добавлен AVPlayerLooper для повторения трека
|
|
449
446
|
private func rebuildPlayerQueue() {
|
|
450
447
|
guard let player = player else { return }
|
|
451
448
|
|
|
452
|
-
|
|
449
|
+
if let obs = looperTimeObserver {
|
|
450
|
+
player.removeTimeObserver(obs)
|
|
451
|
+
looperTimeObserver = nil
|
|
452
|
+
}
|
|
453
453
|
playerLooper?.disableLooping()
|
|
454
454
|
playerLooper = nil
|
|
455
455
|
templateItem = nil
|
|
456
|
+
lastLooperTime = 0
|
|
456
457
|
|
|
457
458
|
player.removeAllItems()
|
|
458
459
|
playerItems.removeAll()
|
|
459
460
|
|
|
460
461
|
let startIndex = max(0, currentIndex)
|
|
461
462
|
|
|
462
|
-
// ✅ РЕЖИМ ПОВТОРА ТРЕКА (RepeatMode.Track = 1)
|
|
463
463
|
if repeatMode == 1 && startIndex < queue.count {
|
|
464
464
|
let track = queue[startIndex]
|
|
465
465
|
guard let urlString = track["url"] as? String,
|
|
466
466
|
let url = URL(string: urlString) else { return }
|
|
467
467
|
|
|
468
|
-
// ✅ Создаём template item
|
|
469
468
|
let item = AVPlayerItem(url: url)
|
|
470
469
|
templateItem = item
|
|
471
470
|
playerItems.append(item)
|
|
472
471
|
|
|
473
|
-
// ✅ Создаём looper для бесконечного повтора
|
|
474
472
|
playerLooper = AVPlayerLooper(player: player, templateItem: item)
|
|
475
473
|
|
|
474
|
+
let interval = CMTime(seconds: 0.1, preferredTimescale: 1000)
|
|
475
|
+
looperTimeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
|
|
476
|
+
guard let self = self else { return }
|
|
477
|
+
let timeSeconds = CMTimeGetSeconds(time)
|
|
478
|
+
guard !timeSeconds.isNaN else { return }
|
|
479
|
+
|
|
480
|
+
if self.lastLooperTime > 1.0 && timeSeconds < 0.5 {
|
|
481
|
+
self.eventEmitter("playback-track-ended", [
|
|
482
|
+
"index": self.currentIndex,
|
|
483
|
+
"position": self.lastLooperTime,
|
|
484
|
+
])
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
self.lastLooperTime = timeSeconds
|
|
488
|
+
}
|
|
489
|
+
|
|
476
490
|
currentIndex = startIndex
|
|
477
491
|
updateNowPlayingInfo()
|
|
478
492
|
return
|
|
479
493
|
}
|
|
480
494
|
|
|
481
|
-
// ✅ ОБЫЧНАЯ ОЧЕРЕДЬ (RepeatMode.Off / RepeatMode.Queue)
|
|
482
495
|
for i in startIndex..<queue.count {
|
|
483
496
|
let track = queue[i]
|
|
484
497
|
guard let urlString = track["url"] as? String,
|
|
@@ -547,8 +560,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
547
560
|
}
|
|
548
561
|
}
|
|
549
562
|
|
|
550
|
-
// ── Remote commands ─────────────────────────────────────────────────────────
|
|
551
|
-
|
|
552
563
|
private func setupRemoteCommands() {
|
|
553
564
|
let center = MPRemoteCommandCenter.shared()
|
|
554
565
|
UIApplication.shared.beginReceivingRemoteControlEvents()
|
|
@@ -638,8 +649,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
638
649
|
}
|
|
639
650
|
}
|
|
640
651
|
|
|
641
|
-
// ── Observers ───────────────────────────────────────────────────────────────
|
|
642
|
-
|
|
643
652
|
private func setupNotificationObservers() {
|
|
644
653
|
NotificationCenter.default.addObserver(
|
|
645
654
|
self,
|
|
@@ -685,12 +694,10 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
685
694
|
}
|
|
686
695
|
}
|
|
687
696
|
|
|
688
|
-
// ✅ ИСПРАВЛЕНО: AVPlayerLooper сам обрабатывает повтор трека
|
|
689
697
|
@objc private func playerItemDidFinish(_ notification: Notification) {
|
|
690
698
|
guard let finishedItem = notification.object as? AVPlayerItem,
|
|
691
699
|
finishedItem === player?.currentItem else { return }
|
|
692
700
|
|
|
693
|
-
// ✅ Если режим повтора трека - AVPlayerLooper сам обрабатывает, выходим
|
|
694
701
|
if repeatMode == 1 {
|
|
695
702
|
return
|
|
696
703
|
}
|
|
@@ -713,7 +720,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
713
720
|
"lastPosition": 0.0,
|
|
714
721
|
])
|
|
715
722
|
} else if repeatMode == 2 {
|
|
716
|
-
// ✅ ПОВТОР ВСЕЙ ОЧЕРЕДИ
|
|
717
723
|
currentIndex = 0
|
|
718
724
|
rebuildPlayerQueue()
|
|
719
725
|
player?.play()
|
|
@@ -762,8 +768,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
762
768
|
if reason == .oldDeviceUnavailable { player?.pause() }
|
|
763
769
|
}
|
|
764
770
|
|
|
765
|
-
// ── Progress timer ──────────────────────────────────────────────────────────
|
|
766
|
-
|
|
767
771
|
private func startProgressTimer() {
|
|
768
772
|
stopProgressTimer()
|
|
769
773
|
progressTimer = Timer.scheduledTimer(withTimeInterval: progressInterval, repeats: true) { [weak self] _ in
|
|
@@ -781,8 +785,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
781
785
|
progressTimer = nil
|
|
782
786
|
}
|
|
783
787
|
|
|
784
|
-
// ── Audio session mapping ───────────────────────────────────────────────────
|
|
785
|
-
|
|
786
788
|
private func mapIOSCategory(_ string: String?) -> AVAudioSession.Category {
|
|
787
789
|
switch string {
|
|
788
790
|
case "playAndRecord": return .playAndRecord
|
package/ios/GliphPlayerModule.mm
CHANGED
|
@@ -246,8 +246,6 @@ RCT_EXPORT_METHOD(updateOptions:(NSDictionary *)options
|
|
|
246
246
|
[_player updateOptions:options resolve:resolve reject:reject];
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
// ── New Architecture (TurboModule) ────────────────────────────────────────────
|
|
250
|
-
|
|
251
249
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
252
250
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
253
251
|
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
package/package.json
CHANGED