tiny-essentials 1.28.7 → 1.28.9
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/changelog/1/28/9.md +17 -0
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyDebugger.min.js +1 -0
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyMediaPlayer.min.js +1 -1
- package/dist/v1/TinyRadioFm.min.js +1 -1
- package/dist/v1/TinyUploadClicker.min.js +1 -1
- package/dist/v1/basics/browserDetector.cjs +228 -0
- package/dist/v1/basics/browserDetector.d.mts +66 -0
- package/dist/v1/basics/browserDetector.mjs +207 -0
- package/dist/v1/basics/index.cjs +6 -0
- package/dist/v1/basics/index.d.mts +2 -1
- package/dist/v1/basics/index.mjs +2 -1
- package/dist/v1/build/TinyDebugger.cjs +7 -0
- package/dist/v1/build/TinyDebugger.d.mts +3 -0
- package/dist/v1/build/TinyDebugger.mjs +2 -0
- package/dist/v1/index.cjs +8 -0
- package/dist/v1/index.d.mts +3 -1
- package/dist/v1/index.mjs +4 -2
- package/dist/v1/libs/TinyDebugger.cjs +380 -0
- package/dist/v1/libs/TinyDebugger.d.mts +132 -0
- package/dist/v1/libs/TinyDebugger.mjs +351 -0
- package/dist/v1/libs/TinyMediaPlayer/HtmlAudio.cjs +39 -3
- package/dist/v1/libs/TinyMediaPlayer/HtmlAudio.d.mts +10 -0
- package/dist/v1/libs/TinyMediaPlayer/HtmlAudio.mjs +35 -3
- package/dist/v1/libs/TinyMediaPlayer/HtmlVideo.cjs +39 -3
- package/dist/v1/libs/TinyMediaPlayer/HtmlVideo.d.mts +10 -0
- package/dist/v1/libs/TinyMediaPlayer/HtmlVideo.mjs +35 -3
- package/dist/v1/libs/TinyMediaPlayer/Mock.cjs +333 -0
- package/dist/v1/libs/TinyMediaPlayer/Mock.d.mts +154 -0
- package/dist/v1/libs/TinyMediaPlayer/Mock.mjs +296 -0
- package/dist/v1/libs/TinyMediaPlayer/SoundCloud.cjs +30 -0
- package/dist/v1/libs/TinyMediaPlayer/SoundCloud.d.mts +10 -0
- package/dist/v1/libs/TinyMediaPlayer/SoundCloud.mjs +26 -0
- package/dist/v1/libs/TinyMediaPlayer/Youtube.cjs +39 -3
- package/dist/v1/libs/TinyMediaPlayer/Youtube.d.mts +10 -0
- package/dist/v1/libs/TinyMediaPlayer/Youtube.mjs +32 -3
- package/dist/v1/libs/TinyMediaPlayer/index.cjs +18 -0
- package/dist/v1/libs/TinyMediaPlayer/index.d.mts +10 -0
- package/dist/v1/libs/TinyMediaPlayer/index.mjs +16 -0
- package/dist/v1/libs/TinyMediaPlayer.cjs +156 -31
- package/dist/v1/libs/TinyMediaPlayer.d.mts +43 -7
- package/dist/v1/libs/TinyMediaPlayer.mjs +148 -31
- package/dist/v1/libs/TinyPromiseQueue.cjs +6 -4
- package/dist/v1/libs/TinyPromiseQueue.d.mts +8 -6
- package/dist/v1/libs/TinyPromiseQueue.mjs +6 -4
- package/dist/v1/libs/TinyRadioFm.cjs +271 -138
- package/dist/v1/libs/TinyRadioFm.d.mts +67 -3
- package/dist/v1/libs/TinyRadioFm.mjs +239 -123
- package/docs/v1/README.md +2 -0
- package/docs/v1/basics/browserDetector.md +135 -0
- package/docs/v1/libs/TinyDebugger.md +119 -0
- package/docs/v1/libs/TinyMediaPlayer.md +26 -22
- package/docs/v1/libs/TinyRadioFm.md +25 -3
- package/package.json +13 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var TinyDebugger = require('./TinyDebugger.cjs');
|
|
4
4
|
var mediaContent = require('../basics/mediaContent.cjs');
|
|
5
5
|
var utils = require('./utils.cjs');
|
|
6
6
|
var index = require('./TinyMediaPlayer/index.cjs');
|
|
@@ -34,6 +34,9 @@ const checkDestroy = utils.createCheckDestroyed('TinyMediaPlayer');
|
|
|
34
34
|
* @property {boolean} [repeatCurrentOnPrev=false] - If true, clicking 'previous' repeats the current track on the first click.
|
|
35
35
|
* @property {boolean} [smoothPlayPauseVolume=false] - If true, volume fades smoothly during play/pause transitions.
|
|
36
36
|
* @property {boolean} [smoothStopVolume=false] - If true, volume fades smoothly to zero when stopping.
|
|
37
|
+
* @property {boolean} [debugMode=false] - Whether to enable internal debug logging.
|
|
38
|
+
* @property {boolean} [useLogColors=false] - Whether to enable log color support.
|
|
39
|
+
* @property {Console} [logger=console] - A custom logger object (must implement console methods).
|
|
37
40
|
*/
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -46,7 +49,7 @@ const checkDestroy = utils.createCheckDestroyed('TinyMediaPlayer');
|
|
|
46
49
|
/**
|
|
47
50
|
* A universal media player manager capable of orchestrating multiple API adapters.
|
|
48
51
|
*/
|
|
49
|
-
class TinyMediaPlayer extends
|
|
52
|
+
class TinyMediaPlayer extends TinyDebugger {
|
|
50
53
|
static BaseMediaAdapter = index.BaseMediaAdapter;
|
|
51
54
|
|
|
52
55
|
/**
|
|
@@ -266,11 +269,18 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
266
269
|
* @throws {RangeError} If numeric options are out of valid ranges.
|
|
267
270
|
*/
|
|
268
271
|
constructor(options = {}) {
|
|
269
|
-
super();
|
|
270
272
|
if (!objChecker.isValidObj(options)) {
|
|
271
273
|
throw new TypeError('Options must be a non-null object.');
|
|
272
274
|
}
|
|
273
275
|
|
|
276
|
+
// Debug Configuration
|
|
277
|
+
super({
|
|
278
|
+
id: '[_blue_TinyMediaPlayer_reset_] :debug:',
|
|
279
|
+
logger: options.logger ?? console,
|
|
280
|
+
debugMode: options.debugMode ?? false,
|
|
281
|
+
useLogColors: options.useLogColors ?? false,
|
|
282
|
+
});
|
|
283
|
+
|
|
274
284
|
// Volume configuration
|
|
275
285
|
if (options.persistVolume !== undefined) {
|
|
276
286
|
if (typeof options.persistVolume !== 'boolean') {
|
|
@@ -318,6 +328,8 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
318
328
|
}
|
|
319
329
|
|
|
320
330
|
if (this.#persistVolume) this.#loadVolumeFromStorage();
|
|
331
|
+
|
|
332
|
+
this.log('info', 'Instance initialized.', { options });
|
|
321
333
|
}
|
|
322
334
|
|
|
323
335
|
// ==========================================
|
|
@@ -335,6 +347,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
335
347
|
const parsedVolume = parseFloat(storedValue);
|
|
336
348
|
if (!isNaN(parsedVolume) && parsedVolume >= 0 && parsedVolume <= 1) {
|
|
337
349
|
this.#volume = parsedVolume;
|
|
350
|
+
this.log('info', `Volume loaded from storage: ${this.#volume}`);
|
|
338
351
|
}
|
|
339
352
|
}
|
|
340
353
|
}
|
|
@@ -350,6 +363,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
350
363
|
try {
|
|
351
364
|
if (this.#persistVolume && typeof window !== 'undefined' && window.localStorage) {
|
|
352
365
|
window.localStorage.setItem(this.#volumeStorageKey, this.#volume.toString());
|
|
366
|
+
this.log('info', `Volume saved to storage: ${this.#volume}`);
|
|
353
367
|
}
|
|
354
368
|
} catch (error) {
|
|
355
369
|
console.warn('[TinyMediaPlayer] Failed to save volume to localStorage.', error);
|
|
@@ -421,10 +435,15 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
421
435
|
const startTime = performance.now();
|
|
422
436
|
const duration = this.#fadeVolumeSpeed;
|
|
423
437
|
|
|
438
|
+
this.log('info', `Starting volume fade: ${startVolume} -> ${targetVolume}`);
|
|
439
|
+
|
|
424
440
|
return new Promise((resolve) => {
|
|
425
441
|
/** @param {number} currentTime */
|
|
426
442
|
const step = (currentTime) => {
|
|
427
|
-
if (signal.aborted)
|
|
443
|
+
if (signal.aborted) {
|
|
444
|
+
this.log('info', 'Volume fade aborted.');
|
|
445
|
+
return resolve();
|
|
446
|
+
}
|
|
428
447
|
|
|
429
448
|
const elapsed = currentTime - startTime;
|
|
430
449
|
const progress = Math.min(elapsed / duration, 1);
|
|
@@ -436,6 +455,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
436
455
|
requestAnimationFrame(step);
|
|
437
456
|
} else {
|
|
438
457
|
adapter.setVolume(targetVolume);
|
|
458
|
+
this.log('info', `Volume fade completed: ${targetVolume}`);
|
|
439
459
|
resolve();
|
|
440
460
|
}
|
|
441
461
|
};
|
|
@@ -477,6 +497,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
477
497
|
}
|
|
478
498
|
|
|
479
499
|
this.#playlist = value;
|
|
500
|
+
this.log('info', `Playlist updated. New size: ${this.#playlist.length}`);
|
|
480
501
|
this.emit('playlistUpdate', this.playlist);
|
|
481
502
|
|
|
482
503
|
// Validation to correct currentIndex ensuring it aligns with the new playlist limits
|
|
@@ -497,6 +518,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
497
518
|
if (oldAdapter) await oldAdapter.stop();
|
|
498
519
|
if (this.#currentIndex !== -1) await this.play();
|
|
499
520
|
} catch (error) {
|
|
521
|
+
this.log('error', 'Background transition error on playlist update:', error);
|
|
500
522
|
console.warn('[TinyMediaPlayer] Background transition error on playlist update:', error);
|
|
501
523
|
}
|
|
502
524
|
})();
|
|
@@ -525,6 +547,8 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
525
547
|
|
|
526
548
|
if (this.#currentIndex === value) return; // Optimization: do nothing if index is the same
|
|
527
549
|
|
|
550
|
+
this.log('info', `Index change requested: ${this.#currentIndex} -> ${value}`);
|
|
551
|
+
|
|
528
552
|
const wasPlaying = this.#isPlaying;
|
|
529
553
|
let oldAdapter = null;
|
|
530
554
|
|
|
@@ -546,6 +570,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
546
570
|
if (oldAdapter) await oldAdapter.stop();
|
|
547
571
|
if (this.#currentIndex !== -1) await this.play();
|
|
548
572
|
} catch (error) {
|
|
573
|
+
this.log('error', 'Background transition error on index update:', error);
|
|
549
574
|
console.warn('[TinyMediaPlayer] Background transition error on index update:', error);
|
|
550
575
|
}
|
|
551
576
|
})();
|
|
@@ -569,6 +594,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
569
594
|
throw new TypeError(`Loop mode must be one of: ${validModes.join(', ')}.`);
|
|
570
595
|
}
|
|
571
596
|
this.#loopMode = value;
|
|
597
|
+
this.log('info', `Loop mode changed to: ${this.#loopMode}`);
|
|
572
598
|
this.emit('loopModeChange', this.#loopMode);
|
|
573
599
|
}
|
|
574
600
|
|
|
@@ -588,6 +614,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
588
614
|
throw new TypeError('Random mode state must be a boolean.');
|
|
589
615
|
}
|
|
590
616
|
this.#isRandom = value;
|
|
617
|
+
this.log('info', `Random mode: ${this.#isRandom}`);
|
|
591
618
|
this.emit('randomModeChange', this.#isRandom);
|
|
592
619
|
}
|
|
593
620
|
|
|
@@ -617,6 +644,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
617
644
|
// Abort any ongoing fade to ensure manual control is immediate
|
|
618
645
|
if (this.#fadeController) this.#fadeController.abort();
|
|
619
646
|
|
|
647
|
+
this.log('info', `Volume changed: ${this.#volume} -> ${value}`);
|
|
620
648
|
this.#volume = value;
|
|
621
649
|
this.#saveVolumeToStorage();
|
|
622
650
|
this.emit('volumeChange', this.#volume);
|
|
@@ -648,6 +676,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
648
676
|
throw new TypeError('Persist volume parameter must be a boolean.');
|
|
649
677
|
}
|
|
650
678
|
this.#persistVolume = value;
|
|
679
|
+
this.log('info', `Persist volume: ${this.#persistVolume}`);
|
|
651
680
|
if (value) {
|
|
652
681
|
this.#saveVolumeToStorage();
|
|
653
682
|
}
|
|
@@ -693,30 +722,31 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
693
722
|
}
|
|
694
723
|
|
|
695
724
|
/**
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
* @param {boolean} value - True to mute, false to unMute.
|
|
699
|
-
* @throws {TypeError} If the value is not a boolean.
|
|
725
|
+
* Mutes the current playback.
|
|
726
|
+
* @returns {Promise<void>}
|
|
700
727
|
*/
|
|
701
|
-
|
|
728
|
+
async mute() {
|
|
702
729
|
checkDestroy(this.#destroyed);
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
this.#isMuted
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
try {
|
|
711
|
-
const adapter = this.#getActiveAdapter();
|
|
712
|
-
if (adapter) {
|
|
713
|
-
value ? adapter.mute() : adapter.unMute();
|
|
714
|
-
}
|
|
715
|
-
} catch (error) {
|
|
716
|
-
// If no active adapter, we just wait for the next play() call to apply the state
|
|
717
|
-
}
|
|
730
|
+
const adapter = this.#getActiveAdapter();
|
|
731
|
+
if (!adapter) return;
|
|
732
|
+
await adapter.mute();
|
|
733
|
+
this.#isMuted = true;
|
|
734
|
+
this.emit('muteChange', this.#isMuted);
|
|
735
|
+
this.log('info', 'Playback muted successfully.');
|
|
736
|
+
}
|
|
718
737
|
|
|
738
|
+
/**
|
|
739
|
+
* Unmutes the current playback.
|
|
740
|
+
* @returns {Promise<void>}
|
|
741
|
+
*/
|
|
742
|
+
async unMute() {
|
|
743
|
+
checkDestroy(this.#destroyed);
|
|
744
|
+
const adapter = this.#getActiveAdapter();
|
|
745
|
+
if (!adapter) return;
|
|
746
|
+
await adapter.unMute();
|
|
747
|
+
this.#isMuted = false;
|
|
719
748
|
this.emit('muteChange', this.#isMuted);
|
|
749
|
+
this.log('info', 'Playback unmuted successfully.');
|
|
720
750
|
}
|
|
721
751
|
|
|
722
752
|
// ==========================================
|
|
@@ -773,6 +803,8 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
773
803
|
throw new TypeError('Adapter must be an instance of BaseMediaAdapter.');
|
|
774
804
|
}
|
|
775
805
|
|
|
806
|
+
this.log('info', 'Registering new adapter:', adapter);
|
|
807
|
+
|
|
776
808
|
const events = new Map();
|
|
777
809
|
this.#adapterHandlers.set(adapter, events);
|
|
778
810
|
|
|
@@ -795,6 +827,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
795
827
|
if (!(adapter instanceof index.BaseMediaAdapter)) {
|
|
796
828
|
throw new TypeError('Adapter must be an instance of BaseMediaAdapter.');
|
|
797
829
|
}
|
|
830
|
+
this.log('info', 'Removing adapter:', adapter);
|
|
798
831
|
this.#removeHandler(adapter);
|
|
799
832
|
return this.#adapters.delete(adapter);
|
|
800
833
|
}
|
|
@@ -809,6 +842,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
809
842
|
if (!(adapter instanceof index.BaseMediaAdapter)) {
|
|
810
843
|
throw new TypeError('Adapter must be an instance of BaseMediaAdapter.');
|
|
811
844
|
}
|
|
845
|
+
this.log('info', 'Destroying adapter:', adapter);
|
|
812
846
|
this.#removeHandler(adapter);
|
|
813
847
|
const result = this.#adapters.delete(adapter);
|
|
814
848
|
if (result) adapter.destroy();
|
|
@@ -833,6 +867,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
833
867
|
*/
|
|
834
868
|
destroyAllAdapters() {
|
|
835
869
|
checkDestroy(this.#destroyed);
|
|
870
|
+
this.log('info', 'Destroying all adapters.');
|
|
836
871
|
this.#adapters.forEach((adapter) => {
|
|
837
872
|
this.#removeHandler(adapter);
|
|
838
873
|
adapter.destroy();
|
|
@@ -845,6 +880,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
845
880
|
*/
|
|
846
881
|
clearAdapters() {
|
|
847
882
|
checkDestroy(this.#destroyed);
|
|
883
|
+
this.log('info', 'Clearing all adapters.');
|
|
848
884
|
this.#adapters.forEach((adapter) => this.#removeHandler(adapter));
|
|
849
885
|
this.#adapters.clear();
|
|
850
886
|
}
|
|
@@ -877,7 +913,9 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
877
913
|
const currentContent = this.#playlist[this.#currentIndex];
|
|
878
914
|
const adapter = this.getMediaAdapter(currentContent);
|
|
879
915
|
if (adapter) return adapter;
|
|
880
|
-
|
|
916
|
+
const errorMsg = `No compatible adapter found for content ID: ${currentContent.id}.`;
|
|
917
|
+
this.log('error', errorMsg);
|
|
918
|
+
throw new Error(errorMsg);
|
|
881
919
|
}
|
|
882
920
|
|
|
883
921
|
// ==========================================
|
|
@@ -898,6 +936,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
898
936
|
// If the event is already in the list, do nothing to avoid duplicates
|
|
899
937
|
if (this.#adapterEventNames.has(eventName)) return;
|
|
900
938
|
|
|
939
|
+
this.log('info', `Adding adapter event listener: ${eventName}`);
|
|
901
940
|
this.#adapterEventNames.add(eventName);
|
|
902
941
|
|
|
903
942
|
// Synchronize: Add the listener to all already registered adapters
|
|
@@ -919,6 +958,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
919
958
|
|
|
920
959
|
if (!this.#adapterEventNames.has(eventName)) return;
|
|
921
960
|
|
|
961
|
+
this.log('info', `Removing adapter event listener: ${eventName}`);
|
|
922
962
|
this.#adapterEventNames.delete(eventName);
|
|
923
963
|
|
|
924
964
|
// Synchronize: Remove the listener from all registered adapters
|
|
@@ -966,6 +1006,8 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
966
1006
|
/** @type {(...args: any[]) => any} */
|
|
967
1007
|
const handler = (...args) => {
|
|
968
1008
|
if (!this.#destroyed) {
|
|
1009
|
+
if (eventName !== 'timeupdate')
|
|
1010
|
+
this.log('debug', `Adapter event triggered: ${eventName}`, ...args);
|
|
969
1011
|
this.emit(eventName, ...args);
|
|
970
1012
|
}
|
|
971
1013
|
};
|
|
@@ -1011,6 +1053,8 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1011
1053
|
mediaContent.valMediaContentMetadata(newContent);
|
|
1012
1054
|
|
|
1013
1055
|
const newLength = this.#playlist.push(newContent);
|
|
1056
|
+
this.log('info', `Track added. New length: ${newLength}`, newContent);
|
|
1057
|
+
|
|
1014
1058
|
if (this.#currentIndex === -1) {
|
|
1015
1059
|
this.#currentIndex = 0;
|
|
1016
1060
|
this.emit('trackChange', this.#currentIndex);
|
|
@@ -1067,6 +1111,8 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1067
1111
|
throw new RangeError(`Index ${index} is out of bounds for the current playlist.`);
|
|
1068
1112
|
}
|
|
1069
1113
|
|
|
1114
|
+
this.log('info', `Removing track at index: ${index}`);
|
|
1115
|
+
|
|
1070
1116
|
if (index === this.#currentIndex) {
|
|
1071
1117
|
// Stop the current track if it is the one being removed
|
|
1072
1118
|
await this.stop();
|
|
@@ -1103,6 +1149,8 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1103
1149
|
throw new TypeError('Search query must be a string or a boolean evaluation function.');
|
|
1104
1150
|
}
|
|
1105
1151
|
|
|
1152
|
+
this.log('info', 'Searching playlist...', { query });
|
|
1153
|
+
|
|
1106
1154
|
const results = [];
|
|
1107
1155
|
|
|
1108
1156
|
for (let i = 0; i < this.#playlist.length; i++) {
|
|
@@ -1125,6 +1173,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1125
1173
|
}
|
|
1126
1174
|
}
|
|
1127
1175
|
|
|
1176
|
+
this.log('info', `Search completed. Found ${results.length} results.`);
|
|
1128
1177
|
return results;
|
|
1129
1178
|
}
|
|
1130
1179
|
|
|
@@ -1133,6 +1182,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1133
1182
|
*/
|
|
1134
1183
|
async clearPlaylist() {
|
|
1135
1184
|
checkDestroy(this.#destroyed);
|
|
1185
|
+
this.log('info', 'Clearing entire playlist.');
|
|
1136
1186
|
await this.stop();
|
|
1137
1187
|
this.#playlist = [];
|
|
1138
1188
|
this.#currentIndex = -1;
|
|
@@ -1150,6 +1200,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1150
1200
|
*/
|
|
1151
1201
|
async play() {
|
|
1152
1202
|
checkDestroy(this.#destroyed);
|
|
1203
|
+
this.log('info', `Attempting to play track at index: ${this.#currentIndex}`);
|
|
1153
1204
|
const adapter = this.#getActiveAdapter();
|
|
1154
1205
|
if (!adapter) return;
|
|
1155
1206
|
|
|
@@ -1160,10 +1211,16 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1160
1211
|
adapter.setVolume(this.#volume);
|
|
1161
1212
|
}
|
|
1162
1213
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1214
|
+
try {
|
|
1215
|
+
await adapter.play(this.#playlist[this.#currentIndex]);
|
|
1216
|
+
if (this.#isMuted) await adapter.mute();
|
|
1217
|
+
this.#isPlaying = true;
|
|
1218
|
+
this.log('info', 'Playback started successfully.');
|
|
1219
|
+
this.emit('play', this.#currentIndex);
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
this.log('error', 'Failed to play track:', error);
|
|
1222
|
+
throw error;
|
|
1223
|
+
}
|
|
1167
1224
|
}
|
|
1168
1225
|
|
|
1169
1226
|
/**
|
|
@@ -1172,6 +1229,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1172
1229
|
*/
|
|
1173
1230
|
async pause() {
|
|
1174
1231
|
checkDestroy(this.#destroyed);
|
|
1232
|
+
this.log('info', 'Attempting to pause playback.');
|
|
1175
1233
|
const adapter = this.#getActiveAdapter();
|
|
1176
1234
|
if (!adapter) return;
|
|
1177
1235
|
|
|
@@ -1181,6 +1239,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1181
1239
|
|
|
1182
1240
|
await adapter.pause();
|
|
1183
1241
|
this.#isPlaying = false;
|
|
1242
|
+
this.log('info', 'Playback paused.');
|
|
1184
1243
|
this.emit('pause', this.#currentIndex);
|
|
1185
1244
|
}
|
|
1186
1245
|
|
|
@@ -1190,6 +1249,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1190
1249
|
*/
|
|
1191
1250
|
async stop() {
|
|
1192
1251
|
checkDestroy(this.#destroyed);
|
|
1252
|
+
this.log('info', 'Attempting to stop playback.');
|
|
1193
1253
|
const adapter = this.#getActiveAdapter();
|
|
1194
1254
|
if (!adapter) return;
|
|
1195
1255
|
|
|
@@ -1199,6 +1259,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1199
1259
|
|
|
1200
1260
|
await adapter.stop();
|
|
1201
1261
|
this.#isPlaying = false;
|
|
1262
|
+
this.log('info', 'Playback stopped.');
|
|
1202
1263
|
this.emit('stop', this.#currentIndex);
|
|
1203
1264
|
}
|
|
1204
1265
|
|
|
@@ -1210,9 +1271,11 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1210
1271
|
checkDestroy(this.#destroyed);
|
|
1211
1272
|
if (this.#playlist.length === 0) return;
|
|
1212
1273
|
|
|
1274
|
+
this.log('info', 'Next track requested.');
|
|
1213
1275
|
await this.stop();
|
|
1214
1276
|
|
|
1215
1277
|
if (this.#loopMode === 'TRACK') {
|
|
1278
|
+
this.log('info', 'Loop mode: TRACK. Repeating current track.');
|
|
1216
1279
|
// Index remains the same
|
|
1217
1280
|
await this.play();
|
|
1218
1281
|
return;
|
|
@@ -1220,13 +1283,15 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1220
1283
|
|
|
1221
1284
|
if (this.#isRandom) {
|
|
1222
1285
|
this.#currentIndex = this._getWeightedRandomIndex();
|
|
1286
|
+
this.log('info', `Random mode: Selected index ${this.#currentIndex}`);
|
|
1223
1287
|
} else {
|
|
1224
1288
|
let nextIndex = this.#currentIndex + 1;
|
|
1225
1289
|
if (nextIndex >= this.#playlist.length) {
|
|
1226
1290
|
if (this.#loopMode === 'PLAYLIST') {
|
|
1227
1291
|
nextIndex = 0;
|
|
1292
|
+
this.log('info', 'Loop mode: PLAYLIST. Returning to start.');
|
|
1228
1293
|
} else {
|
|
1229
|
-
|
|
1294
|
+
this.log('info', 'End of playlist reached. Stopping.');
|
|
1230
1295
|
return;
|
|
1231
1296
|
}
|
|
1232
1297
|
}
|
|
@@ -1247,6 +1312,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1247
1312
|
|
|
1248
1313
|
// UX: Repeat current track if "repeat on prev" is enabled and it's the first click
|
|
1249
1314
|
if (this.#repeatCurrentOnPrev && !this.#prevClickedToRepeat) {
|
|
1315
|
+
this.log('info', 'UX: Repeat current track on first "prev" click.');
|
|
1250
1316
|
this.#prevClickedToRepeat = true;
|
|
1251
1317
|
|
|
1252
1318
|
// Reset the "repeat" state after 2 seconds of inactivity to prevent glitches
|
|
@@ -1268,17 +1334,20 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1268
1334
|
}
|
|
1269
1335
|
this.#prevClickedToRepeat = false;
|
|
1270
1336
|
|
|
1337
|
+
this.log('info', 'Previous track requested.');
|
|
1271
1338
|
await this.stop();
|
|
1272
1339
|
|
|
1273
1340
|
if (this.#isRandom) {
|
|
1274
1341
|
this.#currentIndex = this._getWeightedRandomIndex();
|
|
1342
|
+
this.log('info', `Random mode: Selected index ${this.#currentIndex}`);
|
|
1275
1343
|
} else {
|
|
1276
1344
|
let prevIndex = this.#currentIndex - 1;
|
|
1277
1345
|
if (prevIndex < 0) {
|
|
1278
1346
|
if (this.#loopMode === 'PLAYLIST') {
|
|
1279
1347
|
prevIndex = this.#playlist.length - 1;
|
|
1348
|
+
this.log('info', 'Loop mode: PLAYLIST. Returning to end.');
|
|
1280
1349
|
} else {
|
|
1281
|
-
|
|
1350
|
+
this.log('info', 'Start of playlist reached. Stopping.');
|
|
1282
1351
|
return;
|
|
1283
1352
|
}
|
|
1284
1353
|
}
|
|
@@ -1289,6 +1358,29 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1289
1358
|
await this.play();
|
|
1290
1359
|
}
|
|
1291
1360
|
|
|
1361
|
+
/**
|
|
1362
|
+
* Jumps directly to a specific index in the playlist.
|
|
1363
|
+
* @param {number} index - The target index to skip to.
|
|
1364
|
+
* @returns {Promise<void>}
|
|
1365
|
+
* @throws {TypeError} If the index is not a number.
|
|
1366
|
+
* @throws {RangeError} If the index is out of the playlist bounds.
|
|
1367
|
+
*/
|
|
1368
|
+
async skipTo(index) {
|
|
1369
|
+
checkDestroy(this.#destroyed);
|
|
1370
|
+
if (typeof index !== 'number') {
|
|
1371
|
+
throw new TypeError('Index must be a number.');
|
|
1372
|
+
}
|
|
1373
|
+
if (!this.existsTrack(index)) {
|
|
1374
|
+
throw new RangeError(`Index ${index} is out of bounds for the current playlist.`);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
this.log('info', `Skipping to index: ${index}`);
|
|
1378
|
+
await this.stop();
|
|
1379
|
+
this.#currentIndex = index;
|
|
1380
|
+
this.emit('trackChange', this.#currentIndex);
|
|
1381
|
+
await this.play();
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1292
1384
|
/**
|
|
1293
1385
|
* Jumps to a specific absolute time in the timeline.
|
|
1294
1386
|
* @param {number} timeMs - The target time in milliseconds.
|
|
@@ -1305,6 +1397,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1305
1397
|
throw new RangeError('Seek time cannot be negative.');
|
|
1306
1398
|
}
|
|
1307
1399
|
|
|
1400
|
+
this.log('info', `Seeking to: ${timeMs}ms`);
|
|
1308
1401
|
const adapter = this.#getActiveAdapter();
|
|
1309
1402
|
if (adapter) {
|
|
1310
1403
|
await adapter.seek(timeMs);
|
|
@@ -1324,6 +1417,7 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1324
1417
|
throw new TypeError('Step amount must be a number in milliseconds.');
|
|
1325
1418
|
}
|
|
1326
1419
|
|
|
1420
|
+
this.log('info', `Stepping by: ${stepMs}ms`);
|
|
1327
1421
|
const adapter = this.#getActiveAdapter();
|
|
1328
1422
|
if (!adapter) return;
|
|
1329
1423
|
|
|
@@ -1396,6 +1490,34 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1396
1490
|
}
|
|
1397
1491
|
}
|
|
1398
1492
|
|
|
1493
|
+
/**
|
|
1494
|
+
* Checks whether the media adapter is paused.
|
|
1495
|
+
* @returns {boolean} True if the adapter is ready, false otherwise.
|
|
1496
|
+
*/
|
|
1497
|
+
get isPaused() {
|
|
1498
|
+
checkDestroy(this.#destroyed);
|
|
1499
|
+
try {
|
|
1500
|
+
const adapter = this.#getActiveAdapter();
|
|
1501
|
+
return adapter ? adapter.isPaused() : false;
|
|
1502
|
+
} catch {
|
|
1503
|
+
return false;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/**
|
|
1508
|
+
* Checks whether the media adapter is ended.
|
|
1509
|
+
* @returns {boolean} True if the adapter is ready, false otherwise.
|
|
1510
|
+
*/
|
|
1511
|
+
get isEnded() {
|
|
1512
|
+
checkDestroy(this.#destroyed);
|
|
1513
|
+
try {
|
|
1514
|
+
const adapter = this.#getActiveAdapter();
|
|
1515
|
+
return adapter ? adapter.isEnded() : true;
|
|
1516
|
+
} catch {
|
|
1517
|
+
return true;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1399
1521
|
/**
|
|
1400
1522
|
* Retrieves the metadata of the currently loaded content, returning a structured
|
|
1401
1523
|
* object containing details such as the content ID, title, author, and duration.
|
|
@@ -1448,12 +1570,15 @@ class TinyMediaPlayer extends events.EventEmitter {
|
|
|
1448
1570
|
async destroy() {
|
|
1449
1571
|
if (this.#destroyed) return;
|
|
1450
1572
|
|
|
1573
|
+
this.log('info', 'Destroying TinyMediaPlayer instance.');
|
|
1574
|
+
|
|
1451
1575
|
// 1. Stop current playback if active to halt media processes
|
|
1452
1576
|
try {
|
|
1453
1577
|
if (this.#isPlaying) {
|
|
1454
1578
|
await this.stop();
|
|
1455
1579
|
}
|
|
1456
1580
|
} catch (error) {
|
|
1581
|
+
this.log('error', 'Non-fatal error during playback termination:', error);
|
|
1457
1582
|
console.warn('[TinyMediaPlayer] Non-fatal error during playback termination:', error);
|
|
1458
1583
|
}
|
|
1459
1584
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import TinyDebugger from './TinyDebugger.mjs';
|
|
2
2
|
import { BaseMediaAdapter } from './TinyMediaPlayer/index.mjs';
|
|
3
3
|
export type PictureDataType = import('../basics/mediaContent.mjs').PictureDataType;
|
|
4
4
|
export type MediaContent = import('../basics/mediaContent.mjs').MediaContent<PictureDataType>;
|
|
@@ -32,6 +32,18 @@ export type TinyMediaPlayerOptions = {
|
|
|
32
32
|
* - If true, volume fades smoothly to zero when stopping.
|
|
33
33
|
*/
|
|
34
34
|
smoothStopVolume?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* - Whether to enable internal debug logging.
|
|
37
|
+
*/
|
|
38
|
+
debugMode?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* - Whether to enable log color support.
|
|
41
|
+
*/
|
|
42
|
+
useLogColors?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* - A custom logger object (must implement console methods).
|
|
45
|
+
*/
|
|
46
|
+
logger?: Console;
|
|
35
47
|
};
|
|
36
48
|
export type SearchResult = {
|
|
37
49
|
/**
|
|
@@ -67,6 +79,9 @@ export type SearchResult = {
|
|
|
67
79
|
* @property {boolean} [repeatCurrentOnPrev=false] - If true, clicking 'previous' repeats the current track on the first click.
|
|
68
80
|
* @property {boolean} [smoothPlayPauseVolume=false] - If true, volume fades smoothly during play/pause transitions.
|
|
69
81
|
* @property {boolean} [smoothStopVolume=false] - If true, volume fades smoothly to zero when stopping.
|
|
82
|
+
* @property {boolean} [debugMode=false] - Whether to enable internal debug logging.
|
|
83
|
+
* @property {boolean} [useLogColors=false] - Whether to enable log color support.
|
|
84
|
+
* @property {Console} [logger=console] - A custom logger object (must implement console methods).
|
|
70
85
|
*/
|
|
71
86
|
/**
|
|
72
87
|
* Represents a search match containing the media object and its exact playlist index.
|
|
@@ -77,7 +92,7 @@ export type SearchResult = {
|
|
|
77
92
|
/**
|
|
78
93
|
* A universal media player manager capable of orchestrating multiple API adapters.
|
|
79
94
|
*/
|
|
80
|
-
declare class TinyMediaPlayer extends
|
|
95
|
+
declare class TinyMediaPlayer extends TinyDebugger {
|
|
81
96
|
#private;
|
|
82
97
|
static BaseMediaAdapter: typeof BaseMediaAdapter;
|
|
83
98
|
/**
|
|
@@ -223,12 +238,15 @@ declare class TinyMediaPlayer extends EventEmitter {
|
|
|
223
238
|
/** @returns {boolean} True if the player is currently muted. */
|
|
224
239
|
get isMuted(): boolean;
|
|
225
240
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
|
|
229
|
-
|
|
241
|
+
* Mutes the current playback.
|
|
242
|
+
* @returns {Promise<void>}
|
|
243
|
+
*/
|
|
244
|
+
mute(): Promise<void>;
|
|
245
|
+
/**
|
|
246
|
+
* Unmutes the current playback.
|
|
247
|
+
* @returns {Promise<void>}
|
|
230
248
|
*/
|
|
231
|
-
|
|
249
|
+
unMute(): Promise<void>;
|
|
232
250
|
get adapters(): BaseMediaAdapter[];
|
|
233
251
|
get adaptersSize(): number;
|
|
234
252
|
static get defaultAdapterEventNames(): string[];
|
|
@@ -356,6 +374,14 @@ declare class TinyMediaPlayer extends EventEmitter {
|
|
|
356
374
|
* @returns {Promise<void>}
|
|
357
375
|
*/
|
|
358
376
|
prev(): Promise<void>;
|
|
377
|
+
/**
|
|
378
|
+
* Jumps directly to a specific index in the playlist.
|
|
379
|
+
* @param {number} index - The target index to skip to.
|
|
380
|
+
* @returns {Promise<void>}
|
|
381
|
+
* @throws {TypeError} If the index is not a number.
|
|
382
|
+
* @throws {RangeError} If the index is out of the playlist bounds.
|
|
383
|
+
*/
|
|
384
|
+
skipTo(index: number): Promise<void>;
|
|
359
385
|
/**
|
|
360
386
|
* Jumps to a specific absolute time in the timeline.
|
|
361
387
|
* @param {number} timeMs - The target time in milliseconds.
|
|
@@ -391,6 +417,16 @@ declare class TinyMediaPlayer extends EventEmitter {
|
|
|
391
417
|
* @returns {number} A value between 0 and 100.
|
|
392
418
|
*/
|
|
393
419
|
getPlaybackPercentage(): number;
|
|
420
|
+
/**
|
|
421
|
+
* Checks whether the media adapter is paused.
|
|
422
|
+
* @returns {boolean} True if the adapter is ready, false otherwise.
|
|
423
|
+
*/
|
|
424
|
+
get isPaused(): boolean;
|
|
425
|
+
/**
|
|
426
|
+
* Checks whether the media adapter is ended.
|
|
427
|
+
* @returns {boolean} True if the adapter is ready, false otherwise.
|
|
428
|
+
*/
|
|
429
|
+
get isEnded(): boolean;
|
|
394
430
|
/**
|
|
395
431
|
* Retrieves the metadata of the currently loaded content, returning a structured
|
|
396
432
|
* object containing details such as the content ID, title, author, and duration.
|