tiny-essentials 1.28.0 → 1.28.1

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.
Files changed (168) hide show
  1. package/changelog/1/28/1.md +23 -0
  2. package/dist/v1/TinyAdvancedRaffle.min.js +1 -1
  3. package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
  4. package/dist/v1/TinyAnalogClock.min.js +1 -1
  5. package/dist/v1/TinyBasicsEs.min.js +1 -1
  6. package/dist/v1/TinyDayNightCycle.min.js +1 -1
  7. package/dist/v1/TinyDragDropDetector.min.js +1 -1
  8. package/dist/v1/TinyDragger.min.js +1 -1
  9. package/dist/v1/TinyElementObserver.min.js +1 -1
  10. package/dist/v1/TinyEssentials.min.js +1 -1
  11. package/dist/v1/TinyGamepad.min.js +1 -1
  12. package/dist/v1/TinyHtml.min.js +1 -1
  13. package/dist/v1/TinyI18.min.js +1 -1
  14. package/dist/v1/TinyIframeEvents.min.js +1 -1
  15. package/dist/v1/TinyLocalStorage.min.js +1 -1
  16. package/dist/v1/TinyMediaPlayer.min.js +1 -1
  17. package/dist/v1/TinyNewWinEvents.min.js +1 -1
  18. package/dist/v1/TinyNotifyCenter.min.js +1 -1
  19. package/dist/v1/TinyRadioFm.min.js +1 -1
  20. package/dist/v1/TinyRateLimiter.min.js +1 -1
  21. package/dist/v1/TinySmartScroller.min.js +1 -1
  22. package/dist/v1/TinyTextDiffer.min.js +1 -1
  23. package/dist/v1/TinyTextarea.min.js +1 -1
  24. package/dist/v1/TinyTimeout.min.js +1 -1
  25. package/dist/v1/TinyToastNotify.min.js +1 -1
  26. package/dist/v1/TinyUploadClicker.min.js +1 -1
  27. package/dist/v1/UltraRandomMsgGen.min.js +1 -1
  28. package/dist/v1/basics/array.cjs +57 -9
  29. package/dist/v1/basics/array.d.mts +27 -11
  30. package/dist/v1/basics/array.mjs +51 -9
  31. package/dist/v1/basics/clock.cjs +94 -42
  32. package/dist/v1/basics/clock.d.mts +10 -10
  33. package/dist/v1/basics/clock.mjs +91 -54
  34. package/dist/v1/basics/deprecated/html.cjs +246 -0
  35. package/dist/v1/basics/deprecated/html.d.mts +63 -0
  36. package/dist/v1/basics/deprecated/html.mjs +190 -0
  37. package/dist/v1/basics/html.cjs +149 -130
  38. package/dist/v1/basics/html.d.mts +26 -61
  39. package/dist/v1/basics/html.mjs +113 -105
  40. package/dist/v1/basics/index.cjs +2 -0
  41. package/dist/v1/basics/index.d.mts +3 -1
  42. package/dist/v1/basics/index.mjs +3 -3
  43. package/dist/v1/basics/mediaContent.cjs +51 -9
  44. package/dist/v1/basics/mediaContent.d.mts +0 -87
  45. package/dist/v1/basics/mediaContent.mjs +40 -10
  46. package/dist/v1/basics/objChecker.cjs +7 -5
  47. package/dist/v1/basics/objChecker.d.mts +3 -2
  48. package/dist/v1/basics/objChecker.mjs +6 -6
  49. package/dist/v1/basics/objFilter.cjs +40 -7
  50. package/dist/v1/basics/objFilter.d.mts +6 -6
  51. package/dist/v1/basics/objFilter.mjs +32 -7
  52. package/dist/v1/basics/simpleMath.cjs +81 -25
  53. package/dist/v1/basics/simpleMath.d.mts +2 -2
  54. package/dist/v1/basics/simpleMath.mjs +70 -27
  55. package/dist/v1/basics/text.cjs +76 -31
  56. package/dist/v1/basics/text.d.mts +69 -7
  57. package/dist/v1/basics/text.mjs +62 -31
  58. package/dist/v1/index.cjs +4 -0
  59. package/dist/v1/index.d.mts +4 -1
  60. package/dist/v1/index.mjs +4 -3
  61. package/dist/v1/libs/TinyAdvancedRaffle.cjs +64 -50
  62. package/dist/v1/libs/TinyAdvancedRaffle.d.mts +4 -13
  63. package/dist/v1/libs/TinyAdvancedRaffle.mjs +62 -49
  64. package/dist/v1/libs/TinyAfterScrollWatcher.cjs +17 -1
  65. package/dist/v1/libs/TinyAfterScrollWatcher.d.mts +4 -1
  66. package/dist/v1/libs/TinyAfterScrollWatcher.mjs +15 -1
  67. package/dist/v1/libs/TinyAnalogClock.cjs +60 -0
  68. package/dist/v1/libs/TinyAnalogClock.d.mts +6 -0
  69. package/dist/v1/libs/TinyAnalogClock.mjs +57 -0
  70. package/dist/v1/libs/TinyClipboard.cjs +2 -0
  71. package/dist/v1/libs/TinyClipboard.d.mts +4 -2
  72. package/dist/v1/libs/TinyClipboard.mjs +2 -0
  73. package/dist/v1/libs/TinyColorConverter.cjs +1 -0
  74. package/dist/v1/libs/TinyColorConverter.d.mts +2 -1
  75. package/dist/v1/libs/TinyColorConverter.mjs +1 -0
  76. package/dist/v1/libs/TinyDayNightCycle.cjs +67 -42
  77. package/dist/v1/libs/TinyDayNightCycle.d.mts +2 -7
  78. package/dist/v1/libs/TinyDayNightCycle.mjs +65 -41
  79. package/dist/v1/libs/TinyDragDropDetector.cjs +25 -0
  80. package/dist/v1/libs/TinyDragDropDetector.d.mts +6 -0
  81. package/dist/v1/libs/TinyDragDropDetector.mjs +22 -0
  82. package/dist/v1/libs/TinyDragger.cjs +46 -19
  83. package/dist/v1/libs/TinyDragger.mjs +45 -18
  84. package/dist/v1/libs/TinyElementObserver.cjs +39 -0
  85. package/dist/v1/libs/TinyElementObserver.d.mts +6 -0
  86. package/dist/v1/libs/TinyElementObserver.mjs +36 -0
  87. package/dist/v1/libs/TinyGamepad.cjs +131 -1
  88. package/dist/v1/libs/TinyGamepad.mjs +129 -1
  89. package/dist/v1/libs/TinyHtml.cjs +1 -1
  90. package/dist/v1/libs/TinyHtml.mjs +2 -2
  91. package/dist/v1/libs/TinyI18.cjs +61 -0
  92. package/dist/v1/libs/TinyI18.d.mts +14 -0
  93. package/dist/v1/libs/TinyI18.mjs +55 -0
  94. package/dist/v1/libs/TinyIframeEvents.cjs +18 -1
  95. package/dist/v1/libs/TinyIframeEvents.mjs +18 -1
  96. package/dist/v1/libs/TinyLocalStorage.cjs +48 -0
  97. package/dist/v1/libs/TinyLocalStorage.d.mts +6 -0
  98. package/dist/v1/libs/TinyLocalStorage.mjs +45 -0
  99. package/dist/v1/libs/TinyMediaPlayer.cjs +72 -0
  100. package/dist/v1/libs/TinyMediaPlayer.d.mts +10 -0
  101. package/dist/v1/libs/TinyMediaPlayer.mjs +66 -0
  102. package/dist/v1/libs/TinyNewWinEvents.cjs +29 -0
  103. package/dist/v1/libs/TinyNewWinEvents.d.mts +6 -0
  104. package/dist/v1/libs/TinyNewWinEvents.mjs +26 -0
  105. package/dist/v1/libs/TinyNotifyCenter.cjs +36 -0
  106. package/dist/v1/libs/TinyNotifyCenter.d.mts +6 -0
  107. package/dist/v1/libs/TinyNotifyCenter.mjs +32 -0
  108. package/dist/v1/libs/TinyRadioFm.cjs +38 -0
  109. package/dist/v1/libs/TinyRadioFm.d.mts +1 -0
  110. package/dist/v1/libs/TinyRadioFm.mjs +35 -0
  111. package/dist/v1/libs/TinyRateLimiter.cjs +52 -0
  112. package/dist/v1/libs/TinyRateLimiter.d.mts +6 -0
  113. package/dist/v1/libs/TinyRateLimiter.mjs +48 -0
  114. package/dist/v1/libs/TinySmartScroller.cjs +52 -3
  115. package/dist/v1/libs/TinySmartScroller.d.mts +17 -15
  116. package/dist/v1/libs/TinySmartScroller.mjs +51 -5
  117. package/dist/v1/libs/TinyTextDiffer.cjs +19 -18
  118. package/dist/v1/libs/TinyTextDiffer.d.mts +1 -0
  119. package/dist/v1/libs/TinyTextDiffer.mjs +16 -17
  120. package/dist/v1/libs/TinyTextarea.cjs +19 -0
  121. package/dist/v1/libs/TinyTextarea.d.mts +6 -0
  122. package/dist/v1/libs/TinyTextarea.mjs +17 -0
  123. package/dist/v1/libs/TinyTimeout.cjs +11 -1
  124. package/dist/v1/libs/TinyTimeout.mjs +9 -1
  125. package/dist/v1/libs/TinyToastNotify.cjs +35 -0
  126. package/dist/v1/libs/TinyToastNotify.d.mts +6 -0
  127. package/dist/v1/libs/TinyToastNotify.mjs +31 -0
  128. package/dist/v1/libs/TinyUploadClicker.cjs +19 -0
  129. package/dist/v1/libs/TinyUploadClicker.d.mts +6 -0
  130. package/dist/v1/libs/TinyUploadClicker.mjs +17 -0
  131. package/dist/v1/libs/UltraRandomMsgGen.cjs +148 -35
  132. package/dist/v1/libs/UltraRandomMsgGen.d.mts +17 -3
  133. package/dist/v1/libs/UltraRandomMsgGen.mjs +133 -34
  134. package/dist/v1/libs/utils.cjs +21 -0
  135. package/dist/v1/libs/utils.d.mts +2 -0
  136. package/dist/v1/libs/utils.mjs +16 -0
  137. package/docs/v1/README.md +1 -0
  138. package/docs/v1/basics/array.md +2 -2
  139. package/docs/v1/basics/deprecated/html.md +17 -0
  140. package/docs/v1/basics/fullScreen.md +1 -1
  141. package/docs/v1/basics/html.md +12 -26
  142. package/docs/v1/basics/mediaContent.md +1 -1
  143. package/docs/v1/basics/objChecker.md +1 -1
  144. package/docs/v1/basics/text.md +2 -2
  145. package/docs/v1/fileManager/main.md +1 -1
  146. package/docs/v1/libs/ColorSafeStringify.md +1 -1
  147. package/docs/v1/libs/TinyAfterScrollWatcher.md +1 -1
  148. package/docs/v1/libs/TinyAnalogClock.md +1 -1
  149. package/docs/v1/libs/TinyArrayComparator.md +1 -1
  150. package/docs/v1/libs/TinyArrayPaginator.md +1 -1
  151. package/docs/v1/libs/TinyClassManager/TinyPluginInliner.md +2 -2
  152. package/docs/v1/libs/TinyClassManager.md +1 -1
  153. package/docs/v1/libs/TinyClipboard.md +1 -1
  154. package/docs/v1/libs/TinyCookieConsent.md +1 -1
  155. package/docs/v1/libs/TinyDomReadyManager.md +1 -1
  156. package/docs/v1/libs/TinyDragDropDetector.md +1 -1
  157. package/docs/v1/libs/TinyDragger.md +1 -1
  158. package/docs/v1/libs/TinyElementObserver.md +1 -1
  159. package/docs/v1/libs/TinyHtml.md +1 -1
  160. package/docs/v1/libs/TinyIframeEvents.md +2 -2
  161. package/docs/v1/libs/TinyLevelUp.md +1 -1
  162. package/docs/v1/libs/TinyLoadingScreen.md +1 -1
  163. package/docs/v1/libs/TinyNewWinEvents.md +2 -2
  164. package/docs/v1/libs/TinyNotifications.md +1 -1
  165. package/docs/v1/libs/TinyPromiseQueue.md +2 -2
  166. package/docs/v1/libs/TinyTextDiffer.md +1 -1
  167. package/docs/v1/libs/TinyTextarea.md +1 -1
  168. package/package.json +10 -1
