expo-native-track-player 0.1.1

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 (53) hide show
  1. package/ExpoNativeTrackPlayer.podspec +21 -0
  2. package/LICENSE +20 -0
  3. package/README.md +267 -0
  4. package/android/build.gradle +71 -0
  5. package/android/src/main/AndroidManifest.xml +12 -0
  6. package/android/src/main/java/com/exponativetrackplayer/ExpoNativeTrackPlayerModule.kt +528 -0
  7. package/android/src/main/java/com/exponativetrackplayer/ExpoNativeTrackPlayerPackage.kt +34 -0
  8. package/android/src/main/java/com/exponativetrackplayer/ExpoNativeTrackPlayerService.kt +118 -0
  9. package/ios/ExpoNativeTrackPlayer.h +6 -0
  10. package/ios/ExpoNativeTrackPlayer.mm +475 -0
  11. package/lib/module/NativeExpoNativeTrackPlayer.js +5 -0
  12. package/lib/module/NativeExpoNativeTrackPlayer.js.map +1 -0
  13. package/lib/module/constants.js +31 -0
  14. package/lib/module/constants.js.map +1 -0
  15. package/lib/module/events.js +56 -0
  16. package/lib/module/events.js.map +1 -0
  17. package/lib/module/hooks.js +105 -0
  18. package/lib/module/hooks.js.map +1 -0
  19. package/lib/module/index.js +150 -0
  20. package/lib/module/index.js.map +1 -0
  21. package/lib/module/package.json +1 -0
  22. package/lib/module/types/ResourceObject.js +2 -0
  23. package/lib/module/types/ResourceObject.js.map +1 -0
  24. package/lib/module/types/Track.js +4 -0
  25. package/lib/module/types/Track.js.map +1 -0
  26. package/lib/module/types/TrackMetadataBase.js +2 -0
  27. package/lib/module/types/TrackMetadataBase.js.map +1 -0
  28. package/lib/typescript/package.json +1 -0
  29. package/lib/typescript/src/NativeExpoNativeTrackPlayer.d.ts +57 -0
  30. package/lib/typescript/src/NativeExpoNativeTrackPlayer.d.ts.map +1 -0
  31. package/lib/typescript/src/constants.d.ts +23 -0
  32. package/lib/typescript/src/constants.d.ts.map +1 -0
  33. package/lib/typescript/src/events.d.ts +163 -0
  34. package/lib/typescript/src/events.d.ts.map +1 -0
  35. package/lib/typescript/src/hooks.d.ts +12 -0
  36. package/lib/typescript/src/hooks.d.ts.map +1 -0
  37. package/lib/typescript/src/index.d.ts +65 -0
  38. package/lib/typescript/src/index.d.ts.map +1 -0
  39. package/lib/typescript/src/types/ResourceObject.d.ts +5 -0
  40. package/lib/typescript/src/types/ResourceObject.d.ts.map +1 -0
  41. package/lib/typescript/src/types/Track.d.ts +22 -0
  42. package/lib/typescript/src/types/Track.d.ts.map +1 -0
  43. package/lib/typescript/src/types/TrackMetadataBase.d.ts +18 -0
  44. package/lib/typescript/src/types/TrackMetadataBase.d.ts.map +1 -0
  45. package/package.json +172 -0
  46. package/src/NativeExpoNativeTrackPlayer.ts +70 -0
  47. package/src/constants.ts +40 -0
  48. package/src/events.ts +136 -0
  49. package/src/hooks.ts +149 -0
  50. package/src/index.tsx +250 -0
  51. package/src/types/ResourceObject.ts +4 -0
  52. package/src/types/Track.ts +23 -0
  53. package/src/types/TrackMetadataBase.ts +17 -0
