tunzo-player 1.0.7 → 1.0.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/dist/core/player.d.ts +13 -16
- package/dist/core/player.js +77 -237
- package/package.json +29 -26
- package/src/core/player.ts +142 -272
- package/tsconfig.json +1 -2
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,24 +9,23 @@ 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
|
-
private static isInitialized;
|
|
14
12
|
/** Initialize with playlist and quality */
|
|
15
|
-
static initialize(playlist
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
static
|
|
19
|
-
static
|
|
20
|
-
static
|
|
21
|
-
static
|
|
22
|
-
static
|
|
23
|
-
static
|
|
24
|
-
static
|
|
13
|
+
static initialize(playlist: any[], quality?: number): void;
|
|
14
|
+
/** Call this once on user gesture to unlock audio in WebView */
|
|
15
|
+
static unlockAudio(): void;
|
|
16
|
+
static play(song: any, index?: number): void;
|
|
17
|
+
static pause(): void;
|
|
18
|
+
static resume(): void;
|
|
19
|
+
static togglePlayPause(): void;
|
|
20
|
+
static next(): void;
|
|
21
|
+
static prev(): void;
|
|
22
|
+
static seek(seconds: number): void;
|
|
23
|
+
static autoNext(): void;
|
|
24
|
+
static playRandom(): void;
|
|
25
25
|
static toggleShuffle(): void;
|
|
26
26
|
static addToQueue(song: any): void;
|
|
27
27
|
static removeFromQueue(index: number): void;
|
|
28
28
|
static reorderQueue(from: number, to: number): void;
|
|
29
|
-
static seekTo(time: number): Promise<void>;
|
|
30
29
|
static getCurrentTime(): number;
|
|
31
30
|
static getDuration(): number;
|
|
32
31
|
static formatTime(time: number): string;
|
|
@@ -35,6 +34,4 @@ export declare class Player {
|
|
|
35
34
|
static setQuality(index: number): void;
|
|
36
35
|
static getQueue(): any[];
|
|
37
36
|
static getPlaylist(): any[];
|
|
38
|
-
static getShuffleStatus(): boolean;
|
|
39
|
-
static destroy(): Promise<void>;
|
|
40
37
|
}
|
package/dist/core/player.js
CHANGED
|
@@ -1,218 +1,94 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.Player = void 0;
|
|
13
|
-
const core_1 = require("@capacitor/core");
|
|
14
|
-
const native_audio_1 = require("@capacitor-community/native-audio");
|
|
15
4
|
class Player {
|
|
16
5
|
/** Initialize with playlist and quality */
|
|
17
|
-
static initialize(playlist
|
|
18
|
-
|
|
19
|
-
this.audioInstance = new Audio();
|
|
20
|
-
}
|
|
21
|
-
this.playlist = [...playlist];
|
|
6
|
+
static initialize(playlist, quality = 3) {
|
|
7
|
+
this.playlist = playlist;
|
|
22
8
|
this.selectedQuality = quality;
|
|
23
|
-
this.isInitialized = true;
|
|
24
|
-
}
|
|
25
|
-
static play(song_1) {
|
|
26
|
-
return __awaiter(this, arguments, void 0, function* (song, index = 0) {
|
|
27
|
-
var _a, _b;
|
|
28
|
-
if (!this.isInitialized) {
|
|
29
|
-
console.error('Player not initialized. Call Player.initialize() first.');
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (!song || !song.downloadUrl) {
|
|
33
|
-
console.error('Invalid song or missing downloadUrl');
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
// Stop current playback
|
|
37
|
-
yield this.cleanupCurrentPlayback();
|
|
38
|
-
this.currentSong = song;
|
|
39
|
-
this.currentIndex = index;
|
|
40
|
-
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');
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
if (core_1.Capacitor.isNativePlatform()) {
|
|
47
|
-
// For native platform, use the correct NativeAudio methods
|
|
48
|
-
yield native_audio_1.NativeAudio.configure({
|
|
49
|
-
fade: false,
|
|
50
|
-
focus: true
|
|
51
|
-
});
|
|
52
|
-
yield native_audio_1.NativeAudio.preload({
|
|
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
|
-
}
|
|
92
|
-
});
|
|
93
9
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
10
|
+
/** Call this once on user gesture to unlock audio in WebView */
|
|
11
|
+
static unlockAudio() {
|
|
12
|
+
this.audio.src = '';
|
|
13
|
+
this.audio.load();
|
|
14
|
+
this.audio.play().catch(() => { });
|
|
15
|
+
}
|
|
16
|
+
static play(song, index = 0) {
|
|
17
|
+
var _a;
|
|
18
|
+
if (!song || !song.downloadUrl)
|
|
19
|
+
return;
|
|
20
|
+
this.currentSong = song;
|
|
21
|
+
this.currentIndex = index;
|
|
22
|
+
this.audio.src = ((_a = song.downloadUrl[this.selectedQuality]) === null || _a === void 0 ? void 0 : _a.url) || '';
|
|
23
|
+
this.audio.load(); // Ensure audio is loaded before play
|
|
24
|
+
this.audio.play().then(() => {
|
|
25
|
+
this.isPlaying = true;
|
|
26
|
+
}).catch((err) => {
|
|
27
|
+
// Handle play errors (autoplay, WebView restrictions)
|
|
28
|
+
this.isPlaying = false;
|
|
29
|
+
console.warn('Audio play failed:', err);
|
|
113
30
|
});
|
|
31
|
+
// Set duration
|
|
32
|
+
this.audio.onloadedmetadata = () => {
|
|
33
|
+
this.duration = this.audio.duration;
|
|
34
|
+
};
|
|
35
|
+
// Set current time
|
|
36
|
+
this.audio.ontimeupdate = () => {
|
|
37
|
+
this.currentTime = this.audio.currentTime;
|
|
38
|
+
};
|
|
39
|
+
// Auto-play next song
|
|
40
|
+
this.audio.onended = () => {
|
|
41
|
+
this.autoNext();
|
|
42
|
+
};
|
|
114
43
|
}
|
|
115
44
|
static pause() {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return;
|
|
119
|
-
try {
|
|
120
|
-
if (core_1.Capacitor.isNativePlatform()) {
|
|
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
|
-
}
|
|
131
|
-
});
|
|
45
|
+
this.audio.pause();
|
|
46
|
+
this.isPlaying = false;
|
|
132
47
|
}
|
|
133
48
|
static resume() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return;
|
|
137
|
-
try {
|
|
138
|
-
if (core_1.Capacitor.isNativePlatform()) {
|
|
139
|
-
yield native_audio_1.NativeAudio.play({ assetId: this.audioId });
|
|
140
|
-
}
|
|
141
|
-
else if (this.audioInstance) {
|
|
142
|
-
yield this.audioInstance.play();
|
|
143
|
-
}
|
|
144
|
-
this.isPlaying = true;
|
|
145
|
-
}
|
|
146
|
-
catch (err) {
|
|
147
|
-
console.error('Resume error:', err);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
49
|
+
this.audio.play();
|
|
50
|
+
this.isPlaying = true;
|
|
150
51
|
}
|
|
151
52
|
static togglePlayPause() {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
else if (this.currentSong) {
|
|
159
|
-
yield this.resume();
|
|
160
|
-
}
|
|
161
|
-
else if (this.playlist.length > 0) {
|
|
162
|
-
yield this.play(this.playlist[0], 0);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
53
|
+
if (this.isPlaying) {
|
|
54
|
+
this.pause();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.resume();
|
|
58
|
+
}
|
|
165
59
|
}
|
|
166
60
|
static next() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
else if (this.currentIndex < this.playlist.length - 1) {
|
|
179
|
-
yield this.play(this.playlist[this.currentIndex + 1], this.currentIndex + 1);
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
// End of playlist
|
|
183
|
-
this.isPlaying = false;
|
|
184
|
-
this.currentSong = null;
|
|
185
|
-
}
|
|
186
|
-
});
|
|
61
|
+
if (this.queue.length > 0) {
|
|
62
|
+
const nextQueued = this.queue.shift();
|
|
63
|
+
const index = this.playlist.findIndex(s => s.id === nextQueued.id);
|
|
64
|
+
this.play(nextQueued, index);
|
|
65
|
+
}
|
|
66
|
+
else if (this.isShuffle) {
|
|
67
|
+
this.playRandom();
|
|
68
|
+
}
|
|
69
|
+
else if (this.currentIndex < this.playlist.length - 1) {
|
|
70
|
+
this.play(this.playlist[this.currentIndex + 1], this.currentIndex + 1);
|
|
71
|
+
}
|
|
187
72
|
}
|
|
188
73
|
static prev() {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
else if (this.currentIndex > 0) {
|
|
197
|
-
yield this.play(this.playlist[this.currentIndex - 1], this.currentIndex - 1);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
74
|
+
if (this.currentIndex > 0) {
|
|
75
|
+
this.play(this.playlist[this.currentIndex - 1], this.currentIndex - 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
static seek(seconds) {
|
|
79
|
+
this.audio.currentTime = seconds;
|
|
200
80
|
}
|
|
201
81
|
static autoNext() {
|
|
202
|
-
|
|
203
|
-
yield this.next();
|
|
204
|
-
});
|
|
82
|
+
this.next();
|
|
205
83
|
}
|
|
206
84
|
static playRandom() {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
yield this.play(this.playlist[randomIndex], randomIndex);
|
|
215
|
-
});
|
|
85
|
+
if (this.playlist.length <= 1)
|
|
86
|
+
return;
|
|
87
|
+
let randomIndex;
|
|
88
|
+
do {
|
|
89
|
+
randomIndex = Math.floor(Math.random() * this.playlist.length);
|
|
90
|
+
} while (randomIndex === this.currentIndex);
|
|
91
|
+
this.play(this.playlist[randomIndex], randomIndex);
|
|
216
92
|
}
|
|
217
93
|
static toggleShuffle() {
|
|
218
94
|
this.isShuffle = !this.isShuffle;
|
|
@@ -223,34 +99,11 @@ class Player {
|
|
|
223
99
|
}
|
|
224
100
|
}
|
|
225
101
|
static removeFromQueue(index) {
|
|
226
|
-
|
|
227
|
-
this.queue.splice(index, 1);
|
|
228
|
-
}
|
|
102
|
+
this.queue.splice(index, 1);
|
|
229
103
|
}
|
|
230
104
|
static reorderQueue(from, to) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
this.queue.splice(to, 0, item);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
static seekTo(time) {
|
|
237
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
238
|
-
if (!this.isInitialized)
|
|
239
|
-
return;
|
|
240
|
-
try {
|
|
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);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
105
|
+
const item = this.queue.splice(from, 1)[0];
|
|
106
|
+
this.queue.splice(to, 0, item);
|
|
254
107
|
}
|
|
255
108
|
static getCurrentTime() {
|
|
256
109
|
return this.currentTime;
|
|
@@ -270,36 +123,23 @@ class Player {
|
|
|
270
123
|
return this.currentSong;
|
|
271
124
|
}
|
|
272
125
|
static setQuality(index) {
|
|
273
|
-
|
|
274
|
-
this.selectedQuality = index;
|
|
275
|
-
}
|
|
126
|
+
this.selectedQuality = index;
|
|
276
127
|
}
|
|
277
128
|
static getQueue() {
|
|
278
|
-
return
|
|
129
|
+
return this.queue;
|
|
279
130
|
}
|
|
280
131
|
static getPlaylist() {
|
|
281
|
-
return
|
|
282
|
-
}
|
|
283
|
-
static getShuffleStatus() {
|
|
284
|
-
return this.isShuffle;
|
|
285
|
-
}
|
|
286
|
-
static destroy() {
|
|
287
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
288
|
-
yield this.cleanupCurrentPlayback();
|
|
289
|
-
this.isInitialized = false;
|
|
290
|
-
});
|
|
132
|
+
return this.playlist;
|
|
291
133
|
}
|
|
292
134
|
}
|
|
293
135
|
exports.Player = Player;
|
|
294
|
-
Player.
|
|
136
|
+
Player.audio = new Audio();
|
|
295
137
|
Player.currentSong = null;
|
|
296
138
|
Player.currentIndex = 0;
|
|
297
139
|
Player.isPlaying = false;
|
|
298
140
|
Player.currentTime = 0;
|
|
299
141
|
Player.duration = 0;
|
|
300
|
-
Player.isShuffle =
|
|
142
|
+
Player.isShuffle = true;
|
|
301
143
|
Player.queue = [];
|
|
302
144
|
Player.playlist = [];
|
|
303
145
|
Player.selectedQuality = 3;
|
|
304
|
-
Player.audioId = 'current-song';
|
|
305
|
-
Player.isInitialized = false;
|
package/package.json
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
2
|
+
"name": "tunzo-player",
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"description": "A music playback service for Angular and Ionic apps with native audio control support.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"publish:patch": "npm version patch && npm run build && npm publish",
|
|
10
|
+
"publish:minor": "npm version minor && npm run build && npm publish",
|
|
11
|
+
"publish:major": "npm version major && npm run build && npm publish",
|
|
12
|
+
"publish": "npm run build && npm publish"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"music",
|
|
16
|
+
"player",
|
|
17
|
+
"angular",
|
|
18
|
+
"ionic",
|
|
19
|
+
"audio",
|
|
20
|
+
"native",
|
|
21
|
+
"playback",
|
|
22
|
+
"service",
|
|
23
|
+
"tunzo",
|
|
24
|
+
"tunzo-player"
|
|
25
|
+
],
|
|
26
|
+
"author": "Kulasekaran",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
}
|
|
28
31
|
}
|
package/src/core/player.ts
CHANGED
|
@@ -1,294 +1,164 @@
|
|
|
1
|
-
|
|
2
|
-
import { Capacitor } from '@capacitor/core';
|
|
3
|
-
import { NativeAudio } from '@capacitor-community/native-audio';
|
|
4
|
-
|
|
5
1
|
export class Player {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
2
|
+
private static audio = new Audio();
|
|
3
|
+
private static currentSong: any = null;
|
|
4
|
+
private static currentIndex = 0;
|
|
5
|
+
private static isPlaying = false;
|
|
6
|
+
private static currentTime = 0;
|
|
7
|
+
private static duration = 0;
|
|
8
|
+
private static isShuffle = true;
|
|
9
|
+
private static queue: any[] = [];
|
|
10
|
+
private static playlist: any[] = [];
|
|
11
|
+
private static selectedQuality = 3;
|
|
12
|
+
|
|
13
|
+
/** Initialize with playlist and quality */
|
|
14
|
+
static initialize(playlist: any[], quality = 3) {
|
|
15
|
+
this.playlist = playlist;
|
|
16
|
+
this.selectedQuality = quality;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Call this once on user gesture to unlock audio in WebView */
|
|
20
|
+
static unlockAudio() {
|
|
21
|
+
this.audio.src = '';
|
|
22
|
+
this.audio.load();
|
|
23
|
+
this.audio.play().catch(() => {});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static play(song: any, index: number = 0) {
|
|
27
|
+
if (!song || !song.downloadUrl) return;
|
|
28
|
+
|
|
29
|
+
this.currentSong = song;
|
|
30
|
+
this.currentIndex = index;
|
|
31
|
+
this.audio.src = song.downloadUrl[this.selectedQuality]?.url || '';
|
|
32
|
+
this.audio.load(); // Ensure audio is loaded before play
|
|
33
|
+
this.audio.play().then(() => {
|
|
34
|
+
this.isPlaying = true;
|
|
35
|
+
}).catch((err) => {
|
|
36
|
+
// Handle play errors (autoplay, WebView restrictions)
|
|
37
|
+
this.isPlaying = false;
|
|
38
|
+
console.warn('Audio play failed:', err);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Set duration
|
|
42
|
+
this.audio.onloadedmetadata = () => {
|
|
43
|
+
this.duration = this.audio.duration;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Set current time
|
|
47
|
+
this.audio.ontimeupdate = () => {
|
|
48
|
+
this.currentTime = this.audio.currentTime;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Auto-play next song
|
|
52
|
+
this.audio.onended = () => {
|
|
53
|
+
this.autoNext();
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static pause() {
|
|
58
|
+
this.audio.pause();
|
|
59
|
+
this.isPlaying = false;
|
|
39
60
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.currentSong = song;
|
|
45
|
-
this.currentIndex = index;
|
|
46
|
-
const url = song.downloadUrl[this.selectedQuality]?.url || song.downloadUrl[0]?.url;
|
|
47
|
-
|
|
48
|
-
if (!url) {
|
|
49
|
-
console.error('No valid URL found for playback');
|
|
50
|
-
return;
|
|
61
|
+
|
|
62
|
+
static resume() {
|
|
63
|
+
this.audio.play();
|
|
64
|
+
this.isPlaying = true;
|
|
51
65
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
await NativeAudio.configure({
|
|
57
|
-
fade: false,
|
|
58
|
-
focus: true
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
await NativeAudio.preload({
|
|
62
|
-
assetId: this.audioId,
|
|
63
|
-
assetPath: url,
|
|
64
|
-
audioChannelNum: 1,
|
|
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;
|
|
66
|
+
|
|
67
|
+
static togglePlayPause() {
|
|
68
|
+
if (this.isPlaying) {
|
|
69
|
+
this.pause();
|
|
74
70
|
} else {
|
|
75
|
-
|
|
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
|
-
};
|
|
71
|
+
this.resume();
|
|
98
72
|
}
|
|
99
|
-
|
|
100
|
-
this.isPlaying = true;
|
|
101
|
-
} catch (err) {
|
|
102
|
-
console.error('Playback error:', err);
|
|
103
|
-
this.isPlaying = false;
|
|
104
73
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
74
|
+
|
|
75
|
+
static next() {
|
|
76
|
+
if (this.queue.length > 0) {
|
|
77
|
+
const nextQueued = this.queue.shift();
|
|
78
|
+
const index = this.playlist.findIndex(s => s.id === nextQueued.id);
|
|
79
|
+
this.play(nextQueued, index);
|
|
80
|
+
} else if (this.isShuffle) {
|
|
81
|
+
this.playRandom();
|
|
82
|
+
} else if (this.currentIndex < this.playlist.length - 1) {
|
|
83
|
+
this.play(this.playlist[this.currentIndex + 1], this.currentIndex + 1);
|
|
114
84
|
}
|
|
115
|
-
} else if (this.audioInstance) {
|
|
116
|
-
this.audioInstance.pause();
|
|
117
|
-
this.audioInstance.src = '';
|
|
118
|
-
this.audioInstance.onloadedmetadata = null;
|
|
119
|
-
this.audioInstance.ontimeupdate = null;
|
|
120
|
-
this.audioInstance.onended = null;
|
|
121
|
-
this.audioInstance.onerror = null;
|
|
122
85
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
if (Capacitor.isNativePlatform()) {
|
|
130
|
-
await NativeAudio.pause({ assetId: this.audioId });
|
|
131
|
-
} else if (this.audioInstance) {
|
|
132
|
-
this.audioInstance.pause();
|
|
86
|
+
|
|
87
|
+
static prev() {
|
|
88
|
+
if (this.currentIndex > 0) {
|
|
89
|
+
this.play(this.playlist[this.currentIndex - 1], this.currentIndex - 1);
|
|
133
90
|
}
|
|
134
|
-
this.isPlaying = false;
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.error('Pause error:', err);
|
|
137
91
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
92
|
+
|
|
93
|
+
static seek(seconds: number) {
|
|
94
|
+
this.audio.currentTime = seconds;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static autoNext() {
|
|
98
|
+
this.next();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static playRandom() {
|
|
102
|
+
if (this.playlist.length <= 1) return;
|
|
103
|
+
|
|
104
|
+
let randomIndex;
|
|
105
|
+
do {
|
|
106
|
+
randomIndex = Math.floor(Math.random() * this.playlist.length);
|
|
107
|
+
} while (randomIndex === this.currentIndex);
|
|
108
|
+
|
|
109
|
+
this.play(this.playlist[randomIndex], randomIndex);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static toggleShuffle() {
|
|
113
|
+
this.isShuffle = !this.isShuffle;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static addToQueue(song: any) {
|
|
117
|
+
if (!this.queue.some(q => q.id === song.id)) {
|
|
118
|
+
this.queue.push(song);
|
|
148
119
|
}
|
|
149
|
-
this.isPlaying = true;
|
|
150
|
-
} catch (err) {
|
|
151
|
-
console.error('Resume error:', err);
|
|
152
120
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (!this.isInitialized) return;
|
|
157
|
-
|
|
158
|
-
if (this.isPlaying) {
|
|
159
|
-
await this.pause();
|
|
160
|
-
} else if (this.currentSong) {
|
|
161
|
-
await this.resume();
|
|
162
|
-
} else if (this.playlist.length > 0) {
|
|
163
|
-
await this.play(this.playlist[0], 0);
|
|
121
|
+
|
|
122
|
+
static removeFromQueue(index: number) {
|
|
123
|
+
this.queue.splice(index, 1);
|
|
164
124
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (this.queue.length > 0) {
|
|
171
|
-
const nextQueued = this.queue.shift();
|
|
172
|
-
const index = this.playlist.findIndex(s => s.id === nextQueued.id);
|
|
173
|
-
await this.play(nextQueued, index);
|
|
174
|
-
} else if (this.isShuffle) {
|
|
175
|
-
await this.playRandom();
|
|
176
|
-
} else if (this.currentIndex < this.playlist.length - 1) {
|
|
177
|
-
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;
|
|
125
|
+
|
|
126
|
+
static reorderQueue(from: number, to: number) {
|
|
127
|
+
const item = this.queue.splice(from, 1)[0];
|
|
128
|
+
this.queue.splice(to, 0, item);
|
|
182
129
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (!this.isInitialized) return;
|
|
187
|
-
|
|
188
|
-
if (this.currentTime > 3) {
|
|
189
|
-
// If more than 3 seconds into song, restart current song
|
|
190
|
-
await this.seekTo(0);
|
|
191
|
-
} else if (this.currentIndex > 0) {
|
|
192
|
-
await this.play(this.playlist[this.currentIndex - 1], this.currentIndex - 1);
|
|
130
|
+
|
|
131
|
+
static getCurrentTime(): number {
|
|
132
|
+
return this.currentTime;
|
|
193
133
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
await this.next();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
static async playRandom() {
|
|
201
|
-
if (!this.isInitialized || this.playlist.length <= 1) return;
|
|
202
|
-
|
|
203
|
-
let randomIndex;
|
|
204
|
-
do {
|
|
205
|
-
randomIndex = Math.floor(Math.random() * this.playlist.length);
|
|
206
|
-
} while (randomIndex === this.currentIndex && this.playlist.length > 1);
|
|
207
|
-
|
|
208
|
-
await this.play(this.playlist[randomIndex], randomIndex);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
static toggleShuffle() {
|
|
212
|
-
this.isShuffle = !this.isShuffle;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
static addToQueue(song: any) {
|
|
216
|
-
if (!this.queue.some(q => q.id === song.id)) {
|
|
217
|
-
this.queue.push(song);
|
|
134
|
+
|
|
135
|
+
static getDuration(): number {
|
|
136
|
+
return this.duration;
|
|
218
137
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
138
|
+
|
|
139
|
+
static formatTime(time: number): string {
|
|
140
|
+
const minutes = Math.floor(time / 60);
|
|
141
|
+
const seconds = Math.floor(time % 60);
|
|
142
|
+
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
|
|
224
143
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (from >= 0 && from < this.queue.length && to >= 0 && to < this.queue.length) {
|
|
229
|
-
const item = this.queue.splice(from, 1)[0];
|
|
230
|
-
this.queue.splice(to, 0, item);
|
|
144
|
+
|
|
145
|
+
static isPlayingSong(): boolean {
|
|
146
|
+
return this.isPlaying;
|
|
231
147
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (!this.isInitialized) return;
|
|
236
|
-
|
|
237
|
-
try {
|
|
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);
|
|
148
|
+
|
|
149
|
+
static getCurrentSong(): any {
|
|
150
|
+
return this.currentSong;
|
|
247
151
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
static getCurrentTime(): number {
|
|
251
|
-
return this.currentTime;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
static getDuration(): number {
|
|
255
|
-
return this.duration;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
static formatTime(time: number): string {
|
|
259
|
-
const minutes = Math.floor(time / 60);
|
|
260
|
-
const seconds = Math.floor(time % 60);
|
|
261
|
-
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
static isPlayingSong(): boolean {
|
|
265
|
-
return this.isPlaying;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
static getCurrentSong(): any {
|
|
269
|
-
return this.currentSong;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
static setQuality(index: number) {
|
|
273
|
-
if (index >= 0) {
|
|
152
|
+
|
|
153
|
+
static setQuality(index: number) {
|
|
274
154
|
this.selectedQuality = index;
|
|
275
155
|
}
|
|
156
|
+
|
|
157
|
+
static getQueue(): any[] {
|
|
158
|
+
return this.queue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static getPlaylist(): any[] {
|
|
162
|
+
return this.playlist;
|
|
163
|
+
}
|
|
276
164
|
}
|
|
277
|
-
|
|
278
|
-
static getQueue(): any[] {
|
|
279
|
-
return [...this.queue];
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
static getPlaylist(): any[] {
|
|
283
|
-
return [...this.playlist];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
static getShuffleStatus(): boolean {
|
|
287
|
-
return this.isShuffle;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
static async destroy() {
|
|
291
|
-
await this.cleanupCurrentPlayback();
|
|
292
|
-
this.isInitialized = false;
|
|
293
|
-
}
|
|
294
|
-
}
|
package/tsconfig.json
CHANGED
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
"esModuleInterop": true, // allow default imports from CommonJS
|
|
15
15
|
"forceConsistentCasingInFileNames": true,
|
|
16
16
|
"strict": true,
|
|
17
|
-
"skipLibCheck": true,
|
|
18
|
-
"types": ["@capacitor-community/native-audio"] // speed up builds, safe for libs
|
|
17
|
+
"skipLibCheck": true // speed up builds, safe for libs
|
|
19
18
|
},
|
|
20
19
|
"include": ["src"] // only compile the src folder
|
|
21
20
|
}
|