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.
@@ -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
  }
@@ -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;
@@ -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;
@@ -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 core_1 = require("@capacitor/core");
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
- if (this.isAndroid) {
23
- this.setupAndroidPlayer();
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
- if ('mediaSession' in navigator) {
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
- if ('mediaSession' in navigator) {
71
- navigator.mediaSession.playbackState = 'paused';
72
- }
54
+ capacitor_media_session_1.MediaSession.setPlaybackState({ playbackState: 'paused' });
73
55
  };
74
56
  this.audio.onwaiting = () => {
75
- if ('mediaSession' in navigator) {
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 && (this.isAndroid ? false : this.audio.paused) && this.currentSong) {
65
+ if (this.intendedPlaying && this.audio.paused && this.currentSong) {
86
66
  console.log('Watchdog: Audio paused unexpectedly. Attempting to resume...');
87
- if (this.isAndroid) {
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
- if (!this.isAndroid) {
99
- this.audio.src = '';
100
- this.audio.load();
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
- if (this.isAndroid) {
117
- this.playAndroid(song, url);
118
- }
119
- else {
120
- this.audio.src = url;
121
- this.audio.load();
122
- this.audio.play().then(() => {
123
- this.isPlaying = true;
124
- this.updateMediaSessionMetadata(song);
125
- keep_awake_1.KeepAwake.keepAwake(); // Keep screen/CPU awake
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
- if (!this.isAndroid && 'mediaSession' in navigator) {
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
- if (!this.isAndroid && 'mediaSession' in navigator) {
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
- if (this.isAndroid) {
238
- capacitor_plugin_playlist_1.Playlist.seekTo({ position: seconds });
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
- if ('mediaSession' in navigator) {
322
- navigator.mediaSession.setActionHandler('play', () => this.resume());
323
- navigator.mediaSession.setActionHandler('pause', () => this.pause());
324
- navigator.mediaSession.setActionHandler('previoustrack', () => this.prev());
325
- navigator.mediaSession.setActionHandler('nexttrack', () => this.next());
326
- navigator.mediaSession.setActionHandler('seekto', (details) => {
327
- if (details.seekTime !== undefined) {
328
- this.seek(details.seekTime);
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
- var _a, _b;
335
- if ('mediaSession' in navigator) {
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
- navigator.mediaSession.metadata = new MediaMetadata({
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
- if ('mediaSession' in navigator && this.duration > 0) {
377
- navigator.mediaSession.setPositionState({
378
- duration: this.duration,
379
- playbackRate: this.audio.playbackRate,
380
- position: this.audio.currentTime
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.40",
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/core": "^8.0.0",
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
  }
@@ -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) {
@@ -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 { Capacitor } from '@capacitor/core';
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
- if (this.isAndroid) {
28
- this.setupAndroidPlayer();
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
- if ('mediaSession' in navigator) {
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
- if ('mediaSession' in navigator) {
85
- navigator.mediaSession.playbackState = 'paused';
86
- }
65
+ MediaSession.setPlaybackState({ playbackState: 'paused' });
87
66
  };
88
67
 
89
68
  this.audio.onwaiting = () => {
90
- if ('mediaSession' in navigator) {
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 && (this.isAndroid ? false : this.audio.paused) && this.currentSong) {
79
+ if (this.intendedPlaying && this.audio.paused && this.currentSong) {
103
80
  console.log('Watchdog: Audio paused unexpectedly. Attempting to resume...');
104
- if (this.isAndroid) {
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
- if (!this.isAndroid) {
116
- this.audio.src = '';
117
- this.audio.load();
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
- if (this.isAndroid) {
138
- this.playAndroid(song, url);
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
- const track: AudioTrack = {
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
- KeepAwake.keepAwake();
192
- } catch (e) {
193
- console.error('Android play failed:', e);
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
- if (!this.isAndroid && 'mediaSession' in navigator) {
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
- if (!this.isAndroid && 'mediaSession' in navigator) {
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
- if (this.isAndroid) {
261
- Playlist.seekTo({ position: seconds });
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
- if ('mediaSession' in navigator) {
359
- navigator.mediaSession.setActionHandler('play', () => this.resume());
360
- navigator.mediaSession.setActionHandler('pause', () => this.pause());
361
- navigator.mediaSession.setActionHandler('previoustrack', () => this.prev());
362
- navigator.mediaSession.setActionHandler('nexttrack', () => this.next());
363
- navigator.mediaSession.setActionHandler('seekto', (details) => {
364
- if (details.seekTime !== undefined) {
365
- this.seek(details.seekTime);
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
- if ('mediaSession' in navigator) {
373
- // Extract artwork from image array
374
- const artwork = [];
375
- if (song.image && Array.isArray(song.image)) {
376
- // Get the highest quality image (last in array, usually 500x500)
377
- const highQualityImage = song.image[song.image.length - 1];
378
- if (highQualityImage && highQualityImage.url) {
379
- let src = highQualityImage.url;
380
- if (src.startsWith('http://')) {
381
- src = src.replace('http://', 'https://');
382
- }
383
- // Skip placeholder images
384
- if (!src.includes('_i/share-image')) {
385
- artwork.push({
386
- src,
387
- sizes: highQualityImage.quality || '500x500',
388
- type: 'image/jpeg'
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
- // Extract artist name from artists.primary array
395
- let artistName = 'Unknown Artist';
396
- if (song.artists?.primary && Array.isArray(song.artists.primary) && song.artists.primary.length > 0) {
397
- artistName = song.artists.primary.map((artist: any) => artist.name).join(', ');
398
- } else if (song.primaryArtists) {
399
- artistName = song.primaryArtists;
400
- } else if (song.artist) {
401
- artistName = song.artist;
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 ('mediaSession' in navigator && this.duration > 0) {
415
- navigator.mediaSession.setPositionState({
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