package/package.json ADDED
@@ -0,0 +1,172 @@
1
+ {
2
+ "name": "expo-native-track-player",
3
+ "version": "0.1.1",
4
+ "description": "TurboModule track player (New Architecture)",
5
+ "main": "./lib/module/index.js",
6
+ "react-native": "./src/index.tsx",
7
+ "source": "./src/index.tsx",
8
+ "types": "./lib/typescript/src/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "react-native": "./src/index.tsx",
12
+ "source": "./src/index.tsx",
13
+ "types": "./lib/typescript/src/index.d.ts",
14
+ "default": "./lib/module/index.js"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "files": [
19
+ "src",
20
+ "lib",
21
+ "android",
22
+ "ios",
23
+ "cpp",
24
+ "*.podspec",
25
+ "react-native.config.js",
26
+ "!ios/build",
27
+ "!android/build",
28
+ "!android/gradle",
29
+ "!android/gradlew",
30
+ "!android/gradlew.bat",
31
+ "!android/local.properties",
32
+ "!**/__tests__",
33
+ "!**/__fixtures__",
34
+ "!**/__mocks__",
35
+ "!**/.*"
36
+ ],
37
+ "scripts": {
38
+ "example": "yarn workspace expo-native-track-player-example",
39
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
40
+ "prepare": "bob build",
41
+ "typecheck": "tsc",
42
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
43
+ "test": "jest",
44
+ "release": "release-it --only-version"
45
+ },
46
+ "keywords": [
47
+ "react-native",
48
+ "ios",
49
+ "android"
50
+ ],
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+.git"
54
+ },
55
+ "author": " <> ()",
56
+ "license": "MIT",
57
+ "bugs": {
58
+ "url": "/issues"
59
+ },
60
+ "homepage": "#readme",
61
+ "publishConfig": {
62
+ "registry": "https://registry.npmjs.org/"
63
+ },
64
+ "devDependencies": {
65
+ "@commitlint/config-conventional": "^19.8.1",
66
+ "@eslint/compat": "^1.3.2",
67
+ "@eslint/eslintrc": "^3.3.1",
68
+ "@eslint/js": "^9.35.0",
69
+ "@react-native/babel-preset": "0.83.0",
70
+ "@react-native/eslint-config": "0.83.0",
71
+ "@release-it/conventional-changelog": "^10.0.1",
72
+ "@types/jest": "^29.5.14",
73
+ "@types/react": "^19.2.0",
74
+ "commitlint": "^19.8.1",
75
+ "del-cli": "^6.0.0",
76
+ "eslint": "^9.35.0",
77
+ "eslint-config-prettier": "^10.1.8",
78
+ "eslint-plugin-prettier": "^5.5.4",
79
+ "jest": "^29.7.0",
80
+ "lefthook": "^2.0.3",
81
+ "prettier": "^2.8.8",
82
+ "react": "19.2.0",
83
+ "react-native": "0.83.0",
84
+ "react-native-builder-bob": "^0.40.17",
85
+ "release-it": "^19.0.4",
86
+ "turbo": "^2.5.6",
87
+ "typescript": "^5.9.2"
88
+ },
89
+ "peerDependencies": {
90
+ "react": "*",
91
+ "react-native": "*"
92
+ },
93
+ "packageManager": "yarn@4.11.0",
94
+ "react-native-builder-bob": {
95
+ "source": "src",
96
+ "output": "lib",
97
+ "targets": [
98
+ [
99
+ "module",
100
+ {
101
+ "esm": true
102
+ }
103
+ ],
104
+ [
105
+ "typescript",
106
+ {
107
+ "project": "tsconfig.build.json"
108
+ }
109
+ ]
110
+ ]
111
+ },
112
+ "codegenConfig": {
113
+ "name": "ExpoNativeTrackPlayerSpec",
114
+ "type": "modules",
115
+ "jsSrcsDir": "src",
116
+ "android": {
117
+ "javaPackageName": "com.exponativetrackplayer"
118
+ }
119
+ },
120
+ "prettier": {
121
+ "quoteProps": "consistent",
122
+ "singleQuote": true,
123
+ "tabWidth": 2,
124
+ "trailingComma": "es5",
125
+ "useTabs": false
126
+ },
127
+ "jest": {
128
+ "preset": "react-native",
129
+ "modulePathIgnorePatterns": [
130
+ "<rootDir>/example/node_modules",
131
+ "<rootDir>/lib/"
132
+ ]
133
+ },
134
+ "commitlint": {
135
+ "extends": [
136
+ "@commitlint/config-conventional"
137
+ ]
138
+ },
139
+ "release-it": {
140
+ "git": {
141
+ "commitMessage": "chore: release ${version}",
142
+ "tagName": "v${version}"
143
+ },
144
+ "npm": {
145
+ "publish": true
146
+ },
147
+ "github": {
148
+ "release": true
149
+ },
150
+ "plugins": {
151
+ "@release-it/conventional-changelog": {
152
+ "preset": {
153
+ "name": "angular"
154
+ }
155
+ }
156
+ }
157
+ },
158
+ "create-react-native-library": {
159
+ "type": "turbo-module",
160
+ "languages": "kotlin-objc",
161
+ "tools": [
162
+ "eslint",
163
+ "jest",
164
+ "lefthook",
165
+ "release-it"
166
+ ],
167
+ "version": "0.57.0"
168
+ },
169
+ "workspaces": [
170
+ "example"
171
+ ]
172
+ }
@@ -0,0 +1,70 @@
1
+ import { TurboModuleRegistry, type TurboModule } from 'react-native';
2
+ export interface TrackMetadata {
3
+ id: string;
4
+ url: string;
5
+ title?: string;
6
+ artist?: string;
7
+ albumName?: string;
8
+ artworkUri?: string;
9
+ trackNumber?: number;
10
+ duration?: number;
11
+ composer?: string;
12
+ conductor?: string;
13
+ genre?: string;
14
+ compilation?: string;
15
+ subtitle?: string;
16
+ description?: string;
17
+ station?: string;
18
+ mediaType?: number;
19
+ type?: string;
20
+ userAgent?: string;
21
+ contentType?: string;
22
+ pitchAlgorithm?: string;
23
+ }
24
+
25
+ export type RepeatMode = 'off' | 'track' | 'queue' | 'loop_portion';
26
+ export type State =
27
+ | 'none'
28
+ | 'ready'
29
+ | 'playing'
30
+ | 'paused'
31
+ | 'stopped'
32
+ | 'loading'
33
+ | 'buffering'
34
+ | 'error'
35
+ | 'ended';
36
+ export type PlaybackState = State;
37
+ export interface Spec extends TurboModule {
38
+ addToQueue(track: TrackMetadata): void;
39
+ addQueue(tracks: Array<TrackMetadata>): void;
40
+ getQueue(): Array<TrackMetadata>;
41
+ removeFromQueue(trackId: string): void;
42
+ clearQueue(): void;
43
+ play(index?: number | null): void;
44
+ pause(): void;
45
+ stop(): void;
46
+ skipToNext(): void;
47
+ skipToPrevious(): void;
48
+ skipToIndex(index: number): void;
49
+ seekTo(positionMs: number): void;
50
+ reset(): void;
51
+ setRepeatMode(
52
+ mode: RepeatMode,
53
+ startMs?: number | null,
54
+ endMs?: number | null
55
+ ): void;
56
+ getRepeatMode(): RepeatMode;
57
+ getCurrentTrack(): TrackMetadata | null;
58
+ getCurrentTrackIndex(): number;
59
+ getPosition(): number;
60
+ getDuration(): number;
61
+ getPlaybackState(): State;
62
+ setVolume(volume: number): void;
63
+ getVolume(): number;
64
+ setRate(rate: number): void;
65
+ getRate(): number;
66
+ addListener(eventName: string): void;
67
+ removeListeners(count: number): void;
68
+ }
69
+
70
+ export default TurboModuleRegistry.getEnforcing<Spec>('ExpoNativeTrackPlayer');
@@ -0,0 +1,40 @@
1
+ export const TrackTypes = {
2
+ Default: 'default',
3
+ Dash: 'dash',
4
+ Hls: 'hls',
5
+ SmoothStreaming: 'smoothstreaming',
6
+ } as const;
7
+
8
+ export type TrackType = (typeof TrackTypes)[keyof typeof TrackTypes];
9
+
10
+ export const PitchAlgorithms = {
11
+ Linear: 'linear',
12
+ Music: 'music',
13
+ Voice: 'voice',
14
+ } as const;
15
+
16
+ export type PitchAlgorithm =
17
+ (typeof PitchAlgorithms)[keyof typeof PitchAlgorithms];
18
+
19
+ export const State: Record<string, string> = {
20
+ None: 'none',
21
+ Ready: 'ready',
22
+ Playing: 'playing',
23
+ Paused: 'paused',
24
+ Stopped: 'stopped',
25
+ Loading: 'loading',
26
+ Buffering: 'buffering',
27
+ Error: 'error',
28
+ Ended: 'ended',
29
+ } as const;
30
+
31
+ export type StateType = (typeof State)[keyof typeof State];
32
+
33
+ export const RepeatModes = {
34
+ Off: 'off',
35
+ Track: 'track',
36
+ Queue: 'queue',
37
+ LoopPortion: 'loop_portion',
38
+ } as const;
39
+
40
+ export type RepeatModeType = (typeof RepeatModes)[keyof typeof RepeatModes];
package/src/events.ts ADDED
@@ -0,0 +1,136 @@
1
+ import { NativeEventEmitter, type EmitterSubscription } from 'react-native';
2
+ import ExpoNativeTrackPlayer, {
3
+ type State,
4
+ type TrackMetadata,
5
+ } from './NativeExpoNativeTrackPlayer';
6
+
7
+ export const Event = {
8
+ PlayerError: 'player-error',
9
+ PlaybackState: 'playback-state',
10
+ PlaybackError: 'playback-error',
11
+ PlaybackQueueEnded: 'playback-queue-ended',
12
+ PlaybackActiveTrackChanged: 'playback-active-track-changed',
13
+ PlaybackPlayWhenReadyChanged: 'playback-play-when-ready-changed',
14
+ PlaybackProgressUpdated: 'playback-progress-updated',
15
+ PlaybackResume: 'android-playback-resume',
16
+ RemotePlay: 'remote-play',
17
+ RemotePlayPause: 'remote-play-pause',
18
+ RemotePause: 'remote-pause',
19
+ RemoteStop: 'remote-stop',
20
+ RemoteNext: 'remote-next',
21
+ RemotePrevious: 'remote-previous',
22
+ RemoteJumpForward: 'remote-jump-forward',
23
+ RemoteJumpBackward: 'remote-jump-backward',
24
+ RemoteSeek: 'remote-seek',
25
+ RemoteSetRating: 'remote-set-rating',
26
+ RemoteDuck: 'remote-duck',
27
+ RemoteLike: 'remote-like',
28
+ RemoteDislike: 'remote-dislike',
29
+ RemoteBookmark: 'remote-bookmark',
30
+ RemotePlayId: 'remote-play-id',
31
+ RemotePlaySearch: 'remote-play-search',
32
+ RemoteSkip: 'remote-skip',
33
+ MetadataChapterReceived: 'metadata-chapter-received',
34
+ MetadataTimedReceived: 'metadata-timed-received',
35
+ MetadataCommonReceived: 'metadata-common-received',
36
+ AndroidConnectorConnected: 'android-controller-connected',
37
+ AndroidConnectorDisconnected: 'android-controller-disconnected',
38
+ } as const;
39
+
40
+ export type EventName = (typeof Event)[keyof typeof Event];
41
+
42
+ export const TrackPlayerEvents = {
43
+ ...Event,
44
+ QueueUpdated: 'queue-updated',
45
+ } as const;
46
+
47
+ export type TrackPlayerEventName =
48
+ (typeof TrackPlayerEvents)[keyof typeof TrackPlayerEvents];
49
+
50
+ export interface PlaybackStateEvent {
51
+ state: State;
52
+ }
53
+
54
+ export interface PlaybackPositionEvent {
55
+ position: number;
56
+ duration: number;
57
+ trackIndex: number;
58
+ }
59
+
60
+ export interface TrackChangedEvent {
61
+ trackIndex: number;
62
+ track: TrackMetadata | null;
63
+ }
64
+
65
+ export interface QueueUpdatedEvent {
66
+ count: number;
67
+ trackIndex: number;
68
+ queue: TrackMetadata[];
69
+ }
70
+
71
+ export interface PlaybackQueueEndedEvent {
72
+ trackIndex: number;
73
+ position: number;
74
+ }
75
+
76
+ export type EventPayloadMap = {
77
+ [Event.PlayerError]: { message?: string } | undefined;
78
+ [Event.PlaybackState]: PlaybackStateEvent;
79
+ [Event.PlaybackError]: { message?: string } | undefined;
80
+ [Event.PlaybackQueueEnded]: PlaybackQueueEndedEvent;
81
+ [Event.PlaybackActiveTrackChanged]: TrackChangedEvent;
82
+ [Event.PlaybackPlayWhenReadyChanged]: { playWhenReady: boolean };
83
+ [Event.PlaybackProgressUpdated]: PlaybackPositionEvent;
84
+ [Event.PlaybackResume]: undefined;
85
+ [Event.RemotePlay]: undefined;
86
+ [Event.RemotePlayPause]: undefined;
87
+ [Event.RemotePause]: undefined;
88
+ [Event.RemoteStop]: undefined;
89
+ [Event.RemoteNext]: undefined;
90
+ [Event.RemotePrevious]: undefined;
91
+ [Event.RemoteJumpForward]: { interval?: number } | undefined;
92
+ [Event.RemoteJumpBackward]: { interval?: number } | undefined;
93
+ [Event.RemoteSeek]: { position: number };
94
+ [Event.RemoteSetRating]: { rating: number };
95
+ [Event.RemoteDuck]: { paused: boolean; permanent: boolean };
96
+ [Event.RemoteLike]: undefined;
97
+ [Event.RemoteDislike]: undefined;
98
+ [Event.RemoteBookmark]: undefined;
99
+ [Event.RemotePlayId]: { id: string };
100
+ [Event.RemotePlaySearch]: { query: string };
101
+ [Event.RemoteSkip]: { interval: number };
102
+ [Event.MetadataChapterReceived]: { title?: string; startTime?: number };
103
+ [Event.MetadataTimedReceived]: { value: string };
104
+ [Event.MetadataCommonReceived]: { title?: string; artist?: string };
105
+ [Event.AndroidConnectorConnected]: undefined;
106
+ [Event.AndroidConnectorDisconnected]: undefined;
107
+ [TrackPlayerEvents.QueueUpdated]: QueueUpdatedEvent;
108
+ };
109
+
110
+ const emitter = new NativeEventEmitter(ExpoNativeTrackPlayer);
111
+
112
+ export function addEventListener<T>(
113
+ eventName: TrackPlayerEventName,
114
+ listener: (event: T) => void
115
+ ): EmitterSubscription {
116
+ return emitter.addListener(eventName, (event: any) => listener(event as T));
117
+ }
118
+
119
+ export function addTypedEventListener<E extends TrackPlayerEventName>(
120
+ eventName: E,
121
+ listener: (event: EventPayloadMap[E]) => void
122
+ ): EmitterSubscription {
123
+ return emitter.addListener(eventName, (event: any) =>
124
+ listener(event as EventPayloadMap[E])
125
+ );
126
+ }
127
+
128
+ export function removeAllEventListeners(eventName: TrackPlayerEventName): void {
129
+ emitter.removeAllListeners(eventName);
130
+ }
131
+
132
+ export const AudioEvents = {
133
+ addListener: addEventListener,
134
+ addTypedListener: addTypedEventListener,
135
+ removeAllListeners: removeAllEventListeners,
136
+ };
package/src/hooks.ts ADDED
@@ -0,0 +1,149 @@
1
+ import { useEffect, useState } from 'react';
2
+ import ExpoNativeTrackPlayer, {
3
+ type State,
4
+ type TrackMetadata,
5
+ } from './NativeExpoNativeTrackPlayer';
6
+ import {
7
+ addEventListener,
8
+ Event,
9
+ type PlaybackStateEvent,
10
+ type QueueUpdatedEvent,
11
+ type TrackChangedEvent,
12
+ TrackPlayerEvents,
13
+ } from './events';
14
+
15
+ export interface PlaybackProgress {
16
+ position: number;
17
+ duration: number;
18
+ trackIndex: number;
19
+ }
20
+
21
+ export function usePlaybackState(): State {
22
+ const [state, setState] = useState<State>('none');
23
+
24
+ useEffect(() => {
25
+ const subscription = addEventListener<PlaybackStateEvent>(
26
+ Event.PlaybackState,
27
+ (event) => setState(event.state)
28
+ );
29
+ return () => subscription.remove();
30
+ }, []);
31
+
32
+ return state;
33
+ }
34
+
35
+ export function useProgress(intervalMs: number = 1000): PlaybackProgress {
36
+ const [progress, setProgress] = useState<PlaybackProgress>({
37
+ position: 0,
38
+ duration: 0,
39
+ trackIndex: -1,
40
+ });
41
+
42
+ useEffect(() => {
43
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0) return;
44
+
45
+ let isActive = true;
46
+
47
+ const update = () => {
48
+ if (!isActive) return;
49
+ try {
50
+ const position = ExpoNativeTrackPlayer.getPosition();
51
+ const duration = ExpoNativeTrackPlayer.getDuration();
52
+ const trackIndex = ExpoNativeTrackPlayer.getCurrentTrackIndex();
53
+ setProgress({ position, duration, trackIndex });
54
+ } catch (error) {
55
+ setProgress({ position: 0, duration: 0, trackIndex: -1 });
56
+ }
57
+ };
58
+
59
+ update();
60
+ const timer = setInterval(update, intervalMs);
61
+ return () => {
62
+ isActive = false;
63
+ clearInterval(timer);
64
+ };
65
+ }, [intervalMs]);
66
+
67
+ return progress;
68
+ }
69
+
70
+ export function useQueue(): TrackMetadata[] {
71
+ const [queue, setQueue] = useState<TrackMetadata[]>([]);
72
+
73
+ useEffect(() => {
74
+ let isActive = true;
75
+
76
+ try {
77
+ const result = ExpoNativeTrackPlayer.getQueue();
78
+ if (isActive) setQueue(result);
79
+ } catch (error) {
80
+ if (isActive) setQueue([]);
81
+ }
82
+
83
+ const subscription = addEventListener<QueueUpdatedEvent>(
84
+ TrackPlayerEvents.QueueUpdated,
85
+ (event) => setQueue(event.queue)
86
+ );
87
+
88
+ return () => {
89
+ isActive = false;
90
+ subscription.remove();
91
+ };
92
+ }, []);
93
+
94
+ return queue;
95
+ }
96
+
97
+ export function useCurrentTrack(): TrackMetadata | null {
98
+ const [track, setTrack] = useState<TrackMetadata | null>(null);
99
+
100
+ useEffect(() => {
101
+ let isActive = true;
102
+
103
+ try {
104
+ const result = ExpoNativeTrackPlayer.getCurrentTrack();
105
+ if (isActive) setTrack(result);
106
+ } catch (error) {
107
+ if (isActive) setTrack(null);
108
+ }
109
+
110
+ const subscription = addEventListener<TrackChangedEvent>(
111
+ Event.PlaybackActiveTrackChanged,
112
+ (event) => setTrack(event.track)
113
+ );
114
+
115
+ return () => {
116
+ isActive = false;
117
+ subscription.remove();
118
+ };
119
+ }, []);
120
+
121
+ return track;
122
+ }
123
+
124
+ export function useCurrentTrackIndex(): number {
125
+ const [index, setIndex] = useState<number>(-1);
126
+
127
+ useEffect(() => {
128
+ let isActive = true;
129
+
130
+ try {
131
+ const result = ExpoNativeTrackPlayer.getCurrentTrackIndex();
132
+ if (isActive) setIndex(result);
133
+ } catch (error) {
134
+ if (isActive) setIndex(-1);
135
+ }
136
+
137
+ const subscription = addEventListener<TrackChangedEvent>(
138
+ Event.PlaybackActiveTrackChanged,
139
+ (event) => setIndex(event.trackIndex)
140
+ );
141
+
142
+ return () => {
143
+ isActive = false;
144
+ subscription.remove();
145
+ };
146
+ }, []);
147
+
148
+ return index;
149
+ }