tunzo-player 1.0.24 → 1.0.25

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 CHANGED
@@ -76,4 +76,36 @@ yarn add tunzo-player
76
76
  | 3 | High (160kbps) |
77
77
  | 4 | Ultra (320kbps) |
78
78
 
79
+ | 4 | Ultra (320kbps) |
80
+
81
+ ## 📱 Native Configuration (Ionic/Capacitor)
82
+
83
+ To ensure background audio works correctly on Android and iOS (preventing the app from pausing when the screen locks), you must configure your native projects.
84
+
85
+ ### **Android (`android/app/src/main/AndroidManifest.xml`)**
86
+
87
+ Add the following permissions inside the `<manifest>` tag:
88
+
89
+ ```xml
90
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
91
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
92
+ ```
93
+
94
+ **Note:** Modern Android versions might require a foreground service notification to keep the audio alive indefinitely. The `MediaSession` API implemented in this package helps, but for guaranteed persistence, consider using a native audio plugin if issues persist.
95
+
96
+ ### **iOS (`ios/App/App/Info.plist`)**
97
+
98
+ Add `audio` to the `UIBackgroundModes` key to allow background playback:
99
+
100
+ ```xml
101
+ <key>UIBackgroundModes</key>
102
+ <array>
103
+ <string>audio</string>
104
+ </array>
105
+ ```
106
+
107
+ ## 🤝 Contributing
108
+
109
+ Contributions are welcome! Please open an issue or submit a pull request.
110
+
79
111
  # tunzo-player
@@ -28,6 +28,8 @@ class Player {
28
28
  url = url.replace('http://', 'https://');
29
29
  }
30
30
  this.audio.src = url;
31
+ // @ts-ignore
32
+ this.audio.title = song.name || song.title || 'Unknown Title'; // Help some browsers identify the track
31
33
  this.audio.preload = 'auto'; // Improve loading
32
34
  this.audio.load(); // Ensure audio is loaded before play
33
35
  this.audio.play().then(() => {
@@ -48,6 +50,22 @@ class Player {
48
50
  // Set current time
49
51
  this.audio.ontimeupdate = () => {
50
52
  this.currentTime = this.audio.currentTime;
53
+ // Update position state less frequently to avoid spamming, but enough to keep sync
54
+ if (Math.floor(this.currentTime) % 5 === 0) {
55
+ this.updatePositionState();
56
+ }
57
+ };
58
+ // Handle buffering/stalled states
59
+ this.audio.onwaiting = () => {
60
+ if ('mediaSession' in navigator) {
61
+ navigator.mediaSession.playbackState = 'none'; // Or 'paused' to indicate buffering
62
+ }
63
+ };
64
+ this.audio.onplaying = () => {
65
+ this.isPlaying = true;
66
+ if ('mediaSession' in navigator) {
67
+ navigator.mediaSession.playbackState = 'playing';
68
+ }
51
69
  };
52
70
  // Auto-play next song
53
71
  this.audio.onended = () => {
@@ -186,20 +204,28 @@ class Player {
186
204
  if (Array.isArray(song.image)) {
187
205
  // Assuming image array contains objects with url/link and quality
188
206
  song.image.forEach((img) => {
189
- const src = img.link || img.url || (typeof img === 'string' ? img : '');
207
+ let src = img.link || img.url || (typeof img === 'string' ? img : '');
190
208
  if (src) {
209
+ // 🚀 Auto-convert http → https for images too
210
+ if (src.startsWith('http://')) {
211
+ src = src.replace('http://', 'https://');
212
+ }
191
213
  artwork.push({ src, sizes: '500x500', type: 'image/jpeg' });
192
214
  }
193
215
  });
194
216
  }
195
217
  else if (typeof song.image === 'string') {
196
- artwork.push({ src: song.image, sizes: '500x500', type: 'image/jpeg' });
218
+ let src = song.image;
219
+ if (src.startsWith('http://')) {
220
+ src = src.replace('http://', 'https://');
221
+ }
222
+ artwork.push({ src: src, sizes: '500x500', type: 'image/jpeg' });
197
223
  }
198
224
  }
199
225
  navigator.mediaSession.metadata = new MediaMetadata({
200
226
  title: song.name || song.title || 'Unknown Title',
201
227
  artist: song.primaryArtists || song.artist || 'Unknown Artist',
202
- album: ((_a = song.album) === null || _a === void 0 ? void 0 : _a.name) || song.album || '',
228
+ album: ((_a = song.album) === null || _a === void 0 ? void 0 : _a.name) || song.album || 'Unknown Album',
203
229
  artwork: artwork.length > 0 ? artwork : undefined
204
230
  });
205
231
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tunzo-player",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
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",
@@ -42,6 +42,8 @@ export class Player {
42
42
  }
43
43
 
44
44
  this.audio.src = url;
45
+ // @ts-ignore
46
+ this.audio.title = song.name || song.title || 'Unknown Title'; // Help some browsers identify the track
45
47
  this.audio.preload = 'auto'; // Improve loading
46
48
  this.audio.load(); // Ensure audio is loaded before play
47
49
  this.audio.play().then(() => {
@@ -64,6 +66,24 @@ export class Player {
64
66
  // Set current time
65
67
  this.audio.ontimeupdate = () => {
66
68
  this.currentTime = this.audio.currentTime;
69
+ // Update position state less frequently to avoid spamming, but enough to keep sync
70
+ if (Math.floor(this.currentTime) % 5 === 0) {
71
+ this.updatePositionState();
72
+ }
73
+ };
74
+
75
+ // Handle buffering/stalled states
76
+ this.audio.onwaiting = () => {
77
+ if ('mediaSession' in navigator) {
78
+ navigator.mediaSession.playbackState = 'none'; // Or 'paused' to indicate buffering
79
+ }
80
+ };
81
+
82
+ this.audio.onplaying = () => {
83
+ this.isPlaying = true;
84
+ if ('mediaSession' in navigator) {
85
+ navigator.mediaSession.playbackState = 'playing';
86
+ }
67
87
  };
68
88
 
69
89
  // Auto-play next song
@@ -226,20 +246,28 @@ export class Player {
226
246
  if (Array.isArray(song.image)) {
227
247
  // Assuming image array contains objects with url/link and quality
228
248
  song.image.forEach((img: any) => {
229
- const src = img.link || img.url || (typeof img === 'string' ? img : '');
249
+ let src = img.link || img.url || (typeof img === 'string' ? img : '');
230
250
  if (src) {
251
+ // 🚀 Auto-convert http → https for images too
252
+ if (src.startsWith('http://')) {
253
+ src = src.replace('http://', 'https://');
254
+ }
231
255
  artwork.push({ src, sizes: '500x500', type: 'image/jpeg' });
232
256
  }
233
257
  });
234
258
  } else if (typeof song.image === 'string') {
235
- artwork.push({ src: song.image, sizes: '500x500', type: 'image/jpeg' });
259
+ let src = song.image;
260
+ if (src.startsWith('http://')) {
261
+ src = src.replace('http://', 'https://');
262
+ }
263
+ artwork.push({ src: src, sizes: '500x500', type: 'image/jpeg' });
236
264
  }
237
265
  }
238
266
 
239
267
  navigator.mediaSession.metadata = new MediaMetadata({
240
268
  title: song.name || song.title || 'Unknown Title',
241
269
  artist: song.primaryArtists || song.artist || 'Unknown Artist',
242
- album: song.album?.name || song.album || '',
270
+ album: song.album?.name || song.album || 'Unknown Album',
243
271
  artwork: artwork.length > 0 ? artwork : undefined
244
272
  });
245
273
  }