tunzo-player 1.0.22 → 1.0.24

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.
@@ -6,4 +6,29 @@ export declare class TunzoPlayerAPI {
6
6
  * @returns Array of song result objects
7
7
  */
8
8
  searchSongs(query: string, limit?: number): Promise<any[]>;
9
+ /**
10
+ * Search for playlists
11
+ * @param query Search keyword
12
+ * @param limit Number of results (default: 1000)
13
+ */
14
+ searchPlaylists(query: string, limit?: number): Promise<any[]>;
15
+ /**
16
+ * Get playlist details
17
+ * @param id Playlist ID
18
+ * @param link Playlist URL/Link (optional but recommended if available)
19
+ * @param limit Number of songs to return (default: 1000)
20
+ */
21
+ getPlaylistDetails(id: string, link?: string, limit?: number): Promise<any>;
22
+ /**
23
+ * Search for albums
24
+ * @param query Search keyword
25
+ * @param limit Number of results (default: 1000)
26
+ */
27
+ searchAlbums(query: string, limit?: number): Promise<any[]>;
28
+ /**
29
+ * Get album details
30
+ * @param id Album ID
31
+ * @param link Album URL/Link (optional but recommended if available)
32
+ */
33
+ getAlbumDetails(id: string, link?: string): Promise<any>;
9
34
  }
@@ -34,5 +34,100 @@ class TunzoPlayerAPI {
34
34
  }
35
35
  });
36
36
  }
