@stormstreaming/stormstreamer 1.0.0-rc.0 → 1.0.0
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/amd/index.js +999 -973
- package/dist/cjs/index.js +8 -8
- package/dist/esm/index.js +12 -12
- package/dist/iife/index.js +8 -8
- package/dist/types/StormStreamer.d.ts +6 -384
- package/dist/types/playback/SoundMeter.d.ts +3 -0
- package/dist/types/playback/StreamerController.d.ts +77 -108
- package/dist/types/stage/ScreenElement.d.ts +6 -0
- package/dist/types/stage/StageController.d.ts +4 -0
- package/dist/umd/index.js +8 -8
- package/package.json +1 -1
- package/samples/embed/index_amd.html +50 -0
- package/samples/embed/index_esm.html +46 -0
- package/samples/embed/index_iife.html +45 -0
- package/samples/index.html +492 -0
- package/samples/index_amd.html +0 -50
- package/samples/index_esm.html +0 -46
- package/samples/index_iife.html +0 -45
- /package/samples/{index_umd.html → embed/index_umd.html} +0 -0
package/dist/amd/index.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* contact@stormstreaming.com
|
|
5
5
|
* https://stormstreaming.com
|
|
6
6
|
*
|
|
7
|
-
* Version: 1.0.0
|
|
8
|
-
* Version:
|
|
7
|
+
* Version: 1.0.0
|
|
8
|
+
* Version: 2/7/2026, 6:37:17 PM
|
|
9
9
|
*
|
|
10
10
|
* LEGAL NOTICE:
|
|
11
11
|
* This software is subject to the terms and conditions defined in
|
|
@@ -2500,6 +2500,9 @@
|
|
|
2500
2500
|
* @private
|
|
2501
2501
|
*/
|
|
2502
2502
|
this._isMutedByBrowser = false;
|
|
2503
|
+
// POPRAWKA: Dodane pola do przechowywania referencji do handlerów
|
|
2504
|
+
this._loadedMetadataHandler = null;
|
|
2505
|
+
this._volumeChangeHandler = null;
|
|
2503
2506
|
/**
|
|
2504
2507
|
* Called whenever browser stops playback
|
|
2505
2508
|
*/
|
|
@@ -2555,20 +2558,24 @@
|
|
|
2555
2558
|
* Fires whenever something changes video object volume
|
|
2556
2559
|
* @param event
|
|
2557
2560
|
*/
|
|
2558
|
-
//
|
|
2559
|
-
this.
|
|
2561
|
+
// POPRAWKA: Zapisanie referencji do handlera
|
|
2562
|
+
this._volumeChangeHandler = () => {
|
|
2560
2563
|
this.dispatchVolumeEvent();
|
|
2561
2564
|
};
|
|
2565
|
+
// @ts-ignore
|
|
2566
|
+
this._videoElement.onvolumechange = this._volumeChangeHandler;
|
|
2562
2567
|
this._videoElement.onpause = () => {
|
|
2563
2568
|
// nothing
|
|
2564
2569
|
};
|
|
2565
|
-
|
|
2570
|
+
// POPRAWKA: Zapisanie referencji do handlera
|
|
2571
|
+
this._loadedMetadataHandler = () => {
|
|
2566
2572
|
this._main.dispatchEvent("metadata", {
|
|
2567
2573
|
ref: this._main,
|
|
2568
2574
|
videoWidth: this._videoElement.videoWidth,
|
|
2569
2575
|
videoHeight: this._videoElement.videoHeight
|
|
2570
2576
|
});
|
|
2571
|
-
}
|
|
2577
|
+
};
|
|
2578
|
+
this._videoElement.addEventListener('loadedmetadata', this._loadedMetadataHandler);
|
|
2572
2579
|
/**
|
|
2573
2580
|
* Updates every second,
|
|
2574
2581
|
* @param event
|
|
@@ -2590,7 +2597,9 @@
|
|
|
2590
2597
|
this._videoElement.onended = event => {
|
|
2591
2598
|
this._logger.info(this, "VideoElement :: onended");
|
|
2592
2599
|
};
|
|
2593
|
-
this._videoElement.onplay = () => {
|
|
2600
|
+
this._videoElement.onplay = () => {
|
|
2601
|
+
// nothing
|
|
2602
|
+
};
|
|
2594
2603
|
}
|
|
2595
2604
|
/**
|
|
2596
2605
|
* Sets new volume for playback. It'll also try to store value in a browser memory
|
|
@@ -2647,6 +2656,65 @@
|
|
|
2647
2656
|
getVideoElement() {
|
|
2648
2657
|
return this._videoElement;
|
|
2649
2658
|
}
|
|
2659
|
+
// POPRAWKA: Dodana metoda destroy
|
|
2660
|
+
/**
|
|
2661
|
+
* Destroys the ScreenElement and cleans up all resources
|
|
2662
|
+
*/
|
|
2663
|
+
destroy() {
|
|
2664
|
+
this._logger.info(this, "Destroying ScreenElement...");
|
|
2665
|
+
try {
|
|
2666
|
+
// 1. Usuń event listener z głównej klasy
|
|
2667
|
+
this._main.removeEventListener("playbackForceMute", this.onForceMute);
|
|
2668
|
+
// 2. Zatrzymaj wszelkie aktywne strumienie w elemencie video
|
|
2669
|
+
if (this._videoElement.srcObject instanceof MediaStream) {
|
|
2670
|
+
const stream = this._videoElement.srcObject;
|
|
2671
|
+
stream.getTracks().forEach(track => {
|
|
2672
|
+
track.enabled = false;
|
|
2673
|
+
track.stop();
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
this._videoElement.pause();
|
|
2677
|
+
this._videoElement.onload = null;
|
|
2678
|
+
this._videoElement.onstalled = null;
|
|
2679
|
+
this._videoElement.onerror = null;
|
|
2680
|
+
this._videoElement.onvolumechange = null;
|
|
2681
|
+
this._videoElement.onpause = null;
|
|
2682
|
+
this._videoElement.ontimeupdate = null;
|
|
2683
|
+
this._videoElement.onended = null;
|
|
2684
|
+
this._videoElement.onplay = null;
|
|
2685
|
+
this._videoElement.onloadstart = null;
|
|
2686
|
+
this._videoElement.onloadeddata = null;
|
|
2687
|
+
this._videoElement.onloadedmetadata = null;
|
|
2688
|
+
this._videoElement.oncanplay = null;
|
|
2689
|
+
this._videoElement.oncanplaythrough = null;
|
|
2690
|
+
this._videoElement.onprogress = null;
|
|
2691
|
+
this._videoElement.onseeking = null;
|
|
2692
|
+
this._videoElement.onseeked = null;
|
|
2693
|
+
this._videoElement.onwaiting = null;
|
|
2694
|
+
this._videoElement.ondurationchange = null;
|
|
2695
|
+
this._videoElement.onratechange = null;
|
|
2696
|
+
this._videoElement.onsuspend = null;
|
|
2697
|
+
this._videoElement.onemptied = null;
|
|
2698
|
+
if (this._loadedMetadataHandler) {
|
|
2699
|
+
this._videoElement.removeEventListener('loadedmetadata', this._loadedMetadataHandler);
|
|
2700
|
+
this._loadedMetadataHandler = null;
|
|
2701
|
+
}
|
|
2702
|
+
this._videoElement.removeAttribute('src');
|
|
2703
|
+
this._videoElement.srcObject = null;
|
|
2704
|
+
try {
|
|
2705
|
+
this._videoElement.load();
|
|
2706
|
+
} catch (e) {
|
|
2707
|
+
// Ignoruj błędy podczas load()
|
|
2708
|
+
}
|
|
2709
|
+
this._videoElement.removeAttribute('playsinline');
|
|
2710
|
+
this._videoElement.removeAttribute('webkit-playsinline');
|
|
2711
|
+
if (this._videoElement.parentNode) this._videoElement.parentNode.removeChild(this._videoElement);
|
|
2712
|
+
this._volumeChangeHandler = null;
|
|
2713
|
+
this._logger.success(this, "ScreenElement successfully destroyed");
|
|
2714
|
+
} catch (error) {
|
|
2715
|
+
this._logger.error(this, "Error during ScreenElement destroy: " + error);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2650
2718
|
}
|
|
2651
2719
|
|
|
2652
2720
|
/**
|
|
@@ -2741,6 +2809,10 @@
|
|
|
2741
2809
|
*/
|
|
2742
2810
|
this._parentOriginalOverflow = '';
|
|
2743
2811
|
this._debug = false;
|
|
2812
|
+
this._animationFrameId = null;
|
|
2813
|
+
this._isDestroying = false;
|
|
2814
|
+
this._fullscreenChangeHandler = null;
|
|
2815
|
+
this._transitionEndHandler = null;
|
|
2744
2816
|
//------------------------------------------------------------------------//
|
|
2745
2817
|
// FULLSCREEN
|
|
2746
2818
|
//------------------------------------------------------------------------//
|
|
@@ -2795,11 +2867,15 @@
|
|
|
2795
2867
|
if (this._autoResizeEnabled) this.handleResize();
|
|
2796
2868
|
});
|
|
2797
2869
|
}
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2870
|
+
this._fullscreenChangeHandler = this.onFullScreenChange;
|
|
2871
|
+
this._transitionEndHandler = () => {
|
|
2872
|
+
this.handleResize();
|
|
2873
|
+
};
|
|
2874
|
+
document.addEventListener('fullscreenchange', this._fullscreenChangeHandler, false);
|
|
2875
|
+
document.addEventListener('webkitfullscreenchange', this._fullscreenChangeHandler, false);
|
|
2876
|
+
document.addEventListener('mozfullscreenchange', this._fullscreenChangeHandler, false);
|
|
2877
|
+
document.addEventListener('webkitendfullscreen', this._fullscreenChangeHandler, false);
|
|
2878
|
+
this._screenElement.getVideoElement().addEventListener('webkitendfullscreen', this._fullscreenChangeHandler, false);
|
|
2803
2879
|
this._main.addEventListener("metadata", event => {
|
|
2804
2880
|
this._videoWidth = event.videoWidth;
|
|
2805
2881
|
this._videoHeight = event.videoHeight;
|
|
@@ -2861,12 +2937,14 @@
|
|
|
2861
2937
|
let result = false;
|
|
2862
2938
|
if (this._parentElement != null && this._videoContainer != null) {
|
|
2863
2939
|
this._logger.info(this, "Detaching from parent: " + this._videoContainer);
|
|
2940
|
+
// POPRAWKA: Usuń event listener z zapisanym handlerem
|
|
2941
|
+
if (this._transitionEndHandler) {
|
|
2942
|
+
this._parentElement.removeEventListener("transitionend", this._transitionEndHandler);
|
|
2943
|
+
}
|
|
2864
2944
|
this._parentElement.removeChild(this._videoContainer);
|
|
2865
2945
|
if (this._resizeObserver) {
|
|
2866
2946
|
this._resizeObserver.unobserve(this._parentElement);
|
|
2867
|
-
this._resizeObserver.disconnect();
|
|
2868
2947
|
}
|
|
2869
|
-
if (this._autoResizeEnabled) this._parentElement.removeEventListener("transitionend", this.handleResize);
|
|
2870
2948
|
this._main.dispatchEvent("containerChange", {
|
|
2871
2949
|
ref: this._main,
|
|
2872
2950
|
container: null
|
|
@@ -2879,16 +2957,22 @@
|
|
|
2879
2957
|
return result;
|
|
2880
2958
|
}
|
|
2881
2959
|
handleResize() {
|
|
2882
|
-
if (!this._parentElement || this._isResizing) return;
|
|
2960
|
+
if (!this._parentElement || this._isResizing || this._isDestroying) return;
|
|
2961
|
+
// POPRAWKA: Anulowanie poprzedniego requestAnimationFrame
|
|
2962
|
+
if (this._animationFrameId !== null) {
|
|
2963
|
+
cancelAnimationFrame(this._animationFrameId);
|
|
2964
|
+
}
|
|
2883
2965
|
this._isResizing = true;
|
|
2884
2966
|
this._parentOriginalOverflow = this._parentElement.style.overflow;
|
|
2885
2967
|
this._parentElement.style.overflow = 'hidden';
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
// Obliczamy nowe wymiary
|
|
2968
|
+
this._animationFrameId = requestAnimationFrame(() => {
|
|
2969
|
+
if (this._isDestroying) return;
|
|
2889
2970
|
this.calculateNewDimensions();
|
|
2890
|
-
this._parentElement
|
|
2971
|
+
if (this._parentElement) {
|
|
2972
|
+
this._parentElement.style.overflow = this._parentOriginalOverflow;
|
|
2973
|
+
}
|
|
2891
2974
|
this._isResizing = false;
|
|
2975
|
+
this._animationFrameId = null;
|
|
2892
2976
|
if (this._tempContainerWidth !== this._containerWidth || this._tempContainerHeight !== this._containerHeight) {
|
|
2893
2977
|
this._main.dispatchEvent("resizeUpdate", {
|
|
2894
2978
|
ref: this._main,
|
|
@@ -3145,7 +3229,83 @@
|
|
|
3145
3229
|
// CLEANUP
|
|
3146
3230
|
//------------------------------------------------------------------------//
|
|
3147
3231
|
destroy() {
|
|
3148
|
-
|
|
3232
|
+
var _a;
|
|
3233
|
+
this._logger.info(this, "Starting StageController destroy...");
|
|
3234
|
+
this._isDestroying = true;
|
|
3235
|
+
try {
|
|
3236
|
+
// 1. Anuluj animacje
|
|
3237
|
+
if (this._animationFrameId !== null) {
|
|
3238
|
+
cancelAnimationFrame(this._animationFrameId);
|
|
3239
|
+
this._animationFrameId = null;
|
|
3240
|
+
}
|
|
3241
|
+
// 2. Odłącz od rodzica
|
|
3242
|
+
this.detachFromParent();
|
|
3243
|
+
// 3. Zatrzymaj i rozłącz ResizeObserver
|
|
3244
|
+
if (this._resizeObserver) {
|
|
3245
|
+
this._resizeObserver.disconnect();
|
|
3246
|
+
}
|
|
3247
|
+
// 4. Usuń event listenery fullscreen
|
|
3248
|
+
if (this._fullscreenChangeHandler) {
|
|
3249
|
+
document.removeEventListener('fullscreenchange', this._fullscreenChangeHandler);
|
|
3250
|
+
document.removeEventListener('webkitfullscreenchange', this._fullscreenChangeHandler);
|
|
3251
|
+
document.removeEventListener('mozfullscreenchange', this._fullscreenChangeHandler);
|
|
3252
|
+
document.removeEventListener('webkitendfullscreen', this._fullscreenChangeHandler);
|
|
3253
|
+
if ((_a = this._screenElement) === null || _a === void 0 ? void 0 : _a.getVideoElement()) {
|
|
3254
|
+
this._screenElement.getVideoElement().removeEventListener('webkitendfullscreen', this._fullscreenChangeHandler);
|
|
3255
|
+
}
|
|
3256
|
+
this._fullscreenChangeHandler = null;
|
|
3257
|
+
}
|
|
3258
|
+
// 6. Zniszcz ScreenElement
|
|
3259
|
+
if (this._screenElement) {
|
|
3260
|
+
// Wyczyść video element
|
|
3261
|
+
const videoElement = this._screenElement.getVideoElement();
|
|
3262
|
+
if (videoElement) {
|
|
3263
|
+
// Zatrzymaj wszelkie strumienie
|
|
3264
|
+
if (videoElement.srcObject instanceof MediaStream) {
|
|
3265
|
+
videoElement.srcObject.getTracks().forEach(track => {
|
|
3266
|
+
track.enabled = false;
|
|
3267
|
+
track.stop();
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
// Wyczyść element
|
|
3271
|
+
videoElement.pause();
|
|
3272
|
+
videoElement.removeAttribute('src');
|
|
3273
|
+
videoElement.srcObject = null;
|
|
3274
|
+
videoElement.load();
|
|
3275
|
+
// Usuń z DOM jeśli jest podłączony
|
|
3276
|
+
if (videoElement.parentNode) {
|
|
3277
|
+
videoElement.parentNode.removeChild(videoElement);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
// Jeśli ScreenElement ma własną metodę destroy, wywołaj ją
|
|
3281
|
+
if (typeof this._screenElement.destroy === 'function') {
|
|
3282
|
+
this._screenElement.destroy();
|
|
3283
|
+
}
|
|
3284
|
+
this._screenElement = null;
|
|
3285
|
+
}
|
|
3286
|
+
// 7. Usuń kontener video z DOM
|
|
3287
|
+
if (this._videoContainer) {
|
|
3288
|
+
if (this._videoContainer.parentNode) {
|
|
3289
|
+
this._videoContainer.parentNode.removeChild(this._videoContainer);
|
|
3290
|
+
}
|
|
3291
|
+
this._videoContainer = null;
|
|
3292
|
+
}
|
|
3293
|
+
// 8. Resetuj zmienne
|
|
3294
|
+
this._containerWidth = 0;
|
|
3295
|
+
this._containerHeight = 0;
|
|
3296
|
+
this._tempContainerWidth = 0;
|
|
3297
|
+
this._tempContainerHeight = 0;
|
|
3298
|
+
this._videoWidth = 0;
|
|
3299
|
+
this._videoHeight = 0;
|
|
3300
|
+
this.isInFullScreenMode = false;
|
|
3301
|
+
this._isResizing = false;
|
|
3302
|
+
this._autoResizeEnabled = false;
|
|
3303
|
+
this._logger.success(this, "StageController successfully destroyed");
|
|
3304
|
+
} catch (error) {
|
|
3305
|
+
this._logger.error(this, "Error during destroy: " + error);
|
|
3306
|
+
} finally {
|
|
3307
|
+
this._isDestroying = false;
|
|
3308
|
+
}
|
|
3149
3309
|
}
|
|
3150
3310
|
}
|
|
3151
3311
|
/**
|
|
@@ -3512,9 +3672,12 @@
|
|
|
3512
3672
|
this._lastEventTime = 0;
|
|
3513
3673
|
this.THROTTLE_INTERVAL = 100; // ms between updates
|
|
3514
3674
|
this._isMonitoring = false;
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
this.
|
|
3675
|
+
this._animationFrameId = null;
|
|
3676
|
+
// Peak levels for VU meter
|
|
3677
|
+
this._instant = 0.0; // Current peak (fast)
|
|
3678
|
+
this._slow = 0.0; // Peak hold with decay
|
|
3679
|
+
// Decay rate for peak hold (0.95 = slow decay, 0.8 = fast decay)
|
|
3680
|
+
this.PEAK_DECAY = 0.92;
|
|
3518
3681
|
this._main = main;
|
|
3519
3682
|
}
|
|
3520
3683
|
attach(stream) {
|
|
@@ -3530,22 +3693,24 @@
|
|
|
3530
3693
|
this._microphone = this._audioContext.createMediaStreamSource(stream);
|
|
3531
3694
|
this._analyser = this._audioContext.createAnalyser();
|
|
3532
3695
|
this._analyser.fftSize = 2048;
|
|
3533
|
-
this._analyser.smoothingTimeConstant = 0.3;
|
|
3534
3696
|
// Connect nodes
|
|
3535
3697
|
this._microphone.connect(this._analyser);
|
|
3536
3698
|
// Start monitoring
|
|
3537
3699
|
this.startMonitoring();
|
|
3538
3700
|
} catch (error) {
|
|
3539
3701
|
console.error('SoundMeter: Error during attach:', error);
|
|
3540
|
-
this.detach();
|
|
3702
|
+
this.detach();
|
|
3541
3703
|
}
|
|
3542
3704
|
}
|
|
3543
|
-
|
|
3544
3705
|
detach() {
|
|
3545
|
-
|
|
3546
|
-
|
|
3706
|
+
if (!this._audioContext && !this._analyser && !this._microphone) {
|
|
3707
|
+
return;
|
|
3708
|
+
}
|
|
3547
3709
|
this._isMonitoring = false;
|
|
3548
|
-
|
|
3710
|
+
if (this._animationFrameId !== null) {
|
|
3711
|
+
cancelAnimationFrame(this._animationFrameId);
|
|
3712
|
+
this._animationFrameId = null;
|
|
3713
|
+
}
|
|
3549
3714
|
if (this._microphone) {
|
|
3550
3715
|
try {
|
|
3551
3716
|
this._microphone.disconnect();
|
|
@@ -3562,14 +3727,22 @@
|
|
|
3562
3727
|
}
|
|
3563
3728
|
this._analyser = null;
|
|
3564
3729
|
}
|
|
3565
|
-
if (
|
|
3730
|
+
if (this._audioContext) {
|
|
3566
3731
|
try {
|
|
3567
|
-
(
|
|
3732
|
+
if (this._audioContext.state !== 'closed') {
|
|
3733
|
+
const audioContextRef = this._audioContext;
|
|
3734
|
+
this._audioContext = null;
|
|
3735
|
+
audioContextRef.suspend().then(() => audioContextRef.close()).catch(e => {
|
|
3736
|
+
console.warn('SoundMeter: Error closing audio context:', e);
|
|
3737
|
+
});
|
|
3738
|
+
} else {
|
|
3739
|
+
this._audioContext = null;
|
|
3740
|
+
}
|
|
3568
3741
|
} catch (e) {
|
|
3569
|
-
console.warn('SoundMeter: Error
|
|
3742
|
+
console.warn('SoundMeter: Error handling audio context:', e);
|
|
3743
|
+
this._audioContext = null;
|
|
3570
3744
|
}
|
|
3571
3745
|
}
|
|
3572
|
-
this._audioContext = null;
|
|
3573
3746
|
this.clear();
|
|
3574
3747
|
}
|
|
3575
3748
|
clear() {
|
|
@@ -3582,24 +3755,29 @@
|
|
|
3582
3755
|
this._isMonitoring = true;
|
|
3583
3756
|
const dataArray = new Float32Array(this._analyser.frequencyBinCount);
|
|
3584
3757
|
const analyze = () => {
|
|
3585
|
-
if (!this._analyser || !this._isMonitoring)
|
|
3758
|
+
if (!this._analyser || !this._isMonitoring || !this._audioContext) {
|
|
3759
|
+
this._animationFrameId = null;
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3586
3762
|
const now = Date.now();
|
|
3587
3763
|
try {
|
|
3588
|
-
// Read time-domain data
|
|
3589
3764
|
this._analyser.getFloatTimeDomainData(dataArray);
|
|
3590
|
-
//
|
|
3591
|
-
let
|
|
3592
|
-
let clipcount = 0;
|
|
3765
|
+
// Peak detection - find maximum amplitude
|
|
3766
|
+
let peak = 0.0;
|
|
3593
3767
|
for (let i = 0; i < dataArray.length; ++i) {
|
|
3594
|
-
const amplitude = dataArray[i];
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
clipcount += 1;
|
|
3768
|
+
const amplitude = Math.abs(dataArray[i]);
|
|
3769
|
+
if (amplitude > peak) {
|
|
3770
|
+
peak = amplitude;
|
|
3598
3771
|
}
|
|
3599
3772
|
}
|
|
3600
|
-
//
|
|
3601
|
-
this._instant = Math.
|
|
3602
|
-
|
|
3773
|
+
// Instant = current peak (clamped to 1.0)
|
|
3774
|
+
this._instant = Math.min(1.0, peak);
|
|
3775
|
+
// Slow = peak hold with decay (classic VU meter behavior)
|
|
3776
|
+
if (this._instant > this._slow) {
|
|
3777
|
+
this._slow = this._instant;
|
|
3778
|
+
} else {
|
|
3779
|
+
this._slow *= this.PEAK_DECAY;
|
|
3780
|
+
}
|
|
3603
3781
|
// Throttle event dispatch
|
|
3604
3782
|
if (now - this._lastEventTime >= this.THROTTLE_INTERVAL) {
|
|
3605
3783
|
this._lastEventTime = now;
|
|
@@ -3612,13 +3790,17 @@
|
|
|
3612
3790
|
} catch (error) {
|
|
3613
3791
|
console.error('SoundMeter: Error during analysis:', error);
|
|
3614
3792
|
this._isMonitoring = false;
|
|
3793
|
+
this._animationFrameId = null;
|
|
3615
3794
|
return;
|
|
3616
3795
|
}
|
|
3617
|
-
|
|
3618
|
-
|
|
3796
|
+
if (this._isMonitoring) {
|
|
3797
|
+
this._animationFrameId = requestAnimationFrame(analyze);
|
|
3798
|
+
}
|
|
3619
3799
|
};
|
|
3620
|
-
|
|
3621
|
-
|
|
3800
|
+
this._animationFrameId = requestAnimationFrame(analyze);
|
|
3801
|
+
}
|
|
3802
|
+
destroy() {
|
|
3803
|
+
this.detach();
|
|
3622
3804
|
}
|
|
3623
3805
|
}
|
|
3624
3806
|
|
|
@@ -4018,7 +4200,6 @@
|
|
|
4018
4200
|
streamStatusInfo.videoWidth = msgJSON.videoWidth;
|
|
4019
4201
|
streamStatusInfo.videoHeight = msgJSON.videoHeight;
|
|
4020
4202
|
streamStatusInfo.currentBitrate = msgJSON.realBitrate;
|
|
4021
|
-
console.log(streamStatusInfo);
|
|
4022
4203
|
this._main.dispatchEvent("streamStatusUpdate", {
|
|
4023
4204
|
ref: this._main,
|
|
4024
4205
|
streamStatus: streamStatusInfo
|
|
@@ -4137,7 +4318,7 @@
|
|
|
4137
4318
|
* @param config
|
|
4138
4319
|
*/
|
|
4139
4320
|
constructor(main) {
|
|
4140
|
-
var _a, _b, _c;
|
|
4321
|
+
var _a, _b, _c, _d;
|
|
4141
4322
|
/**
|
|
4142
4323
|
* Whenever current window is active or not
|
|
4143
4324
|
* @private
|
|
@@ -4151,7 +4332,7 @@
|
|
|
4151
4332
|
'iceServers': []
|
|
4152
4333
|
};
|
|
4153
4334
|
/**
|
|
4154
|
-
* Whenever microphone is
|
|
4335
|
+
* Whenever microphone is currently muted
|
|
4155
4336
|
* @private
|
|
4156
4337
|
*/
|
|
4157
4338
|
this._isMicrophoneMuted = false;
|
|
@@ -4161,7 +4342,7 @@
|
|
|
4161
4342
|
*/
|
|
4162
4343
|
this._pendingMicrophoneState = null;
|
|
4163
4344
|
/**
|
|
4164
|
-
* Whenever we have
|
|
4345
|
+
* Whenever we have checked for permissions
|
|
4165
4346
|
* @private
|
|
4166
4347
|
*/
|
|
4167
4348
|
this._permissionChecked = false;
|
|
@@ -4224,57 +4405,93 @@
|
|
|
4224
4405
|
this._currentOrientation = ((_a = window.screen.orientation) === null || _a === void 0 ? void 0 : _a.type) || '';
|
|
4225
4406
|
this._statusTimer = null;
|
|
4226
4407
|
this._debug = false;
|
|
4408
|
+
// FIELDS FOR OPERATION CANCELLATION
|
|
4409
|
+
this._deviceChangeHandler = null;
|
|
4410
|
+
this._orientationChangeHandler = null;
|
|
4411
|
+
/**
|
|
4412
|
+
* CRITICAL: This flag is checked after EVERY async operation
|
|
4413
|
+
* Set to true immediately when destroy() is called
|
|
4414
|
+
* @private
|
|
4415
|
+
*/
|
|
4416
|
+
this._isDestroyed = false;
|
|
4417
|
+
this._cameraAbortController = null;
|
|
4418
|
+
this._microphoneAbortController = null;
|
|
4419
|
+
this._startCameraAbortController = null;
|
|
4420
|
+
this._switchingCamera = false;
|
|
4421
|
+
this._switchingMicrophone = false;
|
|
4422
|
+
this._firstPublish = true;
|
|
4423
|
+
/**
|
|
4424
|
+
* Counter for tracking active media streams (for debugging)
|
|
4425
|
+
* @private
|
|
4426
|
+
*/
|
|
4427
|
+
this._activeStreamCount = 0;
|
|
4428
|
+
/**
|
|
4429
|
+
* Permission status objects that need cleanup
|
|
4430
|
+
* @private
|
|
4431
|
+
*/
|
|
4432
|
+
this._cameraPermissionStatus = null;
|
|
4433
|
+
this._microphonePermissionStatus = null;
|
|
4434
|
+
/**
|
|
4435
|
+
* Bound handlers for permission changes (needed for removal)
|
|
4436
|
+
* @private
|
|
4437
|
+
*/
|
|
4438
|
+
this._boundCameraPermissionHandler = null;
|
|
4439
|
+
this._boundMicrophonePermissionHandler = null;
|
|
4227
4440
|
/**
|
|
4228
4441
|
* Handles device state changes and initiates publishing if appropriate
|
|
4229
4442
|
* @private
|
|
4230
4443
|
*/
|
|
4231
4444
|
this.onDeviceStateChange = event => {
|
|
4232
4445
|
var _a;
|
|
4446
|
+
if (this._isDestroyed) return;
|
|
4233
4447
|
const usedStreamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
4234
4448
|
if (event.state == exports.InputDevicesState.READY && usedStreamKey != null) {
|
|
4235
4449
|
this.publish(usedStreamKey);
|
|
4236
4450
|
}
|
|
4237
4451
|
};
|
|
4238
4452
|
/**
|
|
4239
|
-
*
|
|
4453
|
+
* Handles orientation changes for mobile devices
|
|
4240
4454
|
*/
|
|
4241
4455
|
this.handleOrientationChange = () => __awaiter(this, void 0, void 0, function* () {
|
|
4242
|
-
var
|
|
4243
|
-
|
|
4456
|
+
var _e, _f;
|
|
4457
|
+
if (this._isDestroyed) return;
|
|
4244
4458
|
yield new Promise(resolve => setTimeout(resolve, 500));
|
|
4245
|
-
|
|
4459
|
+
if (this._isDestroyed) return;
|
|
4460
|
+
const newOrientation = ((_e = window.screen.orientation) === null || _e === void 0 ? void 0 : _e.type) || '';
|
|
4246
4461
|
if (this._currentOrientation !== newOrientation) {
|
|
4247
4462
|
this._logger.info(this, `Orientation changed from ${this._currentOrientation} to ${newOrientation}`);
|
|
4248
4463
|
this._currentOrientation = newOrientation;
|
|
4249
|
-
|
|
4250
|
-
const streamKey = (_e = this._main.getConfigManager()) === null || _e === void 0 ? void 0 : _e.getStreamData().streamKey;
|
|
4464
|
+
const streamKey = (_f = this._main.getConfigManager()) === null || _f === void 0 ? void 0 : _f.getStreamData().streamKey;
|
|
4251
4465
|
this._publishState === exports.PublishState.PUBLISHED;
|
|
4252
|
-
// Zamknij istniejące połączenie i stream
|
|
4253
4466
|
this.closeWebRTCConnection();
|
|
4254
4467
|
if (this._stream) {
|
|
4468
|
+
this._logger.info(this, "📹 [RELEASE] handleOrientationChange() - releasing stream for orientation change");
|
|
4255
4469
|
this._stream.getTracks().forEach(track => {
|
|
4256
4470
|
track.stop();
|
|
4471
|
+
this._logger.info(this, `📹 [RELEASE] handleOrientationChange() - stopped track: ${track.kind}`);
|
|
4257
4472
|
});
|
|
4258
4473
|
this._stream = null;
|
|
4474
|
+
this._activeStreamCount--;
|
|
4259
4475
|
}
|
|
4260
|
-
|
|
4476
|
+
if (this._isDestroyed) return;
|
|
4261
4477
|
try {
|
|
4262
4478
|
yield this.startCamera();
|
|
4263
|
-
|
|
4479
|
+
if (this._isDestroyed) return;
|
|
4264
4480
|
if (streamKey) {
|
|
4265
4481
|
this.publish(streamKey);
|
|
4266
4482
|
}
|
|
4267
4483
|
} catch (error) {
|
|
4484
|
+
if (this._isDestroyed) return;
|
|
4268
4485
|
this._logger.error(this, "Error restarting stream after orientation change: " + JSON.stringify(error));
|
|
4269
4486
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4270
4487
|
}
|
|
4271
4488
|
}
|
|
4272
4489
|
});
|
|
4273
|
-
this.onServerDisconnect = () => {
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
*/
|
|
4490
|
+
this.onServerDisconnect = () => {
|
|
4491
|
+
// Implementation
|
|
4492
|
+
};
|
|
4277
4493
|
this.onStreamKeyTaken = () => {
|
|
4494
|
+
if (this._isDestroyed) return;
|
|
4278
4495
|
if (this._restartTimer != null) {
|
|
4279
4496
|
clearInterval(this._restartTimer);
|
|
4280
4497
|
this._restartTimerCount = 0;
|
|
@@ -4283,6 +4500,10 @@
|
|
|
4283
4500
|
this.setPublishState(exports.PublishState.ERROR);
|
|
4284
4501
|
this._restartTimer = setInterval(() => {
|
|
4285
4502
|
var _a, _b;
|
|
4503
|
+
if (this._isDestroyed) {
|
|
4504
|
+
if (this._restartTimer) clearInterval(this._restartTimer);
|
|
4505
|
+
return;
|
|
4506
|
+
}
|
|
4286
4507
|
if (this._restartTimer != null) {
|
|
4287
4508
|
if (this._restartTimerCount < this._restartTimerMaxCount) {
|
|
4288
4509
|
this._logger.info(this, "WebRTCStreamer :: StreamKeyTaken Interval: " + this._restartTimerCount + "/" + this._restartTimerMaxCount);
|
|
@@ -4293,41 +4514,37 @@
|
|
|
4293
4514
|
this._restartTimerCount = 0;
|
|
4294
4515
|
const streamData = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData();
|
|
4295
4516
|
const streamKey = streamData === null || streamData === void 0 ? void 0 : streamData.streamKey;
|
|
4296
|
-
if (streamKey != null) {
|
|
4297
|
-
|
|
4298
|
-
this.publish(prevStreamKey);
|
|
4517
|
+
if (streamKey != null && !this._isDestroyed) {
|
|
4518
|
+
this.publish(streamKey);
|
|
4299
4519
|
}
|
|
4300
4520
|
}
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4521
|
+
if (!this._isDestroyed) {
|
|
4522
|
+
const usedStreamKey = (_b = this._main.getConfigManager().getStreamData().streamKey) !== null && _b !== void 0 ? _b : "unknown";
|
|
4523
|
+
this._main.dispatchEvent("streamKeyInUseInterval", {
|
|
4524
|
+
ref: this._main,
|
|
4525
|
+
streamKey: usedStreamKey,
|
|
4526
|
+
count: this._restartTimerCount,
|
|
4527
|
+
maxCount: this._restartTimerMaxCount
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4308
4530
|
}
|
|
4309
4531
|
}, 1000);
|
|
4310
4532
|
};
|
|
4311
4533
|
//------------------------------------------------------------------------//
|
|
4312
4534
|
// NETWORK AND RTC
|
|
4313
4535
|
//------------------------------------------------------------------------//
|
|
4314
|
-
/**
|
|
4315
|
-
* Method fires once a connection with wowza is established. It's the main connection where we exchange
|
|
4316
|
-
* ice-candidates and SDP. We'll start setting up peer connections.
|
|
4317
|
-
*/
|
|
4318
4536
|
this.onServerConnect = () => {
|
|
4537
|
+
if (this._isDestroyed) return;
|
|
4319
4538
|
if (this._peerConnection) {
|
|
4320
4539
|
this.closeWebRTCConnection();
|
|
4321
4540
|
}
|
|
4322
4541
|
this._peerConnection = new RTCPeerConnection(this._peerConnectionConfig);
|
|
4323
|
-
// Najpierw dodaj tracki
|
|
4324
4542
|
if (this._stream) {
|
|
4325
4543
|
let localTracks = this._stream.getTracks();
|
|
4326
4544
|
for (let localTrack in localTracks) {
|
|
4327
4545
|
this._peerConnection.addTrack(localTracks[localTrack], this._stream);
|
|
4328
4546
|
}
|
|
4329
4547
|
}
|
|
4330
|
-
// Potem dodaj event handlery
|
|
4331
4548
|
this._peerConnection.onicecandidate = event => {
|
|
4332
4549
|
this.onIceCandidate(event);
|
|
4333
4550
|
};
|
|
@@ -4335,26 +4552,29 @@
|
|
|
4335
4552
|
this.onConnectionStateChange(event);
|
|
4336
4553
|
};
|
|
4337
4554
|
this._peerConnection.onnegotiationneeded = event => __awaiter(this, void 0, void 0, function* () {
|
|
4338
|
-
if (this._peerConnection) {
|
|
4555
|
+
if (this._peerConnection && !this._isDestroyed) {
|
|
4339
4556
|
try {
|
|
4340
4557
|
const description = yield this._peerConnection.createOffer();
|
|
4341
|
-
|
|
4558
|
+
if (!this._isDestroyed) {
|
|
4559
|
+
yield this.onDescriptionSuccess(description);
|
|
4560
|
+
}
|
|
4342
4561
|
} catch (error) {
|
|
4343
4562
|
this.onDescriptionError(error);
|
|
4344
|
-
console.error('Error creating offer:', error);
|
|
4345
4563
|
}
|
|
4346
4564
|
}
|
|
4347
4565
|
});
|
|
4348
4566
|
this.createStatusConnection();
|
|
4349
4567
|
};
|
|
4350
|
-
/**
|
|
4351
|
-
* Method fires once a status connection is established. We'll set an interval for monitoring stream status.
|
|
4352
|
-
*/
|
|
4353
4568
|
this.onStatusServerConnect = () => {
|
|
4569
|
+
if (this._isDestroyed) return;
|
|
4354
4570
|
const usedStreamKey = this._main.getConfigManager().getStreamData().streamKey;
|
|
4355
4571
|
if (this._statusTimer == null) {
|
|
4356
4572
|
if (this._statusConnection != null && usedStreamKey != null) {
|
|
4357
4573
|
this._statusTimer = setInterval(() => {
|
|
4574
|
+
if (this._isDestroyed) {
|
|
4575
|
+
if (this._statusTimer) clearInterval(this._statusTimer);
|
|
4576
|
+
return;
|
|
4577
|
+
}
|
|
4358
4578
|
this.requestStatusData();
|
|
4359
4579
|
}, 1000);
|
|
4360
4580
|
}
|
|
@@ -4362,27 +4582,22 @@
|
|
|
4362
4582
|
};
|
|
4363
4583
|
this.requestStatusData = () => {
|
|
4364
4584
|
var _a;
|
|
4585
|
+
if (this._isDestroyed) return;
|
|
4365
4586
|
(_a = this._statusConnection) === null || _a === void 0 ? void 0 : _a.sendData('{"packetID":"STREAM_STATUS", "streamKey": "' + this._fullStreamName + '"}');
|
|
4366
4587
|
};
|
|
4367
|
-
/**
|
|
4368
|
-
* If for some reason the status connection is disconnected we have to clean the interval
|
|
4369
|
-
*/
|
|
4370
4588
|
this.onStatusServerDisconnect = () => {
|
|
4371
4589
|
if (this._statusTimer != null) clearInterval(this._statusTimer);
|
|
4372
4590
|
this._statusTimer = null;
|
|
4373
4591
|
};
|
|
4374
|
-
/**
|
|
4375
|
-
* This event fires whenever "STREAM_STATUS_RESPONSE" packet form status connection reports stream status along some stream data. This gives
|
|
4376
|
-
* us an insight into whenever our stream is ok (works) or not.
|
|
4377
|
-
* @param event
|
|
4378
|
-
*/
|
|
4379
4592
|
this.onStreamStatsUpdate = event => {
|
|
4593
|
+
if (this._isDestroyed) return;
|
|
4380
4594
|
const update = event.streamStatus;
|
|
4381
4595
|
if (this._publishState == exports.PublishState.PUBLISHED && update.publishState != exports.PublishState.PUBLISHED) this.setPublishState(exports.PublishState.UNPUBLISHED);
|
|
4382
4596
|
if (this._publishState == exports.PublishState.CONNECTED && update.publishState == exports.PublishState.PUBLISHED) this.setPublishState(exports.PublishState.PUBLISHED);
|
|
4383
4597
|
};
|
|
4384
4598
|
this.onDescriptionSuccess = description => {
|
|
4385
4599
|
var _a, _b, _c;
|
|
4600
|
+
if (this._isDestroyed) return;
|
|
4386
4601
|
this._fullStreamName = ((_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey) + "_" + new Date().getTime();
|
|
4387
4602
|
const streamInfo = {
|
|
4388
4603
|
applicationName: (_c = (_b = this._main.getNetworkController()) === null || _b === void 0 ? void 0 : _b.getConnection().getCurrentServer()) === null || _c === void 0 ? void 0 : _c.getApplication(),
|
|
@@ -4396,22 +4611,19 @@
|
|
|
4396
4611
|
videoCodec: "42e01f",
|
|
4397
4612
|
audioCodec: "opus"
|
|
4398
4613
|
});
|
|
4399
|
-
if (this._peerConnection) {
|
|
4614
|
+
if (this._peerConnection && !this._isDestroyed) {
|
|
4400
4615
|
this._peerConnection.setLocalDescription(description).then(() => {
|
|
4401
4616
|
var _a;
|
|
4617
|
+
if (this._isDestroyed) return;
|
|
4402
4618
|
(_a = this._main.getNetworkController()) === null || _a === void 0 ? void 0 : _a.sendMessage('{"direction":"publish", "command":"sendOffer", "streamInfo":' + JSON.stringify(streamInfo) + ', "sdp":' + JSON.stringify(description) + '}');
|
|
4403
4619
|
}).catch(error => {
|
|
4404
4620
|
console.log(error);
|
|
4405
|
-
//this.onWebRTCError(error, self);
|
|
4406
4621
|
});
|
|
4407
4622
|
}
|
|
4408
4623
|
};
|
|
4409
4624
|
//------------------------------------------------------------------------//
|
|
4410
4625
|
// BLUR & FOCUS
|
|
4411
4626
|
//------------------------------------------------------------------------//
|
|
4412
|
-
/**
|
|
4413
|
-
* Methods handles visibility change events
|
|
4414
|
-
*/
|
|
4415
4627
|
this.visibilityChange = () => {
|
|
4416
4628
|
if (document.visibilityState === 'hidden') {
|
|
4417
4629
|
this.onWindowBlur();
|
|
@@ -4419,18 +4631,12 @@
|
|
|
4419
4631
|
this.onWindowFocus();
|
|
4420
4632
|
}
|
|
4421
4633
|
};
|
|
4422
|
-
/**
|
|
4423
|
-
* Reacts to browser changing visibility of the document (or blur)
|
|
4424
|
-
*/
|
|
4425
4634
|
this.onWindowBlur = () => {
|
|
4426
4635
|
if (this._isWindowActive) {
|
|
4427
4636
|
this._logger.warning(this, "Player window is no longer in focus!");
|
|
4428
4637
|
}
|
|
4429
4638
|
this._isWindowActive = false;
|
|
4430
4639
|
};
|
|
4431
|
-
/**
|
|
4432
|
-
* Reacts to browser changing visibility of the document (or focus)
|
|
4433
|
-
*/
|
|
4434
4640
|
this.onWindowFocus = () => {
|
|
4435
4641
|
if (!this._isWindowActive) {
|
|
4436
4642
|
this._logger.info(this, "Player window is focused again!");
|
|
@@ -4442,38 +4648,76 @@
|
|
|
4442
4648
|
this._mungeSDP = new MungeSDP();
|
|
4443
4649
|
this._soundMeter = new SoundMeter(this._main);
|
|
4444
4650
|
this._debug = (_c = (_b = this._main.getConfigManager()) === null || _b === void 0 ? void 0 : _b.getSettingsData().getDebugData().streamerControllerDebug) !== null && _c !== void 0 ? _c : this._debug;
|
|
4651
|
+
// Restore saved microphone mute state
|
|
4652
|
+
const savedMuteState = (_d = this._main.getStorageManager()) === null || _d === void 0 ? void 0 : _d.getField("microphoneMuted");
|
|
4653
|
+
if (savedMuteState !== null) {
|
|
4654
|
+
this._isMicrophoneMuted = savedMuteState === "true";
|
|
4655
|
+
this._logger.info(this, `📹 [INIT] Restored microphone mute state: ${this._isMicrophoneMuted}`);
|
|
4656
|
+
}
|
|
4445
4657
|
// Start initialization process
|
|
4446
4658
|
this.initialize();
|
|
4447
4659
|
}
|
|
4448
4660
|
//------------------------------------------------------------------------//
|
|
4661
|
+
// HELPER: Check if destroyed
|
|
4662
|
+
//------------------------------------------------------------------------//
|
|
4663
|
+
/**
|
|
4664
|
+
* Helper method to check if instance is destroyed
|
|
4665
|
+
* Call this after EVERY await!
|
|
4666
|
+
* @private
|
|
4667
|
+
*/
|
|
4668
|
+
isDestroyedCheck(context) {
|
|
4669
|
+
var _a;
|
|
4670
|
+
if (this._isDestroyed) {
|
|
4671
|
+
if (context) {
|
|
4672
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.info(this, `📹 [ABORT] ${context} - instance destroyed, aborting`);
|
|
4673
|
+
}
|
|
4674
|
+
return true;
|
|
4675
|
+
}
|
|
4676
|
+
return false;
|
|
4677
|
+
}
|
|
4678
|
+
//------------------------------------------------------------------------//
|
|
4449
4679
|
// MAIN METHODS
|
|
4450
4680
|
//------------------------------------------------------------------------//
|
|
4451
4681
|
/**
|
|
4452
4682
|
* Initializes the PlaybackController by setting up event listeners and device handling
|
|
4453
|
-
* This method orchestrates the initialization process by first checking device availability
|
|
4454
|
-
* and permissions, then setting up necessary event listeners and configurations
|
|
4455
4683
|
* @private
|
|
4456
4684
|
*/
|
|
4457
4685
|
initialize() {
|
|
4458
4686
|
var _a, _b;
|
|
4459
4687
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4688
|
+
this._logger.info(this, "📹 [INIT] initialize() - starting initialization");
|
|
4460
4689
|
try {
|
|
4690
|
+
if (this.isDestroyedCheck("initialize start")) return;
|
|
4461
4691
|
yield this.initializeDevices();
|
|
4692
|
+
if (this.isDestroyedCheck("after initializeDevices")) return;
|
|
4462
4693
|
this.setupEventListeners();
|
|
4694
|
+
if (this.isDestroyedCheck("after setupEventListeners")) return;
|
|
4463
4695
|
this.initializeStream();
|
|
4696
|
+
if (this.isDestroyedCheck("after initializeStream")) return;
|
|
4464
4697
|
this.setupOrientationListener();
|
|
4465
|
-
this.setupPermissionListeners();
|
|
4698
|
+
yield this.setupPermissionListeners();
|
|
4699
|
+
if (this.isDestroyedCheck("after setupPermissionListeners")) return;
|
|
4466
4700
|
this.setupDeviceChangeListener();
|
|
4701
|
+
if (this.isDestroyedCheck("before network init")) return;
|
|
4467
4702
|
if ((_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getSettingsData().autoConnect) {
|
|
4468
4703
|
this._logger.info(this, "Initializing NetworkController (autoConnect is true)");
|
|
4469
4704
|
(_b = this._main.getNetworkController()) === null || _b === void 0 ? void 0 : _b.initialize();
|
|
4470
4705
|
} else {
|
|
4471
4706
|
this._logger.warning(this, "Warning - autoConnect is set to false, switching to standby mode!");
|
|
4472
4707
|
}
|
|
4708
|
+
this._logger.success(this, "📹 [INIT] initialize() - completed successfully");
|
|
4473
4709
|
} catch (error) {
|
|
4710
|
+
if (this._isDestroyed) {
|
|
4711
|
+
this._logger.warning(this, "📹 [INIT] initialize() - aborted by destroy()");
|
|
4712
|
+
return;
|
|
4713
|
+
}
|
|
4474
4714
|
this._logger.error(this, "Initialization failed: " + JSON.stringify(error));
|
|
4475
4715
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4476
4716
|
}
|
|
4717
|
+
this._main.dispatchEvent("microphoneStateChange", {
|
|
4718
|
+
ref: this._main,
|
|
4719
|
+
isMuted: this._isMicrophoneMuted
|
|
4720
|
+
});
|
|
4477
4721
|
});
|
|
4478
4722
|
}
|
|
4479
4723
|
/**
|
|
@@ -4481,6 +4725,7 @@
|
|
|
4481
4725
|
* @private
|
|
4482
4726
|
*/
|
|
4483
4727
|
setupEventListeners() {
|
|
4728
|
+
if (this._isDestroyed) return;
|
|
4484
4729
|
this._main.addEventListener("serverConnect", this.onServerConnect, false);
|
|
4485
4730
|
this._main.addEventListener("serverDisconnect", this.onServerDisconnect, false);
|
|
4486
4731
|
this._main.addEventListener("streamKeyInUse", this.onStreamKeyTaken, false);
|
|
@@ -4499,18 +4744,38 @@
|
|
|
4499
4744
|
initializeDevices() {
|
|
4500
4745
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4501
4746
|
try {
|
|
4747
|
+
this._logger.info(this, "📹 [ACQUIRE] initializeDevices() - requesting test getUserMedia for permissions");
|
|
4748
|
+
if (this.isDestroyedCheck("initializeDevices before getUserMedia")) return;
|
|
4502
4749
|
// Request initial device permissions
|
|
4503
4750
|
const stream = yield navigator.mediaDevices.getUserMedia({
|
|
4504
4751
|
video: true,
|
|
4505
4752
|
audio: true
|
|
4506
4753
|
});
|
|
4754
|
+
// CRITICAL: Check IMMEDIATELY after getUserMedia returns
|
|
4755
|
+
if (this._isDestroyed) {
|
|
4756
|
+
this._logger.warning(this, "📹 [RELEASE] initializeDevices() - destroyed during getUserMedia, releasing orphan stream");
|
|
4757
|
+
stream.getTracks().forEach(track => {
|
|
4758
|
+
track.stop();
|
|
4759
|
+
this._logger.info(this, `📹 [RELEASE] initializeDevices() - stopped orphan track: ${track.kind}, id: ${track.id}`);
|
|
4760
|
+
});
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4763
|
+
this._logger.info(this, "📹 [RELEASE] initializeDevices() - stopping test stream immediately");
|
|
4507
4764
|
// Stop the test stream
|
|
4508
|
-
stream.getTracks().forEach(track =>
|
|
4765
|
+
stream.getTracks().forEach(track => {
|
|
4766
|
+
track.stop();
|
|
4767
|
+
this._logger.info(this, `📹 [RELEASE] initializeDevices() - stopped track: ${track.kind}, id: ${track.id}`);
|
|
4768
|
+
});
|
|
4769
|
+
if (this.isDestroyedCheck("initializeDevices before grabDevices")) return;
|
|
4509
4770
|
// Initialize device lists
|
|
4510
4771
|
yield this.grabDevices();
|
|
4511
4772
|
} catch (error) {
|
|
4773
|
+
if (this._isDestroyed) return;
|
|
4774
|
+
console.log(error);
|
|
4512
4775
|
this._logger.error(this, "Error initializing devices: " + JSON.stringify(error));
|
|
4513
|
-
|
|
4776
|
+
if (!this._isDestroyed) {
|
|
4777
|
+
yield this.grabDevices();
|
|
4778
|
+
}
|
|
4514
4779
|
}
|
|
4515
4780
|
});
|
|
4516
4781
|
}
|
|
@@ -4519,56 +4784,92 @@
|
|
|
4519
4784
|
* @private
|
|
4520
4785
|
*/
|
|
4521
4786
|
initializeStream() {
|
|
4787
|
+
if (this._isDestroyed) return;
|
|
4522
4788
|
if (this._selectedCamera || this._selectedMicrophone) {
|
|
4523
4789
|
this.startCamera();
|
|
4524
4790
|
}
|
|
4525
4791
|
}
|
|
4792
|
+
/**
|
|
4793
|
+
* Sets up permission listeners with proper cleanup tracking
|
|
4794
|
+
* @private
|
|
4795
|
+
*/
|
|
4526
4796
|
setupPermissionListeners() {
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4797
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
4798
|
+
if (this._isDestroyed) return;
|
|
4799
|
+
try {
|
|
4800
|
+
const cameraQuery = {
|
|
4801
|
+
name: 'camera'
|
|
4802
|
+
};
|
|
4803
|
+
const microphoneQuery = {
|
|
4804
|
+
name: 'microphone'
|
|
4805
|
+
};
|
|
4806
|
+
// Camera permission
|
|
4807
|
+
this._cameraPermissionStatus = yield navigator.permissions.query(cameraQuery);
|
|
4808
|
+
if (this._isDestroyed) return;
|
|
4809
|
+
this._boundCameraPermissionHandler = () => {
|
|
4810
|
+
if (this._isDestroyed) return;
|
|
4811
|
+
this._permissionChecked = false;
|
|
4812
|
+
this.handlePermissionChange('camera', this._cameraPermissionStatus.state);
|
|
4813
|
+
};
|
|
4814
|
+
this._cameraPermissionStatus.addEventListener('change', this._boundCameraPermissionHandler);
|
|
4815
|
+
// Microphone permission
|
|
4816
|
+
this._microphonePermissionStatus = yield navigator.permissions.query(microphoneQuery);
|
|
4817
|
+
if (this._isDestroyed) return;
|
|
4818
|
+
this._boundMicrophonePermissionHandler = () => {
|
|
4819
|
+
if (this._isDestroyed) return;
|
|
4820
|
+
this._permissionChecked = false;
|
|
4821
|
+
this.handlePermissionChange('microphone', this._microphonePermissionStatus.state);
|
|
4822
|
+
};
|
|
4823
|
+
this._microphonePermissionStatus.addEventListener('change', this._boundMicrophonePermissionHandler);
|
|
4824
|
+
} catch (error) {
|
|
4825
|
+
this._logger.warning(this, "Could not set up permission listeners: " + error);
|
|
4826
|
+
}
|
|
4544
4827
|
});
|
|
4545
4828
|
}
|
|
4829
|
+
/**
|
|
4830
|
+
* Removes permission listeners
|
|
4831
|
+
* @private
|
|
4832
|
+
*/
|
|
4833
|
+
removePermissionListeners() {
|
|
4834
|
+
if (this._cameraPermissionStatus && this._boundCameraPermissionHandler) {
|
|
4835
|
+
this._cameraPermissionStatus.removeEventListener('change', this._boundCameraPermissionHandler);
|
|
4836
|
+
this._cameraPermissionStatus = null;
|
|
4837
|
+
this._boundCameraPermissionHandler = null;
|
|
4838
|
+
}
|
|
4839
|
+
if (this._microphonePermissionStatus && this._boundMicrophonePermissionHandler) {
|
|
4840
|
+
this._microphonePermissionStatus.removeEventListener('change', this._boundMicrophonePermissionHandler);
|
|
4841
|
+
this._microphonePermissionStatus = null;
|
|
4842
|
+
this._boundMicrophonePermissionHandler = null;
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4546
4845
|
/**
|
|
4547
4846
|
* Sets up event listener for device changes and handles them appropriately
|
|
4548
4847
|
* @private
|
|
4549
4848
|
*/
|
|
4550
4849
|
setupDeviceChangeListener() {
|
|
4551
|
-
|
|
4850
|
+
if (this._isDestroyed) return;
|
|
4851
|
+
this._deviceChangeHandler = () => __awaiter(this, void 0, void 0, function* () {
|
|
4552
4852
|
var _a;
|
|
4853
|
+
if (this._isDestroyed) return;
|
|
4854
|
+
if (this._publishState === exports.PublishState.PUBLISHED) {
|
|
4855
|
+
this._logger.info(this, "Device change detected, but already publish - no restarting streamer");
|
|
4856
|
+
return;
|
|
4857
|
+
}
|
|
4553
4858
|
this._logger.info(this, "Device change detected, restarting streamer");
|
|
4554
|
-
// Store current stream key if we're publishing
|
|
4555
4859
|
const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
4556
|
-
const wasPublishing = this._publishState === exports.PublishState.CONNECTED
|
|
4860
|
+
const wasPublishing = this._publishState === exports.PublishState.CONNECTED;
|
|
4557
4861
|
try {
|
|
4558
|
-
// Stop all current operations
|
|
4559
4862
|
this.stop();
|
|
4560
|
-
// Wait a moment for devices to settle
|
|
4561
4863
|
yield new Promise(resolve => setTimeout(resolve, 500));
|
|
4562
|
-
|
|
4864
|
+
if (this._isDestroyed) return;
|
|
4563
4865
|
yield this.start();
|
|
4564
|
-
|
|
4866
|
+
if (this._isDestroyed) return;
|
|
4565
4867
|
if (wasPublishing && streamKey) {
|
|
4566
4868
|
this._logger.info(this, "Resuming publishing after device change");
|
|
4567
4869
|
if (this.isStreamReady(true, true)) {
|
|
4568
4870
|
this.publish(streamKey);
|
|
4569
4871
|
} else {
|
|
4570
4872
|
this._logger.warning(this, "Cannot resume publishing - stream not ready after device change");
|
|
4571
|
-
// Używamy ogólnego eventu zamiast nowego
|
|
4572
4873
|
this._main.dispatchEvent("inputDeviceError", {
|
|
4573
4874
|
ref: this._main
|
|
4574
4875
|
});
|
|
@@ -4576,20 +4877,33 @@
|
|
|
4576
4877
|
}
|
|
4577
4878
|
this._logger.success(this, "Successfully handled device change");
|
|
4578
4879
|
} catch (error) {
|
|
4880
|
+
if (this._isDestroyed) return;
|
|
4579
4881
|
this._logger.error(this, "Error handling device change: " + JSON.stringify(error));
|
|
4580
4882
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4581
4883
|
}
|
|
4582
|
-
})
|
|
4884
|
+
});
|
|
4885
|
+
navigator.mediaDevices.addEventListener('devicechange', this._deviceChangeHandler);
|
|
4583
4886
|
}
|
|
4887
|
+
/**
|
|
4888
|
+
* Handles permission changes for camera or microphone
|
|
4889
|
+
* @private
|
|
4890
|
+
*/
|
|
4584
4891
|
handlePermissionChange(device, state) {
|
|
4585
4892
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4893
|
+
if (this._isDestroyed) return;
|
|
4894
|
+
this._logger.info(this, `📹 [PERMISSION] handlePermissionChange() - device: ${device}, state: ${state}`);
|
|
4586
4895
|
if (state === 'denied') {
|
|
4587
4896
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4588
|
-
|
|
4589
|
-
|
|
4897
|
+
// Stop the media stream AND WebRTC
|
|
4898
|
+
if (this._publishState == exports.PublishState.CONNECTED || this._stream) {
|
|
4899
|
+
this._logger.info(this, "📹 [RELEASE] handlePermissionChange() - permission denied, stopping all streams");
|
|
4900
|
+
this.stopCameraStream();
|
|
4901
|
+
this.closeWebRTCConnection();
|
|
4590
4902
|
}
|
|
4591
4903
|
}
|
|
4904
|
+
if (this._isDestroyed) return;
|
|
4592
4905
|
yield this.grabDevices();
|
|
4906
|
+
if (this._isDestroyed) return;
|
|
4593
4907
|
if (state === 'granted') {
|
|
4594
4908
|
yield this.startCamera();
|
|
4595
4909
|
}
|
|
@@ -4599,7 +4913,17 @@
|
|
|
4599
4913
|
* Handles successful camera stream initialization
|
|
4600
4914
|
*/
|
|
4601
4915
|
onCameraStreamSuccess(stream) {
|
|
4602
|
-
|
|
4916
|
+
var _a, _b;
|
|
4917
|
+
// CRITICAL: Check if we were destroyed while waiting
|
|
4918
|
+
if (this._isDestroyed) {
|
|
4919
|
+
this._logger.warning(this, "📹 [RELEASE] onCameraStreamSuccess() - destroyed, releasing stream immediately");
|
|
4920
|
+
stream.getTracks().forEach(track => {
|
|
4921
|
+
track.stop();
|
|
4922
|
+
});
|
|
4923
|
+
return;
|
|
4924
|
+
}
|
|
4925
|
+
this._activeStreamCount++;
|
|
4926
|
+
this._logger.success(this, `📹 [ACQUIRED] onCameraStreamSuccess() - stream acquired, id: ${stream.id}, active streams: ${this._activeStreamCount}`);
|
|
4603
4927
|
// Get actual stream dimensions
|
|
4604
4928
|
const videoTrack = stream.getVideoTracks()[0];
|
|
4605
4929
|
const audioTrack = stream.getAudioTracks()[0];
|
|
@@ -4608,7 +4932,6 @@
|
|
|
4608
4932
|
const settings = videoTrack.getSettings();
|
|
4609
4933
|
let width = settings.width;
|
|
4610
4934
|
let height = settings.height;
|
|
4611
|
-
// Na urządzeniach mobilnych potrzebujemy skorygować wymiary
|
|
4612
4935
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
4613
4936
|
if (isMobile) {
|
|
4614
4937
|
if (width > height && window.innerWidth < window.innerHeight || width < height && window.innerWidth > window.innerHeight) {
|
|
@@ -4618,12 +4941,10 @@
|
|
|
4618
4941
|
streamData.streamWidth = width;
|
|
4619
4942
|
streamData.streamHeight = height;
|
|
4620
4943
|
streamData.videoTrackPresent = true;
|
|
4621
|
-
// Obliczanie proporcji i konwersja na string
|
|
4622
4944
|
const gcd = (a, b) => b ? gcd(b, a % b) : a;
|
|
4623
4945
|
const divisor = gcd(width, height);
|
|
4624
4946
|
const widthRatio = width / divisor;
|
|
4625
4947
|
const heightRatio = height / divisor;
|
|
4626
|
-
// Sprawdzamy typowe proporcje z pewną tolerancją
|
|
4627
4948
|
const ratio = width / height;
|
|
4628
4949
|
let aspectRatioString = `${widthRatio}:${heightRatio}`;
|
|
4629
4950
|
if (Math.abs(ratio - 16 / 9) < 0.1) {
|
|
@@ -4632,11 +4953,22 @@
|
|
|
4632
4953
|
aspectRatioString = width > height ? "4:3" : "3:4";
|
|
4633
4954
|
}
|
|
4634
4955
|
streamData.aspectRatio = aspectRatioString;
|
|
4956
|
+
this._logger.info(this, `📹 [INFO] Video track - id: ${videoTrack.id}, enabled: ${videoTrack.enabled}, readyState: ${videoTrack.readyState}`);
|
|
4635
4957
|
} else {
|
|
4636
4958
|
streamData.videoTrackPresent = false;
|
|
4637
4959
|
}
|
|
4960
|
+
if (audioTrack) {
|
|
4961
|
+
this._logger.info(this, `📹 [INFO] Audio track - id: ${audioTrack.id}, enabled: ${audioTrack.enabled}, readyState: ${audioTrack.readyState}`);
|
|
4962
|
+
}
|
|
4638
4963
|
streamData.audioTrackPresent = !!audioTrack;
|
|
4639
4964
|
this._logger.info(this, `Publish MetaData :: Resolution: ${streamData.streamWidth}x${streamData.streamHeight} | ` + `Aspect ratio: ${streamData.aspectRatio}, ` + `Video track: ${streamData.videoTrackPresent} | Audio track: ${streamData.audioTrackPresent}`);
|
|
4965
|
+
// Double-check we weren't destroyed during setup
|
|
4966
|
+
if (this._isDestroyed) {
|
|
4967
|
+
this._logger.warning(this, "📹 [RELEASE] onCameraStreamSuccess() - destroyed during setup, releasing");
|
|
4968
|
+
stream.getTracks().forEach(track => track.stop());
|
|
4969
|
+
this._activeStreamCount--;
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4640
4972
|
// Dispatch event with stream data
|
|
4641
4973
|
this._main.dispatchEvent("publishMetadataUpdate", {
|
|
4642
4974
|
ref: this._main,
|
|
@@ -4655,13 +4987,15 @@
|
|
|
4655
4987
|
if (this._cameraList == null || this._microphoneList == null) {
|
|
4656
4988
|
this.grabDevices();
|
|
4657
4989
|
}
|
|
4658
|
-
const videoElement = this._main.getStageController().getScreenElement().getVideoElement();
|
|
4659
|
-
videoElement
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4990
|
+
const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
|
|
4991
|
+
if (videoElement) {
|
|
4992
|
+
videoElement.srcObject = stream;
|
|
4993
|
+
videoElement.autoplay = true;
|
|
4994
|
+
videoElement.playsInline = true;
|
|
4995
|
+
videoElement.disableRemotePlayback = true;
|
|
4996
|
+
videoElement.controls = false;
|
|
4997
|
+
videoElement.muted = true;
|
|
4998
|
+
}
|
|
4665
4999
|
this.setPublishState(exports.PublishState.INITIALIZED);
|
|
4666
5000
|
}
|
|
4667
5001
|
/**
|
|
@@ -4669,6 +5003,7 @@
|
|
|
4669
5003
|
*/
|
|
4670
5004
|
initializeWebRTC() {
|
|
4671
5005
|
var _a;
|
|
5006
|
+
if (this._isDestroyed) return;
|
|
4672
5007
|
if (!this._stream) {
|
|
4673
5008
|
this._logger.error(this, "Cannot initialize WebRTC - no camera stream available");
|
|
4674
5009
|
return;
|
|
@@ -4682,35 +5017,36 @@
|
|
|
4682
5017
|
}
|
|
4683
5018
|
}
|
|
4684
5019
|
/**
|
|
4685
|
-
*
|
|
4686
|
-
*
|
|
4687
|
-
* @param streamKey - klucz streamu
|
|
4688
|
-
* @returns {boolean} - true jeśli udało się rozpocząć publikowanie
|
|
5020
|
+
* Publish method
|
|
4689
5021
|
*/
|
|
4690
5022
|
publish(streamKey) {
|
|
5023
|
+
if (this._isDestroyed) return false;
|
|
5024
|
+
if (this._debug) this._logger.decoratedLog("Publishing: " + streamKey, "dark-red");
|
|
5025
|
+
this._logger.info(this, "Publish: " + streamKey);
|
|
4691
5026
|
if (this._statusTimer != null) clearInterval(this._statusTimer);
|
|
4692
|
-
if (this._main.getConfigManager().getStreamData().streamKey
|
|
4693
|
-
this.
|
|
4694
|
-
return false;
|
|
5027
|
+
if (this._main.getConfigManager().getStreamData().streamKey != null && !this._firstPublish) {
|
|
5028
|
+
this.unpublish();
|
|
4695
5029
|
}
|
|
4696
|
-
if (this._main.getConfigManager().getStreamData().streamKey != null) this.unpublish();
|
|
4697
5030
|
this._main.getConfigManager().getStreamData().streamKey = streamKey;
|
|
4698
5031
|
if (!this.isStreamReady(true, true)) {
|
|
4699
5032
|
this._logger.warning(this, "Cannot publish - stream not ready (missing video or audio track)");
|
|
4700
5033
|
return false;
|
|
4701
5034
|
}
|
|
4702
|
-
if (this._debug) this._logger.decoratedLog("Publishing: " + streamKey, "dark-red");
|
|
4703
|
-
this._logger.info(this, "Publish: " + streamKey);
|
|
4704
5035
|
this.closeWebRTCConnection();
|
|
4705
5036
|
this._main.dispatchEvent("publish", {
|
|
4706
5037
|
ref: this._main,
|
|
4707
5038
|
streamKey: streamKey
|
|
4708
5039
|
});
|
|
4709
5040
|
this.initializeWebRTC();
|
|
5041
|
+
this._firstPublish = false;
|
|
4710
5042
|
return true;
|
|
4711
5043
|
}
|
|
5044
|
+
/**
|
|
5045
|
+
* Stops publishing and cleans up WebRTC connection
|
|
5046
|
+
*/
|
|
4712
5047
|
unpublish() {
|
|
4713
5048
|
if (this._debug) this._logger.decoratedLog("Unpublish", "dark-red");
|
|
5049
|
+
this._logger.info(this, "📹 [UNPUBLISH] unpublish() - stopping WebRTC but keeping camera preview");
|
|
4714
5050
|
if (this._statusConnection != null) {
|
|
4715
5051
|
this._statusConnection.destroy();
|
|
4716
5052
|
this._statusConnection = null;
|
|
@@ -4724,40 +5060,48 @@
|
|
|
4724
5060
|
this._main.dispatchEvent("unpublish", {
|
|
4725
5061
|
ref: this._main
|
|
4726
5062
|
});
|
|
4727
|
-
this.
|
|
5063
|
+
this.closeWebRTCConnection();
|
|
5064
|
+
this.setPublishState(exports.PublishState.UNPUBLISHED);
|
|
4728
5065
|
}
|
|
4729
5066
|
/**
|
|
4730
|
-
*
|
|
5067
|
+
* Stops publishing AND releases camera/microphone
|
|
5068
|
+
*/
|
|
5069
|
+
unpublishAndRelease() {
|
|
5070
|
+
this._logger.info(this, "📹 [UNPUBLISH+RELEASE] unpublishAndRelease() - stopping everything");
|
|
5071
|
+
this.unpublish();
|
|
5072
|
+
this.stopCameraStream();
|
|
5073
|
+
}
|
|
5074
|
+
/**
|
|
5075
|
+
* Sets up orientation listener
|
|
4731
5076
|
* @private
|
|
4732
5077
|
*/
|
|
4733
5078
|
setupOrientationListener() {
|
|
4734
|
-
|
|
5079
|
+
if (this._isDestroyed) return;
|
|
5080
|
+
this._orientationChangeHandler = this.handleOrientationChange;
|
|
4735
5081
|
if (window.screen && window.screen.orientation) {
|
|
4736
|
-
window.screen.orientation.addEventListener('change', this.
|
|
5082
|
+
window.screen.orientation.addEventListener('change', this._orientationChangeHandler);
|
|
4737
5083
|
} else {
|
|
4738
|
-
|
|
4739
|
-
window.addEventListener('orientationchange', this.handleOrientationChange);
|
|
5084
|
+
window.addEventListener('orientationchange', this._orientationChangeHandler);
|
|
4740
5085
|
}
|
|
4741
5086
|
}
|
|
4742
5087
|
//------------------------------------------------------------------------//
|
|
4743
5088
|
// USER MEDIA
|
|
4744
5089
|
//------------------------------------------------------------------------//
|
|
4745
5090
|
/**
|
|
4746
|
-
* Error
|
|
4747
|
-
*
|
|
4748
|
-
* @param error
|
|
5091
|
+
* Error handler for getUserMedia
|
|
4749
5092
|
*/
|
|
4750
5093
|
onUserMediaError(error) {
|
|
4751
5094
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5095
|
+
if (this._isDestroyed) return;
|
|
5096
|
+
this._logger.error(this, `📹 [ERROR] onUserMediaError() - ${error.name}: ${error.message}`);
|
|
4752
5097
|
yield this.grabDevices();
|
|
4753
|
-
// Dodatkowa obsługa specyficznych błędów getUserMedia, jeśli potrzebna
|
|
4754
5098
|
if (error.name === "OverconstrainedError") {
|
|
4755
5099
|
this._logger.warning(this, "Device constraints not satisfied");
|
|
4756
5100
|
}
|
|
4757
5101
|
});
|
|
4758
5102
|
}
|
|
4759
5103
|
/**
|
|
4760
|
-
*
|
|
5104
|
+
* Checks individual device access
|
|
4761
5105
|
* @private
|
|
4762
5106
|
*/
|
|
4763
5107
|
checkIndividualDeviceAccess() {
|
|
@@ -4772,44 +5116,47 @@
|
|
|
4772
5116
|
available: false
|
|
4773
5117
|
}
|
|
4774
5118
|
};
|
|
4775
|
-
|
|
5119
|
+
if (this._isDestroyed) return results;
|
|
4776
5120
|
try {
|
|
4777
5121
|
const devices = yield navigator.mediaDevices.enumerateDevices();
|
|
5122
|
+
if (this._isDestroyed) return results;
|
|
4778
5123
|
results.camera.available = devices.some(device => device.kind === 'videoinput');
|
|
4779
5124
|
results.microphone.available = devices.some(device => device.kind === 'audioinput');
|
|
4780
|
-
// Sprawdzamy czy mamy etykiety urządzeń - ich brak może oznaczać brak uprawnień
|
|
4781
5125
|
const hasLabels = devices.some(device => device.label !== '');
|
|
4782
5126
|
if (!hasLabels) {
|
|
4783
|
-
|
|
5127
|
+
this._logger.info(this, "📹 [ACQUIRE] checkIndividualDeviceAccess() - no labels, requesting permissions");
|
|
5128
|
+
if (this._isDestroyed) return results;
|
|
4784
5129
|
try {
|
|
4785
5130
|
const stream = yield navigator.mediaDevices.getUserMedia({
|
|
4786
5131
|
video: results.camera.available,
|
|
4787
5132
|
audio: results.microphone.available
|
|
4788
5133
|
});
|
|
4789
|
-
//
|
|
5134
|
+
// CRITICAL: Check IMMEDIATELY after getUserMedia
|
|
5135
|
+
if (this._isDestroyed) {
|
|
5136
|
+
this._logger.warning(this, "📹 [RELEASE] checkIndividualDeviceAccess() - destroyed, releasing orphan stream");
|
|
5137
|
+
stream.getTracks().forEach(track => track.stop());
|
|
5138
|
+
return results;
|
|
5139
|
+
}
|
|
4790
5140
|
results.camera.allowed = stream.getVideoTracks().length > 0;
|
|
4791
5141
|
results.microphone.allowed = stream.getAudioTracks().length > 0;
|
|
4792
|
-
|
|
4793
|
-
stream.getTracks().forEach(track =>
|
|
4794
|
-
|
|
5142
|
+
this._logger.info(this, "📹 [RELEASE] checkIndividualDeviceAccess() - stopping test stream");
|
|
5143
|
+
stream.getTracks().forEach(track => {
|
|
5144
|
+
track.stop();
|
|
5145
|
+
this._logger.info(this, `📹 [RELEASE] checkIndividualDeviceAccess() - stopped track: ${track.kind}`);
|
|
5146
|
+
});
|
|
5147
|
+
if (this._isDestroyed) return results;
|
|
4795
5148
|
yield this.grabDevices();
|
|
4796
5149
|
} catch (error) {
|
|
4797
5150
|
console.error('Error requesting permissions:', error);
|
|
4798
|
-
// Nie udało się uzyskać uprawnień
|
|
4799
5151
|
results.camera.allowed = false;
|
|
4800
5152
|
results.microphone.allowed = false;
|
|
4801
5153
|
}
|
|
4802
5154
|
} else {
|
|
4803
|
-
// Jeśli mamy etykiety, prawdopodobnie mamy już uprawnienia
|
|
4804
5155
|
results.camera.allowed = devices.some(device => device.kind === 'videoinput' && device.label !== '');
|
|
4805
5156
|
results.microphone.allowed = devices.some(device => device.kind === 'audioinput' && device.label !== '');
|
|
4806
5157
|
}
|
|
4807
5158
|
} catch (error) {
|
|
4808
5159
|
console.error('Error checking devices:', error);
|
|
4809
|
-
results.camera.available = false;
|
|
4810
|
-
results.microphone.available = false;
|
|
4811
|
-
results.camera.allowed = false;
|
|
4812
|
-
results.microphone.allowed = false;
|
|
4813
5160
|
}
|
|
4814
5161
|
return results;
|
|
4815
5162
|
});
|
|
@@ -4818,21 +5165,18 @@
|
|
|
4818
5165
|
// SOCKETS & SDP
|
|
4819
5166
|
//------------------------------------------------------------------------//
|
|
4820
5167
|
/**
|
|
4821
|
-
*
|
|
4822
|
-
*
|
|
4823
|
-
* @param data
|
|
5168
|
+
* Handles SDP/ICE-Candidate exchange
|
|
4824
5169
|
*/
|
|
4825
5170
|
onSocketMessage(data) {
|
|
4826
5171
|
var _a;
|
|
5172
|
+
if (this._isDestroyed) return;
|
|
4827
5173
|
let msgJSON = JSON.parse(data);
|
|
4828
5174
|
let msgStatus = Number(msgJSON["status"]);
|
|
4829
5175
|
switch (msgStatus) {
|
|
4830
5176
|
case 200:
|
|
4831
|
-
// OK
|
|
4832
5177
|
this._logger.info(this, "SDP Exchange Successful");
|
|
4833
5178
|
let sdpData = msgJSON['sdp'];
|
|
4834
|
-
if (sdpData !== undefined) {
|
|
4835
|
-
// @ts-ignore
|
|
5179
|
+
if (sdpData !== undefined && this._peerConnection) {
|
|
4836
5180
|
this._peerConnection.setRemoteDescription(new RTCSessionDescription(sdpData), () => {}, () => {});
|
|
4837
5181
|
}
|
|
4838
5182
|
let iceCandidates = msgJSON['iceCandidates'];
|
|
@@ -4843,7 +5187,6 @@
|
|
|
4843
5187
|
}
|
|
4844
5188
|
break;
|
|
4845
5189
|
case 503:
|
|
4846
|
-
// NOT OK
|
|
4847
5190
|
this._logger.error(this, "StreamKey already use");
|
|
4848
5191
|
const usedStreamKey = (_a = this._main.getConfigManager().getStreamData().streamKey) !== null && _a !== void 0 ? _a : "unknown";
|
|
4849
5192
|
this._main.dispatchEvent("streamKeyInUse", {
|
|
@@ -4857,14 +5200,8 @@
|
|
|
4857
5200
|
//------------------------------------------------------------------------//
|
|
4858
5201
|
// EVENTS
|
|
4859
5202
|
//------------------------------------------------------------------------//
|
|
4860
|
-
/**
|
|
4861
|
-
* Recives events related to peerConnection (change of state)
|
|
4862
|
-
*
|
|
4863
|
-
* @param event event with its data
|
|
4864
|
-
* @param thisRef reference to player classonConnectionStateChange
|
|
4865
|
-
* @private
|
|
4866
|
-
*/
|
|
4867
5203
|
onConnectionStateChange(event) {
|
|
5204
|
+
if (this._isDestroyed) return;
|
|
4868
5205
|
this._logger.info(this, "Connection State Change: " + JSON.stringify(event));
|
|
4869
5206
|
if (event !== null) {
|
|
4870
5207
|
switch (event.currentTarget.connectionState) {
|
|
@@ -4895,16 +5232,16 @@
|
|
|
4895
5232
|
// DEVICES
|
|
4896
5233
|
//------------------------------------------------------------------------//
|
|
4897
5234
|
/**
|
|
4898
|
-
*
|
|
5235
|
+
* Grabs available devices
|
|
4899
5236
|
*/
|
|
4900
5237
|
grabDevices() {
|
|
4901
5238
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5239
|
+
if (this._isDestroyed) return;
|
|
4902
5240
|
try {
|
|
4903
5241
|
const deviceAccess = yield this.checkIndividualDeviceAccess();
|
|
5242
|
+
if (this._isDestroyed) return;
|
|
4904
5243
|
this._cameraList = new InputDeviceList();
|
|
4905
5244
|
this._microphoneList = new InputDeviceList();
|
|
4906
|
-
// Wysyłamy eventy tylko jeśli nie sprawdzaliśmy wcześniej uprawnień
|
|
4907
|
-
// lub jeśli zmienił się stan uprawnień (resetowana flaga)
|
|
4908
5245
|
if (!this._permissionChecked) {
|
|
4909
5246
|
if (!deviceAccess.camera.allowed) {
|
|
4910
5247
|
this._main.dispatchEvent("cameraAccessDenied", {
|
|
@@ -4933,8 +5270,9 @@
|
|
|
4933
5270
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4934
5271
|
}
|
|
4935
5272
|
}
|
|
4936
|
-
|
|
5273
|
+
if (this._isDestroyed) return;
|
|
4937
5274
|
const devices = yield navigator.mediaDevices.enumerateDevices();
|
|
5275
|
+
if (this._isDestroyed) return;
|
|
4938
5276
|
for (const device of devices) {
|
|
4939
5277
|
if (device.deviceId && device.label) {
|
|
4940
5278
|
if (device.kind === 'videoinput' && deviceAccess.camera.allowed) {
|
|
@@ -4946,8 +5284,8 @@
|
|
|
4946
5284
|
}
|
|
4947
5285
|
}
|
|
4948
5286
|
}
|
|
5287
|
+
if (this._isDestroyed) return;
|
|
4949
5288
|
try {
|
|
4950
|
-
// Aktualizacja wybranych urządzeń
|
|
4951
5289
|
if (deviceAccess.camera.allowed) {
|
|
4952
5290
|
this._selectedCamera = this.pickCamera();
|
|
4953
5291
|
}
|
|
@@ -4955,153 +5293,217 @@
|
|
|
4955
5293
|
this._selectedMicrophone = this.pickMicrophone();
|
|
4956
5294
|
}
|
|
4957
5295
|
} catch (error) {
|
|
5296
|
+
console.log(error);
|
|
4958
5297
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4959
|
-
this._logger.error(this, "
|
|
5298
|
+
this._logger.error(this, "Error on grab devices: " + JSON.stringify(error));
|
|
5299
|
+
}
|
|
5300
|
+
if (!this._isDestroyed) {
|
|
5301
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5302
|
+
ref: this._main,
|
|
5303
|
+
cameraList: this._cameraList.getArray(),
|
|
5304
|
+
microphoneList: this._microphoneList.getArray()
|
|
5305
|
+
});
|
|
4960
5306
|
}
|
|
4961
|
-
// Zawsze wysyłamy aktualizację list urządzeń
|
|
4962
|
-
this._main.dispatchEvent("deviceListUpdate", {
|
|
4963
|
-
ref: this._main,
|
|
4964
|
-
cameraList: this._cameraList.getArray(),
|
|
4965
|
-
microphoneList: this._microphoneList.getArray()
|
|
4966
|
-
});
|
|
4967
5307
|
this._permissionChecked = true;
|
|
4968
5308
|
} catch (error) {
|
|
5309
|
+
if (this._isDestroyed) return;
|
|
4969
5310
|
console.error("Error in grabDevices:", error);
|
|
4970
5311
|
this._cameraList = new InputDeviceList();
|
|
4971
5312
|
this._microphoneList = new InputDeviceList();
|
|
5313
|
+
if (!this._isDestroyed) {
|
|
5314
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5315
|
+
ref: this._main,
|
|
5316
|
+
cameraList: this._cameraList.getArray(),
|
|
5317
|
+
microphoneList: this._microphoneList.getArray()
|
|
5318
|
+
});
|
|
5319
|
+
this._main.dispatchEvent("inputDeviceError", {
|
|
5320
|
+
ref: this._main
|
|
5321
|
+
});
|
|
5322
|
+
}
|
|
5323
|
+
}
|
|
5324
|
+
});
|
|
5325
|
+
}
|
|
5326
|
+
/**
|
|
5327
|
+
* Selects camera by ID
|
|
5328
|
+
*/
|
|
5329
|
+
selectCamera(cameraID) {
|
|
5330
|
+
var _a, _b, _c;
|
|
5331
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5332
|
+
if (this._isDestroyed) return;
|
|
5333
|
+
if (this._cameraAbortController) {
|
|
5334
|
+
this._cameraAbortController.abort();
|
|
5335
|
+
}
|
|
5336
|
+
this._cameraAbortController = new AbortController();
|
|
5337
|
+
const signal = this._cameraAbortController.signal;
|
|
5338
|
+
try {
|
|
5339
|
+
this._switchingCamera = true;
|
|
5340
|
+
this._logger.info(this, `📹 [SWITCH] selectCamera() - switching to camera: ${cameraID}`);
|
|
5341
|
+
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5342
|
+
this._cameraList.get(i).isSelected = false;
|
|
5343
|
+
}
|
|
5344
|
+
this._selectedCamera = null;
|
|
5345
|
+
this.setInputDeviceState(exports.InputDevicesState.UPDATING);
|
|
5346
|
+
this.setCameraState(exports.DeviceState.NOT_INITIALIZED);
|
|
5347
|
+
const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
5348
|
+
const wasPublished = this._publishState === exports.PublishState.CONNECTED;
|
|
5349
|
+
let found = false;
|
|
5350
|
+
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5351
|
+
if (this._cameraList.get(i).id == cameraID) {
|
|
5352
|
+
this._selectedCamera = this._cameraList.get(i);
|
|
5353
|
+
this._selectedCamera.isSelected = true;
|
|
5354
|
+
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("cameraID", this._selectedCamera.id);
|
|
5355
|
+
found = true;
|
|
5356
|
+
break;
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
4972
5359
|
this._main.dispatchEvent("deviceListUpdate", {
|
|
4973
5360
|
ref: this._main,
|
|
4974
5361
|
cameraList: this._cameraList.getArray(),
|
|
4975
5362
|
microphoneList: this._microphoneList.getArray()
|
|
4976
5363
|
});
|
|
4977
|
-
this.
|
|
4978
|
-
|
|
4979
|
-
|
|
5364
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5365
|
+
this.stopCameraStream();
|
|
5366
|
+
if (this._selectedCamera != null) {
|
|
5367
|
+
this._constraints.video.deviceId = this._selectedCamera.id;
|
|
5368
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5369
|
+
yield new Promise((resolve, reject) => {
|
|
5370
|
+
const timeout = setTimeout(resolve, 500);
|
|
5371
|
+
signal.addEventListener('abort', () => {
|
|
5372
|
+
clearTimeout(timeout);
|
|
5373
|
+
reject(new Error('Aborted'));
|
|
5374
|
+
});
|
|
5375
|
+
});
|
|
5376
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5377
|
+
yield this.startCamera();
|
|
5378
|
+
if (this._isDestroyed) return;
|
|
5379
|
+
this.setCameraState(exports.DeviceState.ENABLED);
|
|
5380
|
+
if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) this.setInputDeviceState(exports.InputDevicesState.READY);else this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5381
|
+
if (wasPublished && streamKey && !signal.aborted && !this._isDestroyed) {
|
|
5382
|
+
this.publish(streamKey);
|
|
5383
|
+
}
|
|
5384
|
+
} else {
|
|
5385
|
+
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5386
|
+
}
|
|
5387
|
+
} catch (error) {
|
|
5388
|
+
if (error.message !== 'Aborted' && !this._isDestroyed) {
|
|
5389
|
+
this._logger.error(this, '📹 [ERROR] selectCamera() - Error switching camera: ' + error);
|
|
5390
|
+
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5391
|
+
}
|
|
5392
|
+
} finally {
|
|
5393
|
+
this._switchingCamera = false;
|
|
5394
|
+
if (((_c = this._cameraAbortController) === null || _c === void 0 ? void 0 : _c.signal) === signal) {
|
|
5395
|
+
this._cameraAbortController = null;
|
|
5396
|
+
}
|
|
4980
5397
|
}
|
|
4981
5398
|
});
|
|
4982
5399
|
}
|
|
4983
5400
|
/**
|
|
4984
|
-
* Selects
|
|
4985
|
-
* @param cameraID
|
|
5401
|
+
* Selects microphone by ID
|
|
4986
5402
|
*/
|
|
4987
|
-
|
|
4988
|
-
var _a, _b;
|
|
4989
|
-
|
|
4990
|
-
this.
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
this.setInputDeviceState(exports.InputDevicesState.UPDATING);
|
|
4994
|
-
this.setCameraState(exports.DeviceState.NOT_INITIALIZED);
|
|
4995
|
-
// Zapamiętaj aktualny stream key i stan publikacji
|
|
4996
|
-
const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
4997
|
-
const wasPublished = this._publishState === exports.PublishState.CONNECTED;
|
|
4998
|
-
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
4999
|
-
if (this._cameraList.get(i).id == cameraID) {
|
|
5000
|
-
this._selectedCamera = this._cameraList.get(i);
|
|
5001
|
-
this._selectedCamera.isSelected = true;
|
|
5002
|
-
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("cameraID", this._selectedCamera.id);
|
|
5003
|
-
break;
|
|
5403
|
+
selectMicrophone(micID) {
|
|
5404
|
+
var _a, _b, _c;
|
|
5405
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
5406
|
+
if (this._isDestroyed) return;
|
|
5407
|
+
if (this._microphoneAbortController) {
|
|
5408
|
+
this._microphoneAbortController.abort();
|
|
5004
5409
|
}
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
if (this._selectedCamera != null) {
|
|
5013
|
-
// Update constraints with new device
|
|
5014
|
-
this._constraints.video.deviceId = this._selectedCamera.id;
|
|
5015
|
-
// Restart camera stream
|
|
5016
|
-
this.startCamera().then(() => {
|
|
5017
|
-
// Jeśli stream był opublikowany, publikujemy ponownie
|
|
5018
|
-
this.setCameraState(exports.DeviceState.ENABLED);
|
|
5019
|
-
if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) this.setInputDeviceState(exports.InputDevicesState.READY);else this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5020
|
-
if (wasPublished && streamKey) {
|
|
5021
|
-
this.publish(streamKey);
|
|
5410
|
+
this._microphoneAbortController = new AbortController();
|
|
5411
|
+
const signal = this._microphoneAbortController.signal;
|
|
5412
|
+
try {
|
|
5413
|
+
this._switchingMicrophone = true;
|
|
5414
|
+
this._logger.info(this, `📹 [SWITCH] selectMicrophone() - switching to microphone: ${micID}`);
|
|
5415
|
+
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5416
|
+
this._microphoneList.get(i).isSelected = false;
|
|
5022
5417
|
}
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5036
|
-
this._microphoneList.get(i).isSelected = false;
|
|
5037
|
-
}
|
|
5038
|
-
this._selectedMicrophone = null;
|
|
5039
|
-
this.setInputDeviceState(exports.InputDevicesState.UPDATING);
|
|
5040
|
-
this.setMicrophoneState(exports.DeviceState.NOT_INITIALIZED);
|
|
5041
|
-
this._logger.info(this, "Selecting microphone: " + micID);
|
|
5042
|
-
// Zapamiętaj aktualny stream key i stan publikacji
|
|
5043
|
-
const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
5044
|
-
const wasPublished = this._publishState === exports.PublishState.CONNECTED;
|
|
5045
|
-
// Znajdź i zapisz wybrany mikrofon
|
|
5046
|
-
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5047
|
-
if (this._microphoneList.get(i).id == micID) {
|
|
5048
|
-
this._selectedMicrophone = this._microphoneList.get(i);
|
|
5049
|
-
this._selectedMicrophone.isSelected = true;
|
|
5050
|
-
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("microphoneID", this._selectedMicrophone.id);
|
|
5051
|
-
break;
|
|
5418
|
+
this._selectedMicrophone = null;
|
|
5419
|
+
this.setInputDeviceState(exports.InputDevicesState.UPDATING);
|
|
5420
|
+
this.setMicrophoneState(exports.DeviceState.NOT_INITIALIZED);
|
|
5421
|
+
const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
5422
|
+
const wasPublished = this._publishState === exports.PublishState.CONNECTED;
|
|
5423
|
+
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5424
|
+
if (this._microphoneList.get(i).id == micID) {
|
|
5425
|
+
this._selectedMicrophone = this._microphoneList.get(i);
|
|
5426
|
+
this._selectedMicrophone.isSelected = true;
|
|
5427
|
+
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("microphoneID", this._selectedMicrophone.id);
|
|
5428
|
+
break;
|
|
5429
|
+
}
|
|
5052
5430
|
}
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
cameraList: this._cameraList.getArray(),
|
|
5058
|
-
microphoneList: this._microphoneList.getArray()
|
|
5059
|
-
});
|
|
5060
|
-
// Odłącz SoundMeter przed zmianą strumienia
|
|
5061
|
-
this._soundMeter.detach();
|
|
5062
|
-
// Zamknij istniejące połączenie WebRTC
|
|
5063
|
-
this.closeWebRTCConnection();
|
|
5064
|
-
// Zatrzymaj obecny strumień
|
|
5065
|
-
if (this._stream) {
|
|
5066
|
-
this._stream.getTracks().forEach(track => {
|
|
5067
|
-
track.stop();
|
|
5431
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5432
|
+
ref: this._main,
|
|
5433
|
+
cameraList: this._cameraList.getArray(),
|
|
5434
|
+
microphoneList: this._microphoneList.getArray()
|
|
5068
5435
|
});
|
|
5069
|
-
this.
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5436
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5437
|
+
this._soundMeter.detach();
|
|
5438
|
+
this.closeWebRTCConnection();
|
|
5439
|
+
if (this._stream) {
|
|
5440
|
+
this._logger.info(this, "📹 [RELEASE] selectMicrophone() - stopping current stream");
|
|
5441
|
+
this._stream.getTracks().forEach(track => {
|
|
5442
|
+
track.stop();
|
|
5443
|
+
});
|
|
5444
|
+
this._stream = null;
|
|
5445
|
+
this._activeStreamCount--;
|
|
5446
|
+
}
|
|
5447
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5448
|
+
yield new Promise((resolve, reject) => {
|
|
5449
|
+
const timeout = setTimeout(resolve, 500);
|
|
5450
|
+
signal.addEventListener('abort', () => {
|
|
5451
|
+
clearTimeout(timeout);
|
|
5452
|
+
reject(new Error('Aborted'));
|
|
5453
|
+
});
|
|
5454
|
+
});
|
|
5455
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5073
5456
|
yield this.startCamera();
|
|
5457
|
+
if (this._isDestroyed) return;
|
|
5074
5458
|
this.setMicrophoneState(exports.DeviceState.ENABLED);
|
|
5075
5459
|
if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) {
|
|
5076
5460
|
this.setInputDeviceState(exports.InputDevicesState.READY);
|
|
5077
5461
|
} else {
|
|
5078
5462
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5079
5463
|
}
|
|
5080
|
-
|
|
5081
|
-
if (wasPublished && streamKey) {
|
|
5464
|
+
if (wasPublished && streamKey && !signal.aborted && !this._isDestroyed) {
|
|
5082
5465
|
this.publish(streamKey);
|
|
5083
5466
|
}
|
|
5084
5467
|
} catch (error) {
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5468
|
+
if (error.message !== 'Aborted' && !this._isDestroyed) {
|
|
5469
|
+
console.error("Error changing microphone:", error);
|
|
5470
|
+
this._main.dispatchEvent("inputDeviceError", {
|
|
5471
|
+
ref: this._main
|
|
5472
|
+
});
|
|
5473
|
+
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5474
|
+
}
|
|
5475
|
+
} finally {
|
|
5476
|
+
this._switchingMicrophone = false;
|
|
5477
|
+
if (((_c = this._microphoneAbortController) === null || _c === void 0 ? void 0 : _c.signal) === signal) {
|
|
5478
|
+
this._microphoneAbortController = null;
|
|
5479
|
+
}
|
|
5090
5480
|
}
|
|
5091
5481
|
});
|
|
5092
5482
|
}
|
|
5093
5483
|
/**
|
|
5094
|
-
*
|
|
5095
|
-
*
|
|
5484
|
+
* Starts camera with abort support
|
|
5096
5485
|
* @private
|
|
5097
5486
|
*/
|
|
5098
5487
|
startCamera() {
|
|
5488
|
+
var _a;
|
|
5099
5489
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5490
|
+
if (this._isDestroyed) {
|
|
5491
|
+
this._logger.warning(this, "📹 [ACQUIRE] startCamera() - aborted, instance is destroyed");
|
|
5492
|
+
return;
|
|
5493
|
+
}
|
|
5494
|
+
if (this._startCameraAbortController) {
|
|
5495
|
+
this._startCameraAbortController.abort();
|
|
5496
|
+
}
|
|
5497
|
+
this._startCameraAbortController = new AbortController();
|
|
5498
|
+
const signal = this._startCameraAbortController.signal;
|
|
5499
|
+
// Release existing stream first
|
|
5100
5500
|
if (this._stream) {
|
|
5501
|
+
this._logger.info(this, "📹 [RELEASE] startCamera() - releasing existing stream before acquiring new one");
|
|
5101
5502
|
this._stream.getTracks().forEach(track => {
|
|
5102
5503
|
track.stop();
|
|
5103
5504
|
});
|
|
5104
5505
|
this._stream = null;
|
|
5506
|
+
this._activeStreamCount--;
|
|
5105
5507
|
}
|
|
5106
5508
|
try {
|
|
5107
5509
|
const constraints = {
|
|
@@ -5116,11 +5518,24 @@
|
|
|
5116
5518
|
}
|
|
5117
5519
|
} : false
|
|
5118
5520
|
};
|
|
5521
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5522
|
+
this._logger.info(this, `📹 [ACQUIRE] startCamera() - requesting getUserMedia`);
|
|
5119
5523
|
try {
|
|
5120
5524
|
const stream = yield navigator.mediaDevices.getUserMedia(constraints);
|
|
5525
|
+
// CRITICAL: Check IMMEDIATELY after getUserMedia returns
|
|
5526
|
+
if (signal.aborted || this._isDestroyed) {
|
|
5527
|
+
this._logger.warning(this, "📹 [RELEASE] startCamera() - destroyed during getUserMedia, releasing orphan stream");
|
|
5528
|
+
stream.getTracks().forEach(track => {
|
|
5529
|
+
track.stop();
|
|
5530
|
+
this._logger.info(this, `📹 [RELEASE] startCamera() - stopped orphan track: ${track.kind}, id: ${track.id}`);
|
|
5531
|
+
});
|
|
5532
|
+
return;
|
|
5533
|
+
}
|
|
5121
5534
|
this._stream = stream;
|
|
5122
5535
|
this.onCameraStreamSuccess(this._stream);
|
|
5123
5536
|
} catch (error) {
|
|
5537
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5538
|
+
this._logger.error(this, `📹 [ERROR] startCamera() - getUserMedia failed: ${error.name}: ${error.message}`);
|
|
5124
5539
|
if (constraints.video) {
|
|
5125
5540
|
this.onUserMediaError({
|
|
5126
5541
|
name: error.name || 'Error',
|
|
@@ -5138,8 +5553,13 @@
|
|
|
5138
5553
|
}
|
|
5139
5554
|
if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) this.setInputDeviceState(exports.InputDevicesState.READY);
|
|
5140
5555
|
} catch (error) {
|
|
5556
|
+
if (this._isDestroyed) return;
|
|
5141
5557
|
console.error("Error in startCamera:", error);
|
|
5142
5558
|
yield this.grabDevices();
|
|
5559
|
+
} finally {
|
|
5560
|
+
if (((_a = this._startCameraAbortController) === null || _a === void 0 ? void 0 : _a.signal) === signal) {
|
|
5561
|
+
this._startCameraAbortController = null;
|
|
5562
|
+
}
|
|
5143
5563
|
}
|
|
5144
5564
|
});
|
|
5145
5565
|
}
|
|
@@ -5147,15 +5567,14 @@
|
|
|
5147
5567
|
* Updates WebRTC connection with new stream
|
|
5148
5568
|
*/
|
|
5149
5569
|
updateWebRTCStream() {
|
|
5570
|
+
if (this._isDestroyed) return;
|
|
5150
5571
|
if (!this._peerConnection || !this._stream) {
|
|
5151
5572
|
return;
|
|
5152
5573
|
}
|
|
5153
|
-
// Remove all existing tracks from the peer connection
|
|
5154
5574
|
const senders = this._peerConnection.getSenders();
|
|
5155
5575
|
senders.forEach(sender => {
|
|
5156
5576
|
if (this._peerConnection) this._peerConnection.removeTrack(sender);
|
|
5157
5577
|
});
|
|
5158
|
-
// Add new tracks
|
|
5159
5578
|
this._stream.getTracks().forEach(track => {
|
|
5160
5579
|
if (this._stream != null && this._peerConnection) {
|
|
5161
5580
|
this._peerConnection.addTrack(track, this._stream);
|
|
@@ -5163,33 +5582,24 @@
|
|
|
5163
5582
|
});
|
|
5164
5583
|
}
|
|
5165
5584
|
/**
|
|
5166
|
-
*
|
|
5167
|
-
*/
|
|
5168
|
-
closeStream() {
|
|
5169
|
-
if (this._peerConnection !== undefined && this._peerConnection !== null) this._peerConnection.close();
|
|
5170
|
-
this.setPublishState(exports.PublishState.UNPUBLISHED);
|
|
5171
|
-
}
|
|
5172
|
-
/**
|
|
5173
|
-
* This method selects a camera based on previous uses or saved IDs
|
|
5174
|
-
*
|
|
5585
|
+
* Picks camera based on saved ID or defaults
|
|
5175
5586
|
* @private
|
|
5176
5587
|
*/
|
|
5177
5588
|
pickCamera() {
|
|
5178
5589
|
var _a, _b, _c, _d, _e, _f;
|
|
5590
|
+
if (this._isDestroyed) return null;
|
|
5179
5591
|
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5180
5592
|
this._cameraList.get(i).isSelected = false;
|
|
5181
5593
|
}
|
|
5182
5594
|
let savedCameraID = (_b = (_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.getField("cameraID")) !== null && _b !== void 0 ? _b : null;
|
|
5183
5595
|
if (this._cameraList.getSize() > 0) {
|
|
5184
5596
|
if (savedCameraID) {
|
|
5185
|
-
// Szukamy zapisanej kamery
|
|
5186
5597
|
let found = false;
|
|
5187
5598
|
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5188
5599
|
if (this._cameraList.get(i).id === savedCameraID) {
|
|
5189
5600
|
this._selectedCamera = this._cameraList.get(i);
|
|
5190
5601
|
this._selectedCamera.isSelected = true;
|
|
5191
5602
|
this.setCameraState(exports.DeviceState.ENABLED);
|
|
5192
|
-
// Ustaw deviceId w constraints
|
|
5193
5603
|
found = true;
|
|
5194
5604
|
this._constraints.video.deviceId = this._selectedCamera.id;
|
|
5195
5605
|
break;
|
|
@@ -5206,10 +5616,8 @@
|
|
|
5206
5616
|
this._logger.info(this, "Canceling Publish!");
|
|
5207
5617
|
this._main.getConfigManager().getStreamData().streamKey = null;
|
|
5208
5618
|
}
|
|
5209
|
-
return null;
|
|
5210
5619
|
}
|
|
5211
5620
|
}
|
|
5212
|
-
// Jeśli nie znaleziono zapisanej kamery, używamy pierwszej
|
|
5213
5621
|
if (!this._selectedCamera) {
|
|
5214
5622
|
this._main.dispatchEvent("savedCameraNotFound", {
|
|
5215
5623
|
ref: this._main,
|
|
@@ -5232,20 +5640,22 @@
|
|
|
5232
5640
|
}
|
|
5233
5641
|
}
|
|
5234
5642
|
}
|
|
5235
|
-
this.
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5643
|
+
if (!this._isDestroyed) {
|
|
5644
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5645
|
+
ref: this._main,
|
|
5646
|
+
cameraList: this._cameraList.getArray(),
|
|
5647
|
+
microphoneList: this._microphoneList.getArray()
|
|
5648
|
+
});
|
|
5649
|
+
}
|
|
5240
5650
|
return this._selectedCamera;
|
|
5241
5651
|
}
|
|
5242
5652
|
/**
|
|
5243
|
-
*
|
|
5244
|
-
*
|
|
5653
|
+
* Picks microphone based on saved ID or defaults
|
|
5245
5654
|
* @private
|
|
5246
5655
|
*/
|
|
5247
5656
|
pickMicrophone() {
|
|
5248
5657
|
var _a, _b, _c, _d, _e, _f;
|
|
5658
|
+
if (this._isDestroyed) return null;
|
|
5249
5659
|
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5250
5660
|
this._microphoneList.get(i).isSelected = false;
|
|
5251
5661
|
}
|
|
@@ -5272,7 +5682,6 @@
|
|
|
5272
5682
|
this._logger.info(this, "Canceling Publish!");
|
|
5273
5683
|
this._main.getConfigManager().getStreamData().streamKey = null;
|
|
5274
5684
|
}
|
|
5275
|
-
return null;
|
|
5276
5685
|
}
|
|
5277
5686
|
}
|
|
5278
5687
|
if (!this._selectedMicrophone) {
|
|
@@ -5301,75 +5710,43 @@
|
|
|
5301
5710
|
}
|
|
5302
5711
|
return this._selectedMicrophone;
|
|
5303
5712
|
}
|
|
5304
|
-
/**
|
|
5305
|
-
* Cleans all saved cameras and microphones IDs.
|
|
5306
|
-
*/
|
|
5307
5713
|
clearSavedDevices() {
|
|
5308
5714
|
var _a, _b;
|
|
5309
5715
|
(_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.removeField("cameraID");
|
|
5310
5716
|
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.removeField("microphoneID");
|
|
5311
5717
|
}
|
|
5312
|
-
/**
|
|
5313
|
-
* Messes up camera's and microphone's id (for testing only)
|
|
5314
|
-
*/
|
|
5315
5718
|
messSavedDevices() {
|
|
5316
5719
|
var _a, _b;
|
|
5317
5720
|
(_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.saveField("cameraID", "a");
|
|
5318
5721
|
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("microphoneID", "b");
|
|
5319
5722
|
}
|
|
5320
|
-
/**
|
|
5321
|
-
* Handles microphone muting state
|
|
5322
|
-
* @param microphoneState true to unmute, false to mute
|
|
5323
|
-
*/
|
|
5324
5723
|
muteMicrophone(shouldMute) {
|
|
5724
|
+
var _a;
|
|
5725
|
+
if (this._isDestroyed) return;
|
|
5325
5726
|
if (this._isMicrophoneMuted === shouldMute) {
|
|
5326
|
-
// State hasn't changed, no need to do anything
|
|
5327
5727
|
return;
|
|
5328
5728
|
}
|
|
5329
5729
|
this._isMicrophoneMuted = shouldMute;
|
|
5730
|
+
(_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.saveField("microphoneMuted", shouldMute ? "true" : "false");
|
|
5330
5731
|
if (this._stream) {
|
|
5331
|
-
this.applyMicrophoneState(!shouldMute);
|
|
5732
|
+
this.applyMicrophoneState(!shouldMute);
|
|
5332
5733
|
} else {
|
|
5333
|
-
|
|
5334
|
-
this._pendingMicrophoneState = !shouldMute; // Odwracamy wartość dla przyszłego track.enabled
|
|
5335
|
-
this._logger.info(this, `WebRTCStreamer :: Stream not yet available, storing microphone state (muted: ${shouldMute})`);
|
|
5734
|
+
this._pendingMicrophoneState = !shouldMute;
|
|
5336
5735
|
}
|
|
5337
|
-
// Always dispatch the event to keep UI in sync
|
|
5338
5736
|
this._main.dispatchEvent("microphoneStateChange", {
|
|
5339
5737
|
ref: this._main,
|
|
5340
5738
|
isMuted: this._isMicrophoneMuted
|
|
5341
5739
|
});
|
|
5342
5740
|
}
|
|
5343
|
-
/**
|
|
5344
|
-
* Applies the microphone state to the actual stream tracks
|
|
5345
|
-
*
|
|
5346
|
-
* @param enabled true to enable tracks, false to disable
|
|
5347
|
-
* @private
|
|
5348
|
-
*/
|
|
5349
5741
|
applyMicrophoneState(enabled) {
|
|
5350
|
-
if (!this._stream)
|
|
5351
|
-
this._logger.warning(this, "WebRTCStreamer :: Cannot apply microphone state - stream not available");
|
|
5352
|
-
return;
|
|
5353
|
-
}
|
|
5742
|
+
if (!this._stream) return;
|
|
5354
5743
|
const audioTracks = this._stream.getAudioTracks();
|
|
5355
5744
|
if (audioTracks && audioTracks.length > 0) {
|
|
5356
|
-
this._logger.success(this, `WebRTCStreamer :: ${enabled ? 'Unmuting' : 'Muting'} microphone`);
|
|
5357
5745
|
audioTracks.forEach(track => track.enabled = enabled);
|
|
5358
|
-
} else {
|
|
5359
|
-
this._logger.warning(this, "WebRTCStreamer :: No audio tracks found in stream");
|
|
5360
5746
|
}
|
|
5361
5747
|
}
|
|
5362
|
-
/**
|
|
5363
|
-
* This methods is a final check whenever we're ready to publish a stream
|
|
5364
|
-
*
|
|
5365
|
-
* @param requireVideo - whenever video track is required
|
|
5366
|
-
* @param requireAudio - whenever audio track is required
|
|
5367
|
-
* @returns {boolean} true if stream is ready for publishing
|
|
5368
|
-
*/
|
|
5369
5748
|
isStreamReady(requireVideo = true, requireAudio = true) {
|
|
5370
|
-
if (!this._stream)
|
|
5371
|
-
return false;
|
|
5372
|
-
}
|
|
5749
|
+
if (!this._stream) return false;
|
|
5373
5750
|
const videoTracks = this._stream.getVideoTracks();
|
|
5374
5751
|
const audioTracks = this._stream.getAudioTracks();
|
|
5375
5752
|
const videoReady = !requireVideo || videoTracks.length > 0 && videoTracks[0].readyState === 'live';
|
|
@@ -5378,7 +5755,15 @@
|
|
|
5378
5755
|
}
|
|
5379
5756
|
closeWebRTCConnection() {
|
|
5380
5757
|
if (this._peerConnection) {
|
|
5381
|
-
this.
|
|
5758
|
+
this._logger.info(this, "📡 [WEBRTC] closeWebRTCConnection() - closing peer connection");
|
|
5759
|
+
this._peerConnection.onicecandidate = null;
|
|
5760
|
+
this._peerConnection.onconnectionstatechange = null;
|
|
5761
|
+
this._peerConnection.onnegotiationneeded = null;
|
|
5762
|
+
try {
|
|
5763
|
+
this._peerConnection.close();
|
|
5764
|
+
} catch (e) {
|
|
5765
|
+
// Ignore
|
|
5766
|
+
}
|
|
5382
5767
|
this._peerConnection = null;
|
|
5383
5768
|
}
|
|
5384
5769
|
}
|
|
@@ -5393,6 +5778,7 @@
|
|
|
5393
5778
|
//------------------------------------------------------------------------//
|
|
5394
5779
|
createStatusConnection() {
|
|
5395
5780
|
var _a, _b, _c;
|
|
5781
|
+
if (this._isDestroyed) return;
|
|
5396
5782
|
const serverItem = (_c = (_b = (_a = this._main) === null || _a === void 0 ? void 0 : _a.getNetworkController()) === null || _b === void 0 ? void 0 : _b.getConnection()) === null || _c === void 0 ? void 0 : _c.getCurrentServer();
|
|
5397
5783
|
if (!serverItem) return;
|
|
5398
5784
|
if (this._statusConnection) {
|
|
@@ -5413,6 +5799,7 @@
|
|
|
5413
5799
|
}
|
|
5414
5800
|
setPublishState(newState) {
|
|
5415
5801
|
if (this._publishState == newState) return;
|
|
5802
|
+
if (this._isDestroyed) return;
|
|
5416
5803
|
if (this._debug) this._logger.decoratedLog("Publish State: " + newState, "dark-blue");
|
|
5417
5804
|
this._logger.info(this, "Publish State: " + newState);
|
|
5418
5805
|
if (newState == exports.PublishState.PUBLISHED) this._publishTime = new Date().getTime();
|
|
@@ -5425,9 +5812,9 @@
|
|
|
5425
5812
|
getPublishTime() {
|
|
5426
5813
|
return this._publishState == exports.PublishState.PUBLISHED ? this._publishTime : 0;
|
|
5427
5814
|
}
|
|
5428
|
-
// DEVICE STATE
|
|
5429
5815
|
setInputDeviceState(newState) {
|
|
5430
5816
|
if (this._inputDeviceState == newState) return;
|
|
5817
|
+
if (this._isDestroyed) return;
|
|
5431
5818
|
this._inputDeviceState = newState;
|
|
5432
5819
|
this._main.dispatchEvent("deviceStateChange", {
|
|
5433
5820
|
ref: this._main,
|
|
@@ -5439,9 +5826,9 @@
|
|
|
5439
5826
|
getInputDeviceState() {
|
|
5440
5827
|
return this._inputDeviceState;
|
|
5441
5828
|
}
|
|
5442
|
-
// CAMERA STATE
|
|
5443
5829
|
setCameraState(newState) {
|
|
5444
5830
|
if (this._cameraState == newState) return;
|
|
5831
|
+
if (this._isDestroyed) return;
|
|
5445
5832
|
this._cameraState = newState;
|
|
5446
5833
|
this._main.dispatchEvent("cameraDeviceStateChange", {
|
|
5447
5834
|
ref: this._main,
|
|
@@ -5452,9 +5839,9 @@
|
|
|
5452
5839
|
getCameraState() {
|
|
5453
5840
|
return this._cameraState;
|
|
5454
5841
|
}
|
|
5455
|
-
// MICROPHONE STATE
|
|
5456
5842
|
setMicrophoneState(newState) {
|
|
5457
5843
|
if (this._microphoneState == newState) return;
|
|
5844
|
+
if (this._isDestroyed) return;
|
|
5458
5845
|
this._microphoneState = newState;
|
|
5459
5846
|
this._main.dispatchEvent("microphoneDeviceStateChange", {
|
|
5460
5847
|
ref: this._main,
|
|
@@ -5475,130 +5862,82 @@
|
|
|
5475
5862
|
return this._publishState;
|
|
5476
5863
|
}
|
|
5477
5864
|
//------------------------------------------------------------------------//
|
|
5865
|
+
// DEBUG
|
|
5866
|
+
//------------------------------------------------------------------------//
|
|
5867
|
+
debugMediaState() {
|
|
5868
|
+
var _a, _b;
|
|
5869
|
+
console.group("🎥 Media Debug State");
|
|
5870
|
+
console.log("=== STREAM INFO ===");
|
|
5871
|
+
console.log("this._stream:", this._stream);
|
|
5872
|
+
console.log("Active stream count:", this._activeStreamCount);
|
|
5873
|
+
console.log("Is destroyed:", this._isDestroyed);
|
|
5874
|
+
if (this._stream) {
|
|
5875
|
+
console.log("Stream ID:", this._stream.id);
|
|
5876
|
+
console.log("Stream active:", this._stream.active);
|
|
5877
|
+
console.log("--- Video Tracks ---");
|
|
5878
|
+
this._stream.getVideoTracks().forEach((track, index) => {
|
|
5879
|
+
console.log(` Track ${index}:`, {
|
|
5880
|
+
id: track.id,
|
|
5881
|
+
enabled: track.enabled,
|
|
5882
|
+
readyState: track.readyState,
|
|
5883
|
+
muted: track.muted
|
|
5884
|
+
});
|
|
5885
|
+
});
|
|
5886
|
+
console.log("--- Audio Tracks ---");
|
|
5887
|
+
this._stream.getAudioTracks().forEach((track, index) => {
|
|
5888
|
+
console.log(` Track ${index}:`, {
|
|
5889
|
+
id: track.id,
|
|
5890
|
+
enabled: track.enabled,
|
|
5891
|
+
readyState: track.readyState,
|
|
5892
|
+
muted: track.muted
|
|
5893
|
+
});
|
|
5894
|
+
});
|
|
5895
|
+
}
|
|
5896
|
+
console.log("=== VIDEO ELEMENT ===");
|
|
5897
|
+
const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
|
|
5898
|
+
if ((videoElement === null || videoElement === void 0 ? void 0 : videoElement.srcObject) instanceof MediaStream) {
|
|
5899
|
+
const srcStream = videoElement.srcObject;
|
|
5900
|
+
console.log("Video srcObject stream ID:", srcStream.id);
|
|
5901
|
+
console.log("Video srcObject active:", srcStream.active);
|
|
5902
|
+
console.log("Same as this._stream:", srcStream === this._stream);
|
|
5903
|
+
}
|
|
5904
|
+
console.groupEnd();
|
|
5905
|
+
}
|
|
5906
|
+
//------------------------------------------------------------------------//
|
|
5478
5907
|
// DESTROY & DELETE
|
|
5479
5908
|
//------------------------------------------------------------------------//
|
|
5480
|
-
/**
|
|
5481
|
-
* Method used to stop camera from streaming
|
|
5482
|
-
* @private
|
|
5483
|
-
*/
|
|
5484
5909
|
stopCameraStream() {
|
|
5485
5910
|
var _a, _b;
|
|
5486
5911
|
if (this._stream) {
|
|
5487
|
-
this.
|
|
5912
|
+
this._logger.info(this, `📹 [RELEASE] stopCameraStream() - stopping stream, id: ${this._stream.id}`);
|
|
5913
|
+
this._stream.getTracks().forEach(track => {
|
|
5914
|
+
this._logger.info(this, `📹 [RELEASE] stopCameraStream() - stopping track: ${track.kind}, id: ${track.id}`);
|
|
5915
|
+
track.stop();
|
|
5916
|
+
});
|
|
5488
5917
|
const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
|
|
5489
5918
|
if (videoElement) {
|
|
5490
5919
|
videoElement.srcObject = null;
|
|
5491
5920
|
}
|
|
5492
5921
|
this._soundMeter.detach();
|
|
5493
5922
|
this._stream = null;
|
|
5923
|
+
this._activeStreamCount--;
|
|
5924
|
+
this._logger.info(this, `📹 [RELEASE] stopCameraStream() - complete, active streams: ${this._activeStreamCount}`);
|
|
5494
5925
|
}
|
|
5495
5926
|
}
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
var _a, _b;
|
|
5502
|
-
// 1. First, detach the sound meter
|
|
5503
|
-
if (this._soundMeter) {
|
|
5504
|
-
this._soundMeter.detach();
|
|
5927
|
+
stop() {
|
|
5928
|
+
this._logger.info(this, "📹 [STOP] stop() - stopping all operations");
|
|
5929
|
+
if (this._cameraAbortController) {
|
|
5930
|
+
this._cameraAbortController.abort();
|
|
5931
|
+
this._cameraAbortController = null;
|
|
5505
5932
|
}
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
const tracks = this._stream.getTracks();
|
|
5510
|
-
this._logger.info(this, `Stopping ${tracks.length} tracks from main stream`);
|
|
5511
|
-
tracks.forEach(track => {
|
|
5512
|
-
try {
|
|
5513
|
-
track.enabled = false;
|
|
5514
|
-
track.stop();
|
|
5515
|
-
this._logger.info(this, `Stopped ${track.kind} track: ${track.id}`);
|
|
5516
|
-
} catch (e) {
|
|
5517
|
-
this._logger.error(this, `Error stopping ${track.kind} track: ${e}`);
|
|
5518
|
-
}
|
|
5519
|
-
});
|
|
5520
|
-
this._stream = null;
|
|
5521
|
-
} catch (e) {
|
|
5522
|
-
this._logger.error(this, 'Error stopping main stream');
|
|
5523
|
-
}
|
|
5933
|
+
if (this._microphoneAbortController) {
|
|
5934
|
+
this._microphoneAbortController.abort();
|
|
5935
|
+
this._microphoneAbortController = null;
|
|
5524
5936
|
}
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
if (videoElement && videoElement.srcObject instanceof MediaStream) {
|
|
5529
|
-
const videoTracks = videoElement.srcObject.getTracks();
|
|
5530
|
-
if (videoTracks.length > 0) {
|
|
5531
|
-
this._logger.info(this, `Stopping ${videoTracks.length} tracks from video element`);
|
|
5532
|
-
videoTracks.forEach(track => {
|
|
5533
|
-
try {
|
|
5534
|
-
track.enabled = false;
|
|
5535
|
-
track.stop();
|
|
5536
|
-
} catch (e) {
|
|
5537
|
-
this._logger.error(this, `Error stopping video element track: ${e}`);
|
|
5538
|
-
}
|
|
5539
|
-
});
|
|
5540
|
-
}
|
|
5541
|
-
videoElement.srcObject = null;
|
|
5542
|
-
videoElement.removeAttribute('src');
|
|
5543
|
-
videoElement.load();
|
|
5544
|
-
}
|
|
5545
|
-
} catch (e) {
|
|
5546
|
-
this._logger.error(this, 'Error cleaning video element');
|
|
5937
|
+
if (this._startCameraAbortController) {
|
|
5938
|
+
this._startCameraAbortController.abort();
|
|
5939
|
+
this._startCameraAbortController = null;
|
|
5547
5940
|
}
|
|
5548
|
-
// 4. Handle RTCPeerConnection last, with proper state checking
|
|
5549
|
-
if (this._peerConnection) {
|
|
5550
|
-
try {
|
|
5551
|
-
// Only try to remove tracks if the connection isn't already closed
|
|
5552
|
-
if (this._peerConnection.signalingState !== 'closed') {
|
|
5553
|
-
const senders = this._peerConnection.getSenders();
|
|
5554
|
-
this._logger.info(this, `Cleaning up ${senders.length} senders from peer connection`);
|
|
5555
|
-
senders.forEach(sender => {
|
|
5556
|
-
try {
|
|
5557
|
-
if (sender.track) {
|
|
5558
|
-
sender.track.enabled = false;
|
|
5559
|
-
sender.track.stop();
|
|
5560
|
-
// Only try to remove the track if connection is still open
|
|
5561
|
-
if (this._peerConnection && this._peerConnection.signalingState !== 'closed') {
|
|
5562
|
-
this._peerConnection.removeTrack(sender);
|
|
5563
|
-
}
|
|
5564
|
-
}
|
|
5565
|
-
} catch (e) {
|
|
5566
|
-
this._logger.error(this, `Error stopping sender track: ${e}`);
|
|
5567
|
-
}
|
|
5568
|
-
});
|
|
5569
|
-
}
|
|
5570
|
-
// Now close the peer connection
|
|
5571
|
-
this._peerConnection.close();
|
|
5572
|
-
this._peerConnection = null;
|
|
5573
|
-
} catch (e) {
|
|
5574
|
-
this._logger.error(this, 'Error closing peer connection');
|
|
5575
|
-
}
|
|
5576
|
-
}
|
|
5577
|
-
// 5. Ensure we properly null out our device references
|
|
5578
|
-
this._selectedCamera = null;
|
|
5579
|
-
this._selectedMicrophone = null;
|
|
5580
|
-
// 6. Make a final check for any active tracks at the global level
|
|
5581
|
-
try {
|
|
5582
|
-
const allTracks = [];
|
|
5583
|
-
// Try to find any tracks that might still be active by querying devices
|
|
5584
|
-
navigator.mediaDevices.getUserMedia({
|
|
5585
|
-
audio: false,
|
|
5586
|
-
video: false
|
|
5587
|
-
}).then(() => {
|
|
5588
|
-
// This is just to trigger a device check
|
|
5589
|
-
this._logger.info(this, "Performed final device check");
|
|
5590
|
-
}).catch(() => {
|
|
5591
|
-
// Ignore errors from this check
|
|
5592
|
-
});
|
|
5593
|
-
} catch (e) {
|
|
5594
|
-
// Ignore errors from final check
|
|
5595
|
-
}
|
|
5596
|
-
}
|
|
5597
|
-
/**
|
|
5598
|
-
* Stops all streaming operations and cleans up resources
|
|
5599
|
-
*/
|
|
5600
|
-
stop() {
|
|
5601
|
-
// Stop status connection and clear timer
|
|
5602
5941
|
if (this._statusConnection) {
|
|
5603
5942
|
this._statusConnection.destroy();
|
|
5604
5943
|
this._statusConnection = null;
|
|
@@ -5608,16 +5947,12 @@
|
|
|
5608
5947
|
this._statusTimer = null;
|
|
5609
5948
|
}
|
|
5610
5949
|
this._main.getConfigManager().getStreamData().streamKey = null;
|
|
5611
|
-
// Close WebRTC connection
|
|
5612
5950
|
this.closeWebRTCConnection();
|
|
5613
|
-
// Stop all media streams
|
|
5614
5951
|
this.stopCameraStream();
|
|
5615
|
-
// Reset states
|
|
5616
5952
|
this.setPublishState(exports.PublishState.STOPPED);
|
|
5617
5953
|
this.setInputDeviceState(exports.InputDevicesState.STOPPED);
|
|
5618
5954
|
this.setCameraState(exports.DeviceState.STOPPED);
|
|
5619
5955
|
this.setMicrophoneState(exports.DeviceState.STOPPED);
|
|
5620
|
-
// Clear restart timer if exists
|
|
5621
5956
|
if (this._restartTimer) {
|
|
5622
5957
|
clearInterval(this._restartTimer);
|
|
5623
5958
|
this._restartTimer = null;
|
|
@@ -5625,41 +5960,62 @@
|
|
|
5625
5960
|
this._restartTimerCount = 0;
|
|
5626
5961
|
clearTimeout(this._publishTimer);
|
|
5627
5962
|
}
|
|
5628
|
-
/**
|
|
5629
|
-
* Reinitializes the streaming setup
|
|
5630
|
-
*/
|
|
5631
5963
|
start() {
|
|
5632
5964
|
var _a, _b, _c;
|
|
5633
5965
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5966
|
+
if (this._isDestroyed) return;
|
|
5967
|
+
this._logger.info(this, "📹 [START] start() - reinitializing streaming");
|
|
5634
5968
|
try {
|
|
5635
|
-
// Reset states
|
|
5636
5969
|
this._publishState = exports.PublishState.NOT_INITIALIZED;
|
|
5637
5970
|
this._inputDeviceState = exports.InputDevicesState.NOT_INITIALIZED;
|
|
5638
5971
|
this._cameraState = exports.DeviceState.NOT_INITIALIZED;
|
|
5639
5972
|
this._microphoneState = exports.DeviceState.NOT_INITIALIZED;
|
|
5640
|
-
// Reinitialize devices and stream
|
|
5641
5973
|
yield this.initializeDevices();
|
|
5974
|
+
if (this._isDestroyed) return;
|
|
5642
5975
|
yield this.startCamera();
|
|
5643
|
-
|
|
5976
|
+
if (this._isDestroyed) return;
|
|
5644
5977
|
if ((_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getSettingsData().autoConnect) {
|
|
5645
5978
|
(_b = this._main.getNetworkController()) === null || _b === void 0 ? void 0 : _b.initialize();
|
|
5646
5979
|
}
|
|
5647
|
-
// Reinitialize status connection if needed
|
|
5648
5980
|
if ((_c = this._main.getConfigManager()) === null || _c === void 0 ? void 0 : _c.getStreamData().streamKey) {
|
|
5649
5981
|
this.createStatusConnection();
|
|
5650
5982
|
}
|
|
5651
5983
|
} catch (error) {
|
|
5652
|
-
|
|
5984
|
+
if (this._isDestroyed) return;
|
|
5985
|
+
this._logger.error(this, "📹 [START] start() - failed: " + JSON.stringify(error));
|
|
5653
5986
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5654
5987
|
throw error;
|
|
5655
5988
|
}
|
|
5656
5989
|
});
|
|
5657
5990
|
}
|
|
5658
5991
|
/**
|
|
5659
|
-
*
|
|
5992
|
+
* SYNCHRONOUS destroy - sets flag immediately, cleanup happens in background
|
|
5993
|
+
* This ensures that even if called without await, all async operations will abort
|
|
5660
5994
|
*/
|
|
5661
5995
|
destroy() {
|
|
5662
|
-
|
|
5996
|
+
var _a, _b, _c, _d;
|
|
5997
|
+
// Prevent double destroy
|
|
5998
|
+
if (this._isDestroyed) {
|
|
5999
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.warning(this, "🔴 [DESTROY] Already destroyed, skipping");
|
|
6000
|
+
return;
|
|
6001
|
+
}
|
|
6002
|
+
this._logger.info(this, "🔴 [DESTROY] Starting StreamerController destroy (sync)...");
|
|
6003
|
+
// CRITICAL: Set flag IMMEDIATELY - this will cause all pending async operations to abort
|
|
6004
|
+
this._isDestroyed = true;
|
|
6005
|
+
// Cancel all abort controllers immediately
|
|
6006
|
+
if (this._cameraAbortController) {
|
|
6007
|
+
this._cameraAbortController.abort();
|
|
6008
|
+
this._cameraAbortController = null;
|
|
6009
|
+
}
|
|
6010
|
+
if (this._microphoneAbortController) {
|
|
6011
|
+
this._microphoneAbortController.abort();
|
|
6012
|
+
this._microphoneAbortController = null;
|
|
6013
|
+
}
|
|
6014
|
+
if (this._startCameraAbortController) {
|
|
6015
|
+
this._startCameraAbortController.abort();
|
|
6016
|
+
this._startCameraAbortController = null;
|
|
6017
|
+
}
|
|
6018
|
+
// Stop all timers immediately
|
|
5663
6019
|
if (this._statusTimer != null) {
|
|
5664
6020
|
clearInterval(this._statusTimer);
|
|
5665
6021
|
this._statusTimer = null;
|
|
@@ -5669,13 +6025,23 @@
|
|
|
5669
6025
|
this._restartTimer = null;
|
|
5670
6026
|
}
|
|
5671
6027
|
clearTimeout(this._publishTimer);
|
|
5672
|
-
// Remove
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
6028
|
+
// Remove permission listeners
|
|
6029
|
+
this.removePermissionListeners();
|
|
6030
|
+
// Remove device change listener
|
|
6031
|
+
if (this._deviceChangeHandler) {
|
|
6032
|
+
navigator.mediaDevices.removeEventListener('devicechange', this._deviceChangeHandler);
|
|
6033
|
+
this._deviceChangeHandler = null;
|
|
6034
|
+
}
|
|
6035
|
+
// Remove orientation listener
|
|
6036
|
+
if (this._orientationChangeHandler) {
|
|
6037
|
+
if (window.screen && window.screen.orientation) {
|
|
6038
|
+
window.screen.orientation.removeEventListener('change', this._orientationChangeHandler);
|
|
6039
|
+
} else {
|
|
6040
|
+
window.removeEventListener('orientationchange', this._orientationChangeHandler);
|
|
6041
|
+
}
|
|
6042
|
+
this._orientationChangeHandler = null;
|
|
5677
6043
|
}
|
|
5678
|
-
// Remove
|
|
6044
|
+
// Remove event listeners
|
|
5679
6045
|
try {
|
|
5680
6046
|
this._main.removeEventListener("serverConnect", this.onServerConnect);
|
|
5681
6047
|
this._main.removeEventListener("serverDisconnect", this.onServerDisconnect);
|
|
@@ -5688,28 +6054,46 @@
|
|
|
5688
6054
|
window.removeEventListener("blur", this.onWindowBlur);
|
|
5689
6055
|
window.removeEventListener("focus", this.onWindowFocus);
|
|
5690
6056
|
} catch (e) {
|
|
5691
|
-
|
|
6057
|
+
// Ignore errors
|
|
5692
6058
|
}
|
|
5693
|
-
//
|
|
6059
|
+
// Destroy status connection
|
|
5694
6060
|
if (this._statusConnection) {
|
|
5695
6061
|
this._statusConnection.destroy();
|
|
5696
6062
|
this._statusConnection = null;
|
|
5697
6063
|
}
|
|
5698
|
-
//
|
|
5699
|
-
this.
|
|
5700
|
-
//
|
|
5701
|
-
this.
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
6064
|
+
// Close WebRTC
|
|
6065
|
+
this.closeWebRTCConnection();
|
|
6066
|
+
// Stop camera stream
|
|
6067
|
+
if (this._stream) {
|
|
6068
|
+
this._logger.info(this, "📹 [FORCE_STOP] Stopping main stream");
|
|
6069
|
+
this._stream.getTracks().forEach(track => {
|
|
6070
|
+
track.stop();
|
|
6071
|
+
});
|
|
6072
|
+
this._stream = null;
|
|
6073
|
+
}
|
|
6074
|
+
// Clean video element
|
|
6075
|
+
try {
|
|
6076
|
+
const videoElement = (_c = (_b = this._main.getStageController()) === null || _b === void 0 ? void 0 : _b.getScreenElement()) === null || _c === void 0 ? void 0 : _c.getVideoElement();
|
|
6077
|
+
if (videoElement) {
|
|
6078
|
+
if (videoElement.srcObject instanceof MediaStream) {
|
|
6079
|
+
videoElement.srcObject.getTracks().forEach(track => track.stop());
|
|
6080
|
+
}
|
|
6081
|
+
videoElement.srcObject = null;
|
|
6082
|
+
}
|
|
6083
|
+
} catch (e) {
|
|
6084
|
+
// Ignore
|
|
6085
|
+
}
|
|
6086
|
+
// Destroy sound meter
|
|
6087
|
+
try {
|
|
6088
|
+
(_d = this._soundMeter) === null || _d === void 0 ? void 0 : _d.destroy();
|
|
6089
|
+
} catch (e) {
|
|
6090
|
+
// Ignore
|
|
6091
|
+
}
|
|
6092
|
+
// Reset variables
|
|
6093
|
+
this._selectedCamera = null;
|
|
6094
|
+
this._selectedMicrophone = null;
|
|
6095
|
+
this._activeStreamCount = 0;
|
|
6096
|
+
this._logger.success(this, "🔴 [DESTROY] StreamerController destroyed (sync)");
|
|
5713
6097
|
}
|
|
5714
6098
|
}
|
|
5715
6099
|
|
|
@@ -6294,7 +6678,6 @@
|
|
|
6294
6678
|
this.onStreamStatsUpdate = event => {
|
|
6295
6679
|
var _a;
|
|
6296
6680
|
(_a = this._graph) === null || _a === void 0 ? void 0 : _a.addEntry(event.high * 500);
|
|
6297
|
-
console.log(event.high * 200);
|
|
6298
6681
|
};
|
|
6299
6682
|
this._main = main;
|
|
6300
6683
|
this._object = container;
|
|
@@ -6322,52 +6705,19 @@
|
|
|
6322
6705
|
* Main class of the player. The player itself has no GUI, but can be controlled via provided API.
|
|
6323
6706
|
*/
|
|
6324
6707
|
class StormStreamer extends EventDispatcher {
|
|
6325
|
-
//------------------------------------------------------------------------//
|
|
6326
|
-
// CONSTRUCTOR
|
|
6327
|
-
//------------------------------------------------------------------------//
|
|
6328
|
-
/**
|
|
6329
|
-
* Constructor - creates a new StormStreamer instance
|
|
6330
|
-
*
|
|
6331
|
-
* @param streamConfig - Configuration object for the streamer
|
|
6332
|
-
* @param autoInitialize - Whether to automatically initialize the streamer after creation
|
|
6333
|
-
*/
|
|
6334
6708
|
constructor(streamConfig, autoInitialize = false) {
|
|
6335
6709
|
super();
|
|
6336
|
-
/**
|
|
6337
|
-
* Indicates whether the streamer object is in development mode (provides more debug options)
|
|
6338
|
-
* @private
|
|
6339
|
-
*/
|
|
6340
6710
|
this.DEV_MODE = true;
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
* @private
|
|
6344
|
-
*/
|
|
6345
|
-
this.STREAMER_VERSION = "1.0.0-rc.0";
|
|
6346
|
-
/**
|
|
6347
|
-
* Compile date for this streamer
|
|
6348
|
-
* @private
|
|
6349
|
-
*/
|
|
6350
|
-
this.COMPILE_DATE = "3/28/2025, 9:18:36 AM";
|
|
6351
|
-
/**
|
|
6352
|
-
* Defines from which branch this streamer comes from e.g. "Main", "Experimental"
|
|
6353
|
-
* @private
|
|
6354
|
-
*/
|
|
6711
|
+
this.STREAMER_VERSION = "1.0.0";
|
|
6712
|
+
this.COMPILE_DATE = "2/7/2026, 6:37:16 PM";
|
|
6355
6713
|
this.STREAMER_BRANCH = "Experimental";
|
|
6356
|
-
/**
|
|
6357
|
-
* Defines number of streamer protocol that is required on server-side
|
|
6358
|
-
* @private
|
|
6359
|
-
*/
|
|
6360
6714
|
this.STREAMER_PROTOCOL_VERSION = 1;
|
|
6361
|
-
/**
|
|
6362
|
-
* Indicates whether streamer was initialized or not
|
|
6363
|
-
* @private
|
|
6364
|
-
*/
|
|
6365
6715
|
this._initialized = false;
|
|
6716
|
+
this._isDestroyed = false;
|
|
6366
6717
|
if (typeof window === 'undefined' || !window.document || !window.document.createElement) {
|
|
6367
6718
|
console.error(`StormStreamer Creation Error - No "window" element in the provided context!`);
|
|
6368
6719
|
return;
|
|
6369
6720
|
}
|
|
6370
|
-
// WINDOW.StormStreamerArray
|
|
6371
6721
|
if (this.DEV_MODE && !('StormStreamerArray' in window)) {
|
|
6372
6722
|
window.StormStreamerArray = [];
|
|
6373
6723
|
}
|
|
@@ -6377,17 +6727,13 @@
|
|
|
6377
6727
|
this.setStreamConfig(streamConfig);
|
|
6378
6728
|
if (autoInitialize) this.initialize();
|
|
6379
6729
|
}
|
|
6380
|
-
/**
|
|
6381
|
-
* Initializes the streamer object. From this point, a connection to the server is established and authentication occurs.
|
|
6382
|
-
* It is recommended to add all event listeners before calling this method to ensure they can be properly captured.
|
|
6383
|
-
*/
|
|
6384
6730
|
initialize() {
|
|
6385
|
-
if (this._isRemoved) return;
|
|
6386
|
-
if (this._configManager == null) throw Error("Stream Config was not provided for this streamer!
|
|
6387
|
-
this._storageManager = new StorageManager(this);
|
|
6388
|
-
this._stageController = new StageController(this);
|
|
6389
|
-
this._networkController = new NetworkController(this);
|
|
6390
|
-
this._streamerController = new StreamerController(this);
|
|
6731
|
+
if (this._isRemoved || this._isDestroyed) return;
|
|
6732
|
+
if (this._configManager == null) throw Error("Stream Config was not provided for this streamer!");
|
|
6733
|
+
this._storageManager = new StorageManager(this);
|
|
6734
|
+
this._stageController = new StageController(this);
|
|
6735
|
+
this._networkController = new NetworkController(this);
|
|
6736
|
+
this._streamerController = new StreamerController(this);
|
|
6391
6737
|
this._statsController = new StatsController(this);
|
|
6392
6738
|
this._graphs = [];
|
|
6393
6739
|
this._initialized = true;
|
|
@@ -6395,16 +6741,8 @@
|
|
|
6395
6741
|
ref: this
|
|
6396
6742
|
});
|
|
6397
6743
|
}
|
|
6398
|
-
/**
|
|
6399
|
-
* Sets stream config for the streamer (or overwrites an existing one).
|
|
6400
|
-
*
|
|
6401
|
-
* @param streamConfig - New configuration object for the streamer
|
|
6402
|
-
*/
|
|
6403
6744
|
setStreamConfig(streamConfig) {
|
|
6404
|
-
if (this._isRemoved) return;
|
|
6405
|
-
/**
|
|
6406
|
-
* In case the original streamConfig is modified elsewhere we have to create a separate copy and store it ourselves
|
|
6407
|
-
*/
|
|
6745
|
+
if (this._isRemoved || this._isDestroyed) return;
|
|
6408
6746
|
const copiedStreamConfig = JSON.parse(JSON.stringify(streamConfig));
|
|
6409
6747
|
if (this._configManager == null) {
|
|
6410
6748
|
this._configManager = new ConfigManager(copiedStreamConfig);
|
|
@@ -6429,289 +6767,133 @@
|
|
|
6429
6767
|
});
|
|
6430
6768
|
}
|
|
6431
6769
|
}
|
|
6432
|
-
//------------------------------------------------------------------------//
|
|
6433
6770
|
// PLAYBACK / STREAMING
|
|
6434
|
-
//------------------------------------------------------------------------//
|
|
6435
|
-
/**
|
|
6436
|
-
* Returns true if this streamer instance is currently connected to a Storm Server/Cloud instance.
|
|
6437
|
-
*
|
|
6438
|
-
* @returns Boolean indicating connection status
|
|
6439
|
-
*/
|
|
6440
6771
|
isConnected() {
|
|
6441
6772
|
var _a, _b;
|
|
6442
6773
|
return (_b = (_a = this._networkController) === null || _a === void 0 ? void 0 : _a.getConnection().isConnectionActive()) !== null && _b !== void 0 ? _b : false;
|
|
6443
6774
|
}
|
|
6444
|
-
/**
|
|
6445
|
-
* Mutes the streamer's video object. Audio output will be silenced.
|
|
6446
|
-
*/
|
|
6447
6775
|
mute() {
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
}
|
|
6776
|
+
var _a;
|
|
6777
|
+
if (((_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) != null) {
|
|
6778
|
+
this._stageController.getScreenElement().setMuted(true);
|
|
6779
|
+
return;
|
|
6453
6780
|
}
|
|
6454
6781
|
this._configManager.getSettingsData().getAudioData().muted = true;
|
|
6455
6782
|
}
|
|
6456
|
-
/**
|
|
6457
|
-
* Unmutes the streamer's video object. Audio output will be restored.
|
|
6458
|
-
*/
|
|
6459
6783
|
unmute() {
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
}
|
|
6784
|
+
var _a;
|
|
6785
|
+
if (((_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) != null) {
|
|
6786
|
+
this._stageController.getScreenElement().setMuted(false);
|
|
6787
|
+
return;
|
|
6465
6788
|
}
|
|
6466
6789
|
this._configManager.getSettingsData().getAudioData().muted = false;
|
|
6467
6790
|
}
|
|
6468
|
-
/**
|
|
6469
|
-
* Checks whether the streamer audio is currently muted.
|
|
6470
|
-
*
|
|
6471
|
-
* @returns Boolean indicating mute status
|
|
6472
|
-
*/
|
|
6473
6791
|
isMute() {
|
|
6474
6792
|
var _a, _b, _c, _d;
|
|
6475
6793
|
return (_d = (_c = (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getIfMuted()) !== null && _c !== void 0 ? _c : this._configManager.getSettingsData().getAudioData().muted) !== null && _d !== void 0 ? _d : false;
|
|
6476
6794
|
}
|
|
6477
|
-
/**
|
|
6478
|
-
* Toggles between mute and unmute states. Returns the new mute state.
|
|
6479
|
-
*
|
|
6480
|
-
* @returns New mute state (true = muted, false = unmuted)
|
|
6481
|
-
*/
|
|
6482
6795
|
toggleMute() {
|
|
6483
6796
|
const isMuted = this.isMute();
|
|
6484
|
-
if (isMuted)
|
|
6485
|
-
this.unmute();
|
|
6486
|
-
} else {
|
|
6487
|
-
this.mute();
|
|
6488
|
-
}
|
|
6797
|
+
if (isMuted) this.unmute();else this.mute();
|
|
6489
6798
|
return !isMuted;
|
|
6490
6799
|
}
|
|
6491
|
-
/**
|
|
6492
|
-
* Sets new volume for the streamer (0-100). Once the method is performed, the volumeChange event will be triggered.
|
|
6493
|
-
* If the video was muted prior to the volume change, it will be automatically unmuted.
|
|
6494
|
-
*
|
|
6495
|
-
* @param newVolume - Volume level (0-100)
|
|
6496
|
-
*/
|
|
6497
6800
|
setVolume(newVolume) {
|
|
6498
6801
|
var _a, _b;
|
|
6499
|
-
if (((_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.setVolume(newVolume)) !== undefined)
|
|
6500
|
-
return;
|
|
6501
|
-
}
|
|
6802
|
+
if (((_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.setVolume(newVolume)) !== undefined) return;
|
|
6502
6803
|
this._configManager.getSettingsData().getAudioData().startVolume = newVolume;
|
|
6503
6804
|
}
|
|
6504
|
-
/**
|
|
6505
|
-
* Returns current streamer volume (0-100).
|
|
6506
|
-
*
|
|
6507
|
-
* @returns Current volume level
|
|
6508
|
-
*/
|
|
6509
6805
|
getVolume() {
|
|
6510
6806
|
var _a, _b, _c;
|
|
6511
6807
|
return (_c = (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVolume()) !== null && _c !== void 0 ? _c : this._configManager.getSettingsData().getAudioData().startVolume;
|
|
6512
6808
|
}
|
|
6513
|
-
/**
|
|
6514
|
-
* Returns the list of available camera devices.
|
|
6515
|
-
*
|
|
6516
|
-
* @returns Array of camera input devices
|
|
6517
|
-
*/
|
|
6518
6809
|
getCameraList() {
|
|
6519
6810
|
var _a, _b;
|
|
6520
6811
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCameraList()) !== null && _b !== void 0 ? _b : [];
|
|
6521
6812
|
}
|
|
6522
|
-
/**
|
|
6523
|
-
* Returns the list of available microphone devices.
|
|
6524
|
-
*
|
|
6525
|
-
* @returns Array of microphone input devices
|
|
6526
|
-
*/
|
|
6527
6813
|
getMicrophoneList() {
|
|
6528
6814
|
var _a, _b;
|
|
6529
6815
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getMicrophoneList()) !== null && _b !== void 0 ? _b : [];
|
|
6530
6816
|
}
|
|
6531
|
-
/**
|
|
6532
|
-
* Sets the active camera device by ID.
|
|
6533
|
-
*
|
|
6534
|
-
* @param cameraID - ID of the camera device to use
|
|
6535
|
-
*/
|
|
6536
6817
|
setCamera(cameraID) {
|
|
6537
6818
|
var _a;
|
|
6538
6819
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.selectCamera(cameraID);
|
|
6539
6820
|
}
|
|
6540
|
-
/**
|
|
6541
|
-
* Sets the active microphone device by ID.
|
|
6542
|
-
*
|
|
6543
|
-
* @param microphoneID - ID of the microphone device to use
|
|
6544
|
-
*/
|
|
6545
6821
|
setMicrophone(microphoneID) {
|
|
6546
6822
|
var _a;
|
|
6547
6823
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.selectMicrophone(microphoneID);
|
|
6548
6824
|
}
|
|
6549
|
-
/**
|
|
6550
|
-
* Returns the currently active camera device.
|
|
6551
|
-
*
|
|
6552
|
-
* @returns Current camera device or null if none is active
|
|
6553
|
-
*/
|
|
6554
6825
|
getCurrentCamera() {
|
|
6555
|
-
|
|
6826
|
+
var _a, _b;
|
|
6827
|
+
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCurrentCamera()) !== null && _b !== void 0 ? _b : null;
|
|
6556
6828
|
}
|
|
6557
|
-
/**
|
|
6558
|
-
* Returns the currently active microphone device.
|
|
6559
|
-
*
|
|
6560
|
-
* @returns Current microphone device or null if none is active
|
|
6561
|
-
*/
|
|
6562
6829
|
getCurrentMicrophone() {
|
|
6563
|
-
|
|
6830
|
+
var _a, _b;
|
|
6831
|
+
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCurrentMicrophone()) !== null && _b !== void 0 ? _b : null;
|
|
6564
6832
|
}
|
|
6565
|
-
/**
|
|
6566
|
-
* Mutes or unmutes the microphone.
|
|
6567
|
-
*
|
|
6568
|
-
* @param microphoneState - True to mute, false to unmute
|
|
6569
|
-
*/
|
|
6570
6833
|
muteMicrophone(microphoneState) {
|
|
6571
6834
|
var _a;
|
|
6572
6835
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.muteMicrophone(microphoneState);
|
|
6573
6836
|
}
|
|
6574
|
-
/**
|
|
6575
|
-
* Checks if the microphone is currently muted.
|
|
6576
|
-
*
|
|
6577
|
-
* @returns Boolean indicating if microphone is muted
|
|
6578
|
-
*/
|
|
6579
6837
|
isMicrophoneMuted() {
|
|
6580
6838
|
var _a, _b;
|
|
6581
6839
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.isMicrophoneMuted()) !== null && _b !== void 0 ? _b : false;
|
|
6582
6840
|
}
|
|
6583
|
-
/**
|
|
6584
|
-
* Returns the current publishing state of the streamer.
|
|
6585
|
-
*
|
|
6586
|
-
* @returns Current publishing state
|
|
6587
|
-
*/
|
|
6588
6841
|
getPublishState() {
|
|
6589
6842
|
var _a, _b;
|
|
6590
6843
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getPublishState()) !== null && _b !== void 0 ? _b : exports.PublishState.NOT_INITIALIZED;
|
|
6591
6844
|
}
|
|
6592
|
-
/**
|
|
6593
|
-
* Returns the total time the stream has been publishing in milliseconds.
|
|
6594
|
-
*
|
|
6595
|
-
* @returns Publishing time in milliseconds
|
|
6596
|
-
*/
|
|
6597
6845
|
getPublishTime() {
|
|
6598
6846
|
var _a, _b;
|
|
6599
6847
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getPublishTime()) !== null && _b !== void 0 ? _b : 0;
|
|
6600
6848
|
}
|
|
6601
|
-
/**
|
|
6602
|
-
* Starts publishing a stream with the given stream key.
|
|
6603
|
-
*
|
|
6604
|
-
* @param streamKey - Key identifying the stream to publish
|
|
6605
|
-
* @returns Boolean indicating if publishing was successfully initiated
|
|
6606
|
-
*/
|
|
6607
6849
|
publish(streamKey) {
|
|
6608
6850
|
var _a, _b;
|
|
6609
6851
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.publish(streamKey)) !== null && _b !== void 0 ? _b : false;
|
|
6610
6852
|
}
|
|
6611
|
-
/**
|
|
6612
|
-
* Stops publishing the current stream.
|
|
6613
|
-
*/
|
|
6614
6853
|
unpublish() {
|
|
6615
6854
|
var _a;
|
|
6616
6855
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.unpublish();
|
|
6617
6856
|
}
|
|
6618
|
-
/**
|
|
6619
|
-
* Returns the current state of input devices (camera and microphone).
|
|
6620
|
-
*
|
|
6621
|
-
* @returns Current state of input devices
|
|
6622
|
-
*/
|
|
6623
6857
|
getInputDevicesState() {
|
|
6624
6858
|
var _a, _b;
|
|
6625
6859
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getInputDeviceState()) !== null && _b !== void 0 ? _b : exports.InputDevicesState.NOT_INITIALIZED;
|
|
6626
6860
|
}
|
|
6627
|
-
/**
|
|
6628
|
-
* Returns the current state of the camera device.
|
|
6629
|
-
*
|
|
6630
|
-
* @returns Current camera device state
|
|
6631
|
-
*/
|
|
6632
6861
|
getCameraState() {
|
|
6633
6862
|
var _a, _b;
|
|
6634
6863
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCameraState()) !== null && _b !== void 0 ? _b : exports.DeviceState.NOT_INITIALIZED;
|
|
6635
6864
|
}
|
|
6636
|
-
/**
|
|
6637
|
-
* Returns the current state of the microphone device.
|
|
6638
|
-
*
|
|
6639
|
-
* @returns Current microphone device state
|
|
6640
|
-
*/
|
|
6641
6865
|
getMicrophoneState() {
|
|
6642
6866
|
var _a, _b;
|
|
6643
6867
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getMicrophoneState()) !== null && _b !== void 0 ? _b : exports.DeviceState.NOT_INITIALIZED;
|
|
6644
6868
|
}
|
|
6645
|
-
/**
|
|
6646
|
-
* Clears saved device preferences from storage.
|
|
6647
|
-
*/
|
|
6648
6869
|
clearSavedDevices() {
|
|
6649
6870
|
var _a;
|
|
6650
|
-
|
|
6871
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.clearSavedDevices();
|
|
6651
6872
|
}
|
|
6652
|
-
/**
|
|
6653
|
-
* Randomizes saved device preferences (for testing purposes).
|
|
6654
|
-
*/
|
|
6655
6873
|
messSavedDevices() {
|
|
6656
6874
|
var _a;
|
|
6657
|
-
|
|
6875
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.messSavedDevices();
|
|
6658
6876
|
}
|
|
6659
|
-
/**
|
|
6660
|
-
* Checks if the stream is ready for publishing.
|
|
6661
|
-
*
|
|
6662
|
-
* @returns Boolean indicating if stream is ready
|
|
6663
|
-
*/
|
|
6664
6877
|
isStreamReady() {
|
|
6665
6878
|
var _a, _b;
|
|
6666
6879
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.isStreamReady()) !== null && _b !== void 0 ? _b : false;
|
|
6667
6880
|
}
|
|
6668
|
-
//------------------------------------------------------------------------//
|
|
6669
6881
|
// CONTAINER
|
|
6670
|
-
//------------------------------------------------------------------------//
|
|
6671
|
-
/**
|
|
6672
|
-
* Attaches the streamer to a new parent container using either a container ID (string) or a reference to an HTMLElement.
|
|
6673
|
-
* If the instance is already attached, it will be moved to a new parent.
|
|
6674
|
-
*
|
|
6675
|
-
* @param container - Container ID (string) or HTMLElement reference
|
|
6676
|
-
* @returns Boolean indicating if attachment was successful
|
|
6677
|
-
*/
|
|
6678
6882
|
attachToContainer(container) {
|
|
6679
6883
|
var _a, _b;
|
|
6680
|
-
let result = false;
|
|
6681
6884
|
if (this._initialized) return (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.attachToParent(container)) !== null && _b !== void 0 ? _b : false;
|
|
6682
|
-
return
|
|
6885
|
+
return false;
|
|
6683
6886
|
}
|
|
6684
|
-
/**
|
|
6685
|
-
* Detaches the streamer from the current parent element, if possible.
|
|
6686
|
-
*
|
|
6687
|
-
* @returns Boolean indicating if detachment was successful
|
|
6688
|
-
*/
|
|
6689
6887
|
detachFromContainer() {
|
|
6690
6888
|
var _a, _b;
|
|
6691
|
-
let result = false;
|
|
6692
6889
|
if (this._initialized) return (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.detachFromParent()) !== null && _b !== void 0 ? _b : false;
|
|
6693
|
-
return
|
|
6890
|
+
return false;
|
|
6694
6891
|
}
|
|
6695
|
-
/**
|
|
6696
|
-
* Returns the current parent element of the streamer, or null if none exists.
|
|
6697
|
-
*
|
|
6698
|
-
* @returns Current container element or null
|
|
6699
|
-
*/
|
|
6700
6892
|
getContainer() {
|
|
6701
6893
|
var _a, _b;
|
|
6702
6894
|
return (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getParentElement()) !== null && _b !== void 0 ? _b : null;
|
|
6703
6895
|
}
|
|
6704
|
-
//------------------------------------------------------------------------//
|
|
6705
6896
|
// SIZE & RESIZE
|
|
6706
|
-
//------------------------------------------------------------------------//
|
|
6707
|
-
/**
|
|
6708
|
-
* Sets a new width and height for the streamer. The values can be given as a number (in which case they are
|
|
6709
|
-
* treated as the number of pixels), or as a string ending with "px" (this will also be the number of pixels) or "%",
|
|
6710
|
-
* where the number is treated as a percentage of the parent container's value.
|
|
6711
|
-
*
|
|
6712
|
-
* @param width - Can be provided as number or a string with "%" or "px" suffix
|
|
6713
|
-
* @param height - Can be provided as number or a string with "%" or "px" suffix
|
|
6714
|
-
*/
|
|
6715
6897
|
setSize(width, height) {
|
|
6716
6898
|
if (this._initialized) this._stageController.setSize(width, height);else {
|
|
6717
6899
|
const parsedWidth = NumberUtilities.parseValue(width);
|
|
@@ -6722,13 +6904,6 @@
|
|
|
6722
6904
|
this._configManager.getSettingsData().getVideoData().videoHeightInPixels = parsedHeight.isPixels;
|
|
6723
6905
|
}
|
|
6724
6906
|
}
|
|
6725
|
-
/**
|
|
6726
|
-
* Sets a new width for the streamer. The value can be given as a number (in which case it is treated as the
|
|
6727
|
-
* number of pixels), or as a string ending with "px" (this will also be the number of pixels) or "%", where the
|
|
6728
|
-
* number is treated as a percentage of the parent container's value.
|
|
6729
|
-
*
|
|
6730
|
-
* @param width - Can be provided as number or a string with "%" or "px" suffix
|
|
6731
|
-
*/
|
|
6732
6907
|
setWidth(width) {
|
|
6733
6908
|
if (this._initialized) this._stageController.setWidth(width);else {
|
|
6734
6909
|
const parsedWidth = NumberUtilities.parseValue(width);
|
|
@@ -6736,13 +6911,6 @@
|
|
|
6736
6911
|
this._configManager.getSettingsData().getVideoData().videoWidthInPixels = parsedWidth.isPixels;
|
|
6737
6912
|
}
|
|
6738
6913
|
}
|
|
6739
|
-
/**
|
|
6740
|
-
* Sets a new height for the streamer. The value can be given as a number (in which case it is treated as the
|
|
6741
|
-
* number of pixels), or as a string ending with "px" (this will also be the number of pixels) or "%", where the
|
|
6742
|
-
* number is treated as a percentage of the parent container's value.
|
|
6743
|
-
*
|
|
6744
|
-
* @param height - Can be provided as number or a string with "%" or "px" suffix
|
|
6745
|
-
*/
|
|
6746
6914
|
setHeight(height) {
|
|
6747
6915
|
if (this._initialized) this._stageController.setHeight(height);else {
|
|
6748
6916
|
const parsedHeight = NumberUtilities.parseValue(height);
|
|
@@ -6750,66 +6918,26 @@
|
|
|
6750
6918
|
this._configManager.getSettingsData().getVideoData().videoHeightInPixels = parsedHeight.isPixels;
|
|
6751
6919
|
}
|
|
6752
6920
|
}
|
|
6753
|
-
/**
|
|
6754
|
-
* Returns current streamer width in pixels.
|
|
6755
|
-
*
|
|
6756
|
-
* @returns Current width in pixels
|
|
6757
|
-
*/
|
|
6758
6921
|
getWidth() {
|
|
6759
|
-
if (this._initialized) return this._stageController.getContainerWidth();
|
|
6760
|
-
|
|
6761
|
-
}
|
|
6922
|
+
if (this._initialized) return this._stageController.getContainerWidth();
|
|
6923
|
+
if (this._configManager.getSettingsData().getVideoData().videoWidthInPixels) return this._configManager.getSettingsData().getVideoData().videoWidthValue;
|
|
6762
6924
|
return 0;
|
|
6763
6925
|
}
|
|
6764
|
-
/**
|
|
6765
|
-
* Returns current streamer height in pixels.
|
|
6766
|
-
*
|
|
6767
|
-
* @returns Current height in pixels
|
|
6768
|
-
*/
|
|
6769
6926
|
getHeight() {
|
|
6770
|
-
if (this._initialized) return this._stageController.getContainerHeight();
|
|
6771
|
-
|
|
6772
|
-
}
|
|
6927
|
+
if (this._initialized) return this._stageController.getContainerHeight();
|
|
6928
|
+
if (this._configManager.getSettingsData().getVideoData().videoHeightInPixels) return this._configManager.getSettingsData().getVideoData().videoHeightValue;
|
|
6773
6929
|
return 0;
|
|
6774
6930
|
}
|
|
6775
|
-
/**
|
|
6776
|
-
* Changes the streamer scaling mode. Available modes include fill, letterbox, original, and crop.
|
|
6777
|
-
*
|
|
6778
|
-
* @param newMode - New scaling mode name (fill, letterbox, original, crop)
|
|
6779
|
-
*/
|
|
6780
6931
|
setScalingMode(newMode) {
|
|
6781
|
-
if (this._stageController)
|
|
6782
|
-
this._stageController.setScalingMode(newMode);
|
|
6783
|
-
} else {
|
|
6784
|
-
this._configManager.getSettingsData().getVideoData().scalingMode = newMode;
|
|
6785
|
-
}
|
|
6932
|
+
if (this._stageController) this._stageController.setScalingMode(newMode);else this._configManager.getSettingsData().getVideoData().scalingMode = newMode;
|
|
6786
6933
|
}
|
|
6787
|
-
/**
|
|
6788
|
-
* Returns the current streamer scaling mode.
|
|
6789
|
-
*
|
|
6790
|
-
* @returns Current scaling mode
|
|
6791
|
-
*/
|
|
6792
6934
|
getScalingMode() {
|
|
6793
|
-
if (this._stageController)
|
|
6794
|
-
|
|
6795
|
-
} else {
|
|
6796
|
-
return this._configManager.getSettingsData().getVideoData().scalingMode;
|
|
6797
|
-
}
|
|
6935
|
+
if (this._stageController) return this._stageController.getScalingMode();
|
|
6936
|
+
return this._configManager.getSettingsData().getVideoData().scalingMode;
|
|
6798
6937
|
}
|
|
6799
|
-
/**
|
|
6800
|
-
* Forces the streamer to recalculate its size based on parent internal dimensions.
|
|
6801
|
-
*/
|
|
6802
6938
|
updateToSize() {
|
|
6803
|
-
if (this._initialized)
|
|
6804
|
-
this._stageController.handleResize();
|
|
6805
|
-
}
|
|
6939
|
+
if (this._initialized) this._stageController.handleResize();
|
|
6806
6940
|
}
|
|
6807
|
-
/**
|
|
6808
|
-
* Returns a promise that resolves with a screenshot of the video element as a blob, or null if taking the
|
|
6809
|
-
* screenshot was not possible.
|
|
6810
|
-
*
|
|
6811
|
-
* @returns Promise resolving to a Blob containing the screenshot or null
|
|
6812
|
-
*/
|
|
6813
6941
|
makeScreenshot() {
|
|
6814
6942
|
let canvas = document.createElement('canvas');
|
|
6815
6943
|
let context = canvas.getContext('2d');
|
|
@@ -6820,64 +6948,24 @@
|
|
|
6820
6948
|
let element = this._stageController.getScreenElement().getVideoElement();
|
|
6821
6949
|
if (context) {
|
|
6822
6950
|
context.drawImage(element, 0, 0, canvas.width, canvas.height);
|
|
6823
|
-
canvas.toBlob(blob =>
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
} else {
|
|
6827
|
-
resolve(null);
|
|
6828
|
-
}
|
|
6829
|
-
} else {
|
|
6830
|
-
resolve(null);
|
|
6831
|
-
}
|
|
6951
|
+
canvas.toBlob(blob => resolve(blob), 'image/png');
|
|
6952
|
+
} else resolve(null);
|
|
6953
|
+
} else resolve(null);
|
|
6832
6954
|
});
|
|
6833
6955
|
}
|
|
6834
|
-
//------------------------------------------------------------------------//
|
|
6835
6956
|
// GRAPHS
|
|
6836
|
-
//------------------------------------------------------------------------//
|
|
6837
|
-
/**
|
|
6838
|
-
* Creates a FPS performance graph in the specified location (container ID or reference). The graph is a
|
|
6839
|
-
* separate object that must be started using its start() method and stopped using its stop() method. The dimensions
|
|
6840
|
-
* of the graph depend on the dimensions of the specified container.
|
|
6841
|
-
*
|
|
6842
|
-
* @param container - Element ID or reference to HTMLElement
|
|
6843
|
-
* @returns FPS graph instance
|
|
6844
|
-
*/
|
|
6845
6957
|
createFPSGraph(container) {
|
|
6846
6958
|
return new FPSGraph(this, container);
|
|
6847
6959
|
}
|
|
6848
|
-
/**
|
|
6849
|
-
* Creates a bitrate performance graph in the specified location (container ID or reference). The graph is a
|
|
6850
|
-
* separate object that must be started using its start() method and stopped using its stop() method. The dimensions
|
|
6851
|
-
* of the graph depend on the dimensions of the specified container.
|
|
6852
|
-
*
|
|
6853
|
-
* @param container - Element ID or reference to HTMLElement
|
|
6854
|
-
* @returns Bitrate graph instance
|
|
6855
|
-
*/
|
|
6856
6960
|
createBitrateGraph(container) {
|
|
6857
6961
|
return new BitrateGraph(this, container);
|
|
6858
6962
|
}
|
|
6859
|
-
/**
|
|
6860
|
-
* Creates a microphone graph in the specified location (container ID or reference). The graph is a
|
|
6861
|
-
* separate object that must be started using its start() method and stopped using its stop() method. The dimensions
|
|
6862
|
-
* of the graph depend on the dimensions of the specified container.
|
|
6863
|
-
*
|
|
6864
|
-
* @param container - Element ID or reference to HTMLElement
|
|
6865
|
-
* @returns Bitrate graph instance
|
|
6866
|
-
*/
|
|
6867
6963
|
createMicrophoneGraph(container) {
|
|
6868
6964
|
return new MicrophoneGraph(this, container);
|
|
6869
6965
|
}
|
|
6870
|
-
/**
|
|
6871
|
-
* Adds new graph to the internal collection of active graphs.
|
|
6872
|
-
*
|
|
6873
|
-
* @param newGraph - Graph instance to add
|
|
6874
|
-
*/
|
|
6875
6966
|
addGraph(newGraph) {
|
|
6876
6967
|
if (this._graphs != null) this._graphs.push(newGraph);
|
|
6877
6968
|
}
|
|
6878
|
-
/**
|
|
6879
|
-
* Stops all active performance graphs.
|
|
6880
|
-
*/
|
|
6881
6969
|
stopAllGraphs() {
|
|
6882
6970
|
if (this._graphs != null && this._graphs.length > 0) {
|
|
6883
6971
|
for (let i = 0; i < this._graphs.length; i++) {
|
|
@@ -6885,198 +6973,136 @@
|
|
|
6885
6973
|
}
|
|
6886
6974
|
}
|
|
6887
6975
|
}
|
|
6888
|
-
//------------------------------------------------------------------------//
|
|
6889
6976
|
// FULLSCREEN
|
|
6890
|
-
//------------------------------------------------------------------------//
|
|
6891
|
-
/**
|
|
6892
|
-
* Enters fullscreen mode for the streamer container.
|
|
6893
|
-
*/
|
|
6894
6977
|
enterFullScreen() {
|
|
6895
6978
|
if (this._initialized && this._stageController) this._stageController.enterFullScreen();
|
|
6896
6979
|
}
|
|
6897
|
-
/**
|
|
6898
|
-
* Exits fullscreen mode.
|
|
6899
|
-
*/
|
|
6900
6980
|
exitFullScreen() {
|
|
6901
6981
|
if (this._initialized && this._stageController) this._stageController.exitFullScreen();
|
|
6902
6982
|
}
|
|
6903
|
-
/**
|
|
6904
|
-
* Returns true if the streamer is currently in fullscreen mode.
|
|
6905
|
-
*
|
|
6906
|
-
* @returns Boolean indicating fullscreen status
|
|
6907
|
-
*/
|
|
6908
6983
|
isFullScreenMode() {
|
|
6909
6984
|
if (this._initialized && this._stageController) return this._stageController.isFullScreenMode();
|
|
6910
6985
|
return false;
|
|
6911
6986
|
}
|
|
6912
|
-
|
|
6913
|
-
// SIMPLE GETS & SETS
|
|
6914
|
-
//------------------------------------------------------------------------//
|
|
6915
|
-
/**
|
|
6916
|
-
* Returns the current stream key or null if none is set.
|
|
6917
|
-
*
|
|
6918
|
-
* @returns Current stream key or null
|
|
6919
|
-
*/
|
|
6987
|
+
// GETTERS
|
|
6920
6988
|
getStreamKey() {
|
|
6921
6989
|
var _a, _b, _c;
|
|
6922
6990
|
return (_c = (_b = (_a = this.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData()) === null || _b === void 0 ? void 0 : _b.streamKey) !== null && _c !== void 0 ? _c : null;
|
|
6923
6991
|
}
|
|
6924
|
-
/**
|
|
6925
|
-
* Returns the Stats Controller instance which contains statistical data about streaming performance.
|
|
6926
|
-
*
|
|
6927
|
-
* @returns Stats controller instance or null
|
|
6928
|
-
*/
|
|
6929
6992
|
getStatsController() {
|
|
6930
6993
|
return this._statsController;
|
|
6931
6994
|
}
|
|
6932
|
-
/**
|
|
6933
|
-
* Returns the unique ID of this streamer instance. Each subsequent instance has a higher number.
|
|
6934
|
-
*
|
|
6935
|
-
* @returns Streamer instance ID
|
|
6936
|
-
*/
|
|
6937
6995
|
getStreamerID() {
|
|
6938
6996
|
return this._streamerID;
|
|
6939
6997
|
}
|
|
6940
|
-
/**
|
|
6941
|
-
* Returns the logger instance used by this streamer.
|
|
6942
|
-
*
|
|
6943
|
-
* @returns Logger instance
|
|
6944
|
-
*/
|
|
6945
6998
|
getLogger() {
|
|
6946
6999
|
return this._logger;
|
|
6947
7000
|
}
|
|
6948
|
-
/**
|
|
6949
|
-
* Returns the configuration manager for this streamer.
|
|
6950
|
-
*
|
|
6951
|
-
* @returns Config manager instance or null
|
|
6952
|
-
*/
|
|
6953
7001
|
getConfigManager() {
|
|
6954
7002
|
return this._configManager;
|
|
6955
7003
|
}
|
|
6956
|
-
/**
|
|
6957
|
-
* Returns the network controller which manages all server communication.
|
|
6958
|
-
*
|
|
6959
|
-
* @returns Network controller instance or null
|
|
6960
|
-
*/
|
|
6961
7004
|
getNetworkController() {
|
|
6962
7005
|
return this._networkController;
|
|
6963
7006
|
}
|
|
6964
|
-
/**
|
|
6965
|
-
* Returns the streamer controller which manages media stream operations.
|
|
6966
|
-
*
|
|
6967
|
-
* @returns Streamer controller instance or null
|
|
6968
|
-
*/
|
|
6969
7007
|
getStreamerController() {
|
|
6970
7008
|
return this._streamerController;
|
|
6971
7009
|
}
|
|
6972
|
-
/**
|
|
6973
|
-
* Returns the stage controller which manages visual presentation.
|
|
6974
|
-
*
|
|
6975
|
-
* @returns Stage controller instance or null
|
|
6976
|
-
*/
|
|
6977
7010
|
getStageController() {
|
|
6978
7011
|
return this._stageController;
|
|
6979
7012
|
}
|
|
6980
|
-
/**
|
|
6981
|
-
* Returns the storage manager which handles persistent data storage.
|
|
6982
|
-
*
|
|
6983
|
-
* @returns Storage manager instance or null
|
|
6984
|
-
*/
|
|
6985
7013
|
getStorageManager() {
|
|
6986
7014
|
return this._storageManager;
|
|
6987
7015
|
}
|
|
6988
|
-
/**
|
|
6989
|
-
* Returns the HTML video element used by this streamer instance.
|
|
6990
|
-
*
|
|
6991
|
-
* @returns Video element or null
|
|
6992
|
-
*/
|
|
6993
7016
|
getVideoElement() {
|
|
6994
7017
|
var _a, _b, _c;
|
|
6995
7018
|
return (_c = (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement()) !== null && _c !== void 0 ? _c : null;
|
|
6996
7019
|
}
|
|
6997
|
-
/**
|
|
6998
|
-
* Returns true if this streamer instance has already been initialized.
|
|
6999
|
-
*
|
|
7000
|
-
* @returns Boolean indicating initialization status
|
|
7001
|
-
*/
|
|
7002
7020
|
isInitialized() {
|
|
7003
7021
|
return this._initialized;
|
|
7004
7022
|
}
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
* @returns Streamer version string
|
|
7009
|
-
*/
|
|
7023
|
+
isDestroyed() {
|
|
7024
|
+
return this._isDestroyed;
|
|
7025
|
+
}
|
|
7010
7026
|
getVersion() {
|
|
7011
7027
|
return this.STREAMER_VERSION;
|
|
7012
7028
|
}
|
|
7013
|
-
/**
|
|
7014
|
-
* Returns the development branch of this streamer (e.g., main, experimental).
|
|
7015
|
-
*
|
|
7016
|
-
* @returns Branch name string
|
|
7017
|
-
*/
|
|
7018
7029
|
getBranch() {
|
|
7019
7030
|
return this.STREAMER_BRANCH;
|
|
7020
7031
|
}
|
|
7021
|
-
|
|
7022
|
-
// EVENT
|
|
7023
|
-
//------------------------------------------------------------------------//
|
|
7024
|
-
/**
|
|
7025
|
-
* Dispatches an event with the specified name and data.
|
|
7026
|
-
*
|
|
7027
|
-
* @param eventName - Name of the event to dispatch
|
|
7028
|
-
* @param event - Object containing event data
|
|
7029
|
-
*/
|
|
7032
|
+
// EVENTS
|
|
7030
7033
|
dispatchEvent(eventName, event) {
|
|
7031
7034
|
super.dispatchEvent(eventName, event);
|
|
7032
7035
|
}
|
|
7033
|
-
|
|
7034
|
-
// CLEAN UP
|
|
7035
|
-
//------------------------------------------------------------------------//
|
|
7036
|
-
/**
|
|
7037
|
-
* Starts the streaming process.
|
|
7038
|
-
*
|
|
7039
|
-
* @returns Promise that resolves when streaming has started
|
|
7040
|
-
*/
|
|
7036
|
+
// START / STOP
|
|
7041
7037
|
start() {
|
|
7042
7038
|
var _a;
|
|
7043
7039
|
return __awaiter(this, void 0, void 0, function* () {
|
|
7044
7040
|
return (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.start();
|
|
7045
7041
|
});
|
|
7046
7042
|
}
|
|
7047
|
-
/**
|
|
7048
|
-
* Stops the streaming process.
|
|
7049
|
-
*/
|
|
7050
7043
|
stop() {
|
|
7051
7044
|
var _a;
|
|
7052
|
-
|
|
7045
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.stop();
|
|
7046
|
+
}
|
|
7047
|
+
// DEBUG
|
|
7048
|
+
debugMediaState() {
|
|
7049
|
+
var _a;
|
|
7050
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.debugMediaState();
|
|
7053
7051
|
}
|
|
7054
|
-
//------------------------------------------------------------------------//
|
|
7055
|
-
// CLEAN UP
|
|
7056
|
-
//------------------------------------------------------------------------//
|
|
7057
7052
|
/**
|
|
7058
|
-
* Destroys this instance of StormStreamer, releasing all resources
|
|
7059
|
-
*
|
|
7053
|
+
* Destroys this instance of StormStreamer, releasing all resources.
|
|
7054
|
+
* This method is SYNCHRONOUS - it sets flags immediately to prevent race conditions.
|
|
7055
|
+
* All pending async operations will detect the destroyed state and abort.
|
|
7060
7056
|
*/
|
|
7061
7057
|
destroy() {
|
|
7062
|
-
var _a, _b, _c, _d;
|
|
7063
|
-
|
|
7064
|
-
if (this.
|
|
7065
|
-
|
|
7058
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
7059
|
+
// Prevent double destroy
|
|
7060
|
+
if (this._isDestroyed) {
|
|
7061
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.warning(this, "🔴 [DESTROY] Already destroyed, skipping");
|
|
7062
|
+
return;
|
|
7063
|
+
}
|
|
7064
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.warning(this, "🔴 [DESTROY] Starting streamer instance destruction (sync)...");
|
|
7065
|
+
// CRITICAL: Set flag IMMEDIATELY
|
|
7066
|
+
this._isDestroyed = true;
|
|
7066
7067
|
this._initialized = false;
|
|
7067
7068
|
this._isRemoved = true;
|
|
7068
|
-
//
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
//
|
|
7069
|
+
// Remove from global array
|
|
7070
|
+
if (this.DEV_MODE && 'StormStreamerArray' in window) {
|
|
7071
|
+
window.StormStreamerArray[this._streamerID] = null;
|
|
7072
|
+
}
|
|
7073
|
+
// Stop all graphs
|
|
7074
|
+
this.stopAllGraphs();
|
|
7075
|
+
this._graphs = [];
|
|
7076
|
+
// Destroy network controller
|
|
7077
|
+
if (this._networkController) {
|
|
7078
|
+
(_c = this._logger) === null || _c === void 0 ? void 0 : _c.info(this, "🔴 [DESTROY] Destroying NetworkController...");
|
|
7079
|
+
try {
|
|
7080
|
+
(_d = this._networkController.getConnection()) === null || _d === void 0 ? void 0 : _d.destroy();
|
|
7081
|
+
} catch (e) {/* ignore */}
|
|
7082
|
+
this._networkController = null;
|
|
7083
|
+
}
|
|
7084
|
+
// Destroy streamer controller (this is also sync now)
|
|
7085
|
+
if (this._streamerController) {
|
|
7086
|
+
(_e = this._logger) === null || _e === void 0 ? void 0 : _e.info(this, "🔴 [DESTROY] Destroying StreamerController...");
|
|
7087
|
+
this._streamerController.destroy();
|
|
7088
|
+
this._streamerController = null;
|
|
7089
|
+
}
|
|
7090
|
+
// Destroy stage controller
|
|
7091
|
+
if (this._stageController) {
|
|
7092
|
+
(_f = this._logger) === null || _f === void 0 ? void 0 : _f.info(this, "🔴 [DESTROY] Destroying StageController...");
|
|
7093
|
+
try {
|
|
7094
|
+
this._stageController.destroy();
|
|
7095
|
+
} catch (e) {/* ignore */}
|
|
7096
|
+
this._stageController = null;
|
|
7097
|
+
}
|
|
7098
|
+
// Clear other references
|
|
7099
|
+
this._storageManager = null;
|
|
7100
|
+
this._statsController = null;
|
|
7101
|
+
// Remove all event listeners
|
|
7073
7102
|
this.removeAllEventListeners();
|
|
7103
|
+
(_g = this._logger) === null || _g === void 0 ? void 0 : _g.success(this, "🔴 [DESTROY] Streamer instance destroyed successfully (sync)");
|
|
7074
7104
|
}
|
|
7075
7105
|
}
|
|
7076
|
-
/**
|
|
7077
|
-
* Next ID for the streamer instance. Each subsequent instance has a higher number.
|
|
7078
|
-
* @private
|
|
7079
|
-
*/
|
|
7080
7106
|
StormStreamer.NEXT_STREAMER_ID = 0;
|
|
7081
7107
|
|
|
7082
7108
|
function create(config) {
|