tunzo-player 1.0.40 → 1.0.42
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/api-calles.d.ts +2 -0
- package/dist/core/api-calles.js +34 -0
- package/dist/core/player.d.ts +0 -3
- package/dist/core/player.js +50 -144
- package/package.json +2 -3
- package/src/core/api-calles.ts +37 -0
- package/src/core/player.ts +74 -173
|
@@ -32,10 +32,12 @@ export declare class TunzoPlayerAPI {
|
|
|
32
32
|
* @param limit Number of results (default: 1000)
|
|
33
33
|
*/
|
|
34
34
|
searchAlbums(query: string, limit?: number): Promise<any[]>;
|
|
35
|
+
searchArtist(query: string, limit?: number): Promise<any[]>;
|
|
35
36
|
/**
|
|
36
37
|
* Get album details
|
|
37
38
|
* @param id Album ID
|
|
38
39
|
* @param link Album URL/Link (optional but recommended if available)
|
|
39
40
|
*/
|
|
40
41
|
getAlbumDetails(id: string, link?: string): Promise<any>;
|
|
42
|
+
getartistDetails(id: string): Promise<any>;
|
|
41
43
|
}
|
package/dist/core/api-calles.js
CHANGED
|
@@ -131,6 +131,23 @@ class TunzoPlayerAPI {
|
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
+
searchArtist(query_1) {
|
|
135
|
+
return __awaiter(this, arguments, void 0, function* (query, limit = 1000) {
|
|
136
|
+
var _a;
|
|
137
|
+
try {
|
|
138
|
+
const response = yield fetch(`${this.baseUrl}/search/artists?query=${encodeURIComponent(query)}&limit=${limit}'`);
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
141
|
+
}
|
|
142
|
+
const json = yield response.json();
|
|
143
|
+
return ((_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.results) || [];
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
console.error('error', err);
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
134
151
|
/**
|
|
135
152
|
* Get album details
|
|
136
153
|
* @param id Album ID
|
|
@@ -156,5 +173,22 @@ class TunzoPlayerAPI {
|
|
|
156
173
|
}
|
|
157
174
|
});
|
|
158
175
|
}
|
|
176
|
+
getartistDetails(id) {
|
|
177
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
178
|
+
try {
|
|
179
|
+
let url = `${this.baseUrl}/artists/${id}/songs?page=0&sortBy=popularity&sortOrder=desc`;
|
|
180
|
+
const response = yield fetch(url);
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
183
|
+
}
|
|
184
|
+
const json = yield response.json();
|
|
185
|
+
return (json === null || json === void 0 ? void 0 : json.data) || null;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error("TunzoPlayerAPI Error (getAlbumDetails):", error);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
159
193
|
}
|
|
160
194
|
exports.TunzoPlayerAPI = TunzoPlayerAPI;
|
package/dist/core/player.d.ts
CHANGED
|
@@ -14,10 +14,8 @@ export declare class Player {
|
|
|
14
14
|
private static selectedQuality;
|
|
15
15
|
private static intendedPlaying;
|
|
16
16
|
private static toastCtrl;
|
|
17
|
-
private static isAndroid;
|
|
18
17
|
/** Initialize with playlist and quality */
|
|
19
18
|
static initialize(playlist: any[], quality?: number): void;
|
|
20
|
-
private static setupAndroidPlayer;
|
|
21
19
|
static setToastController(controller: ToastController): void;
|
|
22
20
|
/** Setup audio element for better compatibility */
|
|
23
21
|
private static setupAudioElement;
|
|
@@ -25,7 +23,6 @@ export declare class Player {
|
|
|
25
23
|
/** Call this once on user gesture to unlock audio in WebView */
|
|
26
24
|
static unlockAudio(): void;
|
|
27
25
|
static play(song: any, index?: number): void;
|
|
28
|
-
private static playAndroid;
|
|
29
26
|
static pause(): void;
|
|
30
27
|
static resume(): void;
|
|
31
28
|
static togglePlayPause(): void;
|
package/dist/core/player.js
CHANGED
|
@@ -12,30 +12,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.Player = void 0;
|
|
13
13
|
const rxjs_1 = require("rxjs");
|
|
14
14
|
const keep_awake_1 = require("@capacitor-community/keep-awake");
|
|
15
|
-
const
|
|
16
|
-
const capacitor_plugin_playlist_1 = require("capacitor-plugin-playlist");
|
|
15
|
+
const capacitor_media_session_1 = require("@capgo/capacitor-media-session");
|
|
17
16
|
class Player {
|
|
18
17
|
/** Initialize with playlist and quality */
|
|
19
18
|
static initialize(playlist, quality = 3) {
|
|
20
19
|
this.playlist = playlist;
|
|
21
20
|
this.selectedQuality = quality;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
this.setupMediaSession();
|
|
27
|
-
this.setupAudioElement();
|
|
28
|
-
}
|
|
21
|
+
this.setupMediaSession();
|
|
22
|
+
this.setupAudioElement();
|
|
29
23
|
this.startWatchdog();
|
|
30
24
|
}
|
|
31
|
-
static setupAndroidPlayer() {
|
|
32
|
-
capacitor_plugin_playlist_1.Playlist.addListener('status', (data) => {
|
|
33
|
-
if (data && data.status && (data.status.msgType === capacitor_plugin_playlist_1.RmxAudioStatusMessage.RMXSTATUS_COMPLETED ||
|
|
34
|
-
data.status.msgType === capacitor_plugin_playlist_1.RmxAudioStatusMessage.RMXSTATUS_PLAYLIST_COMPLETED)) {
|
|
35
|
-
this.autoNext();
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
25
|
static setToastController(controller) {
|
|
40
26
|
this.toastCtrl = controller;
|
|
41
27
|
}
|
|
@@ -61,20 +47,14 @@ class Player {
|
|
|
61
47
|
};
|
|
62
48
|
this.audio.onplaying = () => {
|
|
63
49
|
this.isPlaying = true;
|
|
64
|
-
|
|
65
|
-
navigator.mediaSession.playbackState = 'playing';
|
|
66
|
-
}
|
|
50
|
+
capacitor_media_session_1.MediaSession.setPlaybackState({ playbackState: 'playing' });
|
|
67
51
|
};
|
|
68
52
|
this.audio.onpause = () => {
|
|
69
53
|
this.isPlaying = false;
|
|
70
|
-
|
|
71
|
-
navigator.mediaSession.playbackState = 'paused';
|
|
72
|
-
}
|
|
54
|
+
capacitor_media_session_1.MediaSession.setPlaybackState({ playbackState: 'paused' });
|
|
73
55
|
};
|
|
74
56
|
this.audio.onwaiting = () => {
|
|
75
|
-
|
|
76
|
-
navigator.mediaSession.playbackState = 'none';
|
|
77
|
-
}
|
|
57
|
+
capacitor_media_session_1.MediaSession.setPlaybackState({ playbackState: 'none' });
|
|
78
58
|
};
|
|
79
59
|
this.audio.onerror = (e) => {
|
|
80
60
|
console.error('Audio error:', this.audio.error, e);
|
|
@@ -82,24 +62,17 @@ class Player {
|
|
|
82
62
|
}
|
|
83
63
|
static startWatchdog() {
|
|
84
64
|
setInterval(() => {
|
|
85
|
-
if (this.intendedPlaying &&
|
|
65
|
+
if (this.intendedPlaying && this.audio.paused && this.currentSong) {
|
|
86
66
|
console.log('Watchdog: Audio paused unexpectedly. Attempting to resume...');
|
|
87
|
-
|
|
88
|
-
capacitor_plugin_playlist_1.Playlist.play().catch(e => console.warn('Watchdog resume failed:', e));
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
this.audio.play().catch(e => console.warn('Watchdog resume failed:', e));
|
|
92
|
-
}
|
|
67
|
+
this.audio.play().catch(e => console.warn('Watchdog resume failed:', e));
|
|
93
68
|
}
|
|
94
69
|
}, 10000);
|
|
95
70
|
}
|
|
96
71
|
/** Call this once on user gesture to unlock audio in WebView */
|
|
97
72
|
static unlockAudio() {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.audio.play().catch(() => { });
|
|
102
|
-
}
|
|
73
|
+
this.audio.src = '';
|
|
74
|
+
this.audio.load();
|
|
75
|
+
this.audio.play().catch(() => { });
|
|
103
76
|
}
|
|
104
77
|
static play(song, index = 0) {
|
|
105
78
|
var _a;
|
|
@@ -113,95 +86,31 @@ class Player {
|
|
|
113
86
|
if (url.startsWith('http://')) {
|
|
114
87
|
url = url.replace('http://', 'https://');
|
|
115
88
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if ('mediaSession' in navigator) {
|
|
127
|
-
navigator.mediaSession.playbackState = 'playing';
|
|
128
|
-
}
|
|
129
|
-
}).catch((err) => {
|
|
130
|
-
this.isPlaying = false;
|
|
131
|
-
console.warn('Audio play failed:', err);
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
static playAndroid(song, url) {
|
|
136
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
137
|
-
var _a, _b;
|
|
138
|
-
try {
|
|
139
|
-
// Extract artwork
|
|
140
|
-
let artworkUrl = '';
|
|
141
|
-
if (song.image && Array.isArray(song.image)) {
|
|
142
|
-
const highQualityImage = song.image[song.image.length - 1];
|
|
143
|
-
if (highQualityImage && highQualityImage.url) {
|
|
144
|
-
artworkUrl = highQualityImage.url;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
// Extract artist
|
|
148
|
-
let artistName = 'Unknown Artist';
|
|
149
|
-
if (((_a = song.artists) === null || _a === void 0 ? void 0 : _a.primary) && Array.isArray(song.artists.primary) && song.artists.primary.length > 0) {
|
|
150
|
-
artistName = song.artists.primary.map((artist) => artist.name).join(', ');
|
|
151
|
-
}
|
|
152
|
-
else if (song.primaryArtists) {
|
|
153
|
-
artistName = song.primaryArtists;
|
|
154
|
-
}
|
|
155
|
-
else if (song.artist) {
|
|
156
|
-
artistName = song.artist;
|
|
157
|
-
}
|
|
158
|
-
const track = {
|
|
159
|
-
trackId: song.id,
|
|
160
|
-
assetUrl: url,
|
|
161
|
-
albumArt: artworkUrl,
|
|
162
|
-
artist: artistName,
|
|
163
|
-
album: ((_b = song.album) === null || _b === void 0 ? void 0 : _b.name) || 'Unknown Album',
|
|
164
|
-
title: song.name || song.title || 'Unknown Title'
|
|
165
|
-
};
|
|
166
|
-
yield capacitor_plugin_playlist_1.Playlist.clearAllItems();
|
|
167
|
-
yield capacitor_plugin_playlist_1.Playlist.addItem({ item: track });
|
|
168
|
-
yield capacitor_plugin_playlist_1.Playlist.play();
|
|
169
|
-
this.isPlaying = true;
|
|
170
|
-
keep_awake_1.KeepAwake.keepAwake();
|
|
171
|
-
}
|
|
172
|
-
catch (e) {
|
|
173
|
-
console.error('Android play failed:', e);
|
|
174
|
-
this.isPlaying = false;
|
|
175
|
-
}
|
|
89
|
+
this.audio.src = url;
|
|
90
|
+
this.audio.load();
|
|
91
|
+
this.audio.play().then(() => {
|
|
92
|
+
this.isPlaying = true;
|
|
93
|
+
this.updateMediaSessionMetadata(song);
|
|
94
|
+
keep_awake_1.KeepAwake.keepAwake(); // Keep screen/CPU awake
|
|
95
|
+
capacitor_media_session_1.MediaSession.setPlaybackState({ playbackState: 'playing' });
|
|
96
|
+
}).catch((err) => {
|
|
97
|
+
this.isPlaying = false;
|
|
98
|
+
console.warn('Audio play failed:', err);
|
|
176
99
|
});
|
|
177
100
|
}
|
|
178
101
|
static pause() {
|
|
179
102
|
this.intendedPlaying = false;
|
|
103
|
+
this.audio.pause();
|
|
180
104
|
this.isPlaying = false;
|
|
181
|
-
if (this.isAndroid) {
|
|
182
|
-
capacitor_plugin_playlist_1.Playlist.pause();
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
this.audio.pause();
|
|
186
|
-
}
|
|
187
105
|
keep_awake_1.KeepAwake.allowSleep();
|
|
188
|
-
|
|
189
|
-
navigator.mediaSession.playbackState = 'paused';
|
|
190
|
-
}
|
|
106
|
+
capacitor_media_session_1.MediaSession.setPlaybackState({ playbackState: 'paused' });
|
|
191
107
|
}
|
|
192
108
|
static resume() {
|
|
193
109
|
this.intendedPlaying = true;
|
|
110
|
+
this.audio.play();
|
|
194
111
|
this.isPlaying = true;
|
|
195
|
-
if (this.isAndroid) {
|
|
196
|
-
capacitor_plugin_playlist_1.Playlist.play();
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
this.audio.play();
|
|
200
|
-
}
|
|
201
112
|
keep_awake_1.KeepAwake.keepAwake();
|
|
202
|
-
|
|
203
|
-
navigator.mediaSession.playbackState = 'playing';
|
|
204
|
-
}
|
|
113
|
+
capacitor_media_session_1.MediaSession.setPlaybackState({ playbackState: 'playing' });
|
|
205
114
|
}
|
|
206
115
|
static togglePlayPause() {
|
|
207
116
|
if (this.isPlaying) {
|
|
@@ -234,13 +143,8 @@ class Player {
|
|
|
234
143
|
}
|
|
235
144
|
}
|
|
236
145
|
static seek(seconds) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
this.audio.currentTime = seconds;
|
|
242
|
-
this.updatePositionState();
|
|
243
|
-
}
|
|
146
|
+
this.audio.currentTime = seconds;
|
|
147
|
+
this.updatePositionState();
|
|
244
148
|
}
|
|
245
149
|
static autoNext() {
|
|
246
150
|
this.next();
|
|
@@ -318,21 +222,22 @@ class Player {
|
|
|
318
222
|
// Native Media Session (Lock Screen Controls)
|
|
319
223
|
// -------------------------------------------------------------------------
|
|
320
224
|
static setupMediaSession() {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
225
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
226
|
+
yield capacitor_media_session_1.MediaSession.setActionHandler({ action: 'play' }, () => this.resume());
|
|
227
|
+
yield capacitor_media_session_1.MediaSession.setActionHandler({ action: 'pause' }, () => this.pause());
|
|
228
|
+
yield capacitor_media_session_1.MediaSession.setActionHandler({ action: 'previoustrack' }, () => this.prev());
|
|
229
|
+
yield capacitor_media_session_1.MediaSession.setActionHandler({ action: 'nexttrack' }, () => this.next());
|
|
230
|
+
yield capacitor_media_session_1.MediaSession.setActionHandler({ action: 'seekto' }, (details) => {
|
|
231
|
+
const time = details.seekTime;
|
|
232
|
+
if (typeof time === 'number') {
|
|
233
|
+
this.seek(time);
|
|
329
234
|
}
|
|
330
235
|
});
|
|
331
|
-
}
|
|
236
|
+
});
|
|
332
237
|
}
|
|
333
238
|
static updateMediaSessionMetadata(song) {
|
|
334
|
-
|
|
335
|
-
|
|
239
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
240
|
+
var _a, _b;
|
|
336
241
|
// Extract artwork from image array
|
|
337
242
|
const artwork = [];
|
|
338
243
|
if (song.image && Array.isArray(song.image)) {
|
|
@@ -364,22 +269,24 @@ class Player {
|
|
|
364
269
|
else if (song.artist) {
|
|
365
270
|
artistName = song.artist;
|
|
366
271
|
}
|
|
367
|
-
|
|
272
|
+
yield capacitor_media_session_1.MediaSession.setMetadata({
|
|
368
273
|
title: song.name || song.title || 'Unknown Title',
|
|
369
274
|
artist: artistName,
|
|
370
275
|
album: ((_b = song.album) === null || _b === void 0 ? void 0 : _b.name) || 'Unknown Album',
|
|
371
276
|
artwork: artwork.length > 0 ? artwork : undefined
|
|
372
277
|
});
|
|
373
|
-
}
|
|
278
|
+
});
|
|
374
279
|
}
|
|
375
280
|
static updatePositionState() {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
281
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
282
|
+
if (this.duration > 0) {
|
|
283
|
+
yield capacitor_media_session_1.MediaSession.setPositionState({
|
|
284
|
+
duration: this.duration,
|
|
285
|
+
playbackRate: this.audio.playbackRate,
|
|
286
|
+
position: this.audio.currentTime
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
});
|
|
383
290
|
}
|
|
384
291
|
}
|
|
385
292
|
exports.Player = Player;
|
|
@@ -395,4 +302,3 @@ Player.queue$ = new rxjs_1.BehaviorSubject([]);
|
|
|
395
302
|
Player.playlist = [];
|
|
396
303
|
Player.selectedQuality = 3;
|
|
397
304
|
Player.intendedPlaying = false;
|
|
398
|
-
Player.isAndroid = core_1.Capacitor.getPlatform() === 'android';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tunzo-player",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.42",
|
|
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",
|
|
@@ -30,9 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@capacitor-community/keep-awake": "^7.1.0",
|
|
33
|
-
"@capacitor
|
|
33
|
+
"@capgo/capacitor-media-session": "^7.0.1",
|
|
34
34
|
"@ionic/angular": "^8.7.11",
|
|
35
|
-
"capacitor-plugin-playlist": "^0.7.4",
|
|
36
35
|
"rxjs": "^7.8.2"
|
|
37
36
|
}
|
|
38
37
|
}
|
package/src/core/api-calles.ts
CHANGED
|
@@ -124,6 +124,24 @@ export class TunzoPlayerAPI {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
async searchArtist(query: string, limit: number = 1000): Promise<any[]> {
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetch(
|
|
130
|
+
`${this.baseUrl}/search/artists?query=${encodeURIComponent(query)}&limit=${limit}'`
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const json = await response.json();
|
|
138
|
+
return json?.data?.results || [];
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error('error', err);
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
127
145
|
/**
|
|
128
146
|
* Get album details
|
|
129
147
|
* @param id Album ID
|
|
@@ -136,6 +154,25 @@ export class TunzoPlayerAPI {
|
|
|
136
154
|
url += `&link=${encodeURIComponent(link)}`;
|
|
137
155
|
}
|
|
138
156
|
|
|
157
|
+
const response = await fetch(url);
|
|
158
|
+
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const json = await response.json();
|
|
164
|
+
return json?.data || null;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error("TunzoPlayerAPI Error (getAlbumDetails):", error);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async getartistDetails(id: string): Promise<any> {
|
|
172
|
+
try {
|
|
173
|
+
let url = `${this.baseUrl}/artists/${id}/songs?page=0&sortBy=popularity&sortOrder=desc`;
|
|
174
|
+
|
|
175
|
+
|
|
139
176
|
const response = await fetch(url);
|
|
140
177
|
|
|
141
178
|
if (!response.ok) {
|
package/src/core/player.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { BehaviorSubject } from 'rxjs';
|
|
2
2
|
import { KeepAwake } from '@capacitor-community/keep-awake';
|
|
3
3
|
import { ToastController } from '@ionic/angular/standalone';
|
|
4
|
-
import {
|
|
5
|
-
import { Playlist, AudioTrack, RmxAudioStatusMessage } from 'capacitor-plugin-playlist';
|
|
4
|
+
import { MediaSession } from '@capgo/capacitor-media-session';
|
|
6
5
|
|
|
7
6
|
export class Player {
|
|
8
7
|
private static audio = new Audio();
|
|
@@ -18,32 +17,16 @@ export class Player {
|
|
|
18
17
|
private static selectedQuality = 3;
|
|
19
18
|
private static intendedPlaying = false;
|
|
20
19
|
private static toastCtrl: ToastController;
|
|
21
|
-
private static isAndroid = Capacitor.getPlatform() === 'android';
|
|
22
20
|
|
|
23
21
|
/** Initialize with playlist and quality */
|
|
24
22
|
static initialize(playlist: any[], quality = 3) {
|
|
25
23
|
this.playlist = playlist;
|
|
26
24
|
this.selectedQuality = quality;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} else {
|
|
30
|
-
this.setupMediaSession();
|
|
31
|
-
this.setupAudioElement();
|
|
32
|
-
}
|
|
25
|
+
this.setupMediaSession();
|
|
26
|
+
this.setupAudioElement();
|
|
33
27
|
this.startWatchdog();
|
|
34
28
|
}
|
|
35
29
|
|
|
36
|
-
private static setupAndroidPlayer() {
|
|
37
|
-
Playlist.addListener('status', (data) => {
|
|
38
|
-
if (data && data.status && (
|
|
39
|
-
data.status.msgType === RmxAudioStatusMessage.RMXSTATUS_COMPLETED ||
|
|
40
|
-
data.status.msgType === RmxAudioStatusMessage.RMXSTATUS_PLAYLIST_COMPLETED
|
|
41
|
-
)) {
|
|
42
|
-
this.autoNext();
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
30
|
static setToastController(controller: ToastController) {
|
|
48
31
|
this.toastCtrl = controller;
|
|
49
32
|
}
|
|
@@ -74,22 +57,16 @@ export class Player {
|
|
|
74
57
|
|
|
75
58
|
this.audio.onplaying = () => {
|
|
76
59
|
this.isPlaying = true;
|
|
77
|
-
|
|
78
|
-
navigator.mediaSession.playbackState = 'playing';
|
|
79
|
-
}
|
|
60
|
+
MediaSession.setPlaybackState({ playbackState: 'playing' });
|
|
80
61
|
};
|
|
81
62
|
|
|
82
63
|
this.audio.onpause = () => {
|
|
83
64
|
this.isPlaying = false;
|
|
84
|
-
|
|
85
|
-
navigator.mediaSession.playbackState = 'paused';
|
|
86
|
-
}
|
|
65
|
+
MediaSession.setPlaybackState({ playbackState: 'paused' });
|
|
87
66
|
};
|
|
88
67
|
|
|
89
68
|
this.audio.onwaiting = () => {
|
|
90
|
-
|
|
91
|
-
navigator.mediaSession.playbackState = 'none';
|
|
92
|
-
}
|
|
69
|
+
MediaSession.setPlaybackState({ playbackState: 'none' });
|
|
93
70
|
};
|
|
94
71
|
|
|
95
72
|
this.audio.onerror = (e) => {
|
|
@@ -99,24 +76,18 @@ export class Player {
|
|
|
99
76
|
|
|
100
77
|
private static startWatchdog() {
|
|
101
78
|
setInterval(() => {
|
|
102
|
-
if (this.intendedPlaying &&
|
|
79
|
+
if (this.intendedPlaying && this.audio.paused && this.currentSong) {
|
|
103
80
|
console.log('Watchdog: Audio paused unexpectedly. Attempting to resume...');
|
|
104
|
-
|
|
105
|
-
Playlist.play().catch(e => console.warn('Watchdog resume failed:', e));
|
|
106
|
-
} else {
|
|
107
|
-
this.audio.play().catch(e => console.warn('Watchdog resume failed:', e));
|
|
108
|
-
}
|
|
81
|
+
this.audio.play().catch(e => console.warn('Watchdog resume failed:', e));
|
|
109
82
|
}
|
|
110
83
|
}, 10000);
|
|
111
84
|
}
|
|
112
85
|
|
|
113
86
|
/** Call this once on user gesture to unlock audio in WebView */
|
|
114
87
|
static unlockAudio() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.audio.play().catch(() => { });
|
|
119
|
-
}
|
|
88
|
+
this.audio.src = '';
|
|
89
|
+
this.audio.load();
|
|
90
|
+
this.audio.play().catch(() => { });
|
|
120
91
|
}
|
|
121
92
|
|
|
122
93
|
static play(song: any, index: number = 0) {
|
|
@@ -134,97 +105,34 @@ export class Player {
|
|
|
134
105
|
url = url.replace('http://', 'https://');
|
|
135
106
|
}
|
|
136
107
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
} else {
|
|
140
|
-
this.audio.src = url;
|
|
141
|
-
this.audio.load();
|
|
142
|
-
|
|
143
|
-
this.audio.play().then(() => {
|
|
144
|
-
this.isPlaying = true;
|
|
145
|
-
this.updateMediaSessionMetadata(song);
|
|
146
|
-
KeepAwake.keepAwake(); // Keep screen/CPU awake
|
|
147
|
-
if ('mediaSession' in navigator) {
|
|
148
|
-
navigator.mediaSession.playbackState = 'playing';
|
|
149
|
-
}
|
|
150
|
-
}).catch((err) => {
|
|
151
|
-
this.isPlaying = false;
|
|
152
|
-
console.warn('Audio play failed:', err);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private static async playAndroid(song: any, url: string) {
|
|
158
|
-
try {
|
|
159
|
-
// Extract artwork
|
|
160
|
-
let artworkUrl = '';
|
|
161
|
-
if (song.image && Array.isArray(song.image)) {
|
|
162
|
-
const highQualityImage = song.image[song.image.length - 1];
|
|
163
|
-
if (highQualityImage && highQualityImage.url) {
|
|
164
|
-
artworkUrl = highQualityImage.url;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Extract artist
|
|
169
|
-
let artistName = 'Unknown Artist';
|
|
170
|
-
if (song.artists?.primary && Array.isArray(song.artists.primary) && song.artists.primary.length > 0) {
|
|
171
|
-
artistName = song.artists.primary.map((artist: any) => artist.name).join(', ');
|
|
172
|
-
} else if (song.primaryArtists) {
|
|
173
|
-
artistName = song.primaryArtists;
|
|
174
|
-
} else if (song.artist) {
|
|
175
|
-
artistName = song.artist;
|
|
176
|
-
}
|
|
108
|
+
this.audio.src = url;
|
|
109
|
+
this.audio.load();
|
|
177
110
|
|
|
178
|
-
|
|
179
|
-
trackId: song.id,
|
|
180
|
-
assetUrl: url,
|
|
181
|
-
albumArt: artworkUrl,
|
|
182
|
-
artist: artistName,
|
|
183
|
-
album: song.album?.name || 'Unknown Album',
|
|
184
|
-
title: song.name || song.title || 'Unknown Title'
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
await Playlist.clearAllItems();
|
|
188
|
-
await Playlist.addItem({ item: track });
|
|
189
|
-
await Playlist.play();
|
|
111
|
+
this.audio.play().then(() => {
|
|
190
112
|
this.isPlaying = true;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
113
|
+
this.updateMediaSessionMetadata(song);
|
|
114
|
+
KeepAwake.keepAwake(); // Keep screen/CPU awake
|
|
115
|
+
MediaSession.setPlaybackState({ playbackState: 'playing' });
|
|
116
|
+
}).catch((err) => {
|
|
194
117
|
this.isPlaying = false;
|
|
195
|
-
|
|
118
|
+
console.warn('Audio play failed:', err);
|
|
119
|
+
});
|
|
196
120
|
}
|
|
197
121
|
|
|
198
122
|
static pause() {
|
|
199
123
|
this.intendedPlaying = false;
|
|
124
|
+
this.audio.pause();
|
|
200
125
|
this.isPlaying = false;
|
|
201
|
-
|
|
202
|
-
if (this.isAndroid) {
|
|
203
|
-
Playlist.pause();
|
|
204
|
-
} else {
|
|
205
|
-
this.audio.pause();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
126
|
KeepAwake.allowSleep();
|
|
209
|
-
|
|
210
|
-
navigator.mediaSession.playbackState = 'paused';
|
|
211
|
-
}
|
|
127
|
+
MediaSession.setPlaybackState({ playbackState: 'paused' });
|
|
212
128
|
}
|
|
213
129
|
|
|
214
130
|
static resume() {
|
|
215
131
|
this.intendedPlaying = true;
|
|
132
|
+
this.audio.play();
|
|
216
133
|
this.isPlaying = true;
|
|
217
|
-
|
|
218
|
-
if (this.isAndroid) {
|
|
219
|
-
Playlist.play();
|
|
220
|
-
} else {
|
|
221
|
-
this.audio.play();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
134
|
KeepAwake.keepAwake();
|
|
225
|
-
|
|
226
|
-
navigator.mediaSession.playbackState = 'playing';
|
|
227
|
-
}
|
|
135
|
+
MediaSession.setPlaybackState({ playbackState: 'playing' });
|
|
228
136
|
}
|
|
229
137
|
|
|
230
138
|
static togglePlayPause() {
|
|
@@ -257,12 +165,8 @@ export class Player {
|
|
|
257
165
|
}
|
|
258
166
|
|
|
259
167
|
static seek(seconds: number) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
} else {
|
|
263
|
-
this.audio.currentTime = seconds;
|
|
264
|
-
this.updatePositionState();
|
|
265
|
-
}
|
|
168
|
+
this.audio.currentTime = seconds;
|
|
169
|
+
this.updatePositionState();
|
|
266
170
|
}
|
|
267
171
|
|
|
268
172
|
static autoNext() {
|
|
@@ -354,65 +258,62 @@ export class Player {
|
|
|
354
258
|
// Native Media Session (Lock Screen Controls)
|
|
355
259
|
// -------------------------------------------------------------------------
|
|
356
260
|
|
|
357
|
-
private static setupMediaSession() {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
261
|
+
private static async setupMediaSession() {
|
|
262
|
+
await MediaSession.setActionHandler({ action: 'play' }, () => this.resume());
|
|
263
|
+
await MediaSession.setActionHandler({ action: 'pause' }, () => this.pause());
|
|
264
|
+
await MediaSession.setActionHandler({ action: 'previoustrack' }, () => this.prev());
|
|
265
|
+
await MediaSession.setActionHandler({ action: 'nexttrack' }, () => this.next());
|
|
266
|
+
await MediaSession.setActionHandler({ action: 'seekto' }, (details) => {
|
|
267
|
+
const time = details.seekTime;
|
|
268
|
+
if (typeof time === 'number') {
|
|
269
|
+
this.seek(time);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
369
272
|
}
|
|
370
273
|
|
|
371
|
-
private static updateMediaSessionMetadata(song: any) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
});
|
|
390
|
-
}
|
|
274
|
+
private static async updateMediaSessionMetadata(song: any) {
|
|
275
|
+
// Extract artwork from image array
|
|
276
|
+
const artwork = [];
|
|
277
|
+
if (song.image && Array.isArray(song.image)) {
|
|
278
|
+
// Get the highest quality image (last in array, usually 500x500)
|
|
279
|
+
const highQualityImage = song.image[song.image.length - 1];
|
|
280
|
+
if (highQualityImage && highQualityImage.url) {
|
|
281
|
+
let src = highQualityImage.url;
|
|
282
|
+
if (src.startsWith('http://')) {
|
|
283
|
+
src = src.replace('http://', 'https://');
|
|
284
|
+
}
|
|
285
|
+
// Skip placeholder images
|
|
286
|
+
if (!src.includes('_i/share-image')) {
|
|
287
|
+
artwork.push({
|
|
288
|
+
src,
|
|
289
|
+
sizes: highQualityImage.quality || '500x500',
|
|
290
|
+
type: 'image/jpeg'
|
|
291
|
+
});
|
|
391
292
|
}
|
|
392
293
|
}
|
|
294
|
+
}
|
|
393
295
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
navigator.mediaSession.metadata = new MediaMetadata({
|
|
405
|
-
title: song.name || song.title || 'Unknown Title',
|
|
406
|
-
artist: artistName,
|
|
407
|
-
album: song.album?.name || 'Unknown Album',
|
|
408
|
-
artwork: artwork.length > 0 ? artwork : undefined
|
|
409
|
-
});
|
|
296
|
+
// Extract artist name from artists.primary array
|
|
297
|
+
let artistName = 'Unknown Artist';
|
|
298
|
+
if (song.artists?.primary && Array.isArray(song.artists.primary) && song.artists.primary.length > 0) {
|
|
299
|
+
artistName = song.artists.primary.map((artist: any) => artist.name).join(', ');
|
|
300
|
+
} else if (song.primaryArtists) {
|
|
301
|
+
artistName = song.primaryArtists;
|
|
302
|
+
} else if (song.artist) {
|
|
303
|
+
artistName = song.artist;
|
|
410
304
|
}
|
|
305
|
+
|
|
306
|
+
await MediaSession.setMetadata({
|
|
307
|
+
title: song.name || song.title || 'Unknown Title',
|
|
308
|
+
artist: artistName,
|
|
309
|
+
album: song.album?.name || 'Unknown Album',
|
|
310
|
+
artwork: artwork.length > 0 ? artwork : undefined
|
|
311
|
+
});
|
|
411
312
|
}
|
|
412
313
|
|
|
413
|
-
private static updatePositionState() {
|
|
414
|
-
if (
|
|
415
|
-
|
|
314
|
+
private static async updatePositionState() {
|
|
315
|
+
if (this.duration > 0) {
|
|
316
|
+
await MediaSession.setPositionState({
|
|
416
317
|
duration: this.duration,
|
|
417
318
|
playbackRate: this.audio.playbackRate,
|
|
418
319
|
position: this.audio.currentTime
|