@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.
- package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt +2 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt +38 -32
- package/android/src/main/java/com/doublesymmetry/trackplayer/utils/AutoConnectionDetector.kt +97 -0
- package/package.json +1 -2
- package/lib/src/TrackPlayerModule.web.d.ts +0 -2
- package/lib/src/TrackPlayerModule.web.js +0 -2
- package/lib/src/resolveAssetSource.web.d.ts +0 -2
- package/lib/src/resolveAssetSource.web.js +0 -8
- package/lib/web/TrackPlayer/Player.d.ts +0 -40
- package/lib/web/TrackPlayer/Player.js +0 -188
- package/lib/web/TrackPlayer/PlaylistPlayer.d.ts +0 -31
- package/lib/web/TrackPlayer/PlaylistPlayer.js +0 -181
- package/lib/web/TrackPlayer/RepeatMode.d.ts +0 -5
- package/lib/web/TrackPlayer/RepeatMode.js +0 -6
- package/lib/web/TrackPlayer/SetupNotCalledError.d.ts +0 -3
- package/lib/web/TrackPlayer/SetupNotCalledError.js +0 -5
- package/lib/web/TrackPlayer/index.d.ts +0 -3
- package/lib/web/TrackPlayer/index.js +0 -3
- package/lib/web/TrackPlayerModule.d.ts +0 -63
- package/lib/web/TrackPlayerModule.js +0 -153
- package/lib/web/index.d.ts +0 -3
- package/lib/web/index.js +0 -3
- package/src/TrackPlayerModule.web.ts +0 -2
- package/src/resolveAssetSource.web.ts +0 -10
- package/web/TrackPlayer/Player.ts +0 -201
- package/web/TrackPlayer/PlaylistPlayer.ts +0 -215
- package/web/TrackPlayer/RepeatMode.ts +0 -6
- package/web/TrackPlayer/SetupNotCalledError.ts +0 -5
- package/web/TrackPlayer/index.ts +0 -3
- package/web/TrackPlayerModule.ts +0 -181
- 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
|
-
|
|
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
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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.
|
|
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,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
|
-
}
|