@javascriptcommon/react-native-track-player 4.1.7 → 4.1.9

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 (31) hide show
  1. package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt +2 -0
  2. package/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt +38 -32
  3. package/android/src/main/java/com/doublesymmetry/trackplayer/utils/AutoConnectionDetector.kt +97 -0
  4. package/package.json +1 -2
  5. package/lib/src/TrackPlayerModule.web.d.ts +0 -2
  6. package/lib/src/TrackPlayerModule.web.js +0 -2
  7. package/lib/src/resolveAssetSource.web.d.ts +0 -2
  8. package/lib/src/resolveAssetSource.web.js +0 -8
  9. package/lib/web/TrackPlayer/Player.d.ts +0 -40
  10. package/lib/web/TrackPlayer/Player.js +0 -188
  11. package/lib/web/TrackPlayer/PlaylistPlayer.d.ts +0 -31
  12. package/lib/web/TrackPlayer/PlaylistPlayer.js +0 -181
  13. package/lib/web/TrackPlayer/RepeatMode.d.ts +0 -5
  14. package/lib/web/TrackPlayer/RepeatMode.js +0 -6
  15. package/lib/web/TrackPlayer/SetupNotCalledError.d.ts +0 -3
  16. package/lib/web/TrackPlayer/SetupNotCalledError.js +0 -5
  17. package/lib/web/TrackPlayer/index.d.ts +0 -3
  18. package/lib/web/TrackPlayer/index.js +0 -3
  19. package/lib/web/TrackPlayerModule.d.ts +0 -63
  20. package/lib/web/TrackPlayerModule.js +0 -153
  21. package/lib/web/index.d.ts +0 -3
  22. package/lib/web/index.js +0 -3
  23. package/src/TrackPlayerModule.web.ts +0 -2
  24. package/src/resolveAssetSource.web.ts +0 -10
  25. package/web/TrackPlayer/Player.ts +0 -201
  26. package/web/TrackPlayer/PlaylistPlayer.ts +0 -215
  27. package/web/TrackPlayer/RepeatMode.ts +0 -6
  28. package/web/TrackPlayer/SetupNotCalledError.ts +0 -5
  29. package/web/TrackPlayer/index.ts +0 -3
  30. package/web/TrackPlayerModule.ts +0 -181
  31. package/web/index.ts +0 -4
@@ -15,6 +15,7 @@ import com.doublesymmetry.trackplayer.model.Track
15
15
  import com.doublesymmetry.trackplayer.module.MusicEvents.Companion.EVENT_INTENT
16
16
  import com.doublesymmetry.trackplayer.service.MusicService
17
17
  import com.doublesymmetry.trackplayer.utils.AppForegroundTracker
18
+ import com.doublesymmetry.trackplayer.utils.AutoConnectionDetector
18
19
  import com.doublesymmetry.trackplayer.utils.RejectionException
19
20
  import com.doublesymmetry.trackplayer.NativeTrackPlayerSpec
20
21
  import com.facebook.react.bridge.*
@@ -64,6 +65,7 @@ class MusicModule(reactContext: ReactApplicationContext) : NativeTrackPlayerSpec
64
65
 
65
66
  override fun initialize() {
66
67
  AppForegroundTracker.start()
68
+ AutoConnectionDetector(context).registerCarConnectionReceiver()
67
69
  }
68
70
 
