tunzo-player 1.0.27 → 1.0.29
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/player.d.ts +2 -2
- package/dist/core/player.js +46 -89
- package/package.json +1 -2
- package/src/core/player.ts +55 -86
package/dist/core/player.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare class Player {
|
|
|
13
13
|
private static selectedQuality;
|
|
14
14
|
/** Initialize with playlist and quality */
|
|
15
15
|
static initialize(playlist: any[], quality?: number): void;
|
|
16
|
+
/** Setup audio element for better compatibility */
|
|
17
|
+
private static setupAudioElement;
|
|
16
18
|
/** Call this once on user gesture to unlock audio in WebView */
|
|
17
19
|
static unlockAudio(): void;
|
|
18
20
|
static play(song: any, index?: number): void;
|
|
@@ -37,8 +39,6 @@ export declare class Player {
|
|
|
37
39
|
static setQuality(index: number): void;
|
|
38
40
|
static getQueue(): any[];
|
|
39
41
|
static getPlaylist(): any[];
|
|
40
|
-
private static enableBackgroundMode;
|
|
41
|
-
private static disableBackgroundMode;
|
|
42
42
|
private static setupMediaSession;
|
|
43
43
|
private static updateMediaSessionMetadata;
|
|
44
44
|
private static updatePositionState;
|
package/dist/core/player.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.Player = void 0;
|
|
13
4
|
const rxjs_1 = require("rxjs");
|
|
14
|
-
const capacitor_background_mode_1 = require("@anuradev/capacitor-background-mode");
|
|
15
5
|
const keep_awake_1 = require("@capacitor-community/keep-awake");
|
|
16
6
|
class Player {
|
|
17
7
|
/** Initialize with playlist and quality */
|
|
@@ -19,6 +9,48 @@ class Player {
|
|
|
19
9
|
this.playlist = playlist;
|
|
20
10
|
this.selectedQuality = quality;
|
|
21
11
|
this.setupMediaSession();
|
|
12
|
+
this.setupAudioElement();
|
|
13
|
+
}
|
|
14
|
+
/** Setup audio element for better compatibility */
|
|
15
|
+
static setupAudioElement() {
|
|
16
|
+
// Enable background playback
|
|
17
|
+
this.audio.preload = 'auto';
|
|
18
|
+
// @ts-ignore - Some browsers support this
|
|
19
|
+
this.audio.preservesPitch = false;
|
|
20
|
+
// Setup event listeners
|
|
21
|
+
this.audio.onloadedmetadata = () => {
|
|
22
|
+
this.duration = this.audio.duration;
|
|
23
|
+
this.updatePositionState();
|
|
24
|
+
};
|
|
25
|
+
this.audio.ontimeupdate = () => {
|
|
26
|
+
this.currentTime = this.audio.currentTime;
|
|
27
|
+
if (Math.floor(this.currentTime) % 5 === 0) {
|
|
28
|
+
this.updatePositionState();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
this.audio.onended = () => {
|
|
32
|
+
this.autoNext();
|
|
33
|
+
};
|
|
34
|
+
this.audio.onplaying = () => {
|
|
35
|
+
this.isPlaying = true;
|
|
36
|
+
if ('mediaSession' in navigator) {
|
|
37
|
+
navigator.mediaSession.playbackState = 'playing';
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
this.audio.onpause = () => {
|
|
41
|
+
this.isPlaying = false;
|
|
42
|
+
if ('mediaSession' in navigator) {
|
|
43
|
+
navigator.mediaSession.playbackState = 'paused';
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
this.audio.onwaiting = () => {
|
|
47
|
+
if ('mediaSession' in navigator) {
|
|
48
|
+
navigator.mediaSession.playbackState = 'none';
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
this.audio.onerror = (e) => {
|
|
52
|
+
console.error('Audio error:', this.audio.error, e);
|
|
53
|
+
};
|
|
22
54
|
}
|
|
23
55
|
/** Call this once on user gesture to unlock audio in WebView */
|
|
24
56
|
static unlockAudio() {
|
|
@@ -26,7 +58,6 @@ class Player {
|
|
|
26
58
|
this.audio.load();
|
|
27
59
|
this.audio.play().catch(() => { });
|
|
28
60
|
}
|
|
29
|
-
//updated
|
|
30
61
|
static play(song, index = 0) {
|
|
31
62
|
var _a;
|
|
32
63
|
if (!song || !song.downloadUrl)
|
|
@@ -39,14 +70,11 @@ class Player {
|
|
|
39
70
|
url = url.replace('http://', 'https://');
|
|
40
71
|
}
|
|
41
72
|
this.audio.src = url;
|
|
42
|
-
|
|
43
|
-
this.audio.title = song.name || song.title || 'Unknown Title'; // Help some browsers identify the track
|
|
44
|
-
this.audio.preload = 'auto'; // Improve loading
|
|
45
|
-
this.audio.load(); // Ensure audio is loaded before play
|
|
73
|
+
this.audio.load();
|
|
46
74
|
this.audio.play().then(() => {
|
|
47
75
|
this.isPlaying = true;
|
|
48
76
|
this.updateMediaSessionMetadata(song);
|
|
49
|
-
|
|
77
|
+
keep_awake_1.KeepAwake.keepAwake(); // Keep screen/CPU awake
|
|
50
78
|
if ('mediaSession' in navigator) {
|
|
51
79
|
navigator.mediaSession.playbackState = 'playing';
|
|
52
80
|
}
|
|
@@ -54,44 +82,11 @@ class Player {
|
|
|
54
82
|
this.isPlaying = false;
|
|
55
83
|
console.warn('Audio play failed:', err);
|
|
56
84
|
});
|
|
57
|
-
// Set duration
|
|
58
|
-
this.audio.onloadedmetadata = () => {
|
|
59
|
-
this.duration = this.audio.duration;
|
|
60
|
-
this.updatePositionState();
|
|
61
|
-
};
|
|
62
|
-
// Set current time
|
|
63
|
-
this.audio.ontimeupdate = () => {
|
|
64
|
-
this.currentTime = this.audio.currentTime;
|
|
65
|
-
// Update position state less frequently to avoid spamming, but enough to keep sync
|
|
66
|
-
if (Math.floor(this.currentTime) % 5 === 0) {
|
|
67
|
-
this.updatePositionState();
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
// Handle buffering/stalled states
|
|
71
|
-
this.audio.onwaiting = () => {
|
|
72
|
-
if ('mediaSession' in navigator) {
|
|
73
|
-
navigator.mediaSession.playbackState = 'none'; // Or 'paused' to indicate buffering
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
this.audio.onplaying = () => {
|
|
77
|
-
this.isPlaying = true;
|
|
78
|
-
if ('mediaSession' in navigator) {
|
|
79
|
-
navigator.mediaSession.playbackState = 'playing';
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
// Auto-play next song
|
|
83
|
-
this.audio.onended = () => {
|
|
84
|
-
this.autoNext();
|
|
85
|
-
};
|
|
86
|
-
// Catch errors
|
|
87
|
-
this.audio.onerror = (e) => {
|
|
88
|
-
console.error('Audio error:', this.audio.error, e);
|
|
89
|
-
};
|
|
90
85
|
}
|
|
91
86
|
static pause() {
|
|
92
87
|
this.audio.pause();
|
|
93
88
|
this.isPlaying = false;
|
|
94
|
-
|
|
89
|
+
keep_awake_1.KeepAwake.allowSleep();
|
|
95
90
|
if ('mediaSession' in navigator) {
|
|
96
91
|
navigator.mediaSession.playbackState = 'paused';
|
|
97
92
|
}
|
|
@@ -99,6 +94,7 @@ class Player {
|
|
|
99
94
|
static resume() {
|
|
100
95
|
this.audio.play();
|
|
101
96
|
this.isPlaying = true;
|
|
97
|
+
keep_awake_1.KeepAwake.keepAwake();
|
|
102
98
|
if ('mediaSession' in navigator) {
|
|
103
99
|
navigator.mediaSession.playbackState = 'playing';
|
|
104
100
|
}
|
|
@@ -194,43 +190,6 @@ class Player {
|
|
|
194
190
|
return this.playlist;
|
|
195
191
|
}
|
|
196
192
|
// -------------------------------------------------------------------------
|
|
197
|
-
// Capacitor Background Mode & Keep Awake
|
|
198
|
-
// -------------------------------------------------------------------------
|
|
199
|
-
static enableBackgroundMode() {
|
|
200
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
201
|
-
try {
|
|
202
|
-
yield keep_awake_1.KeepAwake.keepAwake();
|
|
203
|
-
yield capacitor_background_mode_1.BackgroundMode.enable({
|
|
204
|
-
title: "Tunzo Player",
|
|
205
|
-
text: "Playing music in background",
|
|
206
|
-
icon: "ic_launcher",
|
|
207
|
-
color: "042730",
|
|
208
|
-
resume: true,
|
|
209
|
-
hidden: false,
|
|
210
|
-
bigText: true,
|
|
211
|
-
disableWebViewOptimization: true
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
catch (err) {
|
|
215
|
-
// Plugin might not be installed or on web
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
static disableBackgroundMode() {
|
|
220
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
221
|
-
try {
|
|
222
|
-
yield keep_awake_1.KeepAwake.allowSleep();
|
|
223
|
-
// We might want to keep background mode enabled if we want to resume later,
|
|
224
|
-
// but for battery saving, we can disable it or move to background.
|
|
225
|
-
// await BackgroundMode.disable();
|
|
226
|
-
yield capacitor_background_mode_1.BackgroundMode.moveToBackground();
|
|
227
|
-
}
|
|
228
|
-
catch (err) {
|
|
229
|
-
// Plugin might not be installed or on web
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
// -------------------------------------------------------------------------
|
|
234
193
|
// Native Media Session (Lock Screen Controls)
|
|
235
194
|
// -------------------------------------------------------------------------
|
|
236
195
|
static setupMediaSession() {
|
|
@@ -252,11 +211,9 @@ class Player {
|
|
|
252
211
|
const artwork = [];
|
|
253
212
|
if (song.image) {
|
|
254
213
|
if (Array.isArray(song.image)) {
|
|
255
|
-
// Assuming image array contains objects with url/link and quality
|
|
256
214
|
song.image.forEach((img) => {
|
|
257
215
|
let src = img.link || img.url || (typeof img === 'string' ? img : '');
|
|
258
216
|
if (src) {
|
|
259
|
-
// 🚀 Auto-convert http → https for images too
|
|
260
217
|
if (src.startsWith('http://')) {
|
|
261
218
|
src = src.replace('http://', 'https://');
|
|
262
219
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tunzo-player",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
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",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@anuradev/capacitor-background-mode": "^7.2.1",
|
|
33
32
|
"@capacitor-community/keep-awake": "^7.1.0",
|
|
34
33
|
"rxjs": "^7.8.2"
|
|
35
34
|
}
|
package/src/core/player.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BehaviorSubject } from 'rxjs';
|
|
2
|
-
import { BackgroundMode } from '@anuradev/capacitor-background-mode';
|
|
3
2
|
import { KeepAwake } from '@capacitor-community/keep-awake';
|
|
4
3
|
|
|
5
4
|
export class Player {
|
|
@@ -20,6 +19,56 @@ export class Player {
|
|
|
20
19
|
this.playlist = playlist;
|
|
21
20
|
this.selectedQuality = quality;
|
|
22
21
|
this.setupMediaSession();
|
|
22
|
+
this.setupAudioElement();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Setup audio element for better compatibility */
|
|
26
|
+
private static setupAudioElement() {
|
|
27
|
+
// Enable background playback
|
|
28
|
+
this.audio.preload = 'auto';
|
|
29
|
+
// @ts-ignore - Some browsers support this
|
|
30
|
+
this.audio.preservesPitch = false;
|
|
31
|
+
|
|
32
|
+
// Setup event listeners
|
|
33
|
+
this.audio.onloadedmetadata = () => {
|
|
34
|
+
this.duration = this.audio.duration;
|
|
35
|
+
this.updatePositionState();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.audio.ontimeupdate = () => {
|
|
39
|
+
this.currentTime = this.audio.currentTime;
|
|
40
|
+
if (Math.floor(this.currentTime) % 5 === 0) {
|
|
41
|
+
this.updatePositionState();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this.audio.onended = () => {
|
|
46
|
+
this.autoNext();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
this.audio.onplaying = () => {
|
|
50
|
+
this.isPlaying = true;
|
|
51
|
+
if ('mediaSession' in navigator) {
|
|
52
|
+
navigator.mediaSession.playbackState = 'playing';
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
this.audio.onpause = () => {
|
|
57
|
+
this.isPlaying = false;
|
|
58
|
+
if ('mediaSession' in navigator) {
|
|
59
|
+
navigator.mediaSession.playbackState = 'paused';
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
this.audio.onwaiting = () => {
|
|
64
|
+
if ('mediaSession' in navigator) {
|
|
65
|
+
navigator.mediaSession.playbackState = 'none';
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
this.audio.onerror = (e) => {
|
|
70
|
+
console.error('Audio error:', this.audio.error, e);
|
|
71
|
+
};
|
|
23
72
|
}
|
|
24
73
|
|
|
25
74
|
/** Call this once on user gesture to unlock audio in WebView */
|
|
@@ -28,7 +77,6 @@ export class Player {
|
|
|
28
77
|
this.audio.load();
|
|
29
78
|
this.audio.play().catch(() => { });
|
|
30
79
|
}
|
|
31
|
-
//updated
|
|
32
80
|
|
|
33
81
|
static play(song: any, index: number = 0) {
|
|
34
82
|
if (!song || !song.downloadUrl) return;
|
|
@@ -44,14 +92,12 @@ export class Player {
|
|
|
44
92
|
}
|
|
45
93
|
|
|
46
94
|
this.audio.src = url;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.audio.preload = 'auto'; // Improve loading
|
|
50
|
-
this.audio.load(); // Ensure audio is loaded before play
|
|
95
|
+
this.audio.load();
|
|
96
|
+
|
|
51
97
|
this.audio.play().then(() => {
|
|
52
98
|
this.isPlaying = true;
|
|
53
99
|
this.updateMediaSessionMetadata(song);
|
|
54
|
-
|
|
100
|
+
KeepAwake.keepAwake(); // Keep screen/CPU awake
|
|
55
101
|
if ('mediaSession' in navigator) {
|
|
56
102
|
navigator.mediaSession.playbackState = 'playing';
|
|
57
103
|
}
|
|
@@ -59,52 +105,12 @@ export class Player {
|
|
|
59
105
|
this.isPlaying = false;
|
|
60
106
|
console.warn('Audio play failed:', err);
|
|
61
107
|
});
|
|
62
|
-
|
|
63
|
-
// Set duration
|
|
64
|
-
this.audio.onloadedmetadata = () => {
|
|
65
|
-
this.duration = this.audio.duration;
|
|
66
|
-
this.updatePositionState();
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// Set current time
|
|
70
|
-
this.audio.ontimeupdate = () => {
|
|
71
|
-
this.currentTime = this.audio.currentTime;
|
|
72
|
-
// Update position state less frequently to avoid spamming, but enough to keep sync
|
|
73
|
-
if (Math.floor(this.currentTime) % 5 === 0) {
|
|
74
|
-
this.updatePositionState();
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// Handle buffering/stalled states
|
|
79
|
-
this.audio.onwaiting = () => {
|
|
80
|
-
if ('mediaSession' in navigator) {
|
|
81
|
-
navigator.mediaSession.playbackState = 'none'; // Or 'paused' to indicate buffering
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
this.audio.onplaying = () => {
|
|
86
|
-
this.isPlaying = true;
|
|
87
|
-
if ('mediaSession' in navigator) {
|
|
88
|
-
navigator.mediaSession.playbackState = 'playing';
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// Auto-play next song
|
|
93
|
-
this.audio.onended = () => {
|
|
94
|
-
this.autoNext();
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Catch errors
|
|
98
|
-
this.audio.onerror = (e) => {
|
|
99
|
-
console.error('Audio error:', this.audio.error, e);
|
|
100
|
-
};
|
|
101
108
|
}
|
|
102
109
|
|
|
103
|
-
|
|
104
110
|
static pause() {
|
|
105
111
|
this.audio.pause();
|
|
106
112
|
this.isPlaying = false;
|
|
107
|
-
|
|
113
|
+
KeepAwake.allowSleep();
|
|
108
114
|
if ('mediaSession' in navigator) {
|
|
109
115
|
navigator.mediaSession.playbackState = 'paused';
|
|
110
116
|
}
|
|
@@ -113,6 +119,7 @@ export class Player {
|
|
|
113
119
|
static resume() {
|
|
114
120
|
this.audio.play();
|
|
115
121
|
this.isPlaying = true;
|
|
122
|
+
KeepAwake.keepAwake();
|
|
116
123
|
if ('mediaSession' in navigator) {
|
|
117
124
|
navigator.mediaSession.playbackState = 'playing';
|
|
118
125
|
}
|
|
@@ -156,12 +163,10 @@ export class Player {
|
|
|
156
163
|
|
|
157
164
|
static playRandom() {
|
|
158
165
|
if (this.playlist.length <= 1) return;
|
|
159
|
-
|
|
160
166
|
let randomIndex;
|
|
161
167
|
do {
|
|
162
168
|
randomIndex = Math.floor(Math.random() * this.playlist.length);
|
|
163
169
|
} while (randomIndex === this.currentIndex);
|
|
164
|
-
|
|
165
170
|
this.play(this.playlist[randomIndex], randomIndex);
|
|
166
171
|
}
|
|
167
172
|
|
|
@@ -225,40 +230,6 @@ export class Player {
|
|
|
225
230
|
return this.playlist;
|
|
226
231
|
}
|
|
227
232
|
|
|
228
|
-
// -------------------------------------------------------------------------
|
|
229
|
-
// Capacitor Background Mode & Keep Awake
|
|
230
|
-
// -------------------------------------------------------------------------
|
|
231
|
-
|
|
232
|
-
private static async enableBackgroundMode() {
|
|
233
|
-
try {
|
|
234
|
-
await KeepAwake.keepAwake();
|
|
235
|
-
await BackgroundMode.enable({
|
|
236
|
-
title: "Tunzo Player",
|
|
237
|
-
text: "Playing music in background",
|
|
238
|
-
icon: "ic_launcher",
|
|
239
|
-
color: "042730",
|
|
240
|
-
resume: true,
|
|
241
|
-
hidden: false,
|
|
242
|
-
bigText: true,
|
|
243
|
-
disableWebViewOptimization: true
|
|
244
|
-
});
|
|
245
|
-
} catch (err) {
|
|
246
|
-
// Plugin might not be installed or on web
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
private static async disableBackgroundMode() {
|
|
251
|
-
try {
|
|
252
|
-
await KeepAwake.allowSleep();
|
|
253
|
-
// We might want to keep background mode enabled if we want to resume later,
|
|
254
|
-
// but for battery saving, we can disable it or move to background.
|
|
255
|
-
// await BackgroundMode.disable();
|
|
256
|
-
await BackgroundMode.moveToBackground();
|
|
257
|
-
} catch (err) {
|
|
258
|
-
// Plugin might not be installed or on web
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
233
|
// -------------------------------------------------------------------------
|
|
263
234
|
// Native Media Session (Lock Screen Controls)
|
|
264
235
|
// -------------------------------------------------------------------------
|
|
@@ -282,11 +253,9 @@ export class Player {
|
|
|
282
253
|
const artwork = [];
|
|
283
254
|
if (song.image) {
|
|
284
255
|
if (Array.isArray(song.image)) {
|
|
285
|
-
// Assuming image array contains objects with url/link and quality
|
|
286
256
|
song.image.forEach((img: any) => {
|
|
287
257
|
let src = img.link || img.url || (typeof img === 'string' ? img : '');
|
|
288
258
|
if (src) {
|
|
289
|
-
// 🚀 Auto-convert http → https for images too
|
|
290
259
|
if (src.startsWith('http://')) {
|
|
291
260
|
src = src.replace('http://', 'https://');
|
|
292
261
|
}
|