@@ -2,6 +2,9 @@
2
2
 
3
3
  var events = require('events');
4
4
  var mediaContent = require('../basics/mediaContent.cjs');
5
+ var utils = require('./utils.cjs');
6
+
7
+ const checkDestroy = utils.createCheckDestroyed('TinyMediaPlayer');
5
8
 
6
9
  /**
7
10
  * @typedef {import('../basics/mediaContent.mjs').MediaContent} MediaContent
@@ -160,6 +163,13 @@ class TinyMediaPlayer extends events.EventEmitter {
160
163
  );
161
164
  }
162
165
 
166
+ /** @type {boolean} */
167
+ #destroyed = false;
168
+
169
+ get destroyed() {
170
+ return this.#destroyed;
171
+ }
172
+
163
173
  /** @type {Map<string, BaseMediaAdapter>} */
164
174
  #adapters = new Map();
165
175
 
@@ -289,6 +299,7 @@ class TinyMediaPlayer extends events.EventEmitter {
289
299
 
290
300
  /** @returns {MediaContent[]} A shallow copy of the current playlist. */
291
301
  get playlist() {
302
+ checkDestroy(this.#destroyed);
292
303
  return [...this.#playlist];
293
304
  }
294
305
 
@@ -297,6 +308,7 @@ class TinyMediaPlayer extends events.EventEmitter {
297
308
  * @throws {TypeError} If the value is not an array.
298
309
  */
299
310
  set playlist(value) {
311
+ checkDestroy(this.#destroyed);
300
312
  if (!Array.isArray(value)) {
301
313
  throw new TypeError('Playlist must be an array of MediaContent objects.');
302
314
  }
@@ -343,6 +355,7 @@ class TinyMediaPlayer extends events.EventEmitter {
343
355
 
344
356
  /** @returns {number} The current active index in the playlist. */
345
357
  get currentIndex() {
358
+ checkDestroy(this.#destroyed);
346
359
  return this.#currentIndex;
347
360
  }
348
361
 
@@ -352,6 +365,7 @@ class TinyMediaPlayer extends events.EventEmitter {
352
365
  * @throws {RangeError} If the index is out of the playlist bounds (unless -1 for empty).
353
366
  */
354
367
  set currentIndex(value) {
368
+ checkDestroy(this.#destroyed);
355
369
  if (typeof value !== 'number') {
356
370
  throw new TypeError('Current index must be a number.');
357
371
  }
@@ -390,6 +404,7 @@ class TinyMediaPlayer extends events.EventEmitter {
390
404
 
391
405
  /** @returns {LoopModeType} The current loop configuration. */
392
406
  get loopMode() {
407
+ checkDestroy(this.#destroyed);
393
408
  return this.#loopMode;
394
409
  }
395
410
 
@@ -398,6 +413,7 @@ class TinyMediaPlayer extends events.EventEmitter {
398
413
  * @throws {TypeError} If the value is not a valid LoopModeType.
399
414
  */
400
415
  set loopMode(value) {
416
+ checkDestroy(this.#destroyed);
401
417
  const validModes = ['NONE', 'TRACK', 'PLAYLIST'];
402
418
  if (!validModes.includes(value)) {
403
419
  throw new TypeError(`Loop mode must be one of: ${validModes.join(', ')}.`);
@@ -408,6 +424,7 @@ class TinyMediaPlayer extends events.EventEmitter {
408
424
 
409
425
  /** @returns {boolean} The current random mode status. */
410
426
  get isRandom() {
427
+ checkDestroy(this.#destroyed);
411
428
  return this.#isRandom;
412
429
  }
413
430
 
@@ -416,6 +433,7 @@ class TinyMediaPlayer extends events.EventEmitter {
416
433
  * @throws {TypeError} If the value is not a boolean.
417
434
  */
418
435
  set isRandom(value) {
436
+ checkDestroy(this.#destroyed);
419
437
  if (typeof value !== 'boolean') {
420
438
  throw new TypeError('Random mode state must be a boolean.');
421
439
  }
@@ -425,11 +443,13 @@ class TinyMediaPlayer extends events.EventEmitter {
425
443
 
426
444
  /** @returns {boolean} True if media is actively playing. */
427
445
  get isPlaying() {
446
+ checkDestroy(this.#destroyed);
428
447
  return this.#isPlaying;
429
448
  }
430
449
 
431
450
  /** @returns {number} The current volume level (0.0 to 1.0). */
432
451
  get volume() {
452
+ checkDestroy(this.#destroyed);
433
453
  return this.#volume;
434
454
  }
435
455
 
@@ -439,6 +459,7 @@ class TinyMediaPlayer extends events.EventEmitter {
439
459
  * @throws {RangeError} If the value is outside the 0.0 to 1.0 range.
440
460
  */
441
461
  set volume(value) {
462
+ checkDestroy(this.#destroyed);
442
463
  if (typeof value !== 'number') throw new TypeError('Volume must be a number.');
443
464
  if (value < 0 || value > 1)
444
465
  throw new RangeError('Volume must be tightly constrained between 0.0 and 1.0.');
@@ -460,6 +481,7 @@ class TinyMediaPlayer extends events.EventEmitter {
460
481
 
461
482
  /** @returns {boolean} Whether the volume cache is currently enabled. */
462
483
  get persistVolume() {
484
+ checkDestroy(this.#destroyed);
463
485
  return this.#persistVolume;
464
486
  }
465
487
 
@@ -468,6 +490,7 @@ class TinyMediaPlayer extends events.EventEmitter {
468
490
  * @throws {TypeError} If the value is not a boolean.
469
491
  */
470
492
  set persistVolume(value) {
493
+ checkDestroy(this.#destroyed);
471
494
  if (typeof value !== 'boolean') {
472
495
  throw new TypeError('Persist volume parameter must be a boolean.');
473
496
  }
@@ -479,6 +502,7 @@ class TinyMediaPlayer extends events.EventEmitter {
479
502
 
480
503
  /** @returns {string} The localStorage key used for volume caching. */
481
504
  get volumeStorageKey() {
505
+ checkDestroy(this.#destroyed);
482
506
  return this.#volumeStorageKey;
483
507
  }
484
508
 
@@ -487,6 +511,7 @@ class TinyMediaPlayer extends events.EventEmitter {
487
511
  * @throws {TypeError} If the value is not a string or is empty.
488
512
  */
489
513
  set volumeStorageKey(value) {
514
+ checkDestroy(this.#destroyed);
490
515
  if (typeof value !== 'string') {
491
516
  throw new TypeError('Volume storage key must be a string.');
492
517
  }
@@ -519,6 +544,7 @@ class TinyMediaPlayer extends events.EventEmitter {
519
544
  * @throws {TypeError} If id is not a string or adapter is invalid.
520
545
  */
521
546
  registerAdapter(id, adapter) {
547
+ checkDestroy(this.#destroyed);
522
548
  if (typeof id !== 'string') {
523
549
  throw new TypeError('Adapter ID must be a string.');
524
550
  }
@@ -556,6 +582,7 @@ class TinyMediaPlayer extends events.EventEmitter {
556
582
  * @throws {TypeError} If content is invalid or missing required base properties.
557
583
  */
558
584
  addTrack(content) {
585
+ checkDestroy(this.#destroyed);
559
586
  if (!content || typeof content !== 'object' || typeof content.url !== 'string')
560
587
  throw new TypeError('Track content must be a valid MediaContent object containing a URL.');
561
588
 
@@ -579,6 +606,7 @@ class TinyMediaPlayer extends events.EventEmitter {
579
606
  * @throws {TypeError} If the index is not a number.
580
607
  */
581
608
  existsTrack(index) {
609
+ checkDestroy(this.#destroyed);
582
610
  if (typeof index !== 'number') {
583
611
  throw new TypeError('Index must be a number.');
584
612
  }
@@ -593,6 +621,7 @@ class TinyMediaPlayer extends events.EventEmitter {
593
621
  * @throws {RangeError} If the index is out of bounds.
594
622
  */
595
623
  getTrack(index) {
624
+ checkDestroy(this.#destroyed);
596
625
  if (typeof index !== 'number') {
597
626
  throw new TypeError('Index must be a number.');
598
627
  }
@@ -610,6 +639,7 @@ class TinyMediaPlayer extends events.EventEmitter {
610
639
  * @throws {RangeError} If the index is out of bounds.
611
640
  */
612
641
  async removeTrack(index) {
642
+ checkDestroy(this.#destroyed);
613
643
  if (typeof index !== 'number') {
614
644
  throw new TypeError('Index must be a number.');
615
645
  }
@@ -648,6 +678,7 @@ class TinyMediaPlayer extends events.EventEmitter {
648
678
  * @throws {TypeError} If the query is neither a string nor a function.
649
679
  */
650
680
  searchTrack(query) {
681
+ checkDestroy(this.#destroyed);
651
682
  if (typeof query !== 'string' && typeof query !== 'function') {
652
683
  throw new TypeError('Search query must be a string or a boolean evaluation function.');
653
684
  }
@@ -681,6 +712,7 @@ class TinyMediaPlayer extends events.EventEmitter {
681
712
  * Clears the entire playlist and stops playback.
682
713
  */
683
714
  async clearPlaylist() {
715
+ checkDestroy(this.#destroyed);
684
716
  await this.stop();
685
717
  this.#playlist = [];
686
718
  this.#currentIndex = -1;
@@ -697,6 +729,7 @@ class TinyMediaPlayer extends events.EventEmitter {
697
729
  * @returns {Promise<void>}
698
730
  */
699
731
  async play() {
732
+ checkDestroy(this.#destroyed);
700
733
  const adapter = this.#getActiveAdapter();
701
734
  if (!adapter) return;
702
735
 
@@ -712,6 +745,7 @@ class TinyMediaPlayer extends events.EventEmitter {
712
745
  * @returns {Promise<void>}
713
746
  */
714
747
  async pause() {
748
+ checkDestroy(this.#destroyed);
715
749
  const adapter = this.#getActiveAdapter();
716
750
  if (!adapter) return;
717
751
 
@@ -725,6 +759,7 @@ class TinyMediaPlayer extends events.EventEmitter {
725
759
  * @returns {Promise<void>}
726
760
  */
727
761
  async stop() {
762
+ checkDestroy(this.#destroyed);
728
763
  const adapter = this.#getActiveAdapter();
729
764
  if (!adapter) return;
730
765
 
@@ -738,6 +773,7 @@ class TinyMediaPlayer extends events.EventEmitter {
738
773
  * @returns {Promise<void>}
739
774
  */
740
775
  async next() {
776
+ checkDestroy(this.#destroyed);
741
777
  if (this.#playlist.length === 0) return;
742
778
 
743
779
  await this.stop();
@@ -772,6 +808,7 @@ class TinyMediaPlayer extends events.EventEmitter {
772
808
  * @returns {Promise<void>}
773
809
  */
774
810
  async prev() {
811
+ checkDestroy(this.#destroyed);
775
812
  if (this.#playlist.length === 0) return;
776
813
 
777
814
  await this.stop();
@@ -803,6 +840,7 @@ class TinyMediaPlayer extends events.EventEmitter {
803
840
  * @returns {Promise<void>}
804
841
  */
805
842
  async seek(timeMs) {
843
+ checkDestroy(this.#destroyed);
806
844
  if (typeof timeMs !== 'number') {
807
845
  throw new TypeError('Seek time must be a number in milliseconds.');
808
846
  }
@@ -824,6 +862,7 @@ class TinyMediaPlayer extends events.EventEmitter {
824
862
  * @returns {Promise<void>}
825
863
  */
826
864
  async step(stepMs) {
865
+ checkDestroy(this.#destroyed);
827
866
  if (typeof stepMs !== 'number') {
828
867
  throw new TypeError('Step amount must be a number in milliseconds.');
829
868
  }
@@ -839,6 +878,39 @@ class TinyMediaPlayer extends events.EventEmitter {
839
878
 
840
879
  await this.seek(targetTime);
841
880
  }
881
+
882
+ /**
883
+ * Safely destroys the TinyMediaPlayer instance.
884
+ * This method stops active playback, clears the internal playlist,
885
+ * removes all registered media adapters, and detaches all event listeners
886
+ * to ensure proper garbage collection and prevent memory leaks.
887
+ *
888
+ * @returns {Promise<void>} A promise that resolves when the destruction sequence is complete.
889
+ */
890
+ async destroy() {
891
+ if (this.#destroyed) return;
892
+
893
+ // 1. Stop current playback if active to halt media processes
894
+ try {
895
+ if (this.#isPlaying) {
896
+ await this.stop();
897
+ }
898
+ } catch (error) {
899
+ console.warn('[TinyMediaPlayer] Non-fatal error during playback termination:', error);
900
+ }
901
+
902
+ // 2. Clear internal state variables
903
+ this.#playlist = [];
904
+ this.#currentIndex = -1;
905
+ this.#isPlaying = false;
906
+
907
+ // 3. Clear all registered API adapters
908
+ this.#adapters.clear();
909
+
910
+ // 4. Remove all event listeners inherited from EventEmitter
911
+ this.removeAllListeners();
912
+ this.#destroyed = true;
913
+ }
842
914
  }
843
915
 
844
916
  module.exports = TinyMediaPlayer;
@@ -88,6 +88,7 @@ declare class TinyMediaPlayer extends EventEmitter<any> {
88
88
  * @param {TinyMediaPlayerOptions} [options={}] - Configuration parameters for the player.
89
89
  */
90
90
  constructor(options?: TinyMediaPlayerOptions);
91
+ get destroyed(): boolean;
91
92
  /**
92
93
  * Calculates a random index based on the weighted probability of each track,
93
94
  * strictly excluding the currently active track to prevent immediate repetition.
@@ -236,6 +237,15 @@ declare class TinyMediaPlayer extends EventEmitter<any> {
236
237
  * @returns {Promise<void>}
237
238
  */
238
239
  step(stepMs: number): Promise<void>;
240
+ /**
241
+ * Safely destroys the TinyMediaPlayer instance.
242
+ * This method stops active playback, clears the internal playlist,
243
+ * removes all registered media adapters, and detaches all event listeners
244
+ * to ensure proper garbage collection and prevent memory leaks.
245
+ *
246
+ * @returns {Promise<void>} A promise that resolves when the destruction sequence is complete.
247
+ */
248
+ destroy(): Promise<void>;
239
249
  #private;
240
250
  }
241
251
  import { EventEmitter } from 'events';
@@ -1,5 +1,7 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import { getMediaContentBase, getMediaContentMetadata, parseMediaMetadata, valMediaContentMetadata, } from '../basics/mediaContent.mjs';
3
+ import { createCheckDestroyed } from './utils.mjs';
4
+ const checkDestroy = createCheckDestroyed('TinyMediaPlayer');
3
5
  /**
4
6
  * @typedef {import('../basics/mediaContent.mjs').MediaContent} MediaContent
5
7
  * @typedef {import('../basics/mediaContent.mjs').MediaContentBase} MediaContentBase
@@ -133,6 +135,11 @@ class TinyMediaPlayer extends EventEmitter {
133
135
  static async parseContent(source, defaultMetadata, metadata, parseFile, callbacks) {
134
136
  return parseMediaMetadata(source, defaultMetadata, metadata, parseFile, callbacks, TinyMediaPlayer.#unknownArtist);
135
137
  }
138
+ /** @type {boolean} */
139
+ #destroyed = false;
140
+ get destroyed() {
141
+ return this.#destroyed;
142
+ }
136
143
  /** @type {Map<string, BaseMediaAdapter>} */
137
144
  #adapters = new Map();
138
145
  /** @type {MediaContent[]} */
@@ -244,6 +251,7 @@ class TinyMediaPlayer extends EventEmitter {
244
251
  // ==========================================
245
252
  /** @returns {MediaContent[]} A shallow copy of the current playlist. */
246
253
  get playlist() {
254
+ checkDestroy(this.#destroyed);
247
255
  return [...this.#playlist];
248
256
  }
249
257
  /**
@@ -251,6 +259,7 @@ class TinyMediaPlayer extends EventEmitter {
251
259
  * @throws {TypeError} If the value is not an array.
252
260
  */
253
261
  set playlist(value) {
262
+ checkDestroy(this.#destroyed);
254
263
  if (!Array.isArray(value)) {
255
264
  throw new TypeError('Playlist must be an array of MediaContent objects.');
256
265
  }
@@ -296,6 +305,7 @@ class TinyMediaPlayer extends EventEmitter {
296
305
  }
297
306
  /** @returns {number} The current active index in the playlist. */
298
307
  get currentIndex() {
308
+ checkDestroy(this.#destroyed);
299
309
  return this.#currentIndex;
300
310
  }
301
311
  /**
@@ -304,6 +314,7 @@ class TinyMediaPlayer extends EventEmitter {
304
314
  * @throws {RangeError} If the index is out of the playlist bounds (unless -1 for empty).
305
315
  */
306
316
  set currentIndex(value) {
317
+ checkDestroy(this.#destroyed);
307
318
  if (typeof value !== 'number') {
308
319
  throw new TypeError('Current index must be a number.');
309
320
  }
@@ -341,6 +352,7 @@ class TinyMediaPlayer extends EventEmitter {
341
352
  }
342
353
  /** @returns {LoopModeType} The current loop configuration. */
343
354
  get loopMode() {
355
+ checkDestroy(this.#destroyed);
344
356
  return this.#loopMode;
345
357
  }
346
358
  /**
@@ -348,6 +360,7 @@ class TinyMediaPlayer extends EventEmitter {
348
360
  * @throws {TypeError} If the value is not a valid LoopModeType.
349
361
  */
350
362
  set loopMode(value) {
363
+ checkDestroy(this.#destroyed);
351
364
  const validModes = ['NONE', 'TRACK', 'PLAYLIST'];
352
365
  if (!validModes.includes(value)) {
353
366
  throw new TypeError(`Loop mode must be one of: ${validModes.join(', ')}.`);
@@ -357,6 +370,7 @@ class TinyMediaPlayer extends EventEmitter {
357
370
  }
358
371
  /** @returns {boolean} The current random mode status. */
359
372
  get isRandom() {
373
+ checkDestroy(this.#destroyed);
360
374
  return this.#isRandom;
361
375
  }
362
376
  /**
@@ -364,6 +378,7 @@ class TinyMediaPlayer extends EventEmitter {
364
378
  * @throws {TypeError} If the value is not a boolean.
365
379
  */
366
380
  set isRandom(value) {
381
+ checkDestroy(this.#destroyed);
367
382
  if (typeof value !== 'boolean') {
368
383
  throw new TypeError('Random mode state must be a boolean.');
369
384
  }
@@ -372,10 +387,12 @@ class TinyMediaPlayer extends EventEmitter {
372
387
  }
373
388
  /** @returns {boolean} True if media is actively playing. */
374
389
  get isPlaying() {
390
+ checkDestroy(this.#destroyed);
375
391
  return this.#isPlaying;
376
392
  }
377
393
  /** @returns {number} The current volume level (0.0 to 1.0). */
378
394
  get volume() {
395
+ checkDestroy(this.#destroyed);
379
396
  return this.#volume;
380
397
  }
381
398
  /**
@@ -384,6 +401,7 @@ class TinyMediaPlayer extends EventEmitter {
384
401
  * @throws {RangeError} If the value is outside the 0.0 to 1.0 range.
385
402
  */
386
403
  set volume(value) {
404
+ checkDestroy(this.#destroyed);
387
405
  if (typeof value !== 'number')
388
406
  throw new TypeError('Volume must be a number.');
389
407
  if (value < 0 || value > 1)
@@ -405,6 +423,7 @@ class TinyMediaPlayer extends EventEmitter {
405
423
  }
406
424
  /** @returns {boolean} Whether the volume cache is currently enabled. */
407
425
  get persistVolume() {
426
+ checkDestroy(this.#destroyed);
408
427
  return this.#persistVolume;
409
428
  }
410
429
  /**
@@ -412,6 +431,7 @@ class TinyMediaPlayer extends EventEmitter {
412
431
  * @throws {TypeError} If the value is not a boolean.
413
432
  */
414
433
  set persistVolume(value) {
434
+ checkDestroy(this.#destroyed);
415
435
  if (typeof value !== 'boolean') {
416
436
  throw new TypeError('Persist volume parameter must be a boolean.');
417
437
  }
@@ -422,6 +442,7 @@ class TinyMediaPlayer extends EventEmitter {
422
442
  }
423
443
  /** @returns {string} The localStorage key used for volume caching. */
424
444
  get volumeStorageKey() {
445
+ checkDestroy(this.#destroyed);
425
446
  return this.#volumeStorageKey;
426
447
  }
427
448
  /**
@@ -429,6 +450,7 @@ class TinyMediaPlayer extends EventEmitter {
429
450
  * @throws {TypeError} If the value is not a string or is empty.
430
451
  */
431
452
  set volumeStorageKey(value) {
453
+ checkDestroy(this.#destroyed);
432
454
  if (typeof value !== 'string') {
433
455
  throw new TypeError('Volume storage key must be a string.');
434
456
  }
@@ -458,6 +480,7 @@ class TinyMediaPlayer extends EventEmitter {
458
480
  * @throws {TypeError} If id is not a string or adapter is invalid.
459
481
  */
460
482
  registerAdapter(id, adapter) {
483
+ checkDestroy(this.#destroyed);
461
484
  if (typeof id !== 'string') {
462
485
  throw new TypeError('Adapter ID must be a string.');
463
486
  }
@@ -492,6 +515,7 @@ class TinyMediaPlayer extends EventEmitter {
492
515
  * @throws {TypeError} If content is invalid or missing required base properties.
493
516
  */
494
517
  addTrack(content) {
518
+ checkDestroy(this.#destroyed);
495
519
  if (!content || typeof content !== 'object' || typeof content.url !== 'string')
496
520
  throw new TypeError('Track content must be a valid MediaContent object containing a URL.');
497
521
  /** @type {MediaContent} */
@@ -512,6 +536,7 @@ class TinyMediaPlayer extends EventEmitter {
512
536
  * @throws {TypeError} If the index is not a number.
513
537
  */
514
538
  existsTrack(index) {
539
+ checkDestroy(this.#destroyed);
515
540
  if (typeof index !== 'number') {
516
541
  throw new TypeError('Index must be a number.');
517
542
  }
@@ -525,6 +550,7 @@ class TinyMediaPlayer extends EventEmitter {
525
550
  * @throws {RangeError} If the index is out of bounds.
526
551
  */
527
552
  getTrack(index) {
553
+ checkDestroy(this.#destroyed);
528
554
  if (typeof index !== 'number') {
529
555
  throw new TypeError('Index must be a number.');
530
556
  }
@@ -541,6 +567,7 @@ class TinyMediaPlayer extends EventEmitter {
541
567
  * @throws {RangeError} If the index is out of bounds.
542
568
  */
543
569
  async removeTrack(index) {
570
+ checkDestroy(this.#destroyed);
544
571
  if (typeof index !== 'number') {
545
572
  throw new TypeError('Index must be a number.');
546
573
  }
@@ -578,6 +605,7 @@ class TinyMediaPlayer extends EventEmitter {
578
605
  * @throws {TypeError} If the query is neither a string nor a function.
579
606
  */
580
607
  searchTrack(query) {
608
+ checkDestroy(this.#destroyed);
581
609
  if (typeof query !== 'string' && typeof query !== 'function') {
582
610
  throw new TypeError('Search query must be a string or a boolean evaluation function.');
583
611
  }
@@ -605,6 +633,7 @@ class TinyMediaPlayer extends EventEmitter {
605
633
  * Clears the entire playlist and stops playback.
606
634
  */
607
635
  async clearPlaylist() {
636
+ checkDestroy(this.#destroyed);
608
637
  await this.stop();
609
638
  this.#playlist = [];
610
639
  this.#currentIndex = -1;
@@ -619,6 +648,7 @@ class TinyMediaPlayer extends EventEmitter {
619
648
  * @returns {Promise<void>}
620
649
  */
621
650
  async play() {
651
+ checkDestroy(this.#destroyed);
622
652
  const adapter = this.#getActiveAdapter();
623
653
  if (!adapter)
624
654
  return;
@@ -633,6 +663,7 @@ class TinyMediaPlayer extends EventEmitter {
633
663
  * @returns {Promise<void>}
634
664
  */
635
665
  async pause() {
666
+ checkDestroy(this.#destroyed);
636
667
  const adapter = this.#getActiveAdapter();
637
668
  if (!adapter)
638
669
  return;
@@ -645,6 +676,7 @@ class TinyMediaPlayer extends EventEmitter {
645
676
  * @returns {Promise<void>}
646
677
  */
647
678
  async stop() {
679
+ checkDestroy(this.#destroyed);
648
680
  const adapter = this.#getActiveAdapter();
649
681
  if (!adapter)
650
682
  return;
@@ -657,6 +689,7 @@ class TinyMediaPlayer extends EventEmitter {
657
689
  * @returns {Promise<void>}
658
690
  */
659
691
  async next() {
692
+ checkDestroy(this.#destroyed);
660
693
  if (this.#playlist.length === 0)
661
694
  return;
662
695
  await this.stop();
@@ -689,6 +722,7 @@ class TinyMediaPlayer extends EventEmitter {
689
722
  * @returns {Promise<void>}
690
723
  */
691
724
  async prev() {
725
+ checkDestroy(this.#destroyed);
692
726
  if (this.#playlist.length === 0)
693
727
  return;
694
728
  await this.stop();
@@ -719,6 +753,7 @@ class TinyMediaPlayer extends EventEmitter {
719
753
  * @returns {Promise<void>}
720
754
  */
721
755
  async seek(timeMs) {
756
+ checkDestroy(this.#destroyed);
722
757
  if (typeof timeMs !== 'number') {
723
758
  throw new TypeError('Seek time must be a number in milliseconds.');
724
759
  }
@@ -738,6 +773,7 @@ class TinyMediaPlayer extends EventEmitter {
738
773
  * @returns {Promise<void>}
739
774
  */
740
775
  async step(stepMs) {
776
+ checkDestroy(this.#destroyed);
741
777
  if (typeof stepMs !== 'number') {
742
778
  throw new TypeError('Step amount must be a number in milliseconds.');
743
779
  }
@@ -751,5 +787,35 @@ class TinyMediaPlayer extends EventEmitter {
751
787
  targetTime = 0;
752
788
  await this.seek(targetTime);
753
789
  }
790
+ /**
791
+ * Safely destroys the TinyMediaPlayer instance.
792
+ * This method stops active playback, clears the internal playlist,
793
+ * removes all registered media adapters, and detaches all event listeners
794
+ * to ensure proper garbage collection and prevent memory leaks.
795
+ *
796
+ * @returns {Promise<void>} A promise that resolves when the destruction sequence is complete.
797
+ */
798
+ async destroy() {
799
+ if (this.#destroyed)
800
+ return;
801
+ // 1. Stop current playback if active to halt media processes
802
+ try {
803
+ if (this.#isPlaying) {
804
+ await this.stop();
805
+ }
806
+ }
807
+ catch (error) {
808
+ console.warn('[TinyMediaPlayer] Non-fatal error during playback termination:', error);
809
+ }
810
+ // 2. Clear internal state variables
811
+ this.#playlist = [];
812
+ this.#currentIndex = -1;
813
+ this.#isPlaying = false;
814
+ // 3. Clear all registered API adapters
815
+ this.#adapters.clear();
816
+ // 4. Remove all event listeners inherited from EventEmitter
817
+ this.removeAllListeners();
818
+ this.#destroyed = true;
819
+ }
754
820
  }
755
821
  export default TinyMediaPlayer;
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var events = require('events');
4
+ var utils = require('./utils.cjs');
5
+
6
+ const checkDestroy = utils.createCheckDestroyed('TinyNewWinEvents');
4
7
 
5
8
  /**
6
9
  * Stores polling intervals associated with window references.
@@ -51,11 +54,27 @@ class TinyNewWinEvents extends events.EventEmitter {
51
54
  /** @type {string} Internal message type for routed communication */
52
55
  #routeEventName = '__TNE_ROUTE__';
53
56
 
57
+ /**
58
+ * Tracks whether the instance has been destroyed.
59
+ * @type {boolean}
60
+ */
61
+ #destroyed = false;
62
+
63
+ /**
64
+ * Gets the current destruction status of the instance.
65
+ *
66
+ * @returns {boolean} True if the instance is destroyed, false otherwise.
67
+ */
68
+ get destroyed() {
69
+ return this.#destroyed;
70
+ }
71
+
54
72
  /**
55
73
  * Gets the internal handshake event name.
56
74
  * @returns {string}
57
75
  */
58
76
  get readyEventName() {
77
+ checkDestroy(this.#destroyed);
59
78
  return this.#readyEventName;
60
79
  }
61
80
 
@@ -65,6 +84,7 @@ class TinyNewWinEvents extends events.EventEmitter {
65
84
  * @throws {TypeError} If the value is not a string.
66
85
  */
67
86
  set readyEventName(name) {
87
+ checkDestroy(this.#destroyed);
68
88
  if (typeof name !== 'string')
69
89
  throw new TypeError('TinyNewWinEvents: readyEventName must be a string.');
70
90
  this.#readyEventName = name;
@@ -75,6 +95,7 @@ class TinyNewWinEvents extends events.EventEmitter {
75
95
  * @returns {string}
76
96
  */
77
97
  get routeEventName() {
98
+ checkDestroy(this.#destroyed);
78
99
  return this.#routeEventName;
79
100
  }
80
101
 
@@ -84,6 +105,7 @@ class TinyNewWinEvents extends events.EventEmitter {
84
105
  * @throws {TypeError} If the value is not a string.
85
106
  */
86
107
  set routeEventName(name) {
108
+ checkDestroy(this.#destroyed);
87
109
  if (typeof name !== 'string')
88
110
  throw new TypeError('TinyNewWinEvents: routeEventName must be a string.');
89
111
  this.#routeEventName = name;
@@ -142,6 +164,7 @@ class TinyNewWinEvents extends events.EventEmitter {
142
164
  * @returns {Window|null}
143
165
  */
144
166
  getWin() {
167
+ checkDestroy(this.#destroyed);
145
168
  return this.#windowRef;
146
169
  }
147
170
 
@@ -200,6 +223,7 @@ class TinyNewWinEvents extends events.EventEmitter {
200
223
  * @returns {void}
201
224
  */
202
225
  close() {
226
+ checkDestroy(this.#destroyed);
203
227
  if (!this.#isHost) throw new Error('Only host can close the window.');
204
228
  if (this.#windowRef && !this.#windowRef.closed) this.#windowRef.close();
205
229
  }
@@ -215,6 +239,7 @@ class TinyNewWinEvents extends events.EventEmitter {
215
239
  * @returns {void}
216
240
  */
217
241
  winEmit(route, payload) {
242
+ checkDestroy(this.#destroyed);
218
243
  if (typeof route !== 'string') throw new TypeError('Event name must be a string.');
219
244
  if (this.isDestroyed()) throw new Error('Cannot emit: instance has been destroyed.');
220
245
  if (!this.#ready) {
@@ -230,6 +255,7 @@ class TinyNewWinEvents extends events.EventEmitter {
230
255
  * @returns {boolean}
231
256
  */
232
257
  isConnected() {
258
+ checkDestroy(this.#destroyed);
233
259
  return this.#ready && this.#windowRef && !this.#windowRef.closed ? true : false;
234
260
  }
235
261
 
@@ -256,6 +282,7 @@ class TinyNewWinEvents extends events.EventEmitter {
256
282
  * @returns {void}
257
283
  */
258
284
  onClose(callback) {
285
+ checkDestroy(this.#destroyed);
259
286
  this.on('WINDOW_REF_CLOSED', callback);
260
287
  }
261
288
 
@@ -266,6 +293,7 @@ class TinyNewWinEvents extends events.EventEmitter {
266
293
  * @returns {void}
267
294
  */
268
295
  offClose(callback) {
296
+ checkDestroy(this.#destroyed);
269
297
  this.off('WINDOW_REF_CLOSED', callback);
270
298
  }
271
299
 
@@ -295,6 +323,7 @@ class TinyNewWinEvents extends events.EventEmitter {
295
323
  this.#ready = false;
296
324
  this.#windowRef = null;
297
325
  this.removeAllListeners();
326
+ this.#destroyed = true;
298
327
  }
299
328
  }
300
329