37
+ /**
38
+ * Search for playlists
39
+ * @param query Search keyword
40
+ * @param limit Number of results (default: 1000)
41
+ */
42
+ searchPlaylists(query_1) {
43
+ return __awaiter(this, arguments, void 0, function* (query, limit = 1000) {
44
+ var _a;
45
+ try {
46
+ const response = yield fetch(`https://saavn.sumit.co/api/search/playlists?query=${encodeURIComponent(query)}&limit=${limit}`);
47
+ if (!response.ok) {
48
+ throw new Error(`HTTP error! status: ${response.status}`);
49
+ }
50
+ const json = yield response.json();
51
+ return ((_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.results) || [];
52
+ }
53
+ catch (error) {
54
+ console.error("TunzoPlayerAPI Error (searchPlaylists):", error);
55
+ return [];
56
+ }
57
+ });
58
+ }
59
+ /**
60
+ * Get playlist details
61
+ * @param id Playlist ID
62
+ * @param link Playlist URL/Link (optional but recommended if available)
63
+ * @param limit Number of songs to return (default: 1000)
64
+ */
65
+ getPlaylistDetails(id_1) {
66
+ return __awaiter(this, arguments, void 0, function* (id, link = "", limit = 1000) {
67
+ try {
68
+ let url = `https://saavn.sumit.co/api/playlists?id=${id}&limit=${limit}`;
69
+ if (link) {
70
+ url += `&link=${encodeURIComponent(link)}`;
71
+ }
72
+ const response = yield fetch(url);
73
+ if (!response.ok) {
74
+ throw new Error(`HTTP error! status: ${response.status}`);
75
+ }
76
+ const json = yield response.json();
77
+ return (json === null || json === void 0 ? void 0 : json.data) || null;
78
+ }
79
+ catch (error) {
80
+ console.error("TunzoPlayerAPI Error (getPlaylistDetails):", error);
81
+ return null;
82
+ }
83
+ });
84
+ }
85
+ /**
86
+ * Search for albums
87
+ * @param query Search keyword
88
+ * @param limit Number of results (default: 1000)
89
+ */
90
+ searchAlbums(query_1) {
91
+ return __awaiter(this, arguments, void 0, function* (query, limit = 1000) {
92
+ var _a;
93
+ try {
94
+ const response = yield fetch(`https://saavn.sumit.co/api/search/albums?query=${encodeURIComponent(query)}&limit=${limit}`);
95
+ if (!response.ok) {
96
+ throw new Error(`HTTP error! status: ${response.status}`);
97
+ }
98
+ const json = yield response.json();
99
+ return ((_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.results) || [];
100
+ }
101
+ catch (error) {
102
+ console.error("TunzoPlayerAPI Error (searchAlbums):", error);
103
+ return [];
104
+ }
105
+ });
106
+ }
107
+ /**
108
+ * Get album details
109
+ * @param id Album ID
110
+ * @param link Album URL/Link (optional but recommended if available)
111
+ */
112
+ getAlbumDetails(id_1) {
113
+ return __awaiter(this, arguments, void 0, function* (id, link = "") {
114
+ try {
115
+ let url = `https://saavn.sumit.co/api/albums?id=${id}`;
116
+ if (link) {
117
+ url += `&link=${encodeURIComponent(link)}`;
118
+ }
119
+ const response = yield fetch(url);
120
+ if (!response.ok) {
121
+ throw new Error(`HTTP error! status: ${response.status}`);
122
+ }
123
+ const json = yield response.json();
124
+ return (json === null || json === void 0 ? void 0 : json.data) || null;
125
+ }
126
+ catch (error) {
127
+ console.error("TunzoPlayerAPI Error (getAlbumDetails):", error);
128
+ return null;
129
+ }
130
+ });
131
+ }
37
132
  }
38
133
  exports.TunzoPlayerAPI = TunzoPlayerAPI;
@@ -37,4 +37,7 @@ export declare class Player {
37
37
  static setQuality(index: number): void;
38
38
  static getQueue(): any[];
39
39
  static getPlaylist(): any[];
40
+ private static setupMediaSession;
41
+ private static updateMediaSessionMetadata;
42
+ private static updatePositionState;
40
43
  }
@@ -7,6 +7,7 @@ class Player {
7
7
  static initialize(playlist, quality = 3) {
8
8
  this.playlist = playlist;
9
9
  this.selectedQuality = quality;
10
+ this.setupMediaSession();
10
11
  }
11
12
  /** Call this once on user gesture to unlock audio in WebView */
12
13
  static unlockAudio() {
@@ -27,9 +28,14 @@ class Player {
27
28
  url = url.replace('http://', 'https://');
28
29
  }
29
30
  this.audio.src = url;
31
+ this.audio.preload = 'auto'; // Improve loading
30
32
  this.audio.load(); // Ensure audio is loaded before play
31
33
  this.audio.play().then(() => {
32
34
  this.isPlaying = true;
35
+ this.updateMediaSessionMetadata(song);
36
+ if ('mediaSession' in navigator) {
37
+ navigator.mediaSession.playbackState = 'playing';
38
+ }
33
39
  }).catch((err) => {
34
40
  this.isPlaying = false;
35
41
  console.warn('Audio play failed:', err);
@@ -37,6 +43,7 @@ class Player {
37
43
  // Set duration
38
44
  this.audio.onloadedmetadata = () => {
39
45
  this.duration = this.audio.duration;
46
+ this.updatePositionState();
40
47
  };
41
48
  // Set current time
42
49
  this.audio.ontimeupdate = () => {
@@ -54,10 +61,16 @@ class Player {
54
61
  static pause() {
55
62
  this.audio.pause();
56
63
  this.isPlaying = false;
64
+ if ('mediaSession' in navigator) {
65
+ navigator.mediaSession.playbackState = 'paused';
66
+ }
57
67
  }
58
68
  static resume() {
59
69
  this.audio.play();
60
70
  this.isPlaying = true;
71
+ if ('mediaSession' in navigator) {
72
+ navigator.mediaSession.playbackState = 'playing';
73
+ }
61
74
  }
62
75
  static togglePlayPause() {
63
76
  if (this.isPlaying) {
@@ -88,6 +101,7 @@ class Player {
88
101
  }
89
102
  static seek(seconds) {
90
103
  this.audio.currentTime = seconds;
104
+ this.updatePositionState();
91
105
  }
92
106
  static autoNext() {
93
107
  this.next();
@@ -148,6 +162,57 @@ class Player {
148
162
  static getPlaylist() {
149
163
  return this.playlist;
150
164
  }
165
+ // -------------------------------------------------------------------------
166
+ // Native Media Session (Lock Screen Controls)
167
+ // -------------------------------------------------------------------------
168
+ static setupMediaSession() {
169
+ if ('mediaSession' in navigator) {
170
+ navigator.mediaSession.setActionHandler('play', () => this.resume());
171
+ navigator.mediaSession.setActionHandler('pause', () => this.pause());
172
+ navigator.mediaSession.setActionHandler('previoustrack', () => this.prev());
173
+ navigator.mediaSession.setActionHandler('nexttrack', () => this.next());
174
+ navigator.mediaSession.setActionHandler('seekto', (details) => {
175
+ if (details.seekTime !== undefined) {
176
+ this.seek(details.seekTime);
177
+ }
178
+ });
179
+ }
180
+ }
181
+ static updateMediaSessionMetadata(song) {
182
+ var _a;
183
+ if ('mediaSession' in navigator) {
184
+ const artwork = [];
185
+ if (song.image) {
186
+ if (Array.isArray(song.image)) {
187
+ // Assuming image array contains objects with url/link and quality
188
+ song.image.forEach((img) => {
189
+ const src = img.link || img.url || (typeof img === 'string' ? img : '');
190
+ if (src) {
191
+ artwork.push({ src, sizes: '500x500', type: 'image/jpeg' });
192
+ }
193
+ });
194
+ }
195
+ else if (typeof song.image === 'string') {
196
+ artwork.push({ src: song.image, sizes: '500x500', type: 'image/jpeg' });
197
+ }
198
+ }
199
+ navigator.mediaSession.metadata = new MediaMetadata({
200
+ title: song.name || song.title || 'Unknown Title',
201
+ artist: song.primaryArtists || song.artist || 'Unknown Artist',
202
+ album: ((_a = song.album) === null || _a === void 0 ? void 0 : _a.name) || song.album || '',
203
+ artwork: artwork.length > 0 ? artwork : undefined
204
+ });
205
+ }
206
+ }
207
+ static updatePositionState() {
208
+ if ('mediaSession' in navigator && this.duration > 0) {
209
+ navigator.mediaSession.setPositionState({
210
+ duration: this.duration,
211
+ playbackRate: this.audio.playbackRate,
212
+ position: this.audio.currentTime
213
+ });
214
+ }
215
+ }
151
216
  }
152
217
  exports.Player = Player;
153
218
  Player.audio = new Audio();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tunzo-player",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
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",
@@ -1,26 +1,124 @@
1
1
  export class TunzoPlayerAPI {
2
- /**
3
- * Search for songs using the saavn.dev API
4
- * @param query Search keyword (e.g., artist name, song name)
5
- * @param limit Number of results to return (default: 250)
6
- * @returns Array of song result objects
7
- */
8
- async searchSongs(query: string, limit: number = 250): Promise<any[]> {
9
- try {
10
- const response = await fetch(
11
- `https://saavn.sumit.co/api/search/songs?query=${encodeURIComponent(query)}&limit=${limit}`
12
- );
13
-
14
- if (!response.ok) {
15
- throw new Error(`HTTP error! status: ${response.status}`);
16
- }
17
-
18
- const json = await response.json();
19
- return json?.data?.results || [];
20
- } catch (error) {
21
- console.error("TunzoPlayerAPI Error:", error);
22
- return [];
2
+ /**
3
+ * Search for songs using the saavn.dev API
4
+ * @param query Search keyword (e.g., artist name, song name)
5
+ * @param limit Number of results to return (default: 250)
6
+ * @returns Array of song result objects
7
+ */
8
+ async searchSongs(query: string, limit: number = 250): Promise<any[]> {
9
+ try {
10
+ const response = await fetch(
11
+ `https://saavn.sumit.co/api/search/songs?query=${encodeURIComponent(query)}&limit=${limit}`
12
+ );
13
+
14
+ if (!response.ok) {
15
+ throw new Error(`HTTP error! status: ${response.status}`);
23
16
  }
17
+
18
+ const json = await response.json();
19
+ return json?.data?.results || [];
20
+ } catch (error) {
21
+ console.error("TunzoPlayerAPI Error:", error);
22
+ return [];
24
23
  }
25
24
  }
26
-
25
+
26
+ /**
27
+ * Search for playlists
28
+ * @param query Search keyword
29
+ * @param limit Number of results (default: 1000)
30
+ */
31
+ async searchPlaylists(query: string, limit: number = 1000): Promise<any[]> {
32
+ try {
33
+ const response = await fetch(
34
+ `https://saavn.sumit.co/api/search/playlists?query=${encodeURIComponent(query)}&limit=${limit}`
35
+ );
36
+
37
+ if (!response.ok) {
38
+ throw new Error(`HTTP error! status: ${response.status}`);
39
+ }
40
+
41
+ const json = await response.json();
42
+ return json?.data?.results || [];
43
+ } catch (error) {
44
+ console.error("TunzoPlayerAPI Error (searchPlaylists):", error);
45
+ return [];
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Get playlist details
51
+ * @param id Playlist ID
52
+ * @param link Playlist URL/Link (optional but recommended if available)
53
+ * @param limit Number of songs to return (default: 1000)
54
+ */
55
+ async getPlaylistDetails(id: string, link: string = "", limit: number = 1000): Promise<any> {
56
+ try {
57
+ let url = `https://saavn.sumit.co/api/playlists?id=${id}&limit=${limit}`;
58
+ if (link) {
59
+ url += `&link=${encodeURIComponent(link)}`;
60
+ }
61
+
62
+ const response = await fetch(url);
63
+
64
+ if (!response.ok) {
65
+ throw new Error(`HTTP error! status: ${response.status}`);
66
+ }
67
+
68
+ const json = await response.json();
69
+ return json?.data || null;
70
+ } catch (error) {
71
+ console.error("TunzoPlayerAPI Error (getPlaylistDetails):", error);
72
+ return null;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Search for albums
78
+ * @param query Search keyword
79
+ * @param limit Number of results (default: 1000)
80
+ */
81
+ async searchAlbums(query: string, limit: number = 1000): Promise<any[]> {
82
+ try {
83
+ const response = await fetch(
84
+ `https://saavn.sumit.co/api/search/albums?query=${encodeURIComponent(query)}&limit=${limit}`
85
+ );
86
+
87
+ if (!response.ok) {
88
+ throw new Error(`HTTP error! status: ${response.status}`);
89
+ }
90
+
91
+ const json = await response.json();
92
+ return json?.data?.results || [];
93
+ } catch (error) {
94
+ console.error("TunzoPlayerAPI Error (searchAlbums):", error);
95
+ return [];
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get album details
101
+ * @param id Album ID
102
+ * @param link Album URL/Link (optional but recommended if available)
103
+ */
104
+ async getAlbumDetails(id: string, link: string = ""): Promise<any> {
105
+ try {
106
+ let url = `https://saavn.sumit.co/api/albums?id=${id}`;
107
+ if (link) {
108
+ url += `&link=${encodeURIComponent(link)}`;
109
+ }
110
+
111
+ const response = await fetch(url);
112
+
113
+ if (!response.ok) {
114
+ throw new Error(`HTTP error! status: ${response.status}`);
115
+ }
116
+
117
+ const json = await response.json();
118
+ return json?.data || null;
119
+ } catch (error) {
120
+ console.error("TunzoPlayerAPI Error (getAlbumDetails):", error);
121
+ return null;
122
+ }
123
+ }
124
+ }
@@ -17,6 +17,7 @@ export class Player {
17
17
  static initialize(playlist: any[], quality = 3) {
18
18
  this.playlist = playlist;
19
19
  this.selectedQuality = quality;
20
+ this.setupMediaSession();
20
21
  }
21
22
 
22
23
  /** Call this once on user gesture to unlock audio in WebView */
@@ -41,9 +42,14 @@ export class Player {
41
42
  }
42
43
 
43
44
  this.audio.src = url;
45
+ this.audio.preload = 'auto'; // Improve loading
44
46
  this.audio.load(); // Ensure audio is loaded before play
45
47
  this.audio.play().then(() => {
46
48
  this.isPlaying = true;
49
+ this.updateMediaSessionMetadata(song);
50
+ if ('mediaSession' in navigator) {
51
+ navigator.mediaSession.playbackState = 'playing';
52
+ }
47
53
  }).catch((err) => {
48
54
  this.isPlaying = false;
49
55
  console.warn('Audio play failed:', err);
@@ -52,6 +58,7 @@ export class Player {
52
58
  // Set duration
53
59
  this.audio.onloadedmetadata = () => {
54
60
  this.duration = this.audio.duration;
61
+ this.updatePositionState();
55
62
  };
56
63
 
57
64
  // Set current time
@@ -74,11 +81,17 @@ export class Player {
74
81
  static pause() {
75
82
  this.audio.pause();
76
83
  this.isPlaying = false;
84
+ if ('mediaSession' in navigator) {
85
+ navigator.mediaSession.playbackState = 'paused';
86
+ }
77
87
  }
78
88
 
79
89
  static resume() {
80
90
  this.audio.play();
81
91
  this.isPlaying = true;
92
+ if ('mediaSession' in navigator) {
93
+ navigator.mediaSession.playbackState = 'playing';
94
+ }
82
95
  }
83
96
 
84
97
  static togglePlayPause() {
@@ -110,6 +123,7 @@ export class Player {
110
123
 
111
124
  static seek(seconds: number) {
112
125
  this.audio.currentTime = seconds;
126
+ this.updatePositionState();
113
127
  }
114
128
 
115
129
  static autoNext() {
@@ -186,4 +200,58 @@ export class Player {
186
200
  static getPlaylist(): any[] {
187
201
  return this.playlist;
188
202
  }
203
+
204
+ // -------------------------------------------------------------------------
205
+ // Native Media Session (Lock Screen Controls)
206
+ // -------------------------------------------------------------------------
207
+
208
+ private static setupMediaSession() {
209
+ if ('mediaSession' in navigator) {
210
+ navigator.mediaSession.setActionHandler('play', () => this.resume());
211
+ navigator.mediaSession.setActionHandler('pause', () => this.pause());
212
+ navigator.mediaSession.setActionHandler('previoustrack', () => this.prev());
213
+ navigator.mediaSession.setActionHandler('nexttrack', () => this.next());
214
+ navigator.mediaSession.setActionHandler('seekto', (details) => {
215
+ if (details.seekTime !== undefined) {
216
+ this.seek(details.seekTime);
217
+ }
218
+ });
219
+ }
220
+ }
221
+
222
+ private static updateMediaSessionMetadata(song: any) {
223
+ if ('mediaSession' in navigator) {
224
+ const artwork = [];
225
+ if (song.image) {
226
+ if (Array.isArray(song.image)) {
227
+ // Assuming image array contains objects with url/link and quality
228
+ song.image.forEach((img: any) => {
229
+ const src = img.link || img.url || (typeof img === 'string' ? img : '');
230
+ if (src) {
231
+ artwork.push({ src, sizes: '500x500', type: 'image/jpeg' });
232
+ }
233
+ });
234
+ } else if (typeof song.image === 'string') {
235
+ artwork.push({ src: song.image, sizes: '500x500', type: 'image/jpeg' });
236
+ }
237
+ }
238
+
239
+ navigator.mediaSession.metadata = new MediaMetadata({
240
+ title: song.name || song.title || 'Unknown Title',
241
+ artist: song.primaryArtists || song.artist || 'Unknown Artist',
242
+ album: song.album?.name || song.album || '',
243
+ artwork: artwork.length > 0 ? artwork : undefined
244
+ });
245
+ }
246
+ }
247
+
248
+ private static updatePositionState() {
249
+ if ('mediaSession' in navigator && this.duration > 0) {
250
+ navigator.mediaSession.setPositionState({
251
+ duration: this.duration,
252
+ playbackRate: this.audio.playbackRate,
253
+ position: this.audio.currentTime
254
+ });
255
+ }
256
+ }
189
257
  }