tunzo-player 1.0.28 → 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 +9 -10
- package/dist/core/player.js +126 -180
- package/package.json +1 -2
- package/src/core/player.ts +124 -153
package/dist/core/player.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BehaviorSubject } from 'rxjs';
|
|
2
2
|
export declare class Player {
|
|
3
|
+
private static audio;
|
|
3
4
|
private static currentSong;
|
|
4
5
|
private static currentIndex;
|
|
5
6
|
private static isPlaying;
|
|
@@ -10,15 +11,13 @@ export declare class Player {
|
|
|
10
11
|
static queue$: BehaviorSubject<any[]>;
|
|
11
12
|
private static playlist;
|
|
12
13
|
private static selectedQuality;
|
|
13
|
-
private static isNative;
|
|
14
|
-
private static webAudio;
|
|
15
14
|
/** Initialize with playlist and quality */
|
|
16
|
-
static initialize(playlist: any[], quality?: number):
|
|
17
|
-
/**
|
|
15
|
+
static initialize(playlist: any[], quality?: number): void;
|
|
16
|
+
/** Setup audio element for better compatibility */
|
|
17
|
+
private static setupAudioElement;
|
|
18
|
+
/** Call this once on user gesture to unlock audio in WebView */
|
|
18
19
|
static unlockAudio(): void;
|
|
19
|
-
static play(song: any, index?: number):
|
|
20
|
-
private static playNative;
|
|
21
|
-
private static playWeb;
|
|
20
|
+
static play(song: any, index?: number): void;
|
|
22
21
|
static pause(): void;
|
|
23
22
|
static resume(): void;
|
|
24
23
|
static togglePlayPause(): void;
|
|
@@ -40,7 +39,7 @@ export declare class Player {
|
|
|
40
39
|
static setQuality(index: number): void;
|
|
41
40
|
static getQueue(): any[];
|
|
42
41
|
static getPlaylist(): any[];
|
|
43
|
-
private static
|
|
44
|
-
private static
|
|
45
|
-
private static
|
|
42
|
+
private static setupMediaSession;
|
|
43
|
+
private static updateMediaSessionMetadata;
|
|
44
|
+
private static updatePositionState;
|
|
46
45
|
}
|
package/dist/core/player.js
CHANGED
|
@@ -1,153 +1,103 @@
|
|
|
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 native_audio_1 = require("@capgo/native-audio");
|
|
15
5
|
const keep_awake_1 = require("@capacitor-community/keep-awake");
|
|
16
|
-
const core_1 = require("@capacitor/core");
|
|
17
6
|
class Player {
|
|
18
7
|
/** Initialize with playlist and quality */
|
|
19
|
-
static initialize(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (this.isNative) {
|
|
25
|
-
try {
|
|
26
|
-
yield native_audio_1.NativeAudio.configure({
|
|
27
|
-
showNotification: true,
|
|
28
|
-
background: true,
|
|
29
|
-
focus: true
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
catch (e) {
|
|
33
|
-
console.warn('NativeAudio configure failed:', e);
|
|
34
|
-
}
|
|
35
|
-
this.setupNativeListeners();
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
this.setupWebListeners();
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
/** Call this once on user gesture to unlock audio in WebView (Web only) */
|
|
43
|
-
static unlockAudio() {
|
|
44
|
-
if (!this.isNative) {
|
|
45
|
-
this.webAudio.src = '';
|
|
46
|
-
this.webAudio.load();
|
|
47
|
-
this.webAudio.play().catch(() => { });
|
|
48
|
-
}
|
|
8
|
+
static initialize(playlist, quality = 3) {
|
|
9
|
+
this.playlist = playlist;
|
|
10
|
+
this.selectedQuality = quality;
|
|
11
|
+
this.setupMediaSession();
|
|
12
|
+
this.setupAudioElement();
|
|
49
13
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
this.playWeb(url, song);
|
|
68
|
-
}
|
|
69
|
-
this.isPlaying = true;
|
|
70
|
-
keep_awake_1.KeepAwake.keepAwake(); // Keep CPU awake for streaming
|
|
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();
|
|
71
29
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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';
|
|
75
38
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
// Unload previous if any (though preload might handle it, safer to be clean)
|
|
83
|
-
// NativeAudio.unload({ assetId: 'currentSong' }).catch(() => {});
|
|
84
|
-
// Prepare artwork for lock screen
|
|
85
|
-
let artworkPath = '';
|
|
86
|
-
if (song.image) {
|
|
87
|
-
if (Array.isArray(song.image)) {
|
|
88
|
-
// Get highest quality image
|
|
89
|
-
const img = song.image[song.image.length - 1];
|
|
90
|
-
artworkPath = img.link || img.url || (typeof img === 'string' ? img : '');
|
|
91
|
-
}
|
|
92
|
-
else if (typeof song.image === 'string') {
|
|
93
|
-
artworkPath = song.image;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (artworkPath.startsWith('http://')) {
|
|
97
|
-
artworkPath = artworkPath.replace('http://', 'https://');
|
|
98
|
-
}
|
|
99
|
-
yield native_audio_1.NativeAudio.preload({
|
|
100
|
-
assetId: 'currentSong',
|
|
101
|
-
assetPath: url,
|
|
102
|
-
isUrl: true,
|
|
103
|
-
audioChannelNum: 1,
|
|
104
|
-
// Metadata for Lock Screen
|
|
105
|
-
notificationMetadata: {
|
|
106
|
-
album: ((_a = song.album) === null || _a === void 0 ? void 0 : _a.name) || song.album || 'Unknown Album',
|
|
107
|
-
artist: song.primaryArtists || song.artist || 'Unknown Artist',
|
|
108
|
-
title: song.name || song.title || 'Unknown Title',
|
|
109
|
-
artworkUrl: artworkPath
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
yield native_audio_1.NativeAudio.play({ assetId: 'currentSong' });
|
|
39
|
+
};
|
|
40
|
+
this.audio.onpause = () => {
|
|
41
|
+
this.isPlaying = false;
|
|
42
|
+
if ('mediaSession' in navigator) {
|
|
43
|
+
navigator.mediaSession.playbackState = 'paused';
|
|
113
44
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
45
|
+
};
|
|
46
|
+
this.audio.onwaiting = () => {
|
|
47
|
+
if ('mediaSession' in navigator) {
|
|
48
|
+
navigator.mediaSession.playbackState = 'none';
|
|
117
49
|
}
|
|
118
|
-
}
|
|
50
|
+
};
|
|
51
|
+
this.audio.onerror = (e) => {
|
|
52
|
+
console.error('Audio error:', this.audio.error, e);
|
|
53
|
+
};
|
|
119
54
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.
|
|
124
|
-
this.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
55
|
+
/** Call this once on user gesture to unlock audio in WebView */
|
|
56
|
+
static unlockAudio() {
|
|
57
|
+
this.audio.src = '';
|
|
58
|
+
this.audio.load();
|
|
59
|
+
this.audio.play().catch(() => { });
|
|
60
|
+
}
|
|
61
|
+
static play(song, index = 0) {
|
|
62
|
+
var _a;
|
|
63
|
+
if (!song || !song.downloadUrl)
|
|
64
|
+
return;
|
|
65
|
+
this.currentSong = song;
|
|
66
|
+
this.currentIndex = index;
|
|
67
|
+
let url = ((_a = song.downloadUrl[this.selectedQuality]) === null || _a === void 0 ? void 0 : _a.url) || '';
|
|
68
|
+
// 🚀 Auto-convert http → https
|
|
69
|
+
if (url.startsWith('http://')) {
|
|
70
|
+
url = url.replace('http://', 'https://');
|
|
130
71
|
}
|
|
72
|
+
this.audio.src = url;
|
|
73
|
+
this.audio.load();
|
|
74
|
+
this.audio.play().then(() => {
|
|
75
|
+
this.isPlaying = true;
|
|
76
|
+
this.updateMediaSessionMetadata(song);
|
|
77
|
+
keep_awake_1.KeepAwake.keepAwake(); // Keep screen/CPU awake
|
|
78
|
+
if ('mediaSession' in navigator) {
|
|
79
|
+
navigator.mediaSession.playbackState = 'playing';
|
|
80
|
+
}
|
|
81
|
+
}).catch((err) => {
|
|
82
|
+
this.isPlaying = false;
|
|
83
|
+
console.warn('Audio play failed:', err);
|
|
84
|
+
});
|
|
131
85
|
}
|
|
132
86
|
static pause() {
|
|
133
|
-
|
|
134
|
-
native_audio_1.NativeAudio.pause({ assetId: 'currentSong' });
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
this.webAudio.pause();
|
|
138
|
-
}
|
|
87
|
+
this.audio.pause();
|
|
139
88
|
this.isPlaying = false;
|
|
140
89
|
keep_awake_1.KeepAwake.allowSleep();
|
|
90
|
+
if ('mediaSession' in navigator) {
|
|
91
|
+
navigator.mediaSession.playbackState = 'paused';
|
|
92
|
+
}
|
|
141
93
|
}
|
|
142
94
|
static resume() {
|
|
143
|
-
|
|
144
|
-
native_audio_1.NativeAudio.resume({ assetId: 'currentSong' });
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
this.webAudio.play();
|
|
148
|
-
}
|
|
95
|
+
this.audio.play();
|
|
149
96
|
this.isPlaying = true;
|
|
150
97
|
keep_awake_1.KeepAwake.keepAwake();
|
|
98
|
+
if ('mediaSession' in navigator) {
|
|
99
|
+
navigator.mediaSession.playbackState = 'playing';
|
|
100
|
+
}
|
|
151
101
|
}
|
|
152
102
|
static togglePlayPause() {
|
|
153
103
|
if (this.isPlaying) {
|
|
@@ -177,13 +127,8 @@ class Player {
|
|
|
177
127
|
}
|
|
178
128
|
}
|
|
179
129
|
static seek(seconds) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
this.webAudio.currentTime = seconds;
|
|
185
|
-
}
|
|
186
|
-
this.currentTime = seconds;
|
|
130
|
+
this.audio.currentTime = seconds;
|
|
131
|
+
this.updatePositionState();
|
|
187
132
|
}
|
|
188
133
|
static autoNext() {
|
|
189
134
|
this.next();
|
|
@@ -245,62 +190,65 @@ class Player {
|
|
|
245
190
|
return this.playlist;
|
|
246
191
|
}
|
|
247
192
|
// -------------------------------------------------------------------------
|
|
248
|
-
//
|
|
193
|
+
// Native Media Session (Lock Screen Controls)
|
|
249
194
|
// -------------------------------------------------------------------------
|
|
250
|
-
static
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// @capgo/native-audio usually emits 'progress' or we use getCurrentTime
|
|
260
|
-
// Checking docs: usually we poll or listen to 'progress'
|
|
261
|
-
// Assuming 'progress' event exists or we use setInterval
|
|
262
|
-
setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
263
|
-
if (this.isPlaying && this.isNative) {
|
|
264
|
-
try {
|
|
265
|
-
const result = yield native_audio_1.NativeAudio.getCurrentTime({ assetId: 'currentSong' });
|
|
266
|
-
this.currentTime = result.currentTime;
|
|
267
|
-
const durResult = yield native_audio_1.NativeAudio.getDuration({ assetId: 'currentSong' });
|
|
268
|
-
this.duration = durResult.duration;
|
|
195
|
+
static setupMediaSession() {
|
|
196
|
+
if ('mediaSession' in navigator) {
|
|
197
|
+
navigator.mediaSession.setActionHandler('play', () => this.resume());
|
|
198
|
+
navigator.mediaSession.setActionHandler('pause', () => this.pause());
|
|
199
|
+
navigator.mediaSession.setActionHandler('previoustrack', () => this.prev());
|
|
200
|
+
navigator.mediaSession.setActionHandler('nexttrack', () => this.next());
|
|
201
|
+
navigator.mediaSession.setActionHandler('seekto', (details) => {
|
|
202
|
+
if (details.seekTime !== undefined) {
|
|
203
|
+
this.seek(details.seekTime);
|
|
269
204
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}), 1000);
|
|
273
|
-
}
|
|
274
|
-
static setupWebListeners() {
|
|
275
|
-
this.webAudio.onended = () => this.autoNext();
|
|
276
|
-
this.webAudio.ontimeupdate = () => {
|
|
277
|
-
this.currentTime = this.webAudio.currentTime;
|
|
278
|
-
this.duration = this.webAudio.duration || 0;
|
|
279
|
-
};
|
|
280
|
-
this.webAudio.onplaying = () => this.isPlaying = true;
|
|
281
|
-
this.webAudio.onpause = () => this.isPlaying = false;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
282
207
|
}
|
|
283
|
-
static
|
|
208
|
+
static updateMediaSessionMetadata(song) {
|
|
284
209
|
var _a;
|
|
285
|
-
// ... (Keep existing Web MediaSession logic if needed, or simplify)
|
|
286
|
-
// Since we are focusing on Native, we can keep the basic one or copy the previous logic
|
|
287
|
-
// For brevity, I'll omit the full implementation here as Native is the priority
|
|
288
|
-
// But to be safe, let's keep a minimal version
|
|
289
210
|
if ('mediaSession' in navigator) {
|
|
211
|
+
const artwork = [];
|
|
212
|
+
if (song.image) {
|
|
213
|
+
if (Array.isArray(song.image)) {
|
|
214
|
+
song.image.forEach((img) => {
|
|
215
|
+
let src = img.link || img.url || (typeof img === 'string' ? img : '');
|
|
216
|
+
if (src) {
|
|
217
|
+
if (src.startsWith('http://')) {
|
|
218
|
+
src = src.replace('http://', 'https://');
|
|
219
|
+
}
|
|
220
|
+
artwork.push({ src, sizes: '500x500', type: 'image/jpeg' });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
else if (typeof song.image === 'string') {
|
|
225
|
+
let src = song.image;
|
|
226
|
+
if (src.startsWith('http://')) {
|
|
227
|
+
src = src.replace('http://', 'https://');
|
|
228
|
+
}
|
|
229
|
+
artwork.push({ src: src, sizes: '500x500', type: 'image/jpeg' });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
290
232
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
291
233
|
title: song.name || song.title || 'Unknown Title',
|
|
292
234
|
artist: song.primaryArtists || song.artist || 'Unknown Artist',
|
|
293
|
-
album: ((_a = song.album) === null || _a === void 0 ? void 0 : _a.name) || song.album || '',
|
|
294
|
-
artwork:
|
|
235
|
+
album: ((_a = song.album) === null || _a === void 0 ? void 0 : _a.name) || song.album || 'Unknown Album',
|
|
236
|
+
artwork: artwork.length > 0 ? artwork : undefined
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
static updatePositionState() {
|
|
241
|
+
if ('mediaSession' in navigator && this.duration > 0) {
|
|
242
|
+
navigator.mediaSession.setPositionState({
|
|
243
|
+
duration: this.duration,
|
|
244
|
+
playbackRate: this.audio.playbackRate,
|
|
245
|
+
position: this.audio.currentTime
|
|
295
246
|
});
|
|
296
|
-
navigator.mediaSession.setActionHandler('play', () => this.resume());
|
|
297
|
-
navigator.mediaSession.setActionHandler('pause', () => this.pause());
|
|
298
|
-
navigator.mediaSession.setActionHandler('previoustrack', () => this.prev());
|
|
299
|
-
navigator.mediaSession.setActionHandler('nexttrack', () => this.next());
|
|
300
247
|
}
|
|
301
248
|
}
|
|
302
249
|
}
|
|
303
250
|
exports.Player = Player;
|
|
251
|
+
Player.audio = new Audio();
|
|
304
252
|
Player.currentSong = null;
|
|
305
253
|
Player.currentIndex = 0;
|
|
306
254
|
Player.isPlaying = false;
|
|
@@ -311,5 +259,3 @@ Player.queue = [];
|
|
|
311
259
|
Player.queue$ = new rxjs_1.BehaviorSubject([]);
|
|
312
260
|
Player.playlist = [];
|
|
313
261
|
Player.selectedQuality = 3;
|
|
314
|
-
Player.isNative = core_1.Capacitor.isNativePlatform();
|
|
315
|
-
Player.webAudio = new Audio(); // Fallback for web
|
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",
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@capacitor-community/keep-awake": "^7.1.0",
|
|
33
|
-
"@capgo/native-audio": "^7.9.9",
|
|
34
33
|
"rxjs": "^7.8.2"
|
|
35
34
|
}
|
|
36
35
|
}
|
package/src/core/player.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { BehaviorSubject } from 'rxjs';
|
|
2
|
-
import { NativeAudio } from '@capgo/native-audio';
|
|
3
2
|
import { KeepAwake } from '@capacitor-community/keep-awake';
|
|
4
|
-
import { Capacitor } from '@capacitor/core';
|
|
5
3
|
|
|
6
4
|
export class Player {
|
|
5
|
+
private static audio = new Audio();
|
|
7
6
|
private static currentSong: any = null;
|
|
8
7
|
private static currentIndex = 0;
|
|
9
8
|
private static isPlaying = false;
|
|
@@ -14,41 +13,72 @@ export class Player {
|
|
|
14
13
|
static queue$ = new BehaviorSubject<any[]>([]);
|
|
15
14
|
private static playlist: any[] = [];
|
|
16
15
|
private static selectedQuality = 3;
|
|
17
|
-
private static isNative = Capacitor.isNativePlatform();
|
|
18
|
-
private static webAudio = new Audio(); // Fallback for web
|
|
19
16
|
|
|
20
17
|
/** Initialize with playlist and quality */
|
|
21
|
-
static
|
|
18
|
+
static initialize(playlist: any[], quality = 3) {
|
|
22
19
|
this.playlist = playlist;
|
|
23
20
|
this.selectedQuality = quality;
|
|
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
|
+
};
|
|
24
37
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
showNotification: true,
|
|
30
|
-
background: true,
|
|
31
|
-
focus: true
|
|
32
|
-
});
|
|
33
|
-
} catch (e) {
|
|
34
|
-
console.warn('NativeAudio configure failed:', e);
|
|
38
|
+
this.audio.ontimeupdate = () => {
|
|
39
|
+
this.currentTime = this.audio.currentTime;
|
|
40
|
+
if (Math.floor(this.currentTime) % 5 === 0) {
|
|
41
|
+
this.updatePositionState();
|
|
35
42
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
+
};
|
|
40
72
|
}
|
|
41
73
|
|
|
42
|
-
/** Call this once on user gesture to unlock audio in WebView
|
|
74
|
+
/** Call this once on user gesture to unlock audio in WebView */
|
|
43
75
|
static unlockAudio() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.webAudio.play().catch(() => { });
|
|
48
|
-
}
|
|
76
|
+
this.audio.src = '';
|
|
77
|
+
this.audio.load();
|
|
78
|
+
this.audio.play().catch(() => { });
|
|
49
79
|
}
|
|
50
80
|
|
|
51
|
-
static
|
|
81
|
+
static play(song: any, index: number = 0) {
|
|
52
82
|
if (!song || !song.downloadUrl) return;
|
|
53
83
|
|
|
54
84
|
this.currentSong = song;
|
|
@@ -61,94 +91,38 @@ export class Player {
|
|
|
61
91
|
url = url.replace('http://', 'https://');
|
|
62
92
|
}
|
|
63
93
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.playWeb(url, song);
|
|
69
|
-
}
|
|
94
|
+
this.audio.src = url;
|
|
95
|
+
this.audio.load();
|
|
96
|
+
|
|
97
|
+
this.audio.play().then(() => {
|
|
70
98
|
this.isPlaying = true;
|
|
71
|
-
|
|
72
|
-
|
|
99
|
+
this.updateMediaSessionMetadata(song);
|
|
100
|
+
KeepAwake.keepAwake(); // Keep screen/CPU awake
|
|
101
|
+
if ('mediaSession' in navigator) {
|
|
102
|
+
navigator.mediaSession.playbackState = 'playing';
|
|
103
|
+
}
|
|
104
|
+
}).catch((err) => {
|
|
73
105
|
this.isPlaying = false;
|
|
74
106
|
console.warn('Audio play failed:', err);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private static async playNative(url: string, song: any) {
|
|
79
|
-
try {
|
|
80
|
-
// Unload previous if any (though preload might handle it, safer to be clean)
|
|
81
|
-
// NativeAudio.unload({ assetId: 'currentSong' }).catch(() => {});
|
|
82
|
-
|
|
83
|
-
// Prepare artwork for lock screen
|
|
84
|
-
let artworkPath = '';
|
|
85
|
-
if (song.image) {
|
|
86
|
-
if (Array.isArray(song.image)) {
|
|
87
|
-
// Get highest quality image
|
|
88
|
-
const img = song.image[song.image.length - 1];
|
|
89
|
-
artworkPath = img.link || img.url || (typeof img === 'string' ? img : '');
|
|
90
|
-
} else if (typeof song.image === 'string') {
|
|
91
|
-
artworkPath = song.image;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (artworkPath.startsWith('http://')) {
|
|
95
|
-
artworkPath = artworkPath.replace('http://', 'https://');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
await NativeAudio.preload({
|
|
99
|
-
assetId: 'currentSong',
|
|
100
|
-
assetPath: url,
|
|
101
|
-
isUrl: true,
|
|
102
|
-
audioChannelNum: 1,
|
|
103
|
-
// Metadata for Lock Screen
|
|
104
|
-
notificationMetadata: {
|
|
105
|
-
album: song.album?.name || song.album || 'Unknown Album',
|
|
106
|
-
artist: song.primaryArtists || song.artist || 'Unknown Artist',
|
|
107
|
-
title: song.name || song.title || 'Unknown Title',
|
|
108
|
-
artworkUrl: artworkPath
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
await NativeAudio.play({ assetId: 'currentSong' });
|
|
113
|
-
|
|
114
|
-
} catch (e) {
|
|
115
|
-
console.error("Native play error", e);
|
|
116
|
-
throw e;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private static playWeb(url: string, song: any) {
|
|
121
|
-
this.webAudio.src = url;
|
|
122
|
-
// @ts-ignore
|
|
123
|
-
this.webAudio.title = song.name || song.title || 'Unknown Title';
|
|
124
|
-
this.webAudio.preload = 'auto';
|
|
125
|
-
this.webAudio.load();
|
|
126
|
-
this.webAudio.play();
|
|
127
|
-
|
|
128
|
-
// Basic MediaSession for Web
|
|
129
|
-
if ('mediaSession' in navigator) {
|
|
130
|
-
this.updateWebMediaSession(song);
|
|
131
|
-
}
|
|
107
|
+
});
|
|
132
108
|
}
|
|
133
109
|
|
|
134
110
|
static pause() {
|
|
135
|
-
|
|
136
|
-
NativeAudio.pause({ assetId: 'currentSong' });
|
|
137
|
-
} else {
|
|
138
|
-
this.webAudio.pause();
|
|
139
|
-
}
|
|
111
|
+
this.audio.pause();
|
|
140
112
|
this.isPlaying = false;
|
|
141
113
|
KeepAwake.allowSleep();
|
|
114
|
+
if ('mediaSession' in navigator) {
|
|
115
|
+
navigator.mediaSession.playbackState = 'paused';
|
|
116
|
+
}
|
|
142
117
|
}
|
|
143
118
|
|
|
144
119
|
static resume() {
|
|
145
|
-
|
|
146
|
-
NativeAudio.resume({ assetId: 'currentSong' });
|
|
147
|
-
} else {
|
|
148
|
-
this.webAudio.play();
|
|
149
|
-
}
|
|
120
|
+
this.audio.play();
|
|
150
121
|
this.isPlaying = true;
|
|
151
122
|
KeepAwake.keepAwake();
|
|
123
|
+
if ('mediaSession' in navigator) {
|
|
124
|
+
navigator.mediaSession.playbackState = 'playing';
|
|
125
|
+
}
|
|
152
126
|
}
|
|
153
127
|
|
|
154
128
|
static togglePlayPause() {
|
|
@@ -179,12 +153,8 @@ export class Player {
|
|
|
179
153
|
}
|
|
180
154
|
|
|
181
155
|
static seek(seconds: number) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
} else {
|
|
185
|
-
this.webAudio.currentTime = seconds;
|
|
186
|
-
}
|
|
187
|
-
this.currentTime = seconds;
|
|
156
|
+
this.audio.currentTime = seconds;
|
|
157
|
+
this.updatePositionState();
|
|
188
158
|
}
|
|
189
159
|
|
|
190
160
|
static autoNext() {
|
|
@@ -261,61 +231,62 @@ export class Player {
|
|
|
261
231
|
}
|
|
262
232
|
|
|
263
233
|
// -------------------------------------------------------------------------
|
|
264
|
-
//
|
|
234
|
+
// Native Media Session (Lock Screen Controls)
|
|
265
235
|
// -------------------------------------------------------------------------
|
|
266
236
|
|
|
267
|
-
private static
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// Assuming 'progress' event exists or we use setInterval
|
|
280
|
-
setInterval(async () => {
|
|
281
|
-
if (this.isPlaying && this.isNative) {
|
|
282
|
-
try {
|
|
283
|
-
const result = await NativeAudio.getCurrentTime({ assetId: 'currentSong' });
|
|
284
|
-
this.currentTime = result.currentTime;
|
|
285
|
-
|
|
286
|
-
const durResult = await NativeAudio.getDuration({ assetId: 'currentSong' });
|
|
287
|
-
this.duration = durResult.duration;
|
|
288
|
-
} catch (e) { }
|
|
289
|
-
}
|
|
290
|
-
}, 1000);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private static setupWebListeners() {
|
|
294
|
-
this.webAudio.onended = () => this.autoNext();
|
|
295
|
-
this.webAudio.ontimeupdate = () => {
|
|
296
|
-
this.currentTime = this.webAudio.currentTime;
|
|
297
|
-
this.duration = this.webAudio.duration || 0;
|
|
298
|
-
};
|
|
299
|
-
this.webAudio.onplaying = () => this.isPlaying = true;
|
|
300
|
-
this.webAudio.onpause = () => this.isPlaying = false;
|
|
237
|
+
private static setupMediaSession() {
|
|
238
|
+
if ('mediaSession' in navigator) {
|
|
239
|
+
navigator.mediaSession.setActionHandler('play', () => this.resume());
|
|
240
|
+
navigator.mediaSession.setActionHandler('pause', () => this.pause());
|
|
241
|
+
navigator.mediaSession.setActionHandler('previoustrack', () => this.prev());
|
|
242
|
+
navigator.mediaSession.setActionHandler('nexttrack', () => this.next());
|
|
243
|
+
navigator.mediaSession.setActionHandler('seekto', (details) => {
|
|
244
|
+
if (details.seekTime !== undefined) {
|
|
245
|
+
this.seek(details.seekTime);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
301
249
|
}
|
|
302
250
|
|
|
303
|
-
private static
|
|
304
|
-
// ... (Keep existing Web MediaSession logic if needed, or simplify)
|
|
305
|
-
// Since we are focusing on Native, we can keep the basic one or copy the previous logic
|
|
306
|
-
// For brevity, I'll omit the full implementation here as Native is the priority
|
|
307
|
-
// But to be safe, let's keep a minimal version
|
|
251
|
+
private static updateMediaSessionMetadata(song: any) {
|
|
308
252
|
if ('mediaSession' in navigator) {
|
|
253
|
+
const artwork = [];
|
|
254
|
+
if (song.image) {
|
|
255
|
+
if (Array.isArray(song.image)) {
|
|
256
|
+
song.image.forEach((img: any) => {
|
|
257
|
+
let src = img.link || img.url || (typeof img === 'string' ? img : '');
|
|
258
|
+
if (src) {
|
|
259
|
+
if (src.startsWith('http://')) {
|
|
260
|
+
src = src.replace('http://', 'https://');
|
|
261
|
+
}
|
|
262
|
+
artwork.push({ src, sizes: '500x500', type: 'image/jpeg' });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
} else if (typeof song.image === 'string') {
|
|
266
|
+
let src = song.image;
|
|
267
|
+
if (src.startsWith('http://')) {
|
|
268
|
+
src = src.replace('http://', 'https://');
|
|
269
|
+
}
|
|
270
|
+
artwork.push({ src: src, sizes: '500x500', type: 'image/jpeg' });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
309
274
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
310
275
|
title: song.name || song.title || 'Unknown Title',
|
|
311
276
|
artist: song.primaryArtists || song.artist || 'Unknown Artist',
|
|
312
|
-
album: song.album?.name || song.album || '',
|
|
313
|
-
artwork:
|
|
277
|
+
album: song.album?.name || song.album || 'Unknown Album',
|
|
278
|
+
artwork: artwork.length > 0 ? artwork : undefined
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private static updatePositionState() {
|
|
284
|
+
if ('mediaSession' in navigator && this.duration > 0) {
|
|
285
|
+
navigator.mediaSession.setPositionState({
|
|
286
|
+
duration: this.duration,
|
|
287
|
+
playbackRate: this.audio.playbackRate,
|
|
288
|
+
position: this.audio.currentTime
|
|
314
289
|
});
|
|
315
|
-
navigator.mediaSession.setActionHandler('play', () => this.resume());
|
|
316
|
-
navigator.mediaSession.setActionHandler('pause', () => this.pause());
|
|
317
|
-
navigator.mediaSession.setActionHandler('previoustrack', () => this.prev());
|
|
318
|
-
navigator.mediaSession.setActionHandler('nexttrack', () => this.next());
|
|
319
290
|
}
|
|
320
291
|
}
|
|
321
292
|
}
|