tunzo-player 1.0.33 → 1.0.39
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.js +1 -1
- package/dist/core/player.d.ts +3 -0
- package/dist/core/player.js +110 -24
- package/package.json +3 -1
- package/src/core/api-calles.ts +1 -1
- package/src/core/player.ts +109 -24
package/dist/core/api-calles.js
CHANGED
|
@@ -48,7 +48,7 @@ class TunzoPlayerAPI {
|
|
|
48
48
|
return __awaiter(this, arguments, void 0, function* (id, limit = 100) {
|
|
49
49
|
var _a;
|
|
50
50
|
try {
|
|
51
|
-
const response = yield fetch(
|
|
51
|
+
const response = yield fetch(`https://tunzo-api.vercel.app/api/songs/${id}/suggestions?limit=${limit}`);
|
|
52
52
|
if (!response.ok) {
|
|
53
53
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
54
54
|
}
|
package/dist/core/player.d.ts
CHANGED
|
@@ -14,8 +14,10 @@ export declare class Player {
|
|
|
14
14
|
private static selectedQuality;
|
|
15
15
|
private static intendedPlaying;
|
|
16
16
|
private static toastCtrl;
|
|
17
|
+
private static isAndroid;
|
|
17
18
|
/** Initialize with playlist and quality */
|
|
18
19
|
static initialize(playlist: any[], quality?: number): void;
|
|
20
|
+
private static setupAndroidPlayer;
|
|
19
21
|
static setToastController(controller: ToastController): void;
|
|
20
22
|
/** Setup audio element for better compatibility */
|
|
21
23
|
private static setupAudioElement;
|
|
@@ -23,6 +25,7 @@ export declare class Player {
|
|
|
23
25
|
/** Call this once on user gesture to unlock audio in WebView */
|
|
24
26
|
static unlockAudio(): void;
|
|
25
27
|
static play(song: any, index?: number): void;
|
|
28
|
+
private static playAndroid;
|
|
26
29
|
static pause(): void;
|
|
27
30
|
static resume(): void;
|
|
28
31
|
static togglePlayPause(): void;
|
package/dist/core/player.js
CHANGED
|
@@ -12,15 +12,30 @@ 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
17
|
class Player {
|
|
16
18
|
/** Initialize with playlist and quality */
|
|
17
19
|
static initialize(playlist, quality = 3) {
|
|
18
20
|
this.playlist = playlist;
|
|
19
21
|
this.selectedQuality = quality;
|
|
20
|
-
this.
|
|
21
|
-
|
|
22
|
+
if (this.isAndroid) {
|
|
23
|
+
this.setupAndroidPlayer();
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
this.setupMediaSession();
|
|
27
|
+
this.setupAudioElement();
|
|
28
|
+
}
|
|
22
29
|
this.startWatchdog();
|
|
23
30
|
}
|
|
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
|
+
}
|
|
24
39
|
static setToastController(controller) {
|
|
25
40
|
this.toastCtrl = controller;
|
|
26
41
|
}
|
|
@@ -67,17 +82,24 @@ class Player {
|
|
|
67
82
|
}
|
|
68
83
|
static startWatchdog() {
|
|
69
84
|
setInterval(() => {
|
|
70
|
-
if (this.intendedPlaying && this.audio.paused && this.currentSong) {
|
|
85
|
+
if (this.intendedPlaying && (this.isAndroid ? false : this.audio.paused) && this.currentSong) {
|
|
71
86
|
console.log('Watchdog: Audio paused unexpectedly. Attempting to resume...');
|
|
72
|
-
this.
|
|
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
|
+
}
|
|
73
93
|
}
|
|
74
94
|
}, 10000);
|
|
75
95
|
}
|
|
76
96
|
/** Call this once on user gesture to unlock audio in WebView */
|
|
77
97
|
static unlockAudio() {
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
if (!this.isAndroid) {
|
|
99
|
+
this.audio.src = '';
|
|
100
|
+
this.audio.load();
|
|
101
|
+
this.audio.play().catch(() => { });
|
|
102
|
+
}
|
|
81
103
|
}
|
|
82
104
|
static play(song, index = 0) {
|
|
83
105
|
var _a;
|
|
@@ -91,35 +113,93 @@ class Player {
|
|
|
91
113
|
if (url.startsWith('http://')) {
|
|
92
114
|
url = url.replace('http://', 'https://');
|
|
93
115
|
}
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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;
|
|
102
175
|
}
|
|
103
|
-
}).catch((err) => {
|
|
104
|
-
this.isPlaying = false;
|
|
105
|
-
console.warn('Audio play failed:', err);
|
|
106
176
|
});
|
|
107
177
|
}
|
|
108
178
|
static pause() {
|
|
109
179
|
this.intendedPlaying = false;
|
|
110
|
-
this.audio.pause();
|
|
111
180
|
this.isPlaying = false;
|
|
181
|
+
if (this.isAndroid) {
|
|
182
|
+
capacitor_plugin_playlist_1.Playlist.pause();
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
this.audio.pause();
|
|
186
|
+
}
|
|
112
187
|
keep_awake_1.KeepAwake.allowSleep();
|
|
113
|
-
if ('mediaSession' in navigator) {
|
|
188
|
+
if (!this.isAndroid && 'mediaSession' in navigator) {
|
|
114
189
|
navigator.mediaSession.playbackState = 'paused';
|
|
115
190
|
}
|
|
116
191
|
}
|
|
117
192
|
static resume() {
|
|
118
193
|
this.intendedPlaying = true;
|
|
119
|
-
this.audio.play();
|
|
120
194
|
this.isPlaying = true;
|
|
195
|
+
if (this.isAndroid) {
|
|
196
|
+
capacitor_plugin_playlist_1.Playlist.play();
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
this.audio.play();
|
|
200
|
+
}
|
|
121
201
|
keep_awake_1.KeepAwake.keepAwake();
|
|
122
|
-
if ('mediaSession' in navigator) {
|
|
202
|
+
if (!this.isAndroid && 'mediaSession' in navigator) {
|
|
123
203
|
navigator.mediaSession.playbackState = 'playing';
|
|
124
204
|
}
|
|
125
205
|
}
|
|
@@ -154,8 +234,13 @@ class Player {
|
|
|
154
234
|
}
|
|
155
235
|
}
|
|
156
236
|
static seek(seconds) {
|
|
157
|
-
this.
|
|
158
|
-
|
|
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
|
+
}
|
|
159
244
|
}
|
|
160
245
|
static autoNext() {
|
|
161
246
|
this.next();
|
|
@@ -310,3 +395,4 @@ Player.queue$ = new rxjs_1.BehaviorSubject([]);
|
|
|
310
395
|
Player.playlist = [];
|
|
311
396
|
Player.selectedQuality = 3;
|
|
312
397
|
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.39",
|
|
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,7 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@capacitor-community/keep-awake": "^7.1.0",
|
|
33
|
+
"@capacitor/core": "^8.0.0",
|
|
33
34
|
"@ionic/angular": "^8.7.11",
|
|
35
|
+
"capacitor-plugin-playlist": "^0.7.4",
|
|
34
36
|
"rxjs": "^7.8.2"
|
|
35
37
|
}
|
|
36
38
|
}
|
package/src/core/api-calles.ts
CHANGED
|
@@ -36,7 +36,7 @@ export class TunzoPlayerAPI {
|
|
|
36
36
|
async suggesstedSongs(id: string, limit: number = 100): Promise<any[]> {
|
|
37
37
|
try {
|
|
38
38
|
const response = await fetch(
|
|
39
|
-
|
|
39
|
+
`https://tunzo-api.vercel.app/api/songs/${id}/suggestions?limit=${limit}`
|
|
40
40
|
);
|
|
41
41
|
|
|
42
42
|
if (!response.ok) {
|
package/src/core/player.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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
6
|
|
|
5
7
|
export class Player {
|
|
6
8
|
private static audio = new Audio();
|
|
@@ -16,16 +18,32 @@ export class Player {
|
|
|
16
18
|
private static selectedQuality = 3;
|
|
17
19
|
private static intendedPlaying = false;
|
|
18
20
|
private static toastCtrl: ToastController;
|
|
21
|
+
private static isAndroid = Capacitor.getPlatform() === 'android';
|
|
19
22
|
|
|
20
23
|
/** Initialize with playlist and quality */
|
|
21
24
|
static initialize(playlist: any[], quality = 3) {
|
|
22
25
|
this.playlist = playlist;
|
|
23
26
|
this.selectedQuality = quality;
|
|
24
|
-
this.
|
|
25
|
-
|
|
27
|
+
if (this.isAndroid) {
|
|
28
|
+
this.setupAndroidPlayer();
|
|
29
|
+
} else {
|
|
30
|
+
this.setupMediaSession();
|
|
31
|
+
this.setupAudioElement();
|
|
32
|
+
}
|
|
26
33
|
this.startWatchdog();
|
|
27
34
|
}
|
|
28
35
|
|
|
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
|
+
|
|
29
47
|
static setToastController(controller: ToastController) {
|
|
30
48
|
this.toastCtrl = controller;
|
|
31
49
|
}
|
|
@@ -81,18 +99,24 @@ export class Player {
|
|
|
81
99
|
|
|
82
100
|
private static startWatchdog() {
|
|
83
101
|
setInterval(() => {
|
|
84
|
-
if (this.intendedPlaying && this.audio.paused && this.currentSong) {
|
|
102
|
+
if (this.intendedPlaying && (this.isAndroid ? false : this.audio.paused) && this.currentSong) {
|
|
85
103
|
console.log('Watchdog: Audio paused unexpectedly. Attempting to resume...');
|
|
86
|
-
this.
|
|
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
|
+
}
|
|
87
109
|
}
|
|
88
110
|
}, 10000);
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
/** Call this once on user gesture to unlock audio in WebView */
|
|
92
114
|
static unlockAudio() {
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
|
|
115
|
+
if (!this.isAndroid) {
|
|
116
|
+
this.audio.src = '';
|
|
117
|
+
this.audio.load();
|
|
118
|
+
this.audio.play().catch(() => { });
|
|
119
|
+
}
|
|
96
120
|
}
|
|
97
121
|
|
|
98
122
|
static play(song: any, index: number = 0) {
|
|
@@ -110,38 +134,95 @@ export class Player {
|
|
|
110
134
|
url = url.replace('http://', 'https://');
|
|
111
135
|
}
|
|
112
136
|
|
|
113
|
-
this.
|
|
114
|
-
|
|
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
|
+
}
|
|
115
156
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
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
|
+
}
|
|
122
166
|
}
|
|
123
|
-
|
|
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
|
+
}
|
|
177
|
+
|
|
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();
|
|
190
|
+
this.isPlaying = true;
|
|
191
|
+
KeepAwake.keepAwake();
|
|
192
|
+
} catch (e) {
|
|
193
|
+
console.error('Android play failed:', e);
|
|
124
194
|
this.isPlaying = false;
|
|
125
|
-
|
|
126
|
-
});
|
|
195
|
+
}
|
|
127
196
|
}
|
|
128
197
|
|
|
129
198
|
static pause() {
|
|
130
199
|
this.intendedPlaying = false;
|
|
131
|
-
this.audio.pause();
|
|
132
200
|
this.isPlaying = false;
|
|
201
|
+
|
|
202
|
+
if (this.isAndroid) {
|
|
203
|
+
Playlist.pause();
|
|
204
|
+
} else {
|
|
205
|
+
this.audio.pause();
|
|
206
|
+
}
|
|
207
|
+
|
|
133
208
|
KeepAwake.allowSleep();
|
|
134
|
-
if ('mediaSession' in navigator) {
|
|
209
|
+
if (!this.isAndroid && 'mediaSession' in navigator) {
|
|
135
210
|
navigator.mediaSession.playbackState = 'paused';
|
|
136
211
|
}
|
|
137
212
|
}
|
|
138
213
|
|
|
139
214
|
static resume() {
|
|
140
215
|
this.intendedPlaying = true;
|
|
141
|
-
this.audio.play();
|
|
142
216
|
this.isPlaying = true;
|
|
217
|
+
|
|
218
|
+
if (this.isAndroid) {
|
|
219
|
+
Playlist.play();
|
|
220
|
+
} else {
|
|
221
|
+
this.audio.play();
|
|
222
|
+
}
|
|
223
|
+
|
|
143
224
|
KeepAwake.keepAwake();
|
|
144
|
-
if ('mediaSession' in navigator) {
|
|
225
|
+
if (!this.isAndroid && 'mediaSession' in navigator) {
|
|
145
226
|
navigator.mediaSession.playbackState = 'playing';
|
|
146
227
|
}
|
|
147
228
|
}
|
|
@@ -176,8 +257,12 @@ export class Player {
|
|
|
176
257
|
}
|
|
177
258
|
|
|
178
259
|
static seek(seconds: number) {
|
|
179
|
-
this.
|
|
180
|
-
|
|
260
|
+
if (this.isAndroid) {
|
|
261
|
+
Playlist.seekTo({ position: seconds });
|
|
262
|
+
} else {
|
|
263
|
+
this.audio.currentTime = seconds;
|
|
264
|
+
this.updatePositionState();
|
|
265
|
+
}
|
|
181
266
|
}
|
|
182
267
|
|
|
183
268
|
static autoNext() {
|