expo-gliph-player 1.0.0
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/LICENSE +21 -0
- package/README.md +348 -0
- package/android/build.gradle +82 -0
- package/android/src/main/AndroidManifest.xml +31 -0
- package/android/src/main/java/com/gliphplayer/DeviceInfoModule.kt +48 -0
- package/android/src/main/java/com/gliphplayer/DeviceInfoPackage.kt +16 -0
- package/android/src/main/java/com/gliphplayer/GliphPlayerModule.kt +360 -0
- package/android/src/main/java/com/gliphplayer/GliphPlayerPackage.kt +48 -0
- package/android/src/main/java/com/gliphplayer/GliphPlayerService.kt +797 -0
- package/android/src/main/res/xml/automotive_app_desc.xml +4 -0
- package/android/src/oldarch/com/gliphplayer/NativeGliphPlayerSpec.kt +56 -0
- package/app.plugin.js +1 -0
- package/expo-gliph-player.podspec +91 -0
- package/ios/GliphAudioPlayer.h +77 -0
- package/ios/GliphAudioPlayer.swift +697 -0
- package/ios/GliphPlayer-Bridging-Header.h +3 -0
- package/ios/GliphPlayerModule.h +12 -0
- package/ios/GliphPlayerModule.mm +243 -0
- package/ios/expo_gliph_player.h +22 -0
- package/lib/commonjs/GliphPlayer.js +310 -0
- package/lib/commonjs/GliphPlayer.js.map +1 -0
- package/lib/commonjs/hooks.js +233 -0
- package/lib/commonjs/hooks.js.map +1 -0
- package/lib/commonjs/index.js +166 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/NativeGliphPlayer.js +19 -0
- package/lib/commonjs/specs/NativeGliphPlayer.js.map +1 -0
- package/lib/commonjs/types.js +175 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/GliphPlayer.js +305 -0
- package/lib/module/GliphPlayer.js.map +1 -0
- package/lib/module/hooks.js +221 -0
- package/lib/module/hooks.js.map +1 -0
- package/lib/module/index.js +20 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/specs/NativeGliphPlayer.js +20 -0
- package/lib/module/specs/NativeGliphPlayer.js.map +1 -0
- package/lib/module/types.js +181 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/src/GliphPlayer.d.ts +113 -0
- package/lib/typescript/src/GliphPlayer.d.ts.map +1 -0
- package/lib/typescript/src/hooks.d.ts +51 -0
- package/lib/typescript/src/hooks.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +11 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeGliphPlayer.d.ts +80 -0
- package/lib/typescript/src/specs/NativeGliphPlayer.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +348 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +62 -0
- package/src/GliphPlayer.ts +356 -0
- package/src/hooks.ts +256 -0
- package/src/index.ts +61 -0
- package/src/specs/NativeGliphPlayer.ts +105 -0
- package/src/types.ts +395 -0
- package/src/withGliphPlayer.js +166 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GliphPlayer.ts
|
|
3
|
+
*
|
|
4
|
+
* Main JS/TS API surface. Wraps the TurboModule (NativeGliphPlayer) and
|
|
5
|
+
* provides a clean, typed interface identical in spirit to react-native-track-player.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NativeEventEmitter, Platform } from 'react-native';
|
|
9
|
+
import NativeGliphPlayer from './specs/NativeGliphPlayer';
|
|
10
|
+
import type {
|
|
11
|
+
Track,
|
|
12
|
+
PlayerOptions,
|
|
13
|
+
UpdateOptions,
|
|
14
|
+
Progress,
|
|
15
|
+
PlaybackState,
|
|
16
|
+
} from './types';
|
|
17
|
+
import { RepeatMode, State } from './types';
|
|
18
|
+
|
|
19
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
let _emitter: NativeEventEmitter | null = null;
|
|
22
|
+
|
|
23
|
+
function getEmitter(): NativeEventEmitter {
|
|
24
|
+
if (!NativeGliphPlayer) {
|
|
25
|
+
throw new Error('[RNGliphPlayer] Native module not found. Is it linked correctly?');
|
|
26
|
+
}
|
|
27
|
+
if (!_emitter) {
|
|
28
|
+
_emitter = new NativeEventEmitter(NativeGliphPlayer as any);
|
|
29
|
+
}
|
|
30
|
+
return _emitter;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Resolve a require()'d asset to a URI string the native side can consume */
|
|
34
|
+
function resolveAssetSource(source: string | number): string {
|
|
35
|
+
if (typeof source === 'number') {
|
|
36
|
+
|
|
37
|
+
const { Image } = require('react-native');
|
|
38
|
+
return Image.resolveAssetSource(source).uri as string;
|
|
39
|
+
}
|
|
40
|
+
return source;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Serialize a Track to a plain object safe for the TurboModule bridge */
|
|
44
|
+
function serializeTrack(track: Track): Record<string, unknown> {
|
|
45
|
+
return {
|
|
46
|
+
id: track.id ?? `track_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
47
|
+
url: resolveAssetSource(track.url as string | number),
|
|
48
|
+
title: track.title,
|
|
49
|
+
artist: track.artist,
|
|
50
|
+
album: track.album ?? '',
|
|
51
|
+
artwork: track.artwork != null ? resolveAssetSource(track.artwork as string | number) : '',
|
|
52
|
+
duration: track.duration ?? -1,
|
|
53
|
+
genre: track.genre ?? '',
|
|
54
|
+
date: track.date ?? '',
|
|
55
|
+
description: track.description ?? '',
|
|
56
|
+
rating: track.rating ?? 0,
|
|
57
|
+
isLiveStream: track.isLiveStream ?? false,
|
|
58
|
+
headers: track.headers ?? {},
|
|
59
|
+
pitchAlgorithm: track.pitchAlgorithm ?? 0,
|
|
60
|
+
userAgent: track.userAgent ?? '',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function checkModule(): NonNullable<typeof NativeGliphPlayer> {
|
|
65
|
+
if (!NativeGliphPlayer) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
'[RNGliphPlayer] Native module not found. ' +
|
|
68
|
+
'Ensure you have rebuilt the app after installing the library and ' +
|
|
69
|
+
'that you are not calling player methods before the JSI bridge is ready.'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return NativeGliphPlayer;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── GliphPlayer API ─────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
const GliphPlayer = {
|
|
78
|
+
// ── Setup ──────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Initialize the player. Must be called once before any other method.
|
|
82
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
83
|
+
*/
|
|
84
|
+
async setupPlayer(options: PlayerOptions = {}): Promise<void> {
|
|
85
|
+
if (!NativeGliphPlayer) {
|
|
86
|
+
throw new Error('[RNGliphPlayer] Native module not ready. Ensure setup is called after app initialization.');
|
|
87
|
+
}
|
|
88
|
+
const opts: Record<string, unknown> = {
|
|
89
|
+
minBuffer: options.minBuffer ?? 15,
|
|
90
|
+
maxBuffer: options.maxBuffer ?? 50,
|
|
91
|
+
playBuffer: options.playBuffer ?? 2.5,
|
|
92
|
+
backBuffer: options.backBuffer ?? 0,
|
|
93
|
+
maxCacheSize: options.maxCacheSize ?? 0,
|
|
94
|
+
iosCategory: options.iosCategory ?? 'playback',
|
|
95
|
+
iosCategoryMode: options.iosCategoryMode ?? 'default',
|
|
96
|
+
iosCategoryOptions: options.iosCategoryOptions ?? [],
|
|
97
|
+
waitForBuffer: options.waitForBuffer ?? true,
|
|
98
|
+
autoHandleInterruptions: options.autoHandleInterruptions ?? false,
|
|
99
|
+
autoUpdateMetadata: options.autoUpdateMetadata ?? true,
|
|
100
|
+
progressUpdateEventInterval: options.progressUpdateEventInterval ?? 1.0,
|
|
101
|
+
android: options.android ?? {},
|
|
102
|
+
};
|
|
103
|
+
return NativeGliphPlayer.setupPlayer(opts);
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Destroy the player and release all resources.
|
|
108
|
+
*/
|
|
109
|
+
destroy(): void {
|
|
110
|
+
checkModule().destroy();
|
|
111
|
+
_emitter = null;
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Returns true if the background service is running (Android).
|
|
116
|
+
*/
|
|
117
|
+
async isServiceRunning(): Promise<boolean> {
|
|
118
|
+
if (Platform.OS !== 'android') {return true;}
|
|
119
|
+
return checkModule().isServiceRunning();
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// ── Queue management ───────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Add one or more tracks to the queue.
|
|
126
|
+
* @param tracks Single track or array of tracks.
|
|
127
|
+
* @param insertBeforeIndex Insert position. Defaults to end of queue.
|
|
128
|
+
* @returns The index of the first inserted track.
|
|
129
|
+
*/
|
|
130
|
+
async add(
|
|
131
|
+
tracks: Track | Track[],
|
|
132
|
+
insertBeforeIndex?: number
|
|
133
|
+
): Promise<number | void> {
|
|
134
|
+
const arr = Array.isArray(tracks) ? tracks : [tracks];
|
|
135
|
+
const serialized = arr.map(serializeTrack);
|
|
136
|
+
// Pass -1 as sentinel for "append to end" — Codegen doesn't support optional number params
|
|
137
|
+
const result = await checkModule().add(serialized, insertBeforeIndex ?? -1);
|
|
138
|
+
return result;
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Remove tracks by their IDs.
|
|
143
|
+
*/
|
|
144
|
+
async remove(trackIds: string | string[]): Promise<void> {
|
|
145
|
+
const ids = Array.isArray(trackIds) ? trackIds : [trackIds];
|
|
146
|
+
return checkModule().remove(ids);
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Remove all tracks after the current one.
|
|
151
|
+
*/
|
|
152
|
+
async removeUpcomingTracks(): Promise<void> {
|
|
153
|
+
return checkModule().removeUpcomingTracks();
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Skip to a specific queue index.
|
|
158
|
+
*/
|
|
159
|
+
async skip(index: number, initialPosition?: number): Promise<void> {
|
|
160
|
+
return checkModule().skip(index, initialPosition ?? -1);
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Skip to the next track.
|
|
165
|
+
*/
|
|
166
|
+
async skipToNext(initialPosition?: number): Promise<void> {
|
|
167
|
+
return checkModule().skipToNext(initialPosition ?? -1);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Skip to the previous track.
|
|
172
|
+
*/
|
|
173
|
+
async skipToPrevious(initialPosition?: number): Promise<void> {
|
|
174
|
+
return checkModule().skipToPrevious(initialPosition ?? -1);
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Move a track from one index to another.
|
|
179
|
+
*/
|
|
180
|
+
async move(fromIndex: number, toIndex: number): Promise<void> {
|
|
181
|
+
return checkModule().move(fromIndex, toIndex);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// ── Playback control ───────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
async play(): Promise<void> {
|
|
187
|
+
return checkModule().play();
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
async pause(): Promise<void> {
|
|
191
|
+
return checkModule().pause();
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
async stop(): Promise<void> {
|
|
195
|
+
return checkModule().stop();
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Stop playback and clear the queue.
|
|
200
|
+
*/
|
|
201
|
+
async reset(): Promise<void> {
|
|
202
|
+
return checkModule().reset();
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Seek to an absolute position in seconds.
|
|
207
|
+
*/
|
|
208
|
+
async seekTo(position: number): Promise<void> {
|
|
209
|
+
return checkModule().seekTo(position);
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Seek by a relative offset in seconds (positive = forward, negative = back).
|
|
214
|
+
*/
|
|
215
|
+
async seekBy(offset: number): Promise<void> {
|
|
216
|
+
return checkModule().seekBy(offset);
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Set volume (0.0 – 1.0).
|
|
221
|
+
*/
|
|
222
|
+
async setVolume(volume: number): Promise<void> {
|
|
223
|
+
if (volume < 0 || volume > 1) {throw new Error('Volume must be between 0 and 1');}
|
|
224
|
+
return checkModule().setVolume(volume);
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
async getVolume(): Promise<number> {
|
|
228
|
+
return checkModule().getVolume();
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Set playback rate (1.0 = normal speed).
|
|
233
|
+
*/
|
|
234
|
+
async setRate(rate: number): Promise<void> {
|
|
235
|
+
return checkModule().setRate(rate);
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
async getRate(): Promise<number> {
|
|
239
|
+
return checkModule().getRate();
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
async setRepeatMode(mode: RepeatMode): Promise<void> {
|
|
243
|
+
return checkModule().setRepeatMode(mode);
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
async getRepeatMode(): Promise<RepeatMode> {
|
|
247
|
+
const mode = await checkModule().getRepeatMode();
|
|
248
|
+
return mode as RepeatMode;
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
// ── Queue getters ──────────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
async getQueue(): Promise<Track[]> {
|
|
254
|
+
const queue = await checkModule().getQueue();
|
|
255
|
+
return queue as Track[];
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
async getActiveTrackIndex(): Promise<number | null> {
|
|
259
|
+
const idx = await checkModule().getActiveTrackIndex();
|
|
260
|
+
// Native returns -1 as sentinel when no track is active
|
|
261
|
+
return (idx as number) < 0 ? null : (idx as number);
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
async getActiveTrack(): Promise<Track | null> {
|
|
265
|
+
const track = await checkModule().getActiveTrack();
|
|
266
|
+
return track as Track | null;
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
async getTrack(index: number): Promise<Track | null> {
|
|
270
|
+
const track = await checkModule().getTrack(index);
|
|
271
|
+
return track as Track | null;
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
async getQueueSize(): Promise<number> {
|
|
275
|
+
return checkModule().getQueueSize();
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
// ── State / progress ───────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
async getPlaybackState(): Promise<PlaybackState> {
|
|
281
|
+
const state = await checkModule().getPlaybackState();
|
|
282
|
+
return state as PlaybackState;
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
async getProgress(): Promise<Progress> {
|
|
286
|
+
const progress = await checkModule().getProgress();
|
|
287
|
+
return progress as Progress;
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
// ── Metadata ───────────────────────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
async updateMetadataForTrack(
|
|
293
|
+
index: number,
|
|
294
|
+
metadata: Partial<Track>
|
|
295
|
+
): Promise<void> {
|
|
296
|
+
return checkModule().updateMetadataForTrack(index, metadata as Record<string, unknown>);
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
async clearNowPlayingMetadata(): Promise<void> {
|
|
300
|
+
return checkModule().clearNowPlayingMetadata();
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
async updateNowPlayingMetadata(metadata: Partial<Track>): Promise<void> {
|
|
304
|
+
return checkModule().updateNowPlayingMetadata(metadata as Record<string, unknown>);
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// ── Options / notification ─────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Configure notification capabilities, jump intervals, etc.
|
|
311
|
+
*/
|
|
312
|
+
async updateOptions(options: UpdateOptions): Promise<void> {
|
|
313
|
+
return checkModule().updateOptions(options as Record<string, unknown>);
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
// ── Event subscription ─────────────────────────────────────────────────────
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Subscribe to a player event.
|
|
320
|
+
* Returns an unsubscribe function — call it to remove the listener.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* const unsub = GliphPlayer.addEventListener(Event.PlaybackState, ({ state }) => {
|
|
324
|
+
* console.log('State:', state);
|
|
325
|
+
* });
|
|
326
|
+
* // later:
|
|
327
|
+
* unsub();
|
|
328
|
+
*/
|
|
329
|
+
addEventListener<E extends string>(
|
|
330
|
+
event: E,
|
|
331
|
+
listener: (data: any) => void
|
|
332
|
+
): () => void {
|
|
333
|
+
const subscription = getEmitter().addListener(event, listener);
|
|
334
|
+
return () => subscription.remove();
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
export default GliphPlayer;
|
|
339
|
+
|
|
340
|
+
// ─── Convenience state helpers ────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
export function isPlaying(state: State): boolean {
|
|
343
|
+
return state === State.Playing;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function isPaused(state: State): boolean {
|
|
347
|
+
return state === State.Paused;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function isBuffering(state: State): boolean {
|
|
351
|
+
return state === State.Buffering || state === State.Loading;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function isStopped(state: State): boolean {
|
|
355
|
+
return state === State.Stopped || state === State.None;
|
|
356
|
+
}
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hooks.ts — React hooks for react-native-gliph-player
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
6
|
+
import GliphPlayer from './GliphPlayer';
|
|
7
|
+
import { Event, State } from './types';
|
|
8
|
+
import type {
|
|
9
|
+
Track,
|
|
10
|
+
Progress,
|
|
11
|
+
PlaybackState,
|
|
12
|
+
RepeatMode,
|
|
13
|
+
EventPayloadByEvent,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
// ─── useTrackPlayerEvents ─────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Subscribe to one or more player events inside a component.
|
|
20
|
+
* Automatically unsubscribes on unmount.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* useTrackPlayerEvents([Event.PlaybackState], ({ state }) => {
|
|
24
|
+
* console.log(state);
|
|
25
|
+
* });
|
|
26
|
+
*/
|
|
27
|
+
export function useTrackPlayerEvents<E extends Event>(
|
|
28
|
+
events: E[],
|
|
29
|
+
handler: (payload: EventPayloadByEvent[E] & { type: E }) => void
|
|
30
|
+
): void {
|
|
31
|
+
const handlerRef = useRef(handler);
|
|
32
|
+
handlerRef.current = handler;
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const unsubs = events.map((event) =>
|
|
36
|
+
GliphPlayer.addEventListener(event, (data: any) => {
|
|
37
|
+
handlerRef.current({ ...data, type: event });
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
return () => unsubs.forEach((u) => u());
|
|
41
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
|
+
}, events);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── usePlaybackState ─────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns the current playback state, updating reactively.
|
|
49
|
+
*/
|
|
50
|
+
export function usePlaybackState(): PlaybackState {
|
|
51
|
+
const [state, setState] = useState<PlaybackState>({ state: State.None });
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
let mounted = true;
|
|
55
|
+
|
|
56
|
+
// Fetch initial state
|
|
57
|
+
GliphPlayer.getPlaybackState()
|
|
58
|
+
.then((s) => { if (mounted) {setState(s);} })
|
|
59
|
+
.catch(() => {});
|
|
60
|
+
|
|
61
|
+
const unsub = GliphPlayer.addEventListener(
|
|
62
|
+
Event.PlaybackState,
|
|
63
|
+
(data: { state: State }) => {
|
|
64
|
+
if (mounted) {setState({ state: data.state });}
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
mounted = false;
|
|
70
|
+
unsub();
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
return state;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── useProgress ─────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns live playback progress (position, duration, buffered).
|
|
81
|
+
* @param updateInterval How often to poll in ms. Defaults to 1000ms.
|
|
82
|
+
* Set to 0 to rely solely on native progress events.
|
|
83
|
+
*/
|
|
84
|
+
export function useProgress(updateInterval = 1000): Progress {
|
|
85
|
+
const [progress, setProgress] = useState<Progress>({
|
|
86
|
+
position: 0,
|
|
87
|
+
duration: 0,
|
|
88
|
+
buffered: 0,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
let mounted = true;
|
|
93
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
94
|
+
|
|
95
|
+
const update = () => {
|
|
96
|
+
GliphPlayer.getProgress()
|
|
97
|
+
.then((p) => { if (mounted) {setProgress(p);} })
|
|
98
|
+
.catch(() => {});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
update();
|
|
102
|
+
|
|
103
|
+
if (updateInterval > 0) {
|
|
104
|
+
timer = setInterval(update, updateInterval);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Also listen to native progress events for immediate updates
|
|
108
|
+
const unsub = GliphPlayer.addEventListener(
|
|
109
|
+
Event.PlaybackProgressUpdated,
|
|
110
|
+
(data: { position: number; duration: number; buffered: number }) => {
|
|
111
|
+
if (mounted) {
|
|
112
|
+
setProgress({
|
|
113
|
+
position: data.position,
|
|
114
|
+
duration: data.duration,
|
|
115
|
+
buffered: data.buffered,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return () => {
|
|
122
|
+
mounted = false;
|
|
123
|
+
if (timer) {clearInterval(timer);}
|
|
124
|
+
unsub();
|
|
125
|
+
};
|
|
126
|
+
}, [updateInterval]);
|
|
127
|
+
|
|
128
|
+
return progress;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ─── useActiveTrack ───────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Returns the currently active track, updating when the track changes.
|
|
135
|
+
*/
|
|
136
|
+
export function useActiveTrack(): Track | null | undefined {
|
|
137
|
+
const [track, setTrack] = useState<Track | null | undefined>(undefined);
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
let mounted = true;
|
|
141
|
+
|
|
142
|
+
GliphPlayer.getActiveTrack()
|
|
143
|
+
.then((t) => { if (mounted) {setTrack(t);} })
|
|
144
|
+
.catch(() => {});
|
|
145
|
+
|
|
146
|
+
const unsub = GliphPlayer.addEventListener(
|
|
147
|
+
Event.PlaybackActiveTrackChanged,
|
|
148
|
+
(data: { track: Track | null }) => {
|
|
149
|
+
if (mounted) {setTrack(data.track);}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return () => {
|
|
154
|
+
mounted = false;
|
|
155
|
+
unsub();
|
|
156
|
+
};
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
return track;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── useQueue ─────────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Returns the current queue, updating when tracks are added/removed/changed.
|
|
166
|
+
*/
|
|
167
|
+
export function useQueue(): Track[] {
|
|
168
|
+
const [queue, setQueue] = useState<Track[]>([]);
|
|
169
|
+
|
|
170
|
+
const refresh = useCallback(() => {
|
|
171
|
+
GliphPlayer.getQueue()
|
|
172
|
+
.then(setQueue)
|
|
173
|
+
.catch(() => {});
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
refresh();
|
|
178
|
+
|
|
179
|
+
// Re-fetch on any track change event
|
|
180
|
+
const unsub1 = GliphPlayer.addEventListener(Event.PlaybackActiveTrackChanged, refresh);
|
|
181
|
+
const unsub2 = GliphPlayer.addEventListener(Event.PlaybackQueueEnded, refresh);
|
|
182
|
+
|
|
183
|
+
return () => {
|
|
184
|
+
unsub1();
|
|
185
|
+
unsub2();
|
|
186
|
+
};
|
|
187
|
+
}, [refresh]);
|
|
188
|
+
|
|
189
|
+
return queue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── useRepeatMode ────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Returns the current repeat mode and a setter.
|
|
196
|
+
*/
|
|
197
|
+
export function useRepeatMode(): [RepeatMode | null, (mode: RepeatMode) => Promise<void>] {
|
|
198
|
+
const [mode, setMode] = useState<RepeatMode | null>(null);
|
|
199
|
+
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
GliphPlayer.getRepeatMode()
|
|
202
|
+
.then(setMode)
|
|
203
|
+
.catch(() => {});
|
|
204
|
+
|
|
205
|
+
const unsub = GliphPlayer.addEventListener(
|
|
206
|
+
Event.PlaybackRepeatModeChanged,
|
|
207
|
+
(data) => {
|
|
208
|
+
setMode(data.mode);
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
return () => unsub();
|
|
213
|
+
}, []);
|
|
214
|
+
|
|
215
|
+
const set = useCallback(async (newMode: RepeatMode) => {
|
|
216
|
+
await GliphPlayer.setRepeatMode(newMode);
|
|
217
|
+
setMode(newMode);
|
|
218
|
+
}, []);
|
|
219
|
+
|
|
220
|
+
return [mode, set];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ─── useVolume ────────────────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Returns the current volume (0–1) and a setter.
|
|
227
|
+
*/
|
|
228
|
+
export function useVolume(): [number, (v: number) => Promise<void>] {
|
|
229
|
+
const [volume, setVolume] = useState(1);
|
|
230
|
+
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
GliphPlayer.getVolume()
|
|
233
|
+
.then(setVolume)
|
|
234
|
+
.catch(() => {});
|
|
235
|
+
}, []);
|
|
236
|
+
|
|
237
|
+
const set = useCallback(async (v: number) => {
|
|
238
|
+
await GliphPlayer.setVolume(v);
|
|
239
|
+
setVolume(v);
|
|
240
|
+
}, []);
|
|
241
|
+
|
|
242
|
+
return [volume, set];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── useIsPlaying ─────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Convenience hook — returns { playing, bufferingDuringPlay }.
|
|
249
|
+
*/
|
|
250
|
+
export function useIsPlaying(): { playing: boolean; bufferingDuringPlay: boolean } {
|
|
251
|
+
const { state } = usePlaybackState();
|
|
252
|
+
return {
|
|
253
|
+
playing: state === State.Playing || state === State.Buffering,
|
|
254
|
+
bufferingDuringPlay: state === State.Buffering,
|
|
255
|
+
};
|
|
256
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* react-native-gliph-player
|
|
3
|
+
* Public API entry point
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Default export — the player API
|
|
7
|
+
export { default } from './GliphPlayer';
|
|
8
|
+
export { default as GliphPlayer } from './GliphPlayer';
|
|
9
|
+
export { isPlaying, isPaused, isBuffering, isStopped } from './GliphPlayer';
|
|
10
|
+
|
|
11
|
+
// Types
|
|
12
|
+
export type {
|
|
13
|
+
Track,
|
|
14
|
+
PlayerOptions,
|
|
15
|
+
UpdateOptions,
|
|
16
|
+
Progress,
|
|
17
|
+
PlaybackState,
|
|
18
|
+
PlaybackError,
|
|
19
|
+
CustomAction,
|
|
20
|
+
EventPayloadByEvent,
|
|
21
|
+
PlaybackStateEvent,
|
|
22
|
+
PlaybackErrorEvent,
|
|
23
|
+
PlaybackActiveTrackChangedEvent,
|
|
24
|
+
PlaybackQueueEndedEvent,
|
|
25
|
+
PlaybackProgressUpdatedEvent,
|
|
26
|
+
PlaybackMetadataReceivedEvent,
|
|
27
|
+
RemoteSeekEvent,
|
|
28
|
+
RemoteJumpForwardEvent,
|
|
29
|
+
RemoteJumpBackwardEvent,
|
|
30
|
+
RemoteSetRatingEvent,
|
|
31
|
+
RemoteDuckEvent,
|
|
32
|
+
RemoteSkipEvent,
|
|
33
|
+
} from './types';
|
|
34
|
+
|
|
35
|
+
// Enums
|
|
36
|
+
export {
|
|
37
|
+
State,
|
|
38
|
+
Event,
|
|
39
|
+
RepeatMode,
|
|
40
|
+
Capability,
|
|
41
|
+
RatingType,
|
|
42
|
+
PitchAlgorithm,
|
|
43
|
+
IOSCategory,
|
|
44
|
+
IOSCategoryMode,
|
|
45
|
+
IOSCategoryOptions,
|
|
46
|
+
AndroidAudioContentType,
|
|
47
|
+
AndroidAudioUsage,
|
|
48
|
+
AppKilledPlaybackBehavior,
|
|
49
|
+
} from './types';
|
|
50
|
+
|
|
51
|
+
// Hooks
|
|
52
|
+
export {
|
|
53
|
+
useTrackPlayerEvents,
|
|
54
|
+
usePlaybackState,
|
|
55
|
+
useProgress,
|
|
56
|
+
useActiveTrack,
|
|
57
|
+
useQueue,
|
|
58
|
+
useRepeatMode,
|
|
59
|
+
useVolume,
|
|
60
|
+
useIsPlaying,
|
|
61
|
+
} from './hooks';
|