tunzo-player 1.0.7 → 1.0.8
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/dist/core/player.d.ts +5 -6
- package/dist/core/player.js +80 -158
- package/package.json +3 -2
- package/src/core/player.ts +82 -162
package/dist/core/player.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare class Player {
|
|
2
|
-
private static
|
|
2
|
+
private static audio;
|
|
3
3
|
private static currentSong;
|
|
4
4
|
private static currentIndex;
|
|
5
5
|
private static isPlaying;
|
|
@@ -9,14 +9,13 @@ export declare class Player {
|
|
|
9
9
|
private static queue;
|
|
10
10
|
private static playlist;
|
|
11
11
|
private static selectedQuality;
|
|
12
|
-
private static readonly audioId;
|
|
13
12
|
private static isInitialized;
|
|
14
|
-
/** Initialize with playlist and quality */
|
|
15
13
|
static initialize(playlist?: any[], quality?: number): void;
|
|
16
14
|
static play(song: any, index?: number): Promise<void>;
|
|
17
|
-
private static
|
|
15
|
+
private static setupMusicControls;
|
|
18
16
|
static pause(): Promise<void>;
|
|
19
17
|
static resume(): Promise<void>;
|
|
18
|
+
static stop(): Promise<void>;
|
|
20
19
|
static togglePlayPause(): Promise<void>;
|
|
21
20
|
static next(): Promise<void>;
|
|
22
21
|
static prev(): Promise<void>;
|
|
@@ -26,10 +25,10 @@ export declare class Player {
|
|
|
26
25
|
static addToQueue(song: any): void;
|
|
27
26
|
static removeFromQueue(index: number): void;
|
|
28
27
|
static reorderQueue(from: number, to: number): void;
|
|
29
|
-
static seekTo(
|
|
28
|
+
static seekTo(seconds: number): Promise<void>;
|
|
30
29
|
static getCurrentTime(): number;
|
|
31
30
|
static getDuration(): number;
|
|
32
|
-
static formatTime(
|
|
31
|
+
static formatTime(t: number): string;
|
|
33
32
|
static isPlayingSong(): boolean;
|
|
34
33
|
static getCurrentSong(): any;
|
|
35
34
|
static setQuality(index: number): void;
|
package/dist/core/player.js
CHANGED
|
@@ -11,12 +11,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.Player = void 0;
|
|
13
13
|
const core_1 = require("@capacitor/core");
|
|
14
|
-
const
|
|
14
|
+
const capacitor_music_controls_plugin_1 = require("capacitor-music-controls-plugin");
|
|
15
15
|
class Player {
|
|
16
|
-
/** Initialize with playlist and quality */
|
|
17
16
|
static initialize(playlist = [], quality = 3) {
|
|
18
|
-
if (!core_1.Capacitor.isNativePlatform()
|
|
19
|
-
this.
|
|
17
|
+
if (!core_1.Capacitor.isNativePlatform()) {
|
|
18
|
+
this.audio = new Audio();
|
|
20
19
|
}
|
|
21
20
|
this.playlist = [...playlist];
|
|
22
21
|
this.selectedQuality = quality;
|
|
@@ -25,133 +24,77 @@ class Player {
|
|
|
25
24
|
static play(song_1) {
|
|
26
25
|
return __awaiter(this, arguments, void 0, function* (song, index = 0) {
|
|
27
26
|
var _a, _b;
|
|
28
|
-
if (!this.isInitialized)
|
|
29
|
-
console.error('Player not initialized. Call Player.initialize() first.');
|
|
27
|
+
if (!this.isInitialized)
|
|
30
28
|
return;
|
|
31
|
-
|
|
32
|
-
if (!song || !song.downloadUrl) {
|
|
33
|
-
console.error('Invalid song or missing downloadUrl');
|
|
29
|
+
if (!(song === null || song === void 0 ? void 0 : song.downloadUrl))
|
|
34
30
|
return;
|
|
35
|
-
|
|
36
|
-
// Stop current playback
|
|
37
|
-
yield this.cleanupCurrentPlayback();
|
|
31
|
+
yield this.stop();
|
|
38
32
|
this.currentSong = song;
|
|
39
33
|
this.currentIndex = index;
|
|
40
34
|
const url = ((_a = song.downloadUrl[this.selectedQuality]) === null || _a === void 0 ? void 0 : _a.url) || ((_b = song.downloadUrl[0]) === null || _b === void 0 ? void 0 : _b.url);
|
|
41
|
-
if (!url)
|
|
42
|
-
console.error('No valid URL found for playback');
|
|
35
|
+
if (!url)
|
|
43
36
|
return;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
assetId: this.audioId,
|
|
54
|
-
assetPath: url,
|
|
55
|
-
audioChannelNum: 1,
|
|
56
|
-
isUrl: true
|
|
57
|
-
});
|
|
58
|
-
yield native_audio_1.NativeAudio.play({
|
|
59
|
-
assetId: this.audioId
|
|
60
|
-
});
|
|
61
|
-
// For native, we need to manually track duration
|
|
62
|
-
this.duration = song.duration || 0;
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
if (!this.audioInstance) {
|
|
66
|
-
this.audioInstance = new Audio();
|
|
67
|
-
}
|
|
68
|
-
this.audioInstance.src = url;
|
|
69
|
-
yield this.audioInstance.play();
|
|
70
|
-
this.audioInstance.onloadedmetadata = () => {
|
|
71
|
-
var _a;
|
|
72
|
-
this.duration = ((_a = this.audioInstance) === null || _a === void 0 ? void 0 : _a.duration) || 0;
|
|
73
|
-
};
|
|
74
|
-
this.audioInstance.ontimeupdate = () => {
|
|
75
|
-
var _a;
|
|
76
|
-
this.currentTime = ((_a = this.audioInstance) === null || _a === void 0 ? void 0 : _a.currentTime) || 0;
|
|
77
|
-
};
|
|
78
|
-
this.audioInstance.onended = () => {
|
|
79
|
-
this.autoNext();
|
|
80
|
-
};
|
|
81
|
-
this.audioInstance.onerror = () => {
|
|
82
|
-
console.error('Error during playback');
|
|
83
|
-
this.isPlaying = false;
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
this.isPlaying = true;
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
console.error('Playback error:', err);
|
|
90
|
-
this.isPlaying = false;
|
|
91
|
-
}
|
|
37
|
+
if (!this.audio)
|
|
38
|
+
this.audio = new Audio();
|
|
39
|
+
this.audio.src = url;
|
|
40
|
+
this.audio.play().catch(console.error);
|
|
41
|
+
this.audio.onloadedmetadata = () => this.duration = this.audio.duration;
|
|
42
|
+
this.audio.ontimeupdate = () => this.currentTime = this.audio.currentTime;
|
|
43
|
+
this.audio.onended = () => this.autoNext();
|
|
44
|
+
yield this.setupMusicControls(song, true);
|
|
45
|
+
this.isPlaying = true;
|
|
92
46
|
});
|
|
93
47
|
}
|
|
94
|
-
static
|
|
48
|
+
static setupMusicControls(song, isPlaying) {
|
|
95
49
|
return __awaiter(this, void 0, void 0, function* () {
|
|
96
|
-
if (core_1.Capacitor.isNativePlatform())
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
50
|
+
if (!core_1.Capacitor.isNativePlatform())
|
|
51
|
+
return;
|
|
52
|
+
yield capacitor_music_controls_plugin_1.CapacitorMusicControls.destroy(); // clear existing
|
|
53
|
+
yield capacitor_music_controls_plugin_1.CapacitorMusicControls.create({
|
|
54
|
+
track: song.name || '',
|
|
55
|
+
artist: song.primaryArtists || '',
|
|
56
|
+
album: song.album || '',
|
|
57
|
+
cover: song.image || '',
|
|
58
|
+
isPlaying,
|
|
59
|
+
dismissable: true,
|
|
60
|
+
hasPrev: true,
|
|
61
|
+
hasNext: true,
|
|
62
|
+
});
|
|
63
|
+
capacitor_music_controls_plugin_1.CapacitorMusicControls.addListener('music-controls-next', () => this.next());
|
|
64
|
+
capacitor_music_controls_plugin_1.CapacitorMusicControls.addListener('music-controls-previous', () => this.prev());
|
|
65
|
+
capacitor_music_controls_plugin_1.CapacitorMusicControls.addListener('music-controls-pause', () => this.pause());
|
|
66
|
+
capacitor_music_controls_plugin_1.CapacitorMusicControls.addListener('music-controls-play', () => this.resume());
|
|
67
|
+
capacitor_music_controls_plugin_1.CapacitorMusicControls.addListener('music-controls-destroy', () => this.stop());
|
|
113
68
|
});
|
|
114
69
|
}
|
|
115
70
|
static pause() {
|
|
116
71
|
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
yield native_audio_1.NativeAudio.pause({ assetId: this.audioId });
|
|
122
|
-
}
|
|
123
|
-
else if (this.audioInstance) {
|
|
124
|
-
this.audioInstance.pause();
|
|
125
|
-
}
|
|
126
|
-
this.isPlaying = false;
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
console.error('Pause error:', err);
|
|
130
|
-
}
|
|
72
|
+
if (this.audio)
|
|
73
|
+
this.audio.pause();
|
|
74
|
+
this.isPlaying = false;
|
|
75
|
+
core_1.Capacitor.isNativePlatform() && capacitor_music_controls_plugin_1.CapacitorMusicControls.updateIsPlaying({ isPlaying: false });
|
|
131
76
|
});
|
|
132
77
|
}
|
|
133
78
|
static resume() {
|
|
134
79
|
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
this.
|
|
145
|
-
|
|
146
|
-
catch (err) {
|
|
147
|
-
console.error('Resume error:', err);
|
|
80
|
+
if (this.audio)
|
|
81
|
+
yield this.audio.play();
|
|
82
|
+
this.isPlaying = true;
|
|
83
|
+
core_1.Capacitor.isNativePlatform() && capacitor_music_controls_plugin_1.CapacitorMusicControls.updateIsPlaying({ isPlaying: true });
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
static stop() {
|
|
87
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
88
|
+
if (this.audio) {
|
|
89
|
+
this.audio.pause();
|
|
90
|
+
this.audio.src = '';
|
|
148
91
|
}
|
|
92
|
+
this.isPlaying = false;
|
|
93
|
+
yield capacitor_music_controls_plugin_1.CapacitorMusicControls.destroy();
|
|
149
94
|
});
|
|
150
95
|
}
|
|
151
96
|
static togglePlayPause() {
|
|
152
97
|
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
-
if (!this.isInitialized)
|
|
154
|
-
return;
|
|
155
98
|
if (this.isPlaying) {
|
|
156
99
|
yield this.pause();
|
|
157
100
|
}
|
|
@@ -165,12 +108,10 @@ class Player {
|
|
|
165
108
|
}
|
|
166
109
|
static next() {
|
|
167
110
|
return __awaiter(this, void 0, void 0, function* () {
|
|
168
|
-
if (!this.isInitialized)
|
|
169
|
-
return;
|
|
170
111
|
if (this.queue.length > 0) {
|
|
171
|
-
const
|
|
172
|
-
const index = this.playlist.findIndex(s => s.id ===
|
|
173
|
-
yield this.play(
|
|
112
|
+
const next = this.queue.shift();
|
|
113
|
+
const index = this.playlist.findIndex(s => s.id === next.id);
|
|
114
|
+
yield this.play(next, index);
|
|
174
115
|
}
|
|
175
116
|
else if (this.isShuffle) {
|
|
176
117
|
yield this.playRandom();
|
|
@@ -178,19 +119,11 @@ class Player {
|
|
|
178
119
|
else if (this.currentIndex < this.playlist.length - 1) {
|
|
179
120
|
yield this.play(this.playlist[this.currentIndex + 1], this.currentIndex + 1);
|
|
180
121
|
}
|
|
181
|
-
else {
|
|
182
|
-
// End of playlist
|
|
183
|
-
this.isPlaying = false;
|
|
184
|
-
this.currentSong = null;
|
|
185
|
-
}
|
|
186
122
|
});
|
|
187
123
|
}
|
|
188
124
|
static prev() {
|
|
189
125
|
return __awaiter(this, void 0, void 0, function* () {
|
|
190
|
-
if (!this.isInitialized)
|
|
191
|
-
return;
|
|
192
126
|
if (this.currentTime > 3) {
|
|
193
|
-
// If more than 3 seconds into song, restart current song
|
|
194
127
|
yield this.seekTo(0);
|
|
195
128
|
}
|
|
196
129
|
else if (this.currentIndex > 0) {
|
|
@@ -205,20 +138,20 @@ class Player {
|
|
|
205
138
|
}
|
|
206
139
|
static playRandom() {
|
|
207
140
|
return __awaiter(this, void 0, void 0, function* () {
|
|
208
|
-
if (
|
|
141
|
+
if (this.playlist.length <= 1)
|
|
209
142
|
return;
|
|
210
|
-
let
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
yield this.play(this.playlist[
|
|
143
|
+
let rand = this.currentIndex;
|
|
144
|
+
while (rand === this.currentIndex) {
|
|
145
|
+
rand = Math.floor(Math.random() * this.playlist.length);
|
|
146
|
+
}
|
|
147
|
+
yield this.play(this.playlist[rand], rand);
|
|
215
148
|
});
|
|
216
149
|
}
|
|
217
150
|
static toggleShuffle() {
|
|
218
151
|
this.isShuffle = !this.isShuffle;
|
|
219
152
|
}
|
|
220
153
|
static addToQueue(song) {
|
|
221
|
-
if (!this.queue.
|
|
154
|
+
if (!this.queue.find(s => s.id === song.id)) {
|
|
222
155
|
this.queue.push(song);
|
|
223
156
|
}
|
|
224
157
|
}
|
|
@@ -228,27 +161,16 @@ class Player {
|
|
|
228
161
|
}
|
|
229
162
|
}
|
|
230
163
|
static reorderQueue(from, to) {
|
|
231
|
-
if (from
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
164
|
+
if (from === to)
|
|
165
|
+
return;
|
|
166
|
+
const [item] = this.queue.splice(from, 1);
|
|
167
|
+
this.queue.splice(to, 0, item);
|
|
235
168
|
}
|
|
236
|
-
static seekTo(
|
|
169
|
+
static seekTo(seconds) {
|
|
237
170
|
return __awaiter(this, void 0, void 0, function* () {
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (core_1.Capacitor.isNativePlatform()) {
|
|
242
|
-
// NativeAudio doesn't support seeking directly, might need a plugin extension
|
|
243
|
-
console.warn('Seeking not fully supported on native platform');
|
|
244
|
-
}
|
|
245
|
-
else if (this.audioInstance) {
|
|
246
|
-
this.audioInstance.currentTime = time;
|
|
247
|
-
}
|
|
248
|
-
this.currentTime = time;
|
|
249
|
-
}
|
|
250
|
-
catch (err) {
|
|
251
|
-
console.error('Seek error:', err);
|
|
171
|
+
if (this.audio) {
|
|
172
|
+
this.audio.currentTime = seconds;
|
|
173
|
+
this.currentTime = seconds;
|
|
252
174
|
}
|
|
253
175
|
});
|
|
254
176
|
}
|
|
@@ -258,10 +180,10 @@ class Player {
|
|
|
258
180
|
static getDuration() {
|
|
259
181
|
return this.duration;
|
|
260
182
|
}
|
|
261
|
-
static formatTime(
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
return `${
|
|
183
|
+
static formatTime(t) {
|
|
184
|
+
const m = Math.floor(t / 60);
|
|
185
|
+
const s = Math.floor(t % 60);
|
|
186
|
+
return `${m}:${s < 10 ? '0' : ''}${s}`;
|
|
265
187
|
}
|
|
266
188
|
static isPlayingSong() {
|
|
267
189
|
return this.isPlaying;
|
|
@@ -270,9 +192,7 @@ class Player {
|
|
|
270
192
|
return this.currentSong;
|
|
271
193
|
}
|
|
272
194
|
static setQuality(index) {
|
|
273
|
-
|
|
274
|
-
this.selectedQuality = index;
|
|
275
|
-
}
|
|
195
|
+
this.selectedQuality = index;
|
|
276
196
|
}
|
|
277
197
|
static getQueue() {
|
|
278
198
|
return [...this.queue];
|
|
@@ -285,13 +205,16 @@ class Player {
|
|
|
285
205
|
}
|
|
286
206
|
static destroy() {
|
|
287
207
|
return __awaiter(this, void 0, void 0, function* () {
|
|
288
|
-
yield this.
|
|
208
|
+
yield this.stop();
|
|
209
|
+
this.playlist = [];
|
|
210
|
+
this.queue = [];
|
|
211
|
+
this.audio = null;
|
|
289
212
|
this.isInitialized = false;
|
|
290
213
|
});
|
|
291
214
|
}
|
|
292
215
|
}
|
|
293
216
|
exports.Player = Player;
|
|
294
|
-
Player.
|
|
217
|
+
Player.audio = null;
|
|
295
218
|
Player.currentSong = null;
|
|
296
219
|
Player.currentIndex = 0;
|
|
297
220
|
Player.isPlaying = false;
|
|
@@ -301,5 +224,4 @@ Player.isShuffle = false;
|
|
|
301
224
|
Player.queue = [];
|
|
302
225
|
Player.playlist = [];
|
|
303
226
|
Player.selectedQuality = 3;
|
|
304
|
-
Player.audioId = 'current-song';
|
|
305
227
|
Player.isInitialized = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tunzo-player",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "A music playback service for Angular and Ionic apps with native audio control support.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"typescript": "^5.8.3"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@capacitor-community/native-audio": "^7.0.0"
|
|
26
|
+
"@capacitor-community/native-audio": "^7.0.0",
|
|
27
|
+
"capacitor-music-controls-plugin": "^6.1.0"
|
|
27
28
|
}
|
|
28
29
|
}
|
package/src/core/player.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
1
|
import { Capacitor } from '@capacitor/core';
|
|
3
|
-
import {
|
|
2
|
+
import { CapacitorMusicControls } from 'capacitor-music-controls-plugin';
|
|
4
3
|
|
|
5
4
|
export class Player {
|
|
6
|
-
private static
|
|
5
|
+
private static audio: HTMLAudioElement | null = null;
|
|
7
6
|
private static currentSong: any = null;
|
|
8
7
|
private static currentIndex = 0;
|
|
9
8
|
private static isPlaying = false;
|
|
@@ -13,148 +12,89 @@ export class Player {
|
|
|
13
12
|
private static queue: any[] = [];
|
|
14
13
|
private static playlist: any[] = [];
|
|
15
14
|
private static selectedQuality = 3;
|
|
16
|
-
private static readonly audioId: string = 'current-song';
|
|
17
15
|
private static isInitialized = false;
|
|
18
16
|
|
|
19
|
-
/** Initialize with playlist and quality */
|
|
20
17
|
static initialize(playlist: any[] = [], quality = 3) {
|
|
21
|
-
if (!Capacitor.isNativePlatform()
|
|
22
|
-
this.
|
|
18
|
+
if (!Capacitor.isNativePlatform()) {
|
|
19
|
+
this.audio = new Audio();
|
|
23
20
|
}
|
|
24
|
-
|
|
25
21
|
this.playlist = [...playlist];
|
|
26
22
|
this.selectedQuality = quality;
|
|
27
23
|
this.isInitialized = true;
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
static async play(song: any, index
|
|
31
|
-
if (!this.isInitialized)
|
|
32
|
-
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!song || !song.downloadUrl) {
|
|
37
|
-
console.error('Invalid song or missing downloadUrl');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
26
|
+
static async play(song: any, index = 0) {
|
|
27
|
+
if (!this.isInitialized) return;
|
|
28
|
+
if (!song?.downloadUrl) return;
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
await this.cleanupCurrentPlayback();
|
|
30
|
+
await this.stop();
|
|
43
31
|
|
|
44
32
|
this.currentSong = song;
|
|
45
33
|
this.currentIndex = index;
|
|
46
34
|
const url = song.downloadUrl[this.selectedQuality]?.url || song.downloadUrl[0]?.url;
|
|
47
35
|
|
|
48
|
-
if (!url)
|
|
49
|
-
console.error('No valid URL found for playback');
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
36
|
+
if (!url) return;
|
|
52
37
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
isUrl: true
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
await NativeAudio.play({
|
|
69
|
-
assetId: this.audioId
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// For native, we need to manually track duration
|
|
73
|
-
this.duration = song.duration || 0;
|
|
74
|
-
} else {
|
|
75
|
-
if (!this.audioInstance) {
|
|
76
|
-
this.audioInstance = new Audio();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
this.audioInstance.src = url;
|
|
80
|
-
await this.audioInstance.play();
|
|
81
|
-
|
|
82
|
-
this.audioInstance.onloadedmetadata = () => {
|
|
83
|
-
this.duration = this.audioInstance?.duration || 0;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
this.audioInstance.ontimeupdate = () => {
|
|
87
|
-
this.currentTime = this.audioInstance?.currentTime || 0;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
this.audioInstance.onended = () => {
|
|
91
|
-
this.autoNext();
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
this.audioInstance.onerror = () => {
|
|
95
|
-
console.error('Error during playback');
|
|
96
|
-
this.isPlaying = false;
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
this.isPlaying = true;
|
|
101
|
-
} catch (err) {
|
|
102
|
-
console.error('Playback error:', err);
|
|
103
|
-
this.isPlaying = false;
|
|
104
|
-
}
|
|
38
|
+
if (!this.audio) this.audio = new Audio();
|
|
39
|
+
|
|
40
|
+
this.audio.src = url;
|
|
41
|
+
this.audio.play().catch(console.error);
|
|
42
|
+
|
|
43
|
+
this.audio.onloadedmetadata = () => this.duration = this.audio!.duration;
|
|
44
|
+
this.audio.ontimeupdate = () => this.currentTime = this.audio!.currentTime;
|
|
45
|
+
this.audio.onended = () => this.autoNext();
|
|
46
|
+
|
|
47
|
+
await this.setupMusicControls(song, true);
|
|
48
|
+
|
|
49
|
+
this.isPlaying = true;
|
|
105
50
|
}
|
|
106
51
|
|
|
107
|
-
private static async
|
|
108
|
-
if (Capacitor.isNativePlatform())
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
52
|
+
private static async setupMusicControls(song: any, isPlaying: boolean) {
|
|
53
|
+
if (!Capacitor.isNativePlatform()) return;
|
|
54
|
+
|
|
55
|
+
await CapacitorMusicControls.destroy(); // clear existing
|
|
56
|
+
await CapacitorMusicControls.create({
|
|
57
|
+
track: song.name || '',
|
|
58
|
+
artist: song.primaryArtists || '',
|
|
59
|
+
album: song.album || '',
|
|
60
|
+
cover: song.image || '',
|
|
61
|
+
isPlaying,
|
|
62
|
+
dismissable: true,
|
|
63
|
+
hasPrev: true,
|
|
64
|
+
hasNext: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
CapacitorMusicControls.addListener('music-controls-next', () => this.next());
|
|
68
|
+
CapacitorMusicControls.addListener('music-controls-previous', () => this.prev());
|
|
69
|
+
CapacitorMusicControls.addListener('music-controls-pause', () => this.pause());
|
|
70
|
+
CapacitorMusicControls.addListener('music-controls-play', () => this.resume());
|
|
71
|
+
CapacitorMusicControls.addListener('music-controls-destroy', () => this.stop());
|
|
123
72
|
}
|
|
124
73
|
|
|
125
74
|
static async pause() {
|
|
126
|
-
if (
|
|
75
|
+
if (this.audio) this.audio.pause();
|
|
76
|
+
this.isPlaying = false;
|
|
77
|
+
Capacitor.isNativePlatform() && CapacitorMusicControls.updateIsPlaying({ isPlaying: false });
|
|
127
78
|
|
|
128
|
-
try {
|
|
129
|
-
if (Capacitor.isNativePlatform()) {
|
|
130
|
-
await NativeAudio.pause({ assetId: this.audioId });
|
|
131
|
-
} else if (this.audioInstance) {
|
|
132
|
-
this.audioInstance.pause();
|
|
133
|
-
}
|
|
134
|
-
this.isPlaying = false;
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.error('Pause error:', err);
|
|
137
|
-
}
|
|
138
79
|
}
|
|
139
80
|
|
|
140
81
|
static async resume() {
|
|
141
|
-
if (
|
|
82
|
+
if (this.audio) await this.audio.play();
|
|
83
|
+
this.isPlaying = true;
|
|
84
|
+
Capacitor.isNativePlatform() && CapacitorMusicControls.updateIsPlaying({ isPlaying: true });
|
|
85
|
+
|
|
86
|
+
}
|
|
142
87
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
await this.audioInstance.play();
|
|
148
|
-
}
|
|
149
|
-
this.isPlaying = true;
|
|
150
|
-
} catch (err) {
|
|
151
|
-
console.error('Resume error:', err);
|
|
88
|
+
static async stop() {
|
|
89
|
+
if (this.audio) {
|
|
90
|
+
this.audio.pause();
|
|
91
|
+
this.audio.src = '';
|
|
152
92
|
}
|
|
93
|
+
this.isPlaying = false;
|
|
94
|
+
await CapacitorMusicControls.destroy();
|
|
153
95
|
}
|
|
154
96
|
|
|
155
97
|
static async togglePlayPause() {
|
|
156
|
-
if (!this.isInitialized) return;
|
|
157
|
-
|
|
158
98
|
if (this.isPlaying) {
|
|
159
99
|
await this.pause();
|
|
160
100
|
} else if (this.currentSong) {
|
|
@@ -165,28 +105,19 @@ export class Player {
|
|
|
165
105
|
}
|
|
166
106
|
|
|
167
107
|
static async next() {
|
|
168
|
-
if (!this.isInitialized) return;
|
|
169
|
-
|
|
170
108
|
if (this.queue.length > 0) {
|
|
171
|
-
const
|
|
172
|
-
const index = this.playlist.findIndex(s => s.id ===
|
|
173
|
-
await this.play(
|
|
109
|
+
const next = this.queue.shift();
|
|
110
|
+
const index = this.playlist.findIndex(s => s.id === next.id);
|
|
111
|
+
await this.play(next, index);
|
|
174
112
|
} else if (this.isShuffle) {
|
|
175
113
|
await this.playRandom();
|
|
176
114
|
} else if (this.currentIndex < this.playlist.length - 1) {
|
|
177
115
|
await this.play(this.playlist[this.currentIndex + 1], this.currentIndex + 1);
|
|
178
|
-
} else {
|
|
179
|
-
// End of playlist
|
|
180
|
-
this.isPlaying = false;
|
|
181
|
-
this.currentSong = null;
|
|
182
116
|
}
|
|
183
117
|
}
|
|
184
118
|
|
|
185
119
|
static async prev() {
|
|
186
|
-
if (!this.isInitialized) return;
|
|
187
|
-
|
|
188
120
|
if (this.currentTime > 3) {
|
|
189
|
-
// If more than 3 seconds into song, restart current song
|
|
190
121
|
await this.seekTo(0);
|
|
191
122
|
} else if (this.currentIndex > 0) {
|
|
192
123
|
await this.play(this.playlist[this.currentIndex - 1], this.currentIndex - 1);
|
|
@@ -198,14 +129,12 @@ export class Player {
|
|
|
198
129
|
}
|
|
199
130
|
|
|
200
131
|
static async playRandom() {
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
await this.play(this.playlist[randomIndex], randomIndex);
|
|
132
|
+
if (this.playlist.length <= 1) return;
|
|
133
|
+
let rand = this.currentIndex;
|
|
134
|
+
while (rand === this.currentIndex) {
|
|
135
|
+
rand = Math.floor(Math.random() * this.playlist.length);
|
|
136
|
+
}
|
|
137
|
+
await this.play(this.playlist[rand], rand);
|
|
209
138
|
}
|
|
210
139
|
|
|
211
140
|
static toggleShuffle() {
|
|
@@ -213,7 +142,7 @@ export class Player {
|
|
|
213
142
|
}
|
|
214
143
|
|
|
215
144
|
static addToQueue(song: any) {
|
|
216
|
-
if (!this.queue.
|
|
145
|
+
if (!this.queue.find(s => s.id === song.id)) {
|
|
217
146
|
this.queue.push(song);
|
|
218
147
|
}
|
|
219
148
|
}
|
|
@@ -225,25 +154,15 @@ export class Player {
|
|
|
225
154
|
}
|
|
226
155
|
|
|
227
156
|
static reorderQueue(from: number, to: number) {
|
|
228
|
-
if (from
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
157
|
+
if (from === to) return;
|
|
158
|
+
const [item] = this.queue.splice(from, 1);
|
|
159
|
+
this.queue.splice(to, 0, item);
|
|
232
160
|
}
|
|
233
161
|
|
|
234
|
-
static async seekTo(
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (Capacitor.isNativePlatform()) {
|
|
239
|
-
// NativeAudio doesn't support seeking directly, might need a plugin extension
|
|
240
|
-
console.warn('Seeking not fully supported on native platform');
|
|
241
|
-
} else if (this.audioInstance) {
|
|
242
|
-
this.audioInstance.currentTime = time;
|
|
243
|
-
}
|
|
244
|
-
this.currentTime = time;
|
|
245
|
-
} catch (err) {
|
|
246
|
-
console.error('Seek error:', err);
|
|
162
|
+
static async seekTo(seconds: number) {
|
|
163
|
+
if (this.audio) {
|
|
164
|
+
this.audio.currentTime = seconds;
|
|
165
|
+
this.currentTime = seconds;
|
|
247
166
|
}
|
|
248
167
|
}
|
|
249
168
|
|
|
@@ -255,10 +174,10 @@ export class Player {
|
|
|
255
174
|
return this.duration;
|
|
256
175
|
}
|
|
257
176
|
|
|
258
|
-
static formatTime(
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
return `${
|
|
177
|
+
static formatTime(t: number): string {
|
|
178
|
+
const m = Math.floor(t / 60);
|
|
179
|
+
const s = Math.floor(t % 60);
|
|
180
|
+
return `${m}:${s < 10 ? '0' : ''}${s}`;
|
|
262
181
|
}
|
|
263
182
|
|
|
264
183
|
static isPlayingSong(): boolean {
|
|
@@ -270,9 +189,7 @@ export class Player {
|
|
|
270
189
|
}
|
|
271
190
|
|
|
272
191
|
static setQuality(index: number) {
|
|
273
|
-
|
|
274
|
-
this.selectedQuality = index;
|
|
275
|
-
}
|
|
192
|
+
this.selectedQuality = index;
|
|
276
193
|
}
|
|
277
194
|
|
|
278
195
|
static getQueue(): any[] {
|
|
@@ -288,7 +205,10 @@ export class Player {
|
|
|
288
205
|
}
|
|
289
206
|
|
|
290
207
|
static async destroy() {
|
|
291
|
-
await this.
|
|
208
|
+
await this.stop();
|
|
209
|
+
this.playlist = [];
|
|
210
|
+
this.queue = [];
|
|
211
|
+
this.audio = null;
|
|
292
212
|
this.isInitialized = false;
|
|
293
213
|
}
|
|
294
|
-
}
|
|
214
|
+
}
|