tunzo-player 1.0.6 → 1.0.7
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/README.md +1 -0
- package/dist/core/player.d.ts +16 -11
- package/dist/core/player.js +238 -66
- package/package.json +7 -1
- package/src/core/player.ts +272 -130
- package/tsconfig.json +2 -1
package/README.md
CHANGED
package/dist/core/player.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare class Player {
|
|
2
|
-
private static
|
|
2
|
+
private static audioInstance;
|
|
3
3
|
private static currentSong;
|
|
4
4
|
private static currentIndex;
|
|
5
5
|
private static isPlaying;
|
|
@@ -9,21 +9,24 @@ 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;
|
|
12
14
|
/** Initialize with playlist and quality */
|
|
13
|
-
static initialize(playlist
|
|
14
|
-
static play(song: any, index?: number): void
|
|
15
|
-
static
|
|
16
|
-
static
|
|
17
|
-
static
|
|
18
|
-
static
|
|
19
|
-
static
|
|
20
|
-
static
|
|
21
|
-
static autoNext(): void
|
|
22
|
-
static playRandom(): void
|
|
15
|
+
static initialize(playlist?: any[], quality?: number): void;
|
|
16
|
+
static play(song: any, index?: number): Promise<void>;
|
|
17
|
+
private static cleanupCurrentPlayback;
|
|
18
|
+
static pause(): Promise<void>;
|
|
19
|
+
static resume(): Promise<void>;
|
|
20
|
+
static togglePlayPause(): Promise<void>;
|
|
21
|
+
static next(): Promise<void>;
|
|
22
|
+
static prev(): Promise<void>;
|
|
23
|
+
static autoNext(): Promise<void>;
|
|
24
|
+
static playRandom(): Promise<void>;
|
|
23
25
|
static toggleShuffle(): void;
|
|
24
26
|
static addToQueue(song: any): void;
|
|
25
27
|
static removeFromQueue(index: number): void;
|
|
26
28
|
static reorderQueue(from: number, to: number): void;
|
|
29
|
+
static seekTo(time: number): Promise<void>;
|
|
27
30
|
static getCurrentTime(): number;
|
|
28
31
|
static getDuration(): number;
|
|
29
32
|
static formatTime(time: number): string;
|
|
@@ -32,4 +35,6 @@ export declare class Player {
|
|
|
32
35
|
static setQuality(index: number): void;
|
|
33
36
|
static getQueue(): any[];
|
|
34
37
|
static getPlaylist(): any[];
|
|
38
|
+
static getShuffleStatus(): boolean;
|
|
39
|
+
static destroy(): Promise<void>;
|
|
35
40
|
}
|
package/dist/core/player.js
CHANGED
|
@@ -1,82 +1,218 @@
|
|
|
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
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
exports.Player = void 0;
|
|
13
|
+
const core_1 = require("@capacitor/core");
|
|
14
|
+
const native_audio_1 = require("@capacitor-community/native-audio");
|
|
4
15
|
class Player {
|
|
5
16
|
/** Initialize with playlist and quality */
|
|
6
|
-
static initialize(playlist, quality = 3) {
|
|
7
|
-
this.
|
|
17
|
+
static initialize(playlist = [], quality = 3) {
|
|
18
|
+
if (!core_1.Capacitor.isNativePlatform() && !this.audioInstance) {
|
|
19
|
+
this.audioInstance = new Audio();
|
|
20
|
+
}
|
|
21
|
+
this.playlist = [...playlist];
|
|
8
22
|
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
|
+
});
|
|
9
93
|
}
|
|
10
|
-
static
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
94
|
+
static cleanupCurrentPlayback() {
|
|
95
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
96
|
+
if (core_1.Capacitor.isNativePlatform()) {
|
|
97
|
+
try {
|
|
98
|
+
yield native_audio_1.NativeAudio.stop({ assetId: this.audioId });
|
|
99
|
+
yield native_audio_1.NativeAudio.unload({ assetId: this.audioId });
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.debug('No audio to unload');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (this.audioInstance) {
|
|
106
|
+
this.audioInstance.pause();
|
|
107
|
+
this.audioInstance.src = '';
|
|
108
|
+
this.audioInstance.onloadedmetadata = null;
|
|
109
|
+
this.audioInstance.ontimeupdate = null;
|
|
110
|
+
this.audioInstance.onended = null;
|
|
111
|
+
this.audioInstance.onerror = null;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
31
114
|
}
|
|
32
115
|
static pause() {
|
|
33
|
-
this
|
|
34
|
-
|
|
116
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
if (!this.isInitialized)
|
|
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
|
+
});
|
|
35
132
|
}
|
|
36
133
|
static resume() {
|
|
37
|
-
this
|
|
38
|
-
|
|
134
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
+
if (!this.isInitialized)
|
|
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
|
+
});
|
|
39
150
|
}
|
|
40
151
|
static togglePlayPause() {
|
|
41
|
-
|
|
42
|
-
this.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
152
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
+
if (!this.isInitialized)
|
|
154
|
+
return;
|
|
155
|
+
if (this.isPlaying) {
|
|
156
|
+
yield this.pause();
|
|
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
|
+
});
|
|
47
165
|
}
|
|
48
166
|
static next() {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
167
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
168
|
+
if (!this.isInitialized)
|
|
169
|
+
return;
|
|
170
|
+
if (this.queue.length > 0) {
|
|
171
|
+
const nextQueued = this.queue.shift();
|
|
172
|
+
const index = this.playlist.findIndex(s => s.id === nextQueued.id);
|
|
173
|
+
yield this.play(nextQueued, index);
|
|
174
|
+
}
|
|
175
|
+
else if (this.isShuffle) {
|
|
176
|
+
yield this.playRandom();
|
|
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
|
+
});
|
|
60
187
|
}
|
|
61
188
|
static prev() {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
189
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
190
|
+
if (!this.isInitialized)
|
|
191
|
+
return;
|
|
192
|
+
if (this.currentTime > 3) {
|
|
193
|
+
// If more than 3 seconds into song, restart current song
|
|
194
|
+
yield this.seekTo(0);
|
|
195
|
+
}
|
|
196
|
+
else if (this.currentIndex > 0) {
|
|
197
|
+
yield this.play(this.playlist[this.currentIndex - 1], this.currentIndex - 1);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
68
200
|
}
|
|
69
201
|
static autoNext() {
|
|
70
|
-
this
|
|
202
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
203
|
+
yield this.next();
|
|
204
|
+
});
|
|
71
205
|
}
|
|
72
206
|
static playRandom() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
207
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
208
|
+
if (!this.isInitialized || this.playlist.length <= 1)
|
|
209
|
+
return;
|
|
210
|
+
let randomIndex;
|
|
211
|
+
do {
|
|
212
|
+
randomIndex = Math.floor(Math.random() * this.playlist.length);
|
|
213
|
+
} while (randomIndex === this.currentIndex && this.playlist.length > 1);
|
|
214
|
+
yield this.play(this.playlist[randomIndex], randomIndex);
|
|
215
|
+
});
|
|
80
216
|
}
|
|
81
217
|
static toggleShuffle() {
|
|
82
218
|
this.isShuffle = !this.isShuffle;
|
|
@@ -87,11 +223,34 @@ class Player {
|
|
|
87
223
|
}
|
|
88
224
|
}
|
|
89
225
|
static removeFromQueue(index) {
|
|
90
|
-
this.queue.
|
|
226
|
+
if (index >= 0 && index < this.queue.length) {
|
|
227
|
+
this.queue.splice(index, 1);
|
|
228
|
+
}
|
|
91
229
|
}
|
|
92
230
|
static reorderQueue(from, to) {
|
|
93
|
-
|
|
94
|
-
|
|
231
|
+
if (from >= 0 && from < this.queue.length && to >= 0 && to < this.queue.length) {
|
|
232
|
+
const item = this.queue.splice(from, 1)[0];
|
|
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
|
+
});
|
|
95
254
|
}
|
|
96
255
|
static getCurrentTime() {
|
|
97
256
|
return this.currentTime;
|
|
@@ -111,23 +270,36 @@ class Player {
|
|
|
111
270
|
return this.currentSong;
|
|
112
271
|
}
|
|
113
272
|
static setQuality(index) {
|
|
114
|
-
|
|
273
|
+
if (index >= 0) {
|
|
274
|
+
this.selectedQuality = index;
|
|
275
|
+
}
|
|
115
276
|
}
|
|
116
277
|
static getQueue() {
|
|
117
|
-
return this.queue;
|
|
278
|
+
return [...this.queue];
|
|
118
279
|
}
|
|
119
280
|
static getPlaylist() {
|
|
120
|
-
return this.playlist;
|
|
281
|
+
return [...this.playlist];
|
|
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
|
+
});
|
|
121
291
|
}
|
|
122
292
|
}
|
|
123
293
|
exports.Player = Player;
|
|
124
|
-
Player.
|
|
294
|
+
Player.audioInstance = null;
|
|
125
295
|
Player.currentSong = null;
|
|
126
296
|
Player.currentIndex = 0;
|
|
127
297
|
Player.isPlaying = false;
|
|
128
298
|
Player.currentTime = 0;
|
|
129
299
|
Player.duration = 0;
|
|
130
|
-
Player.isShuffle =
|
|
300
|
+
Player.isShuffle = false;
|
|
131
301
|
Player.queue = [];
|
|
132
302
|
Player.playlist = [];
|
|
133
303
|
Player.selectedQuality = 3;
|
|
304
|
+
Player.audioId = 'current-song';
|
|
305
|
+
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.7",
|
|
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",
|
|
@@ -18,5 +18,11 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5.8.3"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@capacitor-community/native-audio": "^7.0.0"
|
|
21
27
|
}
|
|
22
28
|
}
|
package/src/core/player.ts
CHANGED
|
@@ -1,152 +1,294 @@
|
|
|
1
|
+
|
|
2
|
+
import { Capacitor } from '@capacitor/core';
|
|
3
|
+
import { NativeAudio } from '@capacitor-community/native-audio';
|
|
4
|
+
|
|
1
5
|
export class Player {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
static play(song: any, index: number = 0) {
|
|
20
|
-
if (!song || !song.downloadUrl) return;
|
|
21
|
-
|
|
22
|
-
this.currentSong = song;
|
|
23
|
-
this.currentIndex = index;
|
|
24
|
-
this.audio.src = song.downloadUrl[this.selectedQuality]?.url || '';
|
|
25
|
-
this.audio.play();
|
|
26
|
-
this.isPlaying = true;
|
|
27
|
-
|
|
28
|
-
// Set duration
|
|
29
|
-
this.audio.onloadedmetadata = () => {
|
|
30
|
-
this.duration = this.audio.duration;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// Set current time
|
|
34
|
-
this.audio.ontimeupdate = () => {
|
|
35
|
-
this.currentTime = this.audio.currentTime;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// Auto-play next song
|
|
39
|
-
this.audio.onended = () => {
|
|
40
|
-
this.autoNext();
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
static pause() {
|
|
45
|
-
this.audio.pause();
|
|
46
|
-
this.isPlaying = false;
|
|
6
|
+
private static audioInstance: HTMLAudioElement | null = null;
|
|
7
|
+
private static currentSong: any = null;
|
|
8
|
+
private static currentIndex = 0;
|
|
9
|
+
private static isPlaying = false;
|
|
10
|
+
private static currentTime = 0;
|
|
11
|
+
private static duration = 0;
|
|
12
|
+
private static isShuffle = false;
|
|
13
|
+
private static queue: any[] = [];
|
|
14
|
+
private static playlist: any[] = [];
|
|
15
|
+
private static selectedQuality = 3;
|
|
16
|
+
private static readonly audioId: string = 'current-song';
|
|
17
|
+
private static isInitialized = false;
|
|
18
|
+
|
|
19
|
+
/** Initialize with playlist and quality */
|
|
20
|
+
static initialize(playlist: any[] = [], quality = 3) {
|
|
21
|
+
if (!Capacitor.isNativePlatform() && !this.audioInstance) {
|
|
22
|
+
this.audioInstance = new Audio();
|
|
47
23
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
24
|
+
|
|
25
|
+
this.playlist = [...playlist];
|
|
26
|
+
this.selectedQuality = quality;
|
|
27
|
+
this.isInitialized = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static async play(song: any, index: number = 0) {
|
|
31
|
+
if (!this.isInitialized) {
|
|
32
|
+
console.error('Player not initialized. Call Player.initialize() first.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!song || !song.downloadUrl) {
|
|
37
|
+
console.error('Invalid song or missing downloadUrl');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Stop current playback
|
|
42
|
+
await this.cleanupCurrentPlayback();
|
|
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;
|
|
52
51
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
if (Capacitor.isNativePlatform()) {
|
|
55
|
+
// For native platform, use the correct NativeAudio methods
|
|
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;
|
|
57
74
|
} else {
|
|
58
|
-
this.
|
|
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
|
+
};
|
|
59
98
|
}
|
|
99
|
+
|
|
100
|
+
this.isPlaying = true;
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error('Playback error:', err);
|
|
103
|
+
this.isPlaying = false;
|
|
60
104
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.play(this.playlist[this.currentIndex + 1], this.currentIndex + 1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private static async cleanupCurrentPlayback() {
|
|
108
|
+
if (Capacitor.isNativePlatform()) {
|
|
109
|
+
try {
|
|
110
|
+
await NativeAudio.stop({ assetId: this.audioId });
|
|
111
|
+
await NativeAudio.unload({ assetId: this.audioId });
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.debug('No audio to unload');
|
|
71
114
|
}
|
|
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;
|
|
72
122
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static async pause() {
|
|
126
|
+
if (!this.isInitialized) return;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
if (Capacitor.isNativePlatform()) {
|
|
130
|
+
await NativeAudio.pause({ assetId: this.audioId });
|
|
131
|
+
} else if (this.audioInstance) {
|
|
132
|
+
this.audioInstance.pause();
|
|
77
133
|
}
|
|
134
|
+
this.isPlaying = false;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error('Pause error:', err);
|
|
78
137
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (this.playlist.length <= 1) return;
|
|
90
|
-
|
|
91
|
-
let randomIndex;
|
|
92
|
-
do {
|
|
93
|
-
randomIndex = Math.floor(Math.random() * this.playlist.length);
|
|
94
|
-
} while (randomIndex === this.currentIndex);
|
|
95
|
-
|
|
96
|
-
this.play(this.playlist[randomIndex], randomIndex);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
static toggleShuffle() {
|
|
100
|
-
this.isShuffle = !this.isShuffle;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
static addToQueue(song: any) {
|
|
104
|
-
if (!this.queue.some(q => q.id === song.id)) {
|
|
105
|
-
this.queue.push(song);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static async resume() {
|
|
141
|
+
if (!this.isInitialized) return;
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
if (Capacitor.isNativePlatform()) {
|
|
145
|
+
await NativeAudio.play({ assetId: this.audioId });
|
|
146
|
+
} else if (this.audioInstance) {
|
|
147
|
+
await this.audioInstance.play();
|
|
106
148
|
}
|
|
149
|
+
this.isPlaying = true;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error('Resume error:', err);
|
|
107
152
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static async togglePlayPause() {
|
|
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);
|
|
111
164
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static async next() {
|
|
168
|
+
if (!this.isInitialized) return;
|
|
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;
|
|
116
182
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static async prev() {
|
|
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);
|
|
120
193
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
static async autoNext() {
|
|
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);
|
|
124
218
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static removeFromQueue(index: number) {
|
|
222
|
+
if (index >= 0 && index < this.queue.length) {
|
|
223
|
+
this.queue.splice(index, 1);
|
|
130
224
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
static reorderQueue(from: number, to: number) {
|
|
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);
|
|
134
231
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
static async seekTo(time: number) {
|
|
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);
|
|
138
247
|
}
|
|
139
|
-
|
|
140
|
-
|
|
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) {
|
|
141
274
|
this.selectedQuality = index;
|
|
142
275
|
}
|
|
143
|
-
|
|
144
|
-
static getQueue(): any[] {
|
|
145
|
-
return this.queue;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
static getPlaylist(): any[] {
|
|
149
|
-
return this.playlist;
|
|
150
|
-
}
|
|
151
276
|
}
|
|
152
|
-
|
|
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,7 +14,8 @@
|
|
|
14
14
|
"esModuleInterop": true, // allow default imports from CommonJS
|
|
15
15
|
"forceConsistentCasingInFileNames": true,
|
|
16
16
|
"strict": true,
|
|
17
|
-
"skipLibCheck": true
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"types": ["@capacitor-community/native-audio"] // speed up builds, safe for libs
|
|
18
19
|
},
|
|
19
20
|
"include": ["src"] // only compile the src folder
|
|
20
21
|
}
|