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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 react-native-gliph-player contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# 🎵 expo-gliph-player
|
|
2
|
+
|
|
3
|
+
[](https://npmjs.com)
|
|
4
|
+
[](https://opensource.org)
|
|
5
|
+
|
|
6
|
+
**Fixed version of react-native-gliph-player with full Expo compatibility.** All iOS bridge fixes, Swift/Objective-C issues, and Android `autoSkipOnError` patches are pre-applied.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 📦 Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install expo-gliph-player
|
|
14
|
+
# or
|
|
15
|
+
yarn add expo-gliph-player
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### From GitHub (Alternative):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
yarn add https://github.com
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🚀 Setup & Configuration
|
|
27
|
+
|
|
28
|
+
### 1. Configure `app.json`
|
|
29
|
+
|
|
30
|
+
Since this package contains native code, you must add the config plugin and configure background capabilities for iOS and Android:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"expo": {
|
|
35
|
+
"plugins": ["expo-gliph-player"],
|
|
36
|
+
"ios": {
|
|
37
|
+
"infoPlist": {
|
|
38
|
+
"UIBackgroundModes": ["audio"]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"android": {
|
|
42
|
+
"permissions": ["android.permission.FOREGROUND_SERVICE"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Build the App
|
|
49
|
+
|
|
50
|
+
This package uses native code. Run prebuild before launching the project:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx expo prebuild
|
|
54
|
+
# then run on device/emulator
|
|
55
|
+
npx expo run:ios
|
|
56
|
+
# or
|
|
57
|
+
npx expo run:android
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 💻 Reactive State
|
|
63
|
+
|
|
64
|
+
Instead of manually fetching state, Gliph Player ships with powerful React Hooks. These hooks automatically re-render your components whenever the audio state changes.
|
|
65
|
+
|
|
66
|
+
### Play/Pause State:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { useIsPlaying } from 'expo-gliph-player';
|
|
70
|
+
|
|
71
|
+
const { playing } = useIsPlaying(); // Returns true or false
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Progress & Duration (in seconds):
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { useProgress } from 'expo-gliph-player';
|
|
78
|
+
|
|
79
|
+
const { position, duration } = useProgress(500); // Updates every 500ms
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### "Now Playing" Track Metadata:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { useActiveTrack } from 'expo-gliph-player';
|
|
86
|
+
|
|
87
|
+
const track = useActiveTrack(); // Contains url, title, artist, artwork
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🎛️ Playback & Queue Controls
|
|
93
|
+
|
|
94
|
+
### Basic Controls
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
await GliphPlayer.play();
|
|
98
|
+
await GliphPlayer.pause();
|
|
99
|
+
await GliphPlayer.stop();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Navigation & Seeking
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
await GliphPlayer.skipToNext();
|
|
106
|
+
await GliphPlayer.skipToPrevious();
|
|
107
|
+
await GliphPlayer.seekTo(60); // Jump to exactly 1 minute (in seconds)
|
|
108
|
+
await GliphPlayer.seekBy(15); // Jump forward 15 seconds (great for podcasts)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Managing the Queue
|
|
112
|
+
|
|
113
|
+
You can dynamically add, remove, or reorder tracks while the audio is playing.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
// Add a track to the end of the queue
|
|
117
|
+
await GliphPlayer.add({
|
|
118
|
+
id: 'new-song',
|
|
119
|
+
url: 'https://example.com',
|
|
120
|
+
title: 'Just Added',
|
|
121
|
+
artist: 'User'
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Remove a specific track by ID
|
|
125
|
+
await GliphPlayer.remove('track-1');
|
|
126
|
+
|
|
127
|
+
// Stop playback and empty the queue entirely
|
|
128
|
+
await GliphPlayer.reset();
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 🔒 Lock Screen & Background Events
|
|
134
|
+
|
|
135
|
+
To ensure your lock screen and notification panel controls actually respond to user input, you must connect native remote events to the player engine.
|
|
136
|
+
|
|
137
|
+
### Inside Components (`useTrackPlayerEvents`)
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { useTrackPlayerEvents, Event } from 'expo-gliph-player';
|
|
141
|
+
import GliphPlayer from 'expo-gliph-player';
|
|
142
|
+
|
|
143
|
+
export function PlaybackObserver() {
|
|
144
|
+
useTrackPlayerEvents([
|
|
145
|
+
Event.RemotePlay,
|
|
146
|
+
Event.RemotePause,
|
|
147
|
+
Event.RemoteNext,
|
|
148
|
+
Event.RemotePrevious
|
|
149
|
+
], (event) => {
|
|
150
|
+
if (event.type === Event.RemotePlay) GliphPlayer.play();
|
|
151
|
+
if (event.type === Event.RemotePause) GliphPlayer.pause();
|
|
152
|
+
if (event.type === Event.RemoteNext) GliphPlayer.skipToNext();
|
|
153
|
+
if (event.type === Event.RemotePrevious) GliphPlayer.skipToPrevious();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return null; // Headless component, acts as a layout listener
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Global Listeners (`GliphPlayer.addEventListener`)
|
|
161
|
+
|
|
162
|
+
To handle audio events even when your React UI components are unmounted, register global listeners inside your app root (`index.js` or `App.tsx` outside the component lifecycle):
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import GliphPlayer, { Event } from 'expo-gliph-player';
|
|
166
|
+
|
|
167
|
+
GliphPlayer.addEventListener(Event.RemotePlay, () => {
|
|
168
|
+
GliphPlayer.play();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
GliphPlayer.addEventListener(Event.RemotePause, () => {
|
|
172
|
+
GliphPlayer.pause();
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 🚀 Full Implementation Example
|
|
179
|
+
|
|
180
|
+
Here is a production-ready, complete example split into the main entry file (`App.tsx`) and the user interface component (`MusicPlayer.tsx`).
|
|
181
|
+
|
|
182
|
+
### 1. App.tsx (Main Entry & Background Engine Setup)
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
import React, { useEffect } from 'react';
|
|
186
|
+
import { View, Platform, PermissionsAndroid } from 'react-native';
|
|
187
|
+
import GliphPlayer, { Capability, AppKilledPlaybackBehavior } from 'expo-gliph-player';
|
|
188
|
+
import { MusicPlayer } from './components/MusicPlayer';
|
|
189
|
+
|
|
190
|
+
const tracks = [
|
|
191
|
+
{
|
|
192
|
+
id: '1',
|
|
193
|
+
url: 'https://example.com',
|
|
194
|
+
title: 'Gliph Journey',
|
|
195
|
+
artist: 'Gliph Labs',
|
|
196
|
+
artwork: 'https://example.com',
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
export default function App() {
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
const setup = async () => {
|
|
203
|
+
// 1. Android Notification Permission (API 33+)
|
|
204
|
+
if (Platform.OS === 'android' && Platform.Version >= 33) {
|
|
205
|
+
await PermissionsAndroid.request('android.permission.POST_NOTIFICATIONS' as any);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 2. Setup the engine
|
|
209
|
+
await GliphPlayer.setupPlayer({
|
|
210
|
+
playBuffer: 0.5, // 0.5s buffer for zero-lag seeking
|
|
211
|
+
android: {
|
|
212
|
+
appKilledPlaybackBehavior: AppKilledPlaybackBehavior.ContinuePlayback,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// 3. Define Lock Screen / Notification Controls
|
|
217
|
+
await GliphPlayer.updateOptions({
|
|
218
|
+
capabilities: [
|
|
219
|
+
Capability.Play, Capability.Pause,
|
|
220
|
+
Capability.SkipToNext, Capability.SkipToPrevious,
|
|
221
|
+
Capability.SeekTo,
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// 4. Add tracks to queue
|
|
226
|
+
await GliphPlayer.add(tracks);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
setup();
|
|
230
|
+
}, []);
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<View style={{ flex: 1, backgroundColor: '#121212' }}>
|
|
234
|
+
<MusicPlayer />
|
|
235
|
+
</View>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### 2. MusicPlayer.tsx (UI Component)
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
import React, { useState } from 'react';
|
|
244
|
+
import { View, Text, TouchableOpacity, Image, StyleSheet } from 'react-native';
|
|
245
|
+
import Slider from '@react-native-community/slider';
|
|
246
|
+
import GliphPlayer, { useIsPlaying, useProgress, useActiveTrack, RepeatMode } from 'expo-gliph-player';
|
|
247
|
+
|
|
248
|
+
export const MusicPlayer = () => {
|
|
249
|
+
const { playing } = useIsPlaying();
|
|
250
|
+
const { position, duration } = useProgress(500);
|
|
251
|
+
const track = useActiveTrack();
|
|
252
|
+
const [repeatMode, setRepeatMode] = useState('off');
|
|
253
|
+
|
|
254
|
+
const togglePlayback = async () => {
|
|
255
|
+
if (playing) {
|
|
256
|
+
await GliphPlayer.pause();
|
|
257
|
+
} else {
|
|
258
|
+
await GliphPlayer.play();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const cycleRepeat = async () => {
|
|
263
|
+
const next = repeatMode === 'off' ? 'one' : repeatMode === 'one' ? 'all' : 'off';
|
|
264
|
+
setRepeatMode(next);
|
|
265
|
+
await GliphPlayer.setRepeatMode(
|
|
266
|
+
next === 'one' ? RepeatMode.Track : next === 'all' ? RepeatMode.Queue : RepeatMode.Off
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<View style={styles.container}>
|
|
272
|
+
<Image source={{ uri: track?.artwork }} style={styles.artwork} />
|
|
273
|
+
<Text style={styles.title}>{track?.title || 'No Track'}</Text>
|
|
274
|
+
|
|
275
|
+
<Slider
|
|
276
|
+
style={{ width: '100%', height: 40 }}
|
|
277
|
+
value={position}
|
|
278
|
+
maximumValue={duration || 1}
|
|
279
|
+
onSlidingComplete={(val) => GliphPlayer.seekTo(val)}
|
|
280
|
+
minimumTrackTintColor="#1DB954"
|
|
281
|
+
/>
|
|
282
|
+
|
|
283
|
+
<View style={styles.controls}>
|
|
284
|
+
<TouchableOpacity onPress={() => GliphPlayer.skipToPrevious()}>
|
|
285
|
+
<Text style={styles.btn}>Prev</Text>
|
|
286
|
+
</TouchableOpacity>
|
|
287
|
+
|
|
288
|
+
<TouchableOpacity onPress={togglePlayback} style={styles.playBtn}>
|
|
289
|
+
<Text style={{ color: '#fff' }}>{playing ? 'PAUSE' : 'PLAY'}</Text>
|
|
290
|
+
</TouchableOpacity>
|
|
291
|
+
|
|
292
|
+
<TouchableOpacity onPress={() => GliphPlayer.skipToNext()}>
|
|
293
|
+
<Text style={styles.btn}>Next</Text>
|
|
294
|
+
</TouchableOpacity>
|
|
295
|
+
</View>
|
|
296
|
+
|
|
297
|
+
<TouchableOpacity onPress={cycleRepeat} style={{ marginTop: 20 }}>
|
|
298
|
+
<Text style={{ color: '#1DB954' }}>Repeat: {repeatMode.toUpperCase()}</Text>
|
|
299
|
+
</TouchableOpacity>
|
|
300
|
+
</View>
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const styles = StyleSheet.create({
|
|
305
|
+
container: { padding: 20, alignItems: 'center', justifyContent: 'center', flex: 1 },
|
|
306
|
+
artwork: { width: 300, height: 300, borderRadius: 10 },
|
|
307
|
+
title: { color: '#fff', fontSize: 24, marginVertical: 20 },
|
|
308
|
+
controls: { flexDirection: 'row', alignItems: 'center', gap: 40, marginTop: 20 },
|
|
309
|
+
playBtn: { width: 80, height: 80, borderRadius: 40, backgroundColor: '#333', justifyContent: 'center', alignItems: 'center' },
|
|
310
|
+
btn: { color: '#fff', fontSize: 18 }
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## 🔧 What's Fixed
|
|
317
|
+
|
|
318
|
+
### iOS:
|
|
319
|
+
- ✅ Fixed Swift/Objective-C bridging with `GliphAudioPlayer.h`.
|
|
320
|
+
- ✅ Removed ReactCodegen dependency (no more build errors).
|
|
321
|
+
- ✅ Fixed `use_frameworks!` compatibility.
|
|
322
|
+
- ✅ Correct Swift module generation.
|
|
323
|
+
|
|
324
|
+
### Android:
|
|
325
|
+
- ✅ Fixed `autoSkipOnError` crash (`NoSuchKeyException`).
|
|
326
|
+
- ✅ Proper foreground service registration.
|
|
327
|
+
- ✅ `MainApplication.kt` autolinking.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## 📋 Requirements
|
|
332
|
+
|
|
333
|
+
- **Expo SDK:** 50+
|
|
334
|
+
- **React Native:** 0.71+
|
|
335
|
+
- **iOS:** 14.0+
|
|
336
|
+
- **Android:** API 21+
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 📄 License
|
|
341
|
+
|
|
342
|
+
MIT
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## 🙏 Credits
|
|
347
|
+
|
|
348
|
+
Based on [react-native-gliph-player](https://github.com) by Gliph Labs.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
dependencies {
|
|
7
|
+
classpath "com.android.tools.build:gradle:8.1.0"
|
|
8
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// ─── Fix #1: Apply the React Native Gradle plugin FIRST ──────────────────────
|
|
13
|
+
// This is required for Codegen to run and generate the TurboModule C++/Java
|
|
14
|
+
// bindings. Without it, JSI linking fails silently on New Architecture.
|
|
15
|
+
apply plugin: 'com.android.library'
|
|
16
|
+
apply plugin: 'kotlin-android'
|
|
17
|
+
apply plugin: 'com.facebook.react'
|
|
18
|
+
|
|
19
|
+
android {
|
|
20
|
+
compileSdkVersion 36
|
|
21
|
+
namespace "com.gliphplayer"
|
|
22
|
+
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion 24
|
|
25
|
+
targetSdkVersion 36
|
|
26
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
buildFeatures {
|
|
30
|
+
buildConfig true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
compileOptions {
|
|
34
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
35
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
kotlinOptions {
|
|
39
|
+
jvmTarget = "17"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sourceSets {
|
|
43
|
+
main {
|
|
44
|
+
if (!isNewArchitectureEnabled()) {
|
|
45
|
+
// Old Architecture: use the hand-written spec that does NOT implement TurboModule
|
|
46
|
+
java.srcDirs += ["src/oldarch"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
repositories {
|
|
53
|
+
google()
|
|
54
|
+
mavenCentral()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
dependencies {
|
|
58
|
+
implementation "com.facebook.react:react-android"
|
|
59
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:2.1.20"
|
|
60
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
|
61
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
|
|
62
|
+
|
|
63
|
+
// ExoPlayer (Media3)
|
|
64
|
+
implementation "androidx.media3:media3-exoplayer:1.3.1"
|
|
65
|
+
implementation "androidx.media3:media3-exoplayer-hls:1.3.1"
|
|
66
|
+
implementation "androidx.media3:media3-exoplayer-dash:1.3.1"
|
|
67
|
+
implementation "androidx.media3:media3-exoplayer-smoothstreaming:1.3.1"
|
|
68
|
+
implementation "androidx.media3:media3-session:1.3.1"
|
|
69
|
+
implementation "androidx.media3:media3-ui:1.3.1"
|
|
70
|
+
implementation "androidx.media3:media3-datasource-okhttp:1.3.1"
|
|
71
|
+
|
|
72
|
+
// OkHttp for custom headers
|
|
73
|
+
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
|
74
|
+
|
|
75
|
+
// AndroidX
|
|
76
|
+
implementation "androidx.core:core-ktx:1.12.0"
|
|
77
|
+
implementation "androidx.media:media:1.7.0"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def isNewArchitectureEnabled() {
|
|
81
|
+
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
|
|
82
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
|
|
4
|
+
<!-- Required permissions -->
|
|
5
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
6
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
|
7
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
8
|
+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
9
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
10
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
11
|
+
|
|
12
|
+
<application>
|
|
13
|
+
<!-- Background playback service -->
|
|
14
|
+
<service
|
|
15
|
+
android:name=".GliphPlayerService"
|
|
16
|
+
android:exported="true"
|
|
17
|
+
android:foregroundServiceType="mediaPlayback"
|
|
18
|
+
android:stopWithTask="false">
|
|
19
|
+
<intent-filter>
|
|
20
|
+
<action android:name="androidx.media3.session.MediaLibraryService" />
|
|
21
|
+
<action android:name="android.media.browse.MediaBrowserService" />
|
|
22
|
+
</intent-filter>
|
|
23
|
+
</service>
|
|
24
|
+
|
|
25
|
+
<!-- Android Auto support -->
|
|
26
|
+
<meta-data
|
|
27
|
+
android:name="com.google.android.gms.car.application"
|
|
28
|
+
android:resource="@xml/automotive_app_desc" />
|
|
29
|
+
</application>
|
|
30
|
+
|
|
31
|
+
</manifest>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package com.gliphplayer
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.media.AudioDeviceInfo
|
|
5
|
+
import android.media.AudioManager
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import com.facebook.react.bridge.Promise
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
10
|
+
import com.facebook.react.bridge.ReactMethod
|
|
11
|
+
|
|
12
|
+
class DeviceInfoModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
13
|
+
|
|
14
|
+
override fun getName(): String {
|
|
15
|
+
return "DeviceInfoModule"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@ReactMethod
|
|
19
|
+
fun getActiveAudioDeviceName(promise: Promise) {
|
|
20
|
+
try {
|
|
21
|
+
val audioManager = reactApplicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
22
|
+
|
|
23
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
24
|
+
val devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
|
25
|
+
|
|
26
|
+
var bluetoothDeviceName: String? = null
|
|
27
|
+
|
|
28
|
+
for (device in devices) {
|
|
29
|
+
if (device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
|
|
30
|
+
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
|
|
31
|
+
bluetoothDeviceName = device.productName.toString()
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (bluetoothDeviceName != null) {
|
|
37
|
+
promise.resolve(bluetoothDeviceName)
|
|
38
|
+
} else {
|
|
39
|
+
promise.resolve(null)
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
promise.resolve(null)
|
|
43
|
+
}
|
|
44
|
+
} catch (e: Exception) {
|
|
45
|
+
promise.reject("ERROR", e.message)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.gliphplayer
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class DeviceInfoPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(DeviceInfoModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
|
+
}
|