expo-gliph-player 1.3.15 → 1.3.17
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 +61 -7
- package/ios/GliphPlayerModule.mm +9 -3
- package/lib/module/GliphPlayer.js +258 -251
- package/package.json +1 -1
|
@@ -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] = []
|
|
@@ -260,6 +264,12 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
260
264
|
|
|
261
265
|
@objc public func reset(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
262
266
|
player?.pause()
|
|
267
|
+
|
|
268
|
+
// ✅ Уничтожаем looper
|
|
269
|
+
playerLooper?.disableLooping()
|
|
270
|
+
playerLooper = nil
|
|
271
|
+
templateItem = nil
|
|
272
|
+
|
|
263
273
|
player?.removeAllItems()
|
|
264
274
|
queue.removeAll()
|
|
265
275
|
playerItems.removeAll()
|
|
@@ -304,8 +314,23 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
304
314
|
|
|
305
315
|
@objc public func getRate() -> Float { return player?.rate ?? 1.0 }
|
|
306
316
|
|
|
317
|
+
// ✅ ИСПРАВЛЕНО: пересоздаём очередь при смене режима
|
|
307
318
|
@objc public func setRepeatMode(_ mode: Int, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
319
|
+
let oldMode = repeatMode
|
|
308
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
|
+
|
|
309
334
|
resolve(nil)
|
|
310
335
|
}
|
|
311
336
|
|
|
@@ -420,12 +445,40 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
420
445
|
])
|
|
421
446
|
}
|
|
422
447
|
|
|
448
|
+
// ✅ ИСПРАВЛЕНО: добавлен AVPlayerLooper для повторения трека
|
|
423
449
|
private func rebuildPlayerQueue() {
|
|
424
450
|
guard let player = player else { return }
|
|
451
|
+
|
|
452
|
+
// ✅ Уничтожаем старый looper
|
|
453
|
+
playerLooper?.disableLooping()
|
|
454
|
+
playerLooper = nil
|
|
455
|
+
templateItem = nil
|
|
456
|
+
|
|
425
457
|
player.removeAllItems()
|
|
426
458
|
playerItems.removeAll()
|
|
427
459
|
|
|
428
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)
|
|
429
482
|
for i in startIndex..<queue.count {
|
|
430
483
|
let track = queue[i]
|
|
431
484
|
guard let urlString = track["url"] as? String,
|
|
@@ -632,10 +685,16 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
632
685
|
}
|
|
633
686
|
}
|
|
634
687
|
|
|
688
|
+
// ✅ ИСПРАВЛЕНО: AVPlayerLooper сам обрабатывает повтор трека
|
|
635
689
|
@objc private func playerItemDidFinish(_ notification: Notification) {
|
|
636
690
|
guard let finishedItem = notification.object as? AVPlayerItem,
|
|
637
691
|
finishedItem === player?.currentItem else { return }
|
|
638
692
|
|
|
693
|
+
// ✅ Если режим повтора трека - AVPlayerLooper сам обрабатывает, выходим
|
|
694
|
+
if repeatMode == 1 {
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
|
|
639
698
|
let nextIndex = currentIndex + 1
|
|
640
699
|
let currentPosition = CMTimeGetSeconds(player?.currentTime() ?? .zero)
|
|
641
700
|
|
|
@@ -644,12 +703,6 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
644
703
|
"position": currentPosition
|
|
645
704
|
])
|
|
646
705
|
|
|
647
|
-
if repeatMode == 1 {
|
|
648
|
-
player?.seek(to: .zero)
|
|
649
|
-
player?.play()
|
|
650
|
-
return
|
|
651
|
-
}
|
|
652
|
-
|
|
653
706
|
if nextIndex < queue.count {
|
|
654
707
|
currentIndex = nextIndex
|
|
655
708
|
updateNowPlayingInfo()
|
|
@@ -660,6 +713,7 @@ public typealias RCTPromiseRejectBlock = (String?, String?, Error?) -> Void
|
|
|
660
713
|
"lastPosition": 0.0,
|
|
661
714
|
])
|
|
662
715
|
} else if repeatMode == 2 {
|
|
716
|
+
// ✅ ПОВТОР ВСЕЙ ОЧЕРЕДИ
|
|
663
717
|
currentIndex = 0
|
|
664
718
|
rebuildPlayerQueue()
|
|
665
719
|
player?.play()
|
package/ios/GliphPlayerModule.mm
CHANGED
|
@@ -22,7 +22,7 @@ RCT_EXPORT_MODULE(RNGliphPlayer)
|
|
|
22
22
|
if (self = [super init]) {
|
|
23
23
|
__weak GliphPlayerModule *weakSelf = self;
|
|
24
24
|
_player = [[GliphAudioPlayer alloc] initWithEventEmitter:^(NSString *event, NSDictionary *data) {
|
|
25
|
-
[weakSelf
|
|
25
|
+
[weakSelf safeSendEventWithName:event body:data];
|
|
26
26
|
}];
|
|
27
27
|
}
|
|
28
28
|
return self;
|
|
@@ -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",
|
|
@@ -61,6 +62,13 @@ RCT_EXPORT_MODULE(RNGliphPlayer)
|
|
|
61
62
|
- (void)startObserving { _hasListeners = YES; }
|
|
62
63
|
- (void)stopObserving { _hasListeners = NO; }
|
|
63
64
|
|
|
65
|
+
- (void)safeSendEventWithName:(NSString *)eventName body:(id)body {
|
|
66
|
+
if (!_hasListeners) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
[self sendEventWithName:eventName body:body];
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
RCT_EXPORT_METHOD(setupPlayer:(NSDictionary *)options
|
|
65
73
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
66
74
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
@@ -245,8 +253,6 @@ RCT_EXPORT_METHOD(updateOptions:(NSDictionary *)options
|
|
|
245
253
|
[_player updateOptions:options resolve:resolve reject:reject];
|
|
246
254
|
}
|
|
247
255
|
|
|
248
|
-
// ── New Architecture (TurboModule) ────────────────────────────────────────────
|
|
249
|
-
|
|
250
256
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
251
257
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
252
258
|
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
@@ -15,291 +15,298 @@ import { State } from './types';
|
|
|
15
15
|
|
|
16
16
|
let _emitter = null;
|
|
17
17
|
function getEmitter() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
if (!NativeGliphPlayer) {
|
|
19
|
+
throw new Error('[RNGliphPlayer] Native module not found. Is it linked correctly?');
|
|
20
|
+
}
|
|
21
|
+
if (!_emitter) {
|
|
22
|
+
_emitter = new NativeEventEmitter(NativeGliphPlayer);
|
|
23
|
+
}
|
|
24
|
+
return _emitter;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** Resolve a require()'d asset to a URI string the native side can consume */
|
|
28
28
|
function resolveAssetSource(source) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
if (typeof source === 'number') {
|
|
30
|
+
const {
|
|
31
|
+
Image
|
|
32
|
+
} = require('react-native');
|
|
33
|
+
return Image.resolveAssetSource(source).uri;
|
|
34
|
+
}
|
|
35
|
+
return source;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** Serialize a Track to a plain object safe for the TurboModule bridge */
|
|
39
39
|
function serializeTrack(track) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
return {
|
|
41
|
+
id: track.id ?? `track_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
42
|
+
url: resolveAssetSource(track.url),
|
|
43
|
+
title: track.title,
|
|
44
|
+
artist: track.artist,
|
|
45
|
+
album: track.album ?? '',
|
|
46
|
+
artwork: track.artwork != null ? resolveAssetSource(track.artwork) : '',
|
|
47
|
+
duration: track.duration ?? -1,
|
|
48
|
+
genre: track.genre ?? '',
|
|
49
|
+
date: track.date ?? '',
|
|
50
|
+
description: track.description ?? '',
|
|
51
|
+
rating: track.rating ?? 0,
|
|
52
|
+
isLiveStream: track.isLiveStream ?? false,
|
|
53
|
+
headers: track.headers ?? {},
|
|
54
|
+
pitchAlgorithm: track.pitchAlgorithm ?? 0,
|
|
55
|
+
userAgent: track.userAgent ?? ''
|
|
56
|
+
};
|
|
57
57
|
}
|
|
58
58
|
function checkModule() {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
if (!NativeGliphPlayer) {
|
|
60
|
+
throw new Error('[RNGliphPlayer] Native module not found. ' + 'Ensure you have rebuilt the app after installing the library and ' + 'that you are not calling player methods before the JSI bridge is ready.');
|
|
61
|
+
}
|
|
62
|
+
return NativeGliphPlayer;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// ─── GliphPlayer API ─────────────────────────────────────────────────────────
|
|
66
66
|
|
|
67
67
|
const GliphPlayer = {
|
|
68
|
-
|
|
68
|
+
// ── Setup ──────────────────────────────────────────────────────────────────
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Initialize the player. Must be called once before any other method.
|
|
72
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
73
|
+
*/
|
|
74
|
+
async setupPlayer(options = {}) {
|
|
75
|
+
if (!NativeGliphPlayer) {
|
|
76
|
+
throw new Error('[RNGliphPlayer] Native module not ready. Ensure setup is called after app initialization.');
|
|
77
|
+
}
|
|
78
|
+
const opts = {
|
|
79
|
+
minBuffer: options.minBuffer ?? 15,
|
|
80
|
+
maxBuffer: options.maxBuffer ?? 50,
|
|
81
|
+
playBuffer: options.playBuffer ?? 2.5,
|
|
82
|
+
backBuffer: options.backBuffer ?? 0,
|
|
83
|
+
maxCacheSize: options.maxCacheSize ?? 0,
|
|
84
|
+
iosCategory: options.iosCategory ?? 'playback',
|
|
85
|
+
iosCategoryMode: options.iosCategoryMode ?? 'default',
|
|
86
|
+
iosCategoryOptions: options.iosCategoryOptions ?? [],
|
|
87
|
+
waitForBuffer: options.waitForBuffer ?? true,
|
|
88
|
+
autoHandleInterruptions: options.autoHandleInterruptions ?? false,
|
|
89
|
+
autoUpdateMetadata: options.autoUpdateMetadata ?? true,
|
|
90
|
+
progressUpdateEventInterval: options.progressUpdateEventInterval ?? 1.0,
|
|
91
|
+
android: options.android ?? {}
|
|
92
|
+
};
|
|
93
|
+
return NativeGliphPlayer.setupPlayer(opts);
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* Destroy the player and release all resources.
|
|
97
|
+
*/
|
|
98
|
+
destroy() {
|
|
99
|
+
checkModule().destroy();
|
|
100
|
+
_emitter = null;
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* Returns true if the background service is running (Android).
|
|
104
|
+
*/
|
|
105
|
+
async isServiceRunning() {
|
|
106
|
+
if (Platform.OS !== 'android') {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return checkModule().isServiceRunning();
|
|
110
|
+
},
|
|
111
|
+
// ── Queue management ───────────────────────────────────────────────────────
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Add one or more tracks to the queue.
|
|
115
|
+
* @param tracks Single track or array of tracks.
|
|
116
|
+
* @param insertBeforeIndex Insert position. Defaults to end of queue.
|
|
117
|
+
* @returns The index of the first inserted track.
|
|
118
|
+
*/
|
|
119
|
+
async add(tracks, insertBeforeIndex) {
|
|
120
|
+
const arr = Array.isArray(tracks) ? tracks : [tracks];
|
|
121
|
+
const serialized = arr.map(serializeTrack);
|
|
122
|
+
// Pass -1 as sentinel for "append to end" — Codegen doesn't support optional number params
|
|
123
|
+
const result = await checkModule().add(serialized, insertBeforeIndex ?? -1);
|
|
124
|
+
return result;
|
|
125
|
+
},
|
|
126
|
+
/**
|
|
127
|
+
* Remove tracks by their IDs.
|
|
128
|
+
*/
|
|
129
|
+
async remove(trackIds) {
|
|
130
|
+
const ids = Array.isArray(trackIds) ? trackIds : [trackIds];
|
|
131
|
+
return checkModule().remove(ids);
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* Remove all tracks after the current one.
|
|
135
|
+
*/
|
|
136
|
+
async removeUpcomingTracks() {
|
|
137
|
+
return checkModule().removeUpcomingTracks();
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* Skip to a specific queue index.
|
|
141
|
+
* @param index - Index of the track to skip to
|
|
142
|
+
* @param initialPosition - Position in seconds to start from (default: -1 = start)
|
|
143
|
+
* @param autoPlay - Whether to automatically start playback (default: true)
|
|
144
|
+
*/
|
|
145
|
+
async skip(index, initialPosition, autoPlay = true) {
|
|
146
|
+
return checkModule().skip(index, initialPosition ?? -1, autoPlay);
|
|
147
|
+
},
|
|
148
|
+
/**
|
|
149
|
+
* Skip to the next track.
|
|
150
|
+
* @param initialPosition - Position in seconds to start from (default: -1 = start)
|
|
151
|
+
* @param autoPlay - Whether to automatically start playback (default: true)
|
|
152
|
+
*/
|
|
153
|
+
async skipToNext(initialPosition, autoPlay = true) {
|
|
154
|
+
return checkModule().skipToNext(initialPosition ?? -1, autoPlay);
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
* Skip to the previous track.
|
|
158
|
+
* @param initialPosition - Position in seconds to start from (default: -1 = start)
|
|
159
|
+
* @param autoPlay - Whether to automatically start playback (default: true)
|
|
160
|
+
*/
|
|
161
|
+
async skipToPrevious(initialPosition, autoPlay = true) {
|
|
162
|
+
return checkModule().skipToPrevious(initialPosition ?? -1, autoPlay);
|
|
163
|
+
},
|
|
164
|
+
/**
|
|
165
|
+
* Move a track from one index to another.
|
|
166
|
+
*/
|
|
167
|
+
async move(fromIndex, toIndex) {
|
|
168
|
+
return checkModule().move(fromIndex, toIndex);
|
|
169
|
+
},
|
|
170
|
+
// ── Playback control ───────────────────────────────────────────────────────
|
|
164
171
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
172
|
+
async play() {
|
|
173
|
+
return checkModule().play();
|
|
174
|
+
},
|
|
175
|
+
async pause() {
|
|
176
|
+
return checkModule().pause();
|
|
177
|
+
},
|
|
178
|
+
async stop() {
|
|
179
|
+
return checkModule().stop();
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Stop playback and clear the queue.
|
|
183
|
+
*/
|
|
184
|
+
async reset() {
|
|
185
|
+
return checkModule().reset();
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* Seek to an absolute position in seconds.
|
|
189
|
+
*/
|
|
190
|
+
async seekTo(position) {
|
|
191
|
+
return checkModule().seekTo(position);
|
|
192
|
+
},
|
|
193
|
+
/**
|
|
194
|
+
* Seek by a relative offset in seconds (positive = forward, negative = back).
|
|
195
|
+
*/
|
|
196
|
+
async seekBy(offset) {
|
|
197
|
+
return checkModule().seekBy(offset);
|
|
198
|
+
},
|
|
199
|
+
/**
|
|
200
|
+
* Set volume (0.0 – 1.0).
|
|
201
|
+
*/
|
|
202
|
+
async setVolume(volume) {
|
|
203
|
+
if (volume < 0 || volume > 1) {
|
|
204
|
+
throw new Error('Volume must be between 0 and 1');
|
|
205
|
+
}
|
|
206
|
+
return checkModule().setVolume(volume);
|
|
207
|
+
},
|
|
208
|
+
async getVolume() {
|
|
209
|
+
return checkModule().getVolume();
|
|
210
|
+
},
|
|
211
|
+
/**
|
|
212
|
+
* Set playback rate (1.0 = normal speed).
|
|
213
|
+
*/
|
|
214
|
+
async setRate(rate) {
|
|
215
|
+
return checkModule().setRate(rate);
|
|
216
|
+
},
|
|
217
|
+
async getRate() {
|
|
218
|
+
return checkModule().getRate();
|
|
219
|
+
},
|
|
220
|
+
async setRepeatMode(mode) {
|
|
221
|
+
return checkModule().setRepeatMode(mode);
|
|
222
|
+
},
|
|
223
|
+
async getRepeatMode() {
|
|
224
|
+
const mode = await checkModule().getRepeatMode();
|
|
225
|
+
return mode;
|
|
226
|
+
},
|
|
227
|
+
// ── Queue getters ──────────────────────────────────────────────────────────
|
|
221
228
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
229
|
+
async getQueue() {
|
|
230
|
+
const queue = await checkModule().getQueue();
|
|
231
|
+
return queue;
|
|
232
|
+
},
|
|
233
|
+
async getActiveTrackIndex() {
|
|
234
|
+
const idx = await checkModule().getActiveTrackIndex();
|
|
235
|
+
// Native returns -1 as sentinel when no track is active
|
|
236
|
+
return idx < 0 ? null : idx;
|
|
237
|
+
},
|
|
238
|
+
async getActiveTrack() {
|
|
239
|
+
const track = await checkModule().getActiveTrack();
|
|
240
|
+
return track;
|
|
241
|
+
},
|
|
242
|
+
async getTrack(index) {
|
|
243
|
+
const track = await checkModule().getTrack(index);
|
|
244
|
+
return track;
|
|
245
|
+
},
|
|
246
|
+
async getQueueSize() {
|
|
247
|
+
return checkModule().getQueueSize();
|
|
248
|
+
},
|
|
249
|
+
// ── State / progress ───────────────────────────────────────────────────────
|
|
243
250
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
async getPlaybackState() {
|
|
252
|
+
const state = await checkModule().getPlaybackState();
|
|
253
|
+
return state;
|
|
254
|
+
},
|
|
255
|
+
async getProgress() {
|
|
256
|
+
const progress = await checkModule().getProgress();
|
|
257
|
+
return progress;
|
|
258
|
+
},
|
|
259
|
+
// ── Metadata ───────────────────────────────────────────────────────────────
|
|
253
260
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
async updateMetadataForTrack(index, metadata) {
|
|
262
|
+
return checkModule().updateMetadataForTrack(index, metadata);
|
|
263
|
+
},
|
|
264
|
+
async clearNowPlayingMetadata() {
|
|
265
|
+
return checkModule().clearNowPlayingMetadata();
|
|
266
|
+
},
|
|
267
|
+
async updateNowPlayingMetadata(metadata) {
|
|
268
|
+
return checkModule().updateNowPlayingMetadata(metadata);
|
|
269
|
+
},
|
|
270
|
+
// ── Options / notification ─────────────────────────────────────────────────
|
|
264
271
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Configure notification capabilities, jump intervals, etc.
|
|
274
|
+
*/
|
|
275
|
+
async updateOptions(options) {
|
|
276
|
+
return checkModule().updateOptions(options);
|
|
277
|
+
},
|
|
278
|
+
// ── Event subscription ─────────────────────────────────────────────────────
|
|
272
279
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Subscribe to a player event.
|
|
282
|
+
* Returns an unsubscribe function — call it to remove the listener.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* const unsub = GliphPlayer.addEventListener(Event.PlaybackState, ({ state }) => {
|
|
286
|
+
* console.log('State:', state);
|
|
287
|
+
* });
|
|
288
|
+
* // later:
|
|
289
|
+
* unsub();
|
|
290
|
+
*/
|
|
291
|
+
addEventListener(event, listener) {
|
|
292
|
+
const subscription = getEmitter().addListener(event, listener);
|
|
293
|
+
return () => subscription.remove();
|
|
294
|
+
}
|
|
288
295
|
};
|
|
289
296
|
export default GliphPlayer;
|
|
290
297
|
|
|
291
298
|
// ─── Convenience state helpers ────────────────────────────────────────────────
|
|
292
299
|
|
|
293
300
|
export function isPlaying(state) {
|
|
294
|
-
|
|
301
|
+
return state === State.Playing;
|
|
295
302
|
}
|
|
296
303
|
export function isPaused(state) {
|
|
297
|
-
|
|
304
|
+
return state === State.Paused;
|
|
298
305
|
}
|
|
299
306
|
export function isBuffering(state) {
|
|
300
|
-
|
|
307
|
+
return state === State.Buffering || state === State.Loading;
|
|
301
308
|
}
|
|
302
309
|
export function isStopped(state) {
|
|
303
|
-
|
|
310
|
+
return state === State.Stopped || state === State.None;
|
|
304
311
|
}
|
|
305
312
|
//# sourceMappingURL=GliphPlayer.js.map
|
package/package.json
CHANGED