69
71
  override fun onServiceConnected(name: ComponentName, service: IBinder) {
@@ -951,30 +951,33 @@ class MusicService : HeadlessJsMediaService() {
951
951
 
952
952
  @SuppressLint("VisibleForTests")
953
953
  private fun selfWake(clientPackageName: String): Boolean {
954
- val reactActivity = reactContext?.currentActivity
955
- if (
956
- // HACK: validate reactActivity is present; if not, send wake intent
957
- (reactActivity == null || reactActivity.isDestroyed)
958
- && Settings.canDrawOverlays(this)
959
- ) {
960
- val currentTime = System.currentTimeMillis()
961
- if (currentTime - lastWake < 100000) {
962
- return false
963
- }
964
- lastWake = currentTime
965
- val activityIntent = packageManager.getLaunchIntentForPackage(packageName)
966
- activityIntent!!.data = "trackplayer://service-bound".toUri()
967
- activityIntent.action = Intent.ACTION_VIEW
968
- activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
969
- var activityOptions = ActivityOptions.makeBasic()
970
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
971
- activityOptions = activityOptions.setPendingIntentBackgroundActivityStartMode(
972
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
973
- }
974
- this.startActivity(activityIntent, activityOptions.toBundle())
975
- return true
976
- }
954
+ // FORK PATCH: AVOID STARTING APP IN FOREGROUND, PREFER STARTING HEADLESS
977
955
  return false
956
+
957
+ // val reactActivity = reactContext?.currentActivity
958
+ // if (
959
+ // // HACK: validate reactActivity is present; if not, send wake intent
960
+ // (reactActivity == null || reactActivity.isDestroyed)
961
+ // && Settings.canDrawOverlays(this)
962
+ // ) {
963
+ // val currentTime = System.currentTimeMillis()
964
+ // if (currentTime - lastWake < 100000) {
965
+ // return false
966
+ // }
967
+ // lastWake = currentTime
968
+ // val activityIntent = packageManager.getLaunchIntentForPackage(packageName)
969
+ // activityIntent!!.data = "trackplayer://service-bound".toUri()
970
+ // activityIntent.action = Intent.ACTION_VIEW
971
+ // activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
972
+ // var activityOptions = ActivityOptions.makeBasic()
973
+ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
974
+ // activityOptions = activityOptions.setPendingIntentBackgroundActivityStartMode(
975
+ // ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
976
+ // }
977
+ // this.startActivity(activityIntent, activityOptions.toBundle())
978
+ // return true
979
+ // }
980
+ // return false
978
981
  }
979
982
 
980
983
  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession {
@@ -1006,6 +1009,7 @@ class MusicService : HeadlessJsMediaService() {
1006
1009
  player.destroy()
1007
1010
  }
1008
1011
 
1012
+ // FORK PATCH
1009
1013
  // -> Attempt to fix https://github.com/doublesymmetry/react-native-track-player/issues/2485
1010
1014
  mediaSession.release()
1011
1015
 
@@ -1086,15 +1090,17 @@ class MusicService : HeadlessJsMediaService() {
1086
1090
  private val rootItem = buildMediaItem(title = "root", mediaId = AA_ROOT_KEY, isPlayable = false)
1087
1091
  private val forYouItem = buildMediaItem(title = "For You", mediaId = AA_FOR_YOU_KEY, isPlayable = false)
1088
1092
 
1089
- override fun onDisconnected(
1090
- session: MediaSession,
1091
- controller: MediaSession.ControllerInfo
1092
- ) {
1093
- emit(MusicEvents.CONNECTOR_DISCONNECTED, Bundle().apply {
1094
- putString("package", controller.packageName)
1095
- })
1096
- super.onDisconnected(session, controller)
1097
- }
1093
+ // FORK PATCH: onDisconnected it's called only a long time after Android Auto disconnection, replaced with AutoConnectionDetector
1094
+ // override fun onDisconnected(
1095
+ // session: MediaSession,
1096
+ // controller: MediaSession.ControllerInfo
1097
+ // ) {
1098
+ //
1099
+ // emit(MusicEvents.CONNECTOR_DISCONNECTED, Bundle().apply {
1100
+ // putString("package", controller.packageName)
1101
+ // })
1102
+ // super.onDisconnected(session, controller)
1103
+ // }
1098
1104
  // Configure commands available to the controller in onConnect()
1099
1105
  @OptIn(UnstableApi::class)
1100
1106
  override fun onConnect(
@@ -0,0 +1,97 @@
1
+ package com.doublesymmetry.trackplayer.utils
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import androidx.car.app.connection.CarConnection
6
+ import androidx.lifecycle.LifecycleOwner
7
+ import com.facebook.react.bridge.Arguments
8
+ import com.facebook.react.bridge.ReactContext
9
+ import com.facebook.react.modules.core.DeviceEventManagerModule
10
+ import com.doublesymmetry.trackplayer.module.MusicEvents
11
+
12
+ class AutoConnectionDetector(
13
+ val context: ReactContext,
14
+ onConnectionChange: ((Boolean) -> Unit)? = null
15
+ ) {
16
+
17
+ companion object {
18
+ const val TAG = "AutoConnectionDetector"
19
+ }
20
+
21
+ var isCarConnected = false
22
+ private var carConnection: CarConnection? = null
23
+ private val onConnectionChangeCallback = onConnectionChange
24
+ private var isObserving = false
25
+
26
+ fun registerCarConnectionReceiver() {
27
+ if (carConnection == null) {
28
+ carConnection = CarConnection(context)
29
+ }
30
+
31
+ val mainHandler = Handler(Looper.getMainLooper())
32
+ mainHandler.post {
33
+ val lifecycleOwner = context.currentActivity as? LifecycleOwner
34
+ if (lifecycleOwner != null && !isObserving) {
35
+ try {
36
+ carConnection?.type?.observe(lifecycleOwner, ::onConnectionStateUpdated)
37
+ isObserving = true
38
+ } catch (_: Throwable) {}
39
+ }
40
+ }
41
+ }
42
+
43
+ private fun onConnectionStateUpdated(connectionState: Int) {
44
+ when (connectionState) {
45
+ CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> {
46
+ if (isCarConnected) {
47
+ notifyCarDisconnected()
48
+ }
49
+ }
50
+ CarConnection.CONNECTION_TYPE_NATIVE -> {
51
+ notifyCarConnected(connectionState)
52
+ }
53
+ CarConnection.CONNECTION_TYPE_PROJECTION -> {
54
+ notifyCarConnected(connectionState)
55
+ }
56
+ }
57
+ }
58
+
59
+ private fun emitConnectorEvent(eventName: String, connectionState: Int?) {
60
+ val params = Arguments.createMap()
61
+ params.putString("package", "com.google.android.projection.gearhead") // Android Auto package name
62
+ params.putBoolean(
63
+ "isAutomotiveController",
64
+ connectionState == CarConnection.CONNECTION_TYPE_NATIVE // Android Automotive
65
+ )
66
+ params.putBoolean(
67
+ "isAutoCompanionController",
68
+ connectionState == CarConnection.CONNECTION_TYPE_PROJECTION // Android Auto
69
+ )
70
+ params.putBoolean("isMediaNotificationController", false)
71
+
72
+ context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
73
+ ?.emit(eventName, params)
74
+ }
75
+
76
+ private fun notifyCarConnected(connectionState: Int) {
77
+ if (isCarConnected) return
78
+ isCarConnected = true
79
+
80
+ // emitConnectionUpdate(params) // old
81
+ // emitConnectorEvent(MusicEvents.CONNECTOR_CONNECTED, connectionState) // new but not necessary (MusicService event works)
82
+
83
+ onConnectionChangeCallback?.invoke(true)
84
+ }
85
+
86
+ private fun notifyCarDisconnected() {
87
+ if (!isCarConnected) return
88
+ isCarConnected = false
89
+
90
+ // emitConnectionUpdate(params) // old
91
+
92
+ // MusicService event doesn't work -> emit manually
93
+ emitConnectorEvent(MusicEvents.CONNECTOR_DISCONNECTED, CarConnection.CONNECTION_TYPE_NOT_CONNECTED)
94
+
95
+ onConnectionChangeCallback?.invoke(false)
96
+ }
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@javascriptcommon/react-native-track-player",
3
- "version": "4.1.7",
3
+ "version": "4.1.9",
4
4
  "description": "A fully fledged audio module created for music apps",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -31,7 +31,6 @@
31
31
  "android/src/**/*",
32
32
  "android/build.gradle",
33
33
  "android/proguard-rules.txt",
34
- "web/**/*",
35
34
  "*.podspec",
36
35
  "specs"
37
36
  ],
@@ -1,2 +0,0 @@
1
- import TrackPlayerModule from '../web';
2
- export default TrackPlayerModule;
@@ -1,2 +0,0 @@
1
- import TrackPlayerModule from '../web';
2
- export default TrackPlayerModule;
@@ -1,2 +0,0 @@
1
- declare const resolveAssetResource: (base64: unknown) => unknown;
2
- export default resolveAssetResource;
@@ -1,8 +0,0 @@
1
- const resolveAssetResource = (base64) => {
2
- if (/^https?:\/\//.test(base64)) {
3
- return base64;
4
- }
5
- // TODO: resolveAssetResource for web
6
- return base64;
7
- };
8
- export default resolveAssetResource;
@@ -1,40 +0,0 @@
1
- import { State } from '../../src/constants/State';
2
- import type { Track, Progress, PlaybackState } from '../../src/interfaces';
3
- export declare class Player {
4
- protected hasInitialized: boolean;
5
- protected element?: HTMLMediaElement;
6
- protected player?: shaka.Player;
7
- protected _current?: Track;
8
- protected _playWhenReady: boolean;
9
- protected _state: PlaybackState;
10
- get current(): Track | undefined;
11
- set current(cur: Track | undefined);
12
- get state(): PlaybackState;
13
- set state(newState: PlaybackState);
14
- get playWhenReady(): boolean;
15
- set playWhenReady(pwr: boolean);
16
- setupPlayer(): Promise<void>;
17
- /**
18
- * event handlers
19
- */
20
- protected onStateUpdate(state: Exclude<State, State.Error>): void;
21
- protected onError(error: any): void;
22
- /**
23
- * player control
24
- */
25
- load(track: Track): Promise<void>;
26
- retry(): Promise<void>;
27
- stop(): Promise<void>;
28
- play(): Promise<void>;
29
- pause(): void;
30
- setRate(rate: number): number;
31
- getRate(): number;
32
- seekBy(offset: number): void;
33
- seekTo(seconds: number): void;
34
- setVolume(volume: number): void;
35
- getVolume(): number;
36
- getDuration(): number;
37
- getPosition(): number;
38
- getProgress(): Progress;
39
- getBufferedPosition(): (index: number) => number;
40
- }
@@ -1,188 +0,0 @@
1
- import { State } from '../../src/constants/State';
2
- import { SetupNotCalledError } from './SetupNotCalledError';
3
- export class Player {
4
- hasInitialized = false;
5
- element;
6
- player;
7
- _current = undefined;
8
- _playWhenReady = false;
9
- _state = { state: State.None };
10
- // current getter/setter
11
- get current() {
12
- return this._current;
13
- }
14
- set current(cur) {
15
- this._current = cur;
16
- }
17
- // state getter/setter
18
- get state() {
19
- return this._state;
20
- }
21
- set state(newState) {
22
- this._state = newState;
23
- }
24
- // playWhenReady getter/setter
25
- get playWhenReady() {
26
- return this._playWhenReady;
27
- }
28
- set playWhenReady(pwr) {
29
- this._playWhenReady = pwr;
30
- }
31
- async setupPlayer() {
32
- // shaka only runs in a browser
33
- if (typeof window === 'undefined')
34
- return;
35
- if (this.hasInitialized === true) {
36
- // TODO: double check the structure of this error message
37
- throw { code: 'player_already_initialized', message: 'The player has already been initialized via setupPlayer.' };
38
- }
39
- // @ts-ignore
40
- const shaka = (await import('shaka-player/dist/shaka-player.ui')).default;
41
- // Install built-in polyfills to patch browser incompatibilities.
42
- shaka.polyfill.installAll();
43
- // Check to see if the browser supports the basic APIs Shaka needs.
44
- if (!shaka.Player.isBrowserSupported()) {
45
- // This browser does not have the minimum set of APIs we need.
46
- this.state = {
47
- state: State.Error,
48
- error: {
49
- code: 'not_supported',
50
- message: 'Browser not supported.',
51
- },
52
- };
53
- throw new Error('Browser not supported.');
54
- }
55
- // build dom element and attach shaka-player
56
- this.element = document.createElement('audio');
57
- this.element.setAttribute('id', 'react-native-track-player');
58
- this.player = new shaka.Player();
59
- this.player?.attach(this.element);
60
- // Listen for relevant events events.
61
- this.player.addEventListener('error', (error) => {
62
- // Extract the shaka.util.Error object from the event.
63
- this.onError(error.detail);
64
- });
65
- this.element.addEventListener('ended', this.onStateUpdate.bind(this, State.Ended));
66
- this.element.addEventListener('playing', this.onStateUpdate.bind(this, State.Playing));
67
- this.element.addEventListener('pause', this.onStateUpdate.bind(this, State.Paused));
68
- this.player.addEventListener('loading', this.onStateUpdate.bind(this, State.Loading));
69
- this.player.addEventListener('loaded', this.onStateUpdate.bind(this, State.Ready));
70
- this.player.addEventListener('buffering', ({ buffering }) => {
71
- if (buffering === true) {
72
- this.onStateUpdate(State.Buffering);
73
- }
74
- });
75
- // Attach player to the window to make it easy to access in the JS console.
76
- // @ts-ignore
77
- window.rntp = this.player;
78
- this.hasInitialized = true;
79
- }
80
- /**
81
- * event handlers
82
- */
83
- onStateUpdate(state) {
84
- this.state = { state };
85
- }
86
- onError(error) {
87
- // unload the current track to allow for clean playback on other
88
- this.player?.unload();
89
- this.state = {
90
- state: State.Error,
91
- error: {
92
- code: error.code.toString(),
93
- message: error.message,
94
- },
95
- };
96
- // Log the error.
97
- console.debug('Error code', error.code, 'object', error);
98
- }
99
- /**
100
- * player control
101
- */
102
- async load(track) {
103
- if (!this.player)
104
- throw new SetupNotCalledError();
105
- await this.player.load(track.url);
106
- this.current = track;
107
- }
108
- async retry() {
109
- if (!this.player)
110
- throw new SetupNotCalledError();
111
- this.player.retryStreaming();
112
- }
113
- async stop() {
114
- if (!this.player)
115
- throw new SetupNotCalledError();
116
- this.current = undefined;
117
- await this.player.unload();
118
- }
119
- play() {
120
- if (!this.element)
121
- throw new SetupNotCalledError();
122
- this.playWhenReady = true;
123
- return this.element.play()
124
- .catch(err => {
125
- console.error(err);
126
- });
127
- }
128
- pause() {
129
- if (!this.element)
130
- throw new SetupNotCalledError();
131
- this.playWhenReady = false;
132
- return this.element.pause();
133
- }
134
- setRate(rate) {
135
- if (!this.element)
136
- throw new SetupNotCalledError();
137
- return this.element.playbackRate = rate;
138
- }
139
- getRate() {
140
- if (!this.element)
141
- throw new SetupNotCalledError();
142
- return this.element.playbackRate;
143
- }
144
- seekBy(offset) {
145
- if (!this.element)
146
- throw new SetupNotCalledError();
147
- this.element.currentTime += offset;
148
- }
149
- seekTo(seconds) {
150
- if (!this.element)
151
- throw new SetupNotCalledError();
152
- this.element.currentTime = seconds;
153
- }
154
- setVolume(volume) {
155
- if (!this.element)
156
- throw new SetupNotCalledError();
157
- this.element.volume = volume;
158
- }
159
- getVolume() {
160
- if (!this.element)
161
- throw new SetupNotCalledError();
162
- return this.element.volume;
163
- }
164
- getDuration() {
165
- if (!this.element)
166
- throw new SetupNotCalledError();
167
- return this.element.duration;
168
- }
169
- getPosition() {
170
- if (!this.element)
171
- throw new SetupNotCalledError();
172
- return this.element.currentTime;
173
- }
174
- getProgress() {
175
- if (!this.element)
176
- throw new SetupNotCalledError();
177
- return {
178
- position: this.element.currentTime,
179
- duration: this.element.duration || 0,
180
- buffered: 0, // TODO: this.element.buffered.end,
181
- };
182
- }
183
- getBufferedPosition() {
184
- if (!this.element)
185
- throw new SetupNotCalledError();
186
- return this.element.buffered.end;
187
- }
188
- }
@@ -1,31 +0,0 @@
1
- import { Player } from './Player';
2
- import type { Track } from '../../src/interfaces';
3
- import { RepeatMode } from './RepeatMode';
4
- import { State } from '../../src';
5
- export declare class PlaylistPlayer extends Player {
6
- protected playlist: Track[];
7
- protected lastIndex?: number;
8
- protected _currentIndex?: number;
9
- protected repeatMode: RepeatMode;
10
- protected onStateUpdate(state: Exclude<State, State.Error>): Promise<void>;
11
- protected onTrackEnded(): Promise<void>;
12
- protected onPlaylistEnded(): void;
13
- protected get currentIndex(): number | undefined;
14
- protected set currentIndex(current: number | undefined);
15
- protected goToIndex(index: number, initialPosition?: number): Promise<void>;
16
- add(tracks: Track[], insertBeforeIndex?: number): Promise<void>;
17
- skip(index: number, initialPosition?: number): Promise<void>;
18
- skipToNext(initialPosition?: number): Promise<void>;
19
- skipToPrevious(initialPosition?: number): Promise<void>;
20
- getTrack(index: number): Track | null;
21
- setRepeatMode(mode: RepeatMode): void;
22
- getRepeatMode(): RepeatMode;
23
- remove(indexes: number[]): Promise<void>;
24
- stop(): Promise<void>;
25
- reset(): Promise<void>;
26
- removeUpcomingTracks(): Promise<void>;
27
- move(fromIndex: number, toIndex: number): Promise<void>;
28
- updateMetadataForTrack(index: number, metadata: Partial<Track>): void;
29
- clearNowPlayingMetadata(): void;
30
- updateNowPlayingMetadata(metadata: Partial<Track>): void;
31
- }
@@ -1,181 +0,0 @@
1
- import { Player } from './Player';
2
- import { RepeatMode } from './RepeatMode';
3
- import { State } from '../../src';
4
- export class PlaylistPlayer extends Player {
5
- // TODO: use immer to make the `playlist` immutable
6
- playlist = [];
7
- lastIndex;
8
- _currentIndex;
9
- repeatMode = RepeatMode.Off;
10
- async onStateUpdate(state) {
11
- super.onStateUpdate(state);
12
- if (state === State.Ended) {
13
- await this.onTrackEnded();
14
- }
15
- }
16
- async onTrackEnded() {
17
- switch (this.repeatMode) {
18
- case RepeatMode.Track:
19
- if (this.currentIndex !== undefined) {
20
- await this.goToIndex(this.currentIndex);
21
- }
22
- break;
23
- case RepeatMode.Playlist:
24
- if (this.currentIndex === this.playlist.length - 1) {
25
- await this.goToIndex(0);
26
- }
27
- else {
28
- await this.skipToNext();
29
- }
30
- break;
31
- default:
32
- try {
33
- await this.skipToNext();
34
- }
35
- catch (err) {
36
- if (err.message !== 'playlist_exhausted') {
37
- throw err;
38
- }
39
- this.onPlaylistEnded();
40
- }
41
- break;
42
- }
43
- }
44
- // eslint-disable-next-line @typescript-eslint/no-empty-function
45
- onPlaylistEnded() { }
46
- get currentIndex() {
47
- return this._currentIndex;
48
- }
49
- set currentIndex(current) {
50
- this.lastIndex = this.currentIndex;
51
- this._currentIndex = current;
52
- }
53
- async goToIndex(index, initialPosition) {
54
- const track = this.playlist[index];
55
- if (!track) {
56
- throw new Error('playlist_exhausted');
57
- }
58
- if (this.currentIndex !== index) {
59
- this.currentIndex = index;
60
- await this.load(track);
61
- }
62
- if (initialPosition) {
63
- this.seekTo(initialPosition);
64
- }
65
- if (this.playWhenReady) {
66
- await this.play();
67
- }
68
- }
69
- async add(tracks, insertBeforeIndex) {
70
- if (insertBeforeIndex !== -1 && insertBeforeIndex !== undefined) {
71
- this.playlist.splice(insertBeforeIndex, 0, ...tracks);
72
- }
73
- else {
74
- this.playlist.push(...tracks);
75
- }
76
- if (this.currentIndex === undefined) {
77
- await this.goToIndex(0);
78
- }
79
- }
80
- async skip(index, initialPosition) {
81
- const track = this.playlist[index];
82
- if (track === undefined) {
83
- throw new Error('index out of bounds');
84
- }
85
- await this.goToIndex(index, initialPosition);
86
- }
87
- async skipToNext(initialPosition) {
88
- if (this.currentIndex === undefined)
89
- return;
90
- const index = this.currentIndex + 1;
91
- await this.goToIndex(index, initialPosition);
92
- }
93
- async skipToPrevious(initialPosition) {
94
- if (this.currentIndex === undefined)
95
- return;
96
- const index = this.currentIndex - 1;
97
- await this.goToIndex(index, initialPosition);
98
- }
99
- getTrack(index) {
100
- const track = this.playlist[index];
101
- return track || null;
102
- }
103
- setRepeatMode(mode) {
104
- this.repeatMode = mode;
105
- }
106
- getRepeatMode() {
107
- return this.repeatMode;
108
- }
109
- async remove(indexes) {
110
- const idxMap = indexes.reduce((acc, elem) => {
111
- acc[elem] = true;
112
- return acc;
113
- }, {});
114
- let isCurrentRemoved = false;
115
- this.playlist = this.playlist.filter((_track, idx) => {
116
- const keep = !idxMap[idx];
117
- if (!keep && idx === this.currentIndex) {
118
- isCurrentRemoved = true;
119
- }
120
- return keep;
121
- });
122
- if (this.currentIndex === undefined) {
123
- return;
124
- }
125
- const hasItems = this.playlist.length > 0;
126
- if (isCurrentRemoved && hasItems) {
127
- await this.goToIndex(this.currentIndex % this.playlist.length);
128
- }
129
- else if (isCurrentRemoved) {
130
- await this.stop();
131
- }
132
- }
133
- async stop() {
134
- await super.stop();
135
- this.currentIndex = undefined;
136
- }
137
- async reset() {
138
- await this.stop();
139
- this.playlist = [];
140
- }
141
- async removeUpcomingTracks() {
142
- if (this.currentIndex === undefined)
143
- return;
144
- this.playlist = this.playlist.slice(0, this.currentIndex + 1);
145
- }
146
- async move(fromIndex, toIndex) {
147
- if (!this.playlist[fromIndex]) {
148
- throw new Error('index out of bounds');
149
- }
150
- if (this.currentIndex === fromIndex) {
151
- throw new Error('you cannot move the currently playing track');
152
- }
153
- if (this.currentIndex === toIndex) {
154
- throw new Error('you cannot replace the currently playing track');
155
- }
156
- // calculate `currentIndex` after move
157
- let shift = undefined;
158
- if (this.currentIndex) {
159
- if (fromIndex < this.currentIndex && toIndex > this.currentIndex) {
160
- shift = -1;
161
- }
162
- else if (fromIndex > this.currentIndex && toIndex < this.currentIndex) {
163
- shift = +1;
164
- }
165
- }
166
- // move the track
167
- const fromItem = this.playlist[fromIndex];
168
- this.playlist.splice(fromIndex, 1);
169
- this.playlist.splice(toIndex, 0, fromItem);
170
- if (this.currentIndex && shift) {
171
- this.currentIndex = this.currentIndex + shift;
172
- }
173
- }
174
- // TODO
175
- // eslint-disable-next-line @typescript-eslint/no-empty-function
176
- updateMetadataForTrack(index, metadata) { }
177
- // eslint-disable-next-line @typescript-eslint/no-empty-function
178
- clearNowPlayingMetadata() { }
179
- // eslint-disable-next-line @typescript-eslint/no-empty-function
180
- updateNowPlayingMetadata(metadata) { }
181
- }