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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -0
  3. package/android/build.gradle +82 -0
  4. package/android/src/main/AndroidManifest.xml +31 -0
  5. package/android/src/main/java/com/gliphplayer/DeviceInfoModule.kt +48 -0
  6. package/android/src/main/java/com/gliphplayer/DeviceInfoPackage.kt +16 -0
  7. package/android/src/main/java/com/gliphplayer/GliphPlayerModule.kt +360 -0
  8. package/android/src/main/java/com/gliphplayer/GliphPlayerPackage.kt +48 -0
  9. package/android/src/main/java/com/gliphplayer/GliphPlayerService.kt +797 -0
  10. package/android/src/main/res/xml/automotive_app_desc.xml +4 -0
  11. package/android/src/oldarch/com/gliphplayer/NativeGliphPlayerSpec.kt +56 -0
  12. package/app.plugin.js +1 -0
  13. package/expo-gliph-player.podspec +91 -0
  14. package/ios/GliphAudioPlayer.h +77 -0
  15. package/ios/GliphAudioPlayer.swift +697 -0
  16. package/ios/GliphPlayer-Bridging-Header.h +3 -0
  17. package/ios/GliphPlayerModule.h +12 -0
  18. package/ios/GliphPlayerModule.mm +243 -0
  19. package/ios/expo_gliph_player.h +22 -0
  20. package/lib/commonjs/GliphPlayer.js +310 -0
  21. package/lib/commonjs/GliphPlayer.js.map +1 -0
  22. package/lib/commonjs/hooks.js +233 -0
  23. package/lib/commonjs/hooks.js.map +1 -0
  24. package/lib/commonjs/index.js +166 -0
  25. package/lib/commonjs/index.js.map +1 -0
  26. package/lib/commonjs/package.json +1 -0
  27. package/lib/commonjs/specs/NativeGliphPlayer.js +19 -0
  28. package/lib/commonjs/specs/NativeGliphPlayer.js.map +1 -0
  29. package/lib/commonjs/types.js +175 -0
  30. package/lib/commonjs/types.js.map +1 -0
  31. package/lib/module/GliphPlayer.js +305 -0
  32. package/lib/module/GliphPlayer.js.map +1 -0
  33. package/lib/module/hooks.js +221 -0
  34. package/lib/module/hooks.js.map +1 -0
  35. package/lib/module/index.js +20 -0
  36. package/lib/module/index.js.map +1 -0
  37. package/lib/module/package.json +1 -0
  38. package/lib/module/specs/NativeGliphPlayer.js +20 -0
  39. package/lib/module/specs/NativeGliphPlayer.js.map +1 -0
  40. package/lib/module/types.js +181 -0
  41. package/lib/module/types.js.map +1 -0
  42. package/lib/typescript/src/GliphPlayer.d.ts +113 -0
  43. package/lib/typescript/src/GliphPlayer.d.ts.map +1 -0
  44. package/lib/typescript/src/hooks.d.ts +51 -0
  45. package/lib/typescript/src/hooks.d.ts.map +1 -0
  46. package/lib/typescript/src/index.d.ts +11 -0
  47. package/lib/typescript/src/index.d.ts.map +1 -0
  48. package/lib/typescript/src/specs/NativeGliphPlayer.d.ts +80 -0
  49. package/lib/typescript/src/specs/NativeGliphPlayer.d.ts.map +1 -0
  50. package/lib/typescript/src/types.d.ts +348 -0
  51. package/lib/typescript/src/types.d.ts.map +1 -0
  52. package/package.json +62 -0
  53. package/src/GliphPlayer.ts +356 -0
  54. package/src/hooks.ts +256 -0
  55. package/src/index.ts +61 -0
  56. package/src/specs/NativeGliphPlayer.ts +105 -0
  57. package/src/types.ts +395 -0
  58. package/src/withGliphPlayer.js +166 -0
@@ -0,0 +1,360 @@
1
+ package com.gliphplayer
2
+
3
+ import android.content.ComponentName
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.content.ServiceConnection
7
+ import android.os.IBinder
8
+ import com.facebook.react.bridge.*
9
+ import com.facebook.react.module.annotations.ReactModule
10
+ import com.facebook.react.modules.core.DeviceEventManagerModule
11
+ import kotlinx.coroutines.*
12
+
13
+ /**
14
+ * GliphPlayerModule
15
+ *
16
+ * Works on both New Architecture (TurboModule) and Old Architecture (bridge).
17
+ * - New Arch: NativeGliphPlayerSpec (newarch/) extends TurboModule
18
+ * - Old Arch: NativeGliphPlayerSpec (oldarch/) extends ReactContextBaseJavaModule
19
+ *
20
+ * @ReactMethod annotations are required for Old Arch method exposure.
21
+ * They are harmless on New Arch.
22
+ */
23
+ @ReactModule(name = GliphPlayerModule.NAME)
24
+ class GliphPlayerModule(
25
+ private val reactContext: ReactApplicationContext
26
+ ) : NativeGliphPlayerSpec(reactContext) {
27
+
28
+ companion object {
29
+ const val NAME = "RNGliphPlayer"
30
+ }
31
+
32
+ private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
33
+ private var playerService: GliphPlayerService? = null
34
+ private var isBound = false
35
+
36
+ private val serviceConnection = object : ServiceConnection {
37
+ override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
38
+ val localBinder = binder as? GliphPlayerService.LocalBinder
39
+ playerService = localBinder?.getService()
40
+ playerService?.setEventEmitter { eventName, data ->
41
+ sendEvent(eventName, data)
42
+ }
43
+ isBound = true
44
+ }
45
+
46
+ override fun onServiceDisconnected(name: ComponentName?) {
47
+ playerService = null
48
+ isBound = false
49
+ }
50
+ }
51
+
52
+ // ── Event emission ──────────────────────────────────────────────────────────
53
+
54
+ private fun sendEvent(eventName: String, data: WritableMap?) {
55
+ reactContext
56
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
57
+ .emit(eventName, data)
58
+ }
59
+
60
+ // ── Fix #2: `override` is required because these methods are declared in the
61
+ // auto-generated NativeGliphPlayerSpec base class. TurboModules are strict —
62
+ // without `override` the Kotlin compiler treats them as new declarations and
63
+ // the spec contract is broken, causing a runtime "method not found" crash.
64
+ @ReactMethod override fun addListener(eventType: String) {}
65
+ @ReactMethod override fun removeListeners(count: Double) {}
66
+
67
+ // ── Setup ───────────────────────────────────────────────────────────────────
68
+
69
+ @ReactMethod
70
+ override fun setupPlayer(options: ReadableMap, promise: Promise) {
71
+ scope.launch {
72
+ try {
73
+ val intent = Intent(reactContext, GliphPlayerService::class.java).apply {
74
+ action = "com.gliphplayer.BIND_LOCAL"
75
+ }
76
+ reactContext.startForegroundService(intent)
77
+ reactContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
78
+
79
+ // Wait for service to bind (max 5s)
80
+ withContext(Dispatchers.Default) {
81
+ var waited = 0
82
+ while (!isBound && waited < 5000) {
83
+ delay(50)
84
+ waited += 50
85
+ }
86
+ }
87
+
88
+ if (!isBound || playerService == null) {
89
+ promise.reject("setup_error", "Failed to bind to GliphPlayerService (Timeout)")
90
+ return@launch
91
+ }
92
+
93
+ playerService?.setupPlayer(options)
94
+ promise.resolve(null)
95
+ } catch (e: Exception) {
96
+ promise.reject("setup_error", e.message, e)
97
+ }
98
+ }
99
+ }
100
+
101
+ // destroy() is synchronous — no @ReactMethod needed (called from JS without await)
102
+ override fun destroy() {
103
+ playerService?.destroy()
104
+ if (isBound) {
105
+ try { reactContext.unbindService(serviceConnection) } catch (_: Exception) {}
106
+ isBound = false
107
+ }
108
+ val intent = Intent(reactContext, GliphPlayerService::class.java)
109
+ reactContext.stopService(intent)
110
+ scope.cancel()
111
+ }
112
+
113
+ @ReactMethod
114
+ override fun isServiceRunning(promise: Promise) {
115
+ promise.resolve(isBound && playerService != null)
116
+ }
117
+
118
+ // ── Queue management ────────────────────────────────────────────────────────
119
+
120
+ @ReactMethod
121
+ override fun add(tracks: ReadableArray, insertBeforeIndex: Double, promise: Promise) {
122
+ scope.launch {
123
+ try {
124
+ // -1 is the sentinel for "append to end"
125
+ val index = if (insertBeforeIndex < 0 || insertBeforeIndex.isNaN()) -1
126
+ else insertBeforeIndex.toInt()
127
+ val result = playerService?.add(tracks, index)
128
+ promise.resolve(result)
129
+ } catch (e: Exception) {
130
+ promise.reject("add_error", e.message, e)
131
+ }
132
+ }
133
+ }
134
+
135
+ @ReactMethod
136
+ override fun remove(tracks: ReadableArray, promise: Promise) {
137
+ scope.launch {
138
+ try { playerService?.remove(tracks); promise.resolve(null) }
139
+ catch (e: Exception) { promise.reject("remove_error", e.message, e) }
140
+ }
141
+ }
142
+
143
+ @ReactMethod
144
+ override fun removeUpcomingTracks(promise: Promise) {
145
+ scope.launch {
146
+ try { playerService?.removeUpcomingTracks(); promise.resolve(null) }
147
+ catch (e: Exception) { promise.reject("remove_error", e.message, e) }
148
+ }
149
+ }
150
+
151
+ @ReactMethod
152
+ override fun skip(index: Double, initialPosition: Double, promise: Promise) {
153
+ scope.launch {
154
+ try {
155
+ playerService?.skip(index.toInt(), if (initialPosition < 0) -1.0 else initialPosition)
156
+ promise.resolve(null)
157
+ } catch (e: Exception) { promise.reject("skip_error", e.message, e) }
158
+ }
159
+ }
160
+
161
+ @ReactMethod
162
+ override fun skipToNext(initialPosition: Double, promise: Promise) {
163
+ scope.launch {
164
+ try {
165
+ playerService?.skipToNext(if (initialPosition < 0) -1.0 else initialPosition)
166
+ promise.resolve(null)
167
+ } catch (e: Exception) { promise.reject("skip_error", e.message, e) }
168
+ }
169
+ }
170
+
171
+ @ReactMethod
172
+ override fun skipToPrevious(initialPosition: Double, promise: Promise) {
173
+ scope.launch {
174
+ try {
175
+ playerService?.skipToPrevious(if (initialPosition < 0) -1.0 else initialPosition)
176
+ promise.resolve(null)
177
+ } catch (e: Exception) { promise.reject("skip_error", e.message, e) }
178
+ }
179
+ }
180
+
181
+ @ReactMethod
182
+ override fun move(fromIndex: Double, toIndex: Double, promise: Promise) {
183
+ scope.launch {
184
+ try { playerService?.move(fromIndex.toInt(), toIndex.toInt()); promise.resolve(null) }
185
+ catch (e: Exception) { promise.reject("move_error", e.message, e) }
186
+ }
187
+ }
188
+
189
+ // ── Playback control ────────────────────────────────────────────────────────
190
+
191
+ @ReactMethod override fun play(promise: Promise) {
192
+ scope.launch {
193
+ try { playerService?.play(); promise.resolve(null) }
194
+ catch (e: Exception) { promise.reject("play_error", e.message, e) }
195
+ }
196
+ }
197
+
198
+ @ReactMethod override fun pause(promise: Promise) {
199
+ scope.launch {
200
+ try { playerService?.pause(); promise.resolve(null) }
201
+ catch (e: Exception) { promise.reject("pause_error", e.message, e) }
202
+ }
203
+ }
204
+
205
+ @ReactMethod override fun stop(promise: Promise) {
206
+ scope.launch {
207
+ try { playerService?.stop(); promise.resolve(null) }
208
+ catch (e: Exception) { promise.reject("stop_error", e.message, e) }
209
+ }
210
+ }
211
+
212
+ @ReactMethod override fun reset(promise: Promise) {
213
+ scope.launch {
214
+ try { playerService?.reset(); promise.resolve(null) }
215
+ catch (e: Exception) { promise.reject("reset_error", e.message, e) }
216
+ }
217
+ }
218
+
219
+ @ReactMethod override fun seekTo(position: Double, promise: Promise) {
220
+ scope.launch {
221
+ try { playerService?.seekTo(position); promise.resolve(null) }
222
+ catch (e: Exception) { promise.reject("seek_error", e.message, e) }
223
+ }
224
+ }
225
+
226
+ @ReactMethod override fun seekBy(offset: Double, promise: Promise) {
227
+ scope.launch {
228
+ try { playerService?.seekBy(offset); promise.resolve(null) }
229
+ catch (e: Exception) { promise.reject("seek_error", e.message, e) }
230
+ }
231
+ }
232
+
233
+ @ReactMethod override fun setVolume(volume: Double, promise: Promise) {
234
+ scope.launch {
235
+ try { playerService?.setVolume(volume.toFloat()); promise.resolve(null) }
236
+ catch (e: Exception) { promise.reject("volume_error", e.message, e) }
237
+ }
238
+ }
239
+
240
+ @ReactMethod override fun getVolume(promise: Promise) {
241
+ scope.launch {
242
+ try { promise.resolve(playerService?.getVolume()?.toDouble() ?: 1.0) }
243
+ catch (e: Exception) { promise.reject("volume_error", e.message, e) }
244
+ }
245
+ }
246
+
247
+ @ReactMethod override fun setRate(rate: Double, promise: Promise) {
248
+ scope.launch {
249
+ try { playerService?.setRate(rate.toFloat()); promise.resolve(null) }
250
+ catch (e: Exception) { promise.reject("rate_error", e.message, e) }
251
+ }
252
+ }
253
+
254
+ @ReactMethod override fun getRate(promise: Promise) {
255
+ scope.launch {
256
+ try { promise.resolve(playerService?.getRate()?.toDouble() ?: 1.0) }
257
+ catch (e: Exception) { promise.reject("rate_error", e.message, e) }
258
+ }
259
+ }
260
+
261
+ @ReactMethod override fun setRepeatMode(mode: Double, promise: Promise) {
262
+ scope.launch {
263
+ try { playerService?.setRepeatMode(mode.toInt()); promise.resolve(null) }
264
+ catch (e: Exception) { promise.reject("repeat_error", e.message, e) }
265
+ }
266
+ }
267
+
268
+ @ReactMethod override fun getRepeatMode(promise: Promise) {
269
+ scope.launch {
270
+ try { promise.resolve(playerService?.getRepeatMode()?.toDouble() ?: 0.0) }
271
+ catch (e: Exception) { promise.reject("repeat_error", e.message, e) }
272
+ }
273
+ }
274
+
275
+ // ── Queue getters ───────────────────────────────────────────────────────────
276
+
277
+ @ReactMethod override fun getQueue(promise: Promise) {
278
+ scope.launch {
279
+ try { promise.resolve(playerService?.getQueue()) }
280
+ catch (e: Exception) { promise.reject("queue_error", e.message, e) }
281
+ }
282
+ }
283
+
284
+ @ReactMethod override fun getActiveTrackIndex(promise: Promise) {
285
+ scope.launch {
286
+ // Returns -1 when no active track (Codegen doesn't support nullable number returns)
287
+ try { promise.resolve(playerService?.getActiveTrackIndex()?.toDouble() ?: -1.0) }
288
+ catch (e: Exception) { promise.reject("queue_error", e.message, e) }
289
+ }
290
+ }
291
+
292
+ @ReactMethod override fun getActiveTrack(promise: Promise) {
293
+ scope.launch {
294
+ try { promise.resolve(playerService?.getActiveTrack()) }
295
+ catch (e: Exception) { promise.reject("queue_error", e.message, e) }
296
+ }
297
+ }
298
+
299
+ @ReactMethod override fun getTrack(index: Double, promise: Promise) {
300
+ scope.launch {
301
+ try { promise.resolve(playerService?.getTrack(index.toInt())) }
302
+ catch (e: Exception) { promise.reject("queue_error", e.message, e) }
303
+ }
304
+ }
305
+
306
+ @ReactMethod override fun getQueueSize(promise: Promise) {
307
+ scope.launch {
308
+ try { promise.resolve(playerService?.getQueueSize()?.toDouble() ?: 0.0) }
309
+ catch (e: Exception) { promise.reject("queue_error", e.message, e) }
310
+ }
311
+ }
312
+
313
+ // ── State / progress ────────────────────────────────────────────────────────
314
+
315
+ @ReactMethod override fun getPlaybackState(promise: Promise) {
316
+ scope.launch {
317
+ try { promise.resolve(playerService?.getPlaybackState()) }
318
+ catch (e: Exception) { promise.reject("state_error", e.message, e) }
319
+ }
320
+ }
321
+
322
+ @ReactMethod override fun getProgress(promise: Promise) {
323
+ scope.launch {
324
+ try { promise.resolve(playerService?.getProgress()) }
325
+ catch (e: Exception) { promise.reject("progress_error", e.message, e) }
326
+ }
327
+ }
328
+
329
+ // ── Metadata ────────────────────────────────────────────────────────────────
330
+
331
+ @ReactMethod override fun updateMetadataForTrack(index: Double, metadata: ReadableMap, promise: Promise) {
332
+ scope.launch {
333
+ try { playerService?.updateMetadataForTrack(index.toInt(), metadata); promise.resolve(null) }
334
+ catch (e: Exception) { promise.reject("metadata_error", e.message, e) }
335
+ }
336
+ }
337
+
338
+ @ReactMethod override fun clearNowPlayingMetadata(promise: Promise) {
339
+ scope.launch {
340
+ try { playerService?.clearNowPlayingMetadata(); promise.resolve(null) }
341
+ catch (e: Exception) { promise.reject("metadata_error", e.message, e) }
342
+ }
343
+ }
344
+
345
+ @ReactMethod override fun updateNowPlayingMetadata(metadata: ReadableMap, promise: Promise) {
346
+ scope.launch {
347
+ try { playerService?.updateNowPlayingMetadata(metadata); promise.resolve(null) }
348
+ catch (e: Exception) { promise.reject("metadata_error", e.message, e) }
349
+ }
350
+ }
351
+
352
+ @ReactMethod override fun updateOptions(options: ReadableMap, promise: Promise) {
353
+ scope.launch {
354
+ try { playerService?.updateOptions(options); promise.resolve(null) }
355
+ catch (e: Exception) { promise.reject("options_error", e.message, e) }
356
+ }
357
+ }
358
+
359
+ override fun getName() = NAME
360
+ }
@@ -0,0 +1,48 @@
1
+ package com.gliphplayer
2
+
3
+ import com.facebook.react.TurboReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+
9
+ /**
10
+ * GliphPlayerPackage
11
+ *
12
+ * Registers the TurboModule with React Native.
13
+ * TurboReactPackage is used instead of ReactPackage to support
14
+ * the New Architecture (lazy loading, JSI binding).
15
+ */
16
+ class GliphPlayerPackage : TurboReactPackage() {
17
+
18
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
19
+ return when (name) {
20
+ GliphPlayerModule.NAME -> GliphPlayerModule(reactContext)
21
+ "DeviceInfoModule" -> DeviceInfoModule(reactContext)
22
+ else -> null
23
+ }
24
+ }
25
+
26
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
27
+ return ReactModuleInfoProvider {
28
+ mapOf(
29
+ GliphPlayerModule.NAME to ReactModuleInfo(
30
+ GliphPlayerModule.NAME,
31
+ GliphPlayerModule.NAME,
32
+ false, // canOverrideExistingModule
33
+ false, // needsEagerInit
34
+ false, // isCxxModule
35
+ true // isTurboModule
36
+ ),
37
+ "DeviceInfoModule" to ReactModuleInfo(
38
+ "DeviceInfoModule",
39
+ "DeviceInfoModule",
40
+ false,
41
+ false,
42
+ false,
43
+ false // Not a TurboModule yet
44
+ )
45
+ )
46
+ }
47
+ }
48
+ }