@stormstreaming/stormstreamer 0.9.3-beta.0 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/amd/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /*
2
2
  * StormStreaming JavaScript Streamer
3
- * Copyright © 2021-2024 Web-Anatomy s.c. All rights reserved.
3
+ * Copyright © 2021-2025 Web-Anatomy s.c. All rights reserved.
4
4
  * contact@stormstreaming.com
5
5
  * https://stormstreaming.com
6
6
  *
7
- * Version: 0.9.3-beta.0
8
- * Version: 3/8/2025, 11:51:36 PM
7
+ * Version: 1.0.0-rc.1
8
+ * Version: 11/17/2025, 10:30:25 AM
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
- // @ts-ignore
2559
- this._videoElement.onvolumechange = () => {
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
- this._videoElement.addEventListener('loadedmetadata', () => {
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
- document.addEventListener('fullscreenchange', this.onFullScreenChange, false);
2799
- document.addEventListener('webkitfullscreenchange', this.onFullScreenChange, false);
2800
- document.addEventListener('mozfullscreenchange', this.onFullScreenChange, false);
2801
- document.addEventListener('webkitendfullscreen', this.onFullScreenChange, false);
2802
- this._screenElement.getVideoElement().addEventListener('webkitendfullscreen', this.onFullScreenChange, false);
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
- // Używamy requestAnimationFrame aby dać przeglądarce czas na zastosowanie overflow
2887
- requestAnimationFrame(() => {
2888
- // Obliczamy nowe wymiary
2968
+ this._animationFrameId = requestAnimationFrame(() => {
2969
+ if (this._isDestroying) return;
2889
2970
  this.calculateNewDimensions();
2890
- this._parentElement.style.overflow = this._parentOriginalOverflow;
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
- this.detachFromParent();
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,10 +3672,13 @@
3512
3672
  this._lastEventTime = 0;
3513
3673
  this.THROTTLE_INTERVAL = 100; // ms between updates
3514
3674
  this._isMonitoring = false;
3675
+ // POPRAWKA: Dodane pole do przechowywania ID animacji
3676
+ this._animationFrameId = null;
3515
3677
  // Moving average variables for smoothing
3516
3678
  this._instant = 0.0;
3517
3679
  this._slow = 0.0;
3518
3680
  this._main = main;
3681
+ console.log('SoundMeter: Created new instance');
3519
3682
  }
3520
3683
  attach(stream) {
3521
3684
  if (!stream.getAudioTracks().length) {
@@ -3542,9 +3705,17 @@
3542
3705
  }
3543
3706
 
3544
3707
  detach() {
3545
- var _a, _b;
3708
+ // POPRAWKA: Zabezpieczenie przed wielokrotnym wywołaniem
3709
+ if (!this._audioContext && !this._analyser && !this._microphone) {
3710
+ return; // Już wyczyszczone
3711
+ }
3546
3712
  // Stop monitoring first
3547
3713
  this._isMonitoring = false;
3714
+ // POPRAWKA: Anulowanie requestAnimationFrame
3715
+ if (this._animationFrameId !== null) {
3716
+ cancelAnimationFrame(this._animationFrameId);
3717
+ this._animationFrameId = null;
3718
+ }
3548
3719
  // Disconnect and cleanup nodes in reverse order
3549
3720
  if (this._microphone) {
3550
3721
  try {
@@ -3562,14 +3733,26 @@
3562
3733
  }
3563
3734
  this._analyser = null;
3564
3735
  }
3565
- if (((_a = this._audioContext) === null || _a === void 0 ? void 0 : _a.state) !== 'closed') {
3736
+ if (this._audioContext) {
3566
3737
  try {
3567
- (_b = this._audioContext) === null || _b === void 0 ? void 0 : _b.close();
3738
+ // POPRAWKA: Sprawdzenie przed użyciem w Promise
3739
+ if (this._audioContext.state !== 'closed') {
3740
+ // Zapisz referencję przed asynchroniczną operacją
3741
+ const audioContextRef = this._audioContext;
3742
+ // Najpierw ustaw na null, żeby uniknąć podwójnego wywołania
3743
+ this._audioContext = null;
3744
+ // Teraz bezpiecznie zamknij
3745
+ audioContextRef.suspend().then(() => audioContextRef.close()).catch(e => {
3746
+ console.warn('SoundMeter: Error closing audio context:', e);
3747
+ });
3748
+ } else {
3749
+ this._audioContext = null;
3750
+ }
3568
3751
  } catch (e) {
3569
- console.warn('SoundMeter: Error closing audio context:', e);
3752
+ console.warn('SoundMeter: Error handling audio context:', e);
3753
+ this._audioContext = null;
3570
3754
  }
3571
3755
  }
3572
- this._audioContext = null;
3573
3756
  this.clear();
3574
3757
  }
3575
3758
  clear() {
@@ -3582,7 +3765,11 @@
3582
3765
  this._isMonitoring = true;
3583
3766
  const dataArray = new Float32Array(this._analyser.frequencyBinCount);
3584
3767
  const analyze = () => {
3585
- if (!this._analyser || !this._isMonitoring) return;
3768
+ // POPRAWKA: Dodatkowe sprawdzenie przed kontynuacją
3769
+ if (!this._analyser || !this._isMonitoring || !this._audioContext) {
3770
+ this._animationFrameId = null;
3771
+ return;
3772
+ }
3586
3773
  const now = Date.now();
3587
3774
  try {
3588
3775
  // Read time-domain data
@@ -3612,13 +3799,21 @@
3612
3799
  } catch (error) {
3613
3800
  console.error('SoundMeter: Error during analysis:', error);
3614
3801
  this._isMonitoring = false;
3802
+ this._animationFrameId = null;
3615
3803
  return;
3616
3804
  }
3617
- // Schedule next analysis
3618
- requestAnimationFrame(analyze);
3805
+ // Schedule next analysis only if still monitoring
3806
+ if (this._isMonitoring) {
3807
+ this._animationFrameId = requestAnimationFrame(analyze);
3808
+ }
3619
3809
  };
3620
3810
  // Start analysis loop
3621
- requestAnimationFrame(analyze);
3811
+ this._animationFrameId = requestAnimationFrame(analyze);
3812
+ }
3813
+ // POPRAWKA: Dodana metoda destroy
3814
+ destroy() {
3815
+ console.log('SoundMeter: Destroying instance');
3816
+ this.detach();
3622
3817
  }
3623
3818
  }
3624
3819
 
@@ -4018,7 +4213,6 @@
4018
4213
  streamStatusInfo.videoWidth = msgJSON.videoWidth;
4019
4214
  streamStatusInfo.videoHeight = msgJSON.videoHeight;
4020
4215
  streamStatusInfo.currentBitrate = msgJSON.realBitrate;
4021
- console.log(streamStatusInfo);
4022
4216
  this._main.dispatchEvent("streamStatusUpdate", {
4023
4217
  ref: this._main,
4024
4218
  streamStatus: streamStatusInfo
@@ -4224,6 +4418,16 @@
4224
4418
  this._currentOrientation = ((_a = window.screen.orientation) === null || _a === void 0 ? void 0 : _a.type) || '';
4225
4419
  this._statusTimer = null;
4226
4420
  this._debug = false;
4421
+ // NOWE POLA DLA ANULOWANIA OPERACJI
4422
+ this._deviceChangeHandler = null;
4423
+ this._orientationChangeHandler = null;
4424
+ this._isDestroying = false;
4425
+ this._cameraAbortController = null;
4426
+ this._microphoneAbortController = null;
4427
+ this._startCameraAbortController = null;
4428
+ this._switchingCamera = false;
4429
+ this._switchingMicrophone = false;
4430
+ this._firstPublish = true;
4227
4431
  /**
4228
4432
  * Handles device state changes and initiates publishing if appropriate
4229
4433
  * @private
@@ -4240,6 +4444,7 @@
4240
4444
  */
4241
4445
  this.handleOrientationChange = () => __awaiter(this, void 0, void 0, function* () {
4242
4446
  var _d, _e;
4447
+ if (this._isDestroying) return;
4243
4448
  // Dajemy chwilę na ustabilizowanie się orientacji
4244
4449
  yield new Promise(resolve => setTimeout(resolve, 500));
4245
4450
  const newOrientation = ((_d = window.screen.orientation) === null || _d === void 0 ? void 0 : _d.type) || '';
@@ -4261,7 +4466,7 @@
4261
4466
  try {
4262
4467
  yield this.startCamera();
4263
4468
  // Jeśli stream był opublikowany, publikujemy ponownie
4264
- if (streamKey) {
4469
+ if (streamKey && !this._isDestroying) {
4265
4470
  this.publish(streamKey);
4266
4471
  }
4267
4472
  } catch (error) {
@@ -4270,7 +4475,9 @@
4270
4475
  }
4271
4476
  }
4272
4477
  });
4273
- this.onServerDisconnect = () => {};
4478
+ this.onServerDisconnect = () => {
4479
+ // Implementation
4480
+ };
4274
4481
  /**
4275
4482
  * Method for handling a situation when a given streamKey is already in use.
4276
4483
  */
@@ -4463,6 +4670,7 @@
4463
4670
  this.initializeStream();
4464
4671
  this.setupOrientationListener();
4465
4672
  this.setupPermissionListeners();
4673
+ this.setupDeviceChangeListener();
4466
4674
  if ((_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getSettingsData().autoConnect) {
4467
4675
  this._logger.info(this, "Initializing NetworkController (autoConnect is true)");
4468
4676
  (_b = this._main.getNetworkController()) === null || _b === void 0 ? void 0 : _b.initialize();
@@ -4508,6 +4716,7 @@
4508
4716
  // Initialize device lists
4509
4717
  yield this.grabDevices();
4510
4718
  } catch (error) {
4719
+ console.log(error);
4511
4720
  this._logger.error(this, "Error initializing devices: " + JSON.stringify(error));
4512
4721
  yield this.grabDevices();
4513
4722
  }
@@ -4542,6 +4751,46 @@
4542
4751
  };
4543
4752
  });
4544
4753
  }
4754
+ /**
4755
+ * Sets up event listener for device changes and handles them appropriately
4756
+ * @private
4757
+ */
4758
+ setupDeviceChangeListener() {
4759
+ this._deviceChangeHandler = () => __awaiter(this, void 0, void 0, function* () {
4760
+ var _a;
4761
+ if (this._isDestroying) return;
4762
+ if (this._publishState === exports.PublishState.PUBLISHED) {
4763
+ this._logger.info(this, "Device change detected, but already publish - no restarting streamer");
4764
+ return;
4765
+ }
4766
+ this._logger.info(this, "Device change detected, restarting streamer");
4767
+ const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
4768
+ const wasPublishing = this._publishState === exports.PublishState.CONNECTED;
4769
+ try {
4770
+ this.stop();
4771
+ yield new Promise(resolve => setTimeout(resolve, 500));
4772
+ if (!this._isDestroying) {
4773
+ yield this.start();
4774
+ if (wasPublishing && streamKey && !this._isDestroying) {
4775
+ this._logger.info(this, "Resuming publishing after device change");
4776
+ if (this.isStreamReady(true, true)) {
4777
+ this.publish(streamKey);
4778
+ } else {
4779
+ this._logger.warning(this, "Cannot resume publishing - stream not ready after device change");
4780
+ this._main.dispatchEvent("inputDeviceError", {
4781
+ ref: this._main
4782
+ });
4783
+ }
4784
+ }
4785
+ }
4786
+ this._logger.success(this, "Successfully handled device change");
4787
+ } catch (error) {
4788
+ this._logger.error(this, "Error handling device change: " + JSON.stringify(error));
4789
+ this.setInputDeviceState(exports.InputDevicesState.INVALID);
4790
+ }
4791
+ });
4792
+ navigator.mediaDevices.addEventListener('devicechange', this._deviceChangeHandler);
4793
+ }
4545
4794
  handlePermissionChange(device, state) {
4546
4795
  return __awaiter(this, void 0, void 0, function* () {
4547
4796
  if (state === 'denied') {
@@ -4617,12 +4866,14 @@
4617
4866
  this.grabDevices();
4618
4867
  }
4619
4868
  const videoElement = this._main.getStageController().getScreenElement().getVideoElement();
4620
- videoElement.srcObject = stream;
4621
- videoElement.autoplay = true;
4622
- videoElement.playsInline = true;
4623
- videoElement.disableRemotePlayback = true;
4624
- videoElement.controls = false;
4625
- videoElement.muted = true;
4869
+ if (videoElement) {
4870
+ videoElement.srcObject = stream;
4871
+ videoElement.autoplay = true;
4872
+ videoElement.playsInline = true;
4873
+ videoElement.disableRemotePlayback = true;
4874
+ videoElement.controls = false;
4875
+ videoElement.muted = true;
4876
+ }
4626
4877
  this.setPublishState(exports.PublishState.INITIALIZED);
4627
4878
  }
4628
4879
  /**
@@ -4649,25 +4900,24 @@
4649
4900
  * @returns {boolean} - true jeśli udało się rozpocząć publikowanie
4650
4901
  */
4651
4902
  publish(streamKey) {
4903
+ if (this._debug) this._logger.decoratedLog("Publishing: " + streamKey, "dark-red");
4904
+ this._logger.info(this, "Publish: " + streamKey);
4652
4905
  if (this._statusTimer != null) clearInterval(this._statusTimer);
4653
- if (this._main.getConfigManager().getStreamData().streamKey == streamKey && this._publishState == exports.PublishState.CONNECTED) {
4654
- this._logger.warning(this, "Already published!");
4655
- return false;
4906
+ if (this._main.getConfigManager().getStreamData().streamKey != null && !this._firstPublish) {
4907
+ this.unpublish();
4656
4908
  }
4657
- if (this._main.getConfigManager().getStreamData().streamKey != null) this.unpublish();
4658
4909
  this._main.getConfigManager().getStreamData().streamKey = streamKey;
4659
4910
  if (!this.isStreamReady(true, true)) {
4660
4911
  this._logger.warning(this, "Cannot publish - stream not ready (missing video or audio track)");
4661
4912
  return false;
4662
4913
  }
4663
- if (this._debug) this._logger.decoratedLog("Publishing: " + streamKey, "dark-red");
4664
- this._logger.info(this, "Publish: " + streamKey);
4665
4914
  this.closeWebRTCConnection();
4666
4915
  this._main.dispatchEvent("publish", {
4667
4916
  ref: this._main,
4668
4917
  streamKey: streamKey
4669
4918
  });
4670
4919
  this.initializeWebRTC();
4920
+ this._firstPublish = false;
4671
4921
  return true;
4672
4922
  }
4673
4923
  unpublish() {
@@ -4692,12 +4942,11 @@
4692
4942
  * @private
4693
4943
  */
4694
4944
  setupOrientationListener() {
4695
- // Sprawdzamy czy urządzenie wspiera event orientationchange
4945
+ this._orientationChangeHandler = this.handleOrientationChange;
4696
4946
  if (window.screen && window.screen.orientation) {
4697
- window.screen.orientation.addEventListener('change', this.handleOrientationChange);
4947
+ window.screen.orientation.addEventListener('change', this._orientationChangeHandler);
4698
4948
  } else {
4699
- // Fallback dla starszych urządzeń
4700
- window.addEventListener('orientationchange', this.handleOrientationChange);
4949
+ window.addEventListener('orientationchange', this._orientationChangeHandler);
4701
4950
  }
4702
4951
  }
4703
4952
  //------------------------------------------------------------------------//
@@ -4916,6 +5165,7 @@
4916
5165
  this._selectedMicrophone = this.pickMicrophone();
4917
5166
  }
4918
5167
  } catch (error) {
5168
+ console.log(error);
4919
5169
  this.setInputDeviceState(exports.InputDevicesState.INVALID);
4920
5170
  this._logger.error(this, "Errror on grab devices: " + JSON.stringify(error));
4921
5171
  }
@@ -4942,94 +5192,145 @@
4942
5192
  });
4943
5193
  }
4944
5194
  /**
4945
- * Selects camera based on camera device ID;
5195
+ * Selects camera based on camera device ID with abort support
4946
5196
  * @param cameraID
4947
5197
  */
4948
5198
  selectCamera(cameraID) {
4949
- var _a, _b;
4950
- for (let i = 0; i < this._cameraList.getSize(); i++) {
4951
- this._cameraList.get(i).isSelected = false;
4952
- }
4953
- this._selectedCamera = null;
4954
- this.setInputDeviceState(exports.InputDevicesState.UPDATING);
4955
- this.setCameraState(exports.DeviceState.NOT_INITIALIZED);
4956
- // Zapamiętaj aktualny stream key i stan publikacji
4957
- const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
4958
- const wasPublished = this._publishState === exports.PublishState.CONNECTED;
4959
- for (let i = 0; i < this._cameraList.getSize(); i++) {
4960
- if (this._cameraList.get(i).id == cameraID) {
4961
- this._selectedCamera = this._cameraList.get(i);
4962
- this._selectedCamera.isSelected = true;
4963
- (_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("cameraID", this._selectedCamera.id);
4964
- break;
5199
+ var _a, _b, _c;
5200
+ return __awaiter(this, void 0, void 0, function* () {
5201
+ // Anuluj poprzednie przełączanie kamery
5202
+ if (this._cameraAbortController) {
5203
+ this._cameraAbortController.abort();
4965
5204
  }
4966
- }
4967
- this._main.dispatchEvent("deviceListUpdate", {
4968
- ref: this._main,
4969
- cameraList: this._cameraList.getArray(),
4970
- microphoneList: this._microphoneList.getArray()
4971
- });
4972
- this.stopCameraStream();
4973
- if (this._selectedCamera != null) {
4974
- // Update constraints with new device
4975
- this._constraints.video.deviceId = this._selectedCamera.id;
4976
- // Restart camera stream
4977
- this.startCamera().then(() => {
4978
- // Jeśli stream był opublikowany, publikujemy ponownie
4979
- this.setCameraState(exports.DeviceState.ENABLED);
4980
- if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) this.setInputDeviceState(exports.InputDevicesState.READY);else this.setInputDeviceState(exports.InputDevicesState.INVALID);
4981
- if (wasPublished && streamKey) {
4982
- this.publish(streamKey);
5205
+ this._cameraAbortController = new AbortController();
5206
+ const signal = this._cameraAbortController.signal;
5207
+ try {
5208
+ this._switchingCamera = true;
5209
+ for (let i = 0; i < this._cameraList.getSize(); i++) {
5210
+ this._cameraList.get(i).isSelected = false;
4983
5211
  }
4984
- });
4985
- } else {
4986
- this.setInputDeviceState(exports.InputDevicesState.INVALID);
4987
- }
5212
+ this._selectedCamera = null;
5213
+ this.setInputDeviceState(exports.InputDevicesState.UPDATING);
5214
+ this.setCameraState(exports.DeviceState.NOT_INITIALIZED);
5215
+ // Zapamiętaj aktualny stream key i stan publikacji
5216
+ const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
5217
+ const wasPublished = this._publishState === exports.PublishState.CONNECTED;
5218
+ let found = false;
5219
+ for (let i = 0; i < this._cameraList.getSize(); i++) {
5220
+ if (this._cameraList.get(i).id == cameraID) {
5221
+ this._selectedCamera = this._cameraList.get(i);
5222
+ this._selectedCamera.isSelected = true;
5223
+ (_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("cameraID", this._selectedCamera.id);
5224
+ found = true;
5225
+ break;
5226
+ }
5227
+ }
5228
+ this._main.dispatchEvent("deviceListUpdate", {
5229
+ ref: this._main,
5230
+ cameraList: this._cameraList.getArray(),
5231
+ microphoneList: this._microphoneList.getArray()
5232
+ });
5233
+ if (signal.aborted) return;
5234
+ this.stopCameraStream();
5235
+ if (this._selectedCamera != null) {
5236
+ // Update constraints with new device
5237
+ this._constraints.video.deviceId = this._selectedCamera.id;
5238
+ // Sprawdź czy nie anulowano
5239
+ if (signal.aborted) return;
5240
+ // Poczekaj z możliwością anulowania
5241
+ yield new Promise((resolve, reject) => {
5242
+ const timeout = setTimeout(resolve, 500);
5243
+ signal.addEventListener('abort', () => {
5244
+ clearTimeout(timeout);
5245
+ reject(new Error('Aborted'));
5246
+ });
5247
+ });
5248
+ if (signal.aborted) return;
5249
+ // Restart camera stream
5250
+ yield this.startCamera();
5251
+ this.setCameraState(exports.DeviceState.ENABLED);
5252
+ if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) this.setInputDeviceState(exports.InputDevicesState.READY);else this.setInputDeviceState(exports.InputDevicesState.INVALID);
5253
+ if (wasPublished && streamKey && !signal.aborted) {
5254
+ this.publish(streamKey);
5255
+ }
5256
+ } else {
5257
+ this.setInputDeviceState(exports.InputDevicesState.INVALID);
5258
+ }
5259
+ } catch (error) {
5260
+ if (error.message !== 'Aborted') {
5261
+ this._logger.error(this, 'Error switching camera: ' + error);
5262
+ this.setInputDeviceState(exports.InputDevicesState.INVALID);
5263
+ }
5264
+ } finally {
5265
+ this._switchingCamera = false;
5266
+ if (((_c = this._cameraAbortController) === null || _c === void 0 ? void 0 : _c.signal) === signal) {
5267
+ this._cameraAbortController = null;
5268
+ }
5269
+ }
5270
+ });
4988
5271
  }
4989
5272
  /**
4990
- * Method tries to select (change) microphone based on its system ID
5273
+ * Method tries to select (change) microphone based on its system ID with abort support
4991
5274
  * @param micID
4992
5275
  */
4993
5276
  selectMicrophone(micID) {
4994
- var _a, _b;
5277
+ var _a, _b, _c;
4995
5278
  return __awaiter(this, void 0, void 0, function* () {
4996
- for (let i = 0; i < this._microphoneList.getSize(); i++) {
4997
- this._microphoneList.get(i).isSelected = false;
5279
+ // Anuluj poprzednie przełączanie mikrofonu
5280
+ if (this._microphoneAbortController) {
5281
+ this._microphoneAbortController.abort();
4998
5282
  }
4999
- this._selectedMicrophone = null;
5000
- this.setInputDeviceState(exports.InputDevicesState.UPDATING);
5001
- this.setMicrophoneState(exports.DeviceState.NOT_INITIALIZED);
5002
- this._logger.info(this, "Selecting microphone: " + micID);
5003
- // Zapamiętaj aktualny stream key i stan publikacji
5004
- const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
5005
- const wasPublished = this._publishState === exports.PublishState.CONNECTED;
5006
- // Znajdź i zapisz wybrany mikrofon
5007
- for (let i = 0; i < this._microphoneList.getSize(); i++) {
5008
- if (this._microphoneList.get(i).id == micID) {
5009
- this._selectedMicrophone = this._microphoneList.get(i);
5010
- this._selectedMicrophone.isSelected = true;
5011
- (_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("microphoneID", this._selectedMicrophone.id);
5012
- break;
5283
+ this._microphoneAbortController = new AbortController();
5284
+ const signal = this._microphoneAbortController.signal;
5285
+ try {
5286
+ this._switchingMicrophone = true;
5287
+ for (let i = 0; i < this._microphoneList.getSize(); i++) {
5288
+ this._microphoneList.get(i).isSelected = false;
5013
5289
  }
5014
- }
5015
- // Zawsze wysyłamy aktualizację list urządzeń
5016
- this._main.dispatchEvent("deviceListUpdate", {
5017
- ref: this._main,
5018
- cameraList: this._cameraList.getArray(),
5019
- microphoneList: this._microphoneList.getArray()
5020
- });
5021
- // Odłącz SoundMeter przed zmianą strumienia
5022
- this._soundMeter.detach();
5023
- // Zamknij istniejące połączenie WebRTC
5024
- this.closeWebRTCConnection();
5025
- // Zatrzymaj obecny strumień
5026
- if (this._stream) {
5027
- this._stream.getTracks().forEach(track => {
5028
- track.stop();
5290
+ this._selectedMicrophone = null;
5291
+ this.setInputDeviceState(exports.InputDevicesState.UPDATING);
5292
+ this.setMicrophoneState(exports.DeviceState.NOT_INITIALIZED);
5293
+ this._logger.info(this, "Selecting microphone: " + micID);
5294
+ // Zapamiętaj aktualny stream key i stan publikacji
5295
+ const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
5296
+ const wasPublished = this._publishState === exports.PublishState.CONNECTED;
5297
+ // Znajdź i zapisz wybrany mikrofon
5298
+ for (let i = 0; i < this._microphoneList.getSize(); i++) {
5299
+ if (this._microphoneList.get(i).id == micID) {
5300
+ this._selectedMicrophone = this._microphoneList.get(i);
5301
+ this._selectedMicrophone.isSelected = true;
5302
+ (_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("microphoneID", this._selectedMicrophone.id);
5303
+ break;
5304
+ }
5305
+ }
5306
+ // Zawsze wysyłamy aktualizację list urządzeń
5307
+ this._main.dispatchEvent("deviceListUpdate", {
5308
+ ref: this._main,
5309
+ cameraList: this._cameraList.getArray(),
5310
+ microphoneList: this._microphoneList.getArray()
5029
5311
  });
5030
- this._stream = null;
5031
- }
5032
- try {
5312
+ if (signal.aborted) return;
5313
+ // Odłącz SoundMeter przed zmianą strumienia
5314
+ this._soundMeter.detach();
5315
+ // Zamknij istniejące połączenie WebRTC
5316
+ this.closeWebRTCConnection();
5317
+ // Zatrzymaj obecny strumień
5318
+ if (this._stream) {
5319
+ this._stream.getTracks().forEach(track => {
5320
+ track.stop();
5321
+ });
5322
+ this._stream = null;
5323
+ }
5324
+ if (signal.aborted) return;
5325
+ // Poczekaj z możliwością anulowania
5326
+ yield new Promise((resolve, reject) => {
5327
+ const timeout = setTimeout(resolve, 500);
5328
+ signal.addEventListener('abort', () => {
5329
+ clearTimeout(timeout);
5330
+ reject(new Error('Aborted'));
5331
+ });
5332
+ });
5333
+ if (signal.aborted) return;
5033
5334
  // Rozpocznij wszystko od nowa
5034
5335
  yield this.startCamera();
5035
5336
  this.setMicrophoneState(exports.DeviceState.ENABLED);
@@ -5039,25 +5340,38 @@
5039
5340
  this.setInputDeviceState(exports.InputDevicesState.INVALID);
5040
5341
  }
5041
5342
  // Jeśli stream był opublikowany, publikujemy ponownie
5042
- if (wasPublished && streamKey) {
5343
+ if (wasPublished && streamKey && !signal.aborted) {
5043
5344
  this.publish(streamKey);
5044
5345
  }
5045
5346
  } catch (error) {
5046
- console.error("Error changing microphone:", error);
5047
- this._main.dispatchEvent("inputDeviceError", {
5048
- ref: this._main
5049
- });
5050
- this.setInputDeviceState(exports.InputDevicesState.INVALID);
5347
+ if (error.message !== 'Aborted') {
5348
+ console.error("Error changing microphone:", error);
5349
+ this._main.dispatchEvent("inputDeviceError", {
5350
+ ref: this._main
5351
+ });
5352
+ this.setInputDeviceState(exports.InputDevicesState.INVALID);
5353
+ }
5354
+ } finally {
5355
+ this._switchingMicrophone = false;
5356
+ if (((_c = this._microphoneAbortController) === null || _c === void 0 ? void 0 : _c.signal) === signal) {
5357
+ this._microphoneAbortController = null;
5358
+ }
5051
5359
  }
5052
5360
  });
5053
5361
  }
5054
5362
  /**
5055
- * This method tries to start a camera.
5056
- *
5363
+ * This method tries to start a camera with abort support
5057
5364
  * @private
5058
5365
  */
5059
5366
  startCamera() {
5367
+ var _a;
5060
5368
  return __awaiter(this, void 0, void 0, function* () {
5369
+ // Anuluj poprzednie uruchamianie kamery
5370
+ if (this._startCameraAbortController) {
5371
+ this._startCameraAbortController.abort();
5372
+ }
5373
+ this._startCameraAbortController = new AbortController();
5374
+ const signal = this._startCameraAbortController.signal;
5061
5375
  if (this._stream) {
5062
5376
  this._stream.getTracks().forEach(track => {
5063
5377
  track.stop();
@@ -5077,11 +5391,18 @@
5077
5391
  }
5078
5392
  } : false
5079
5393
  };
5394
+ if (signal.aborted) return;
5080
5395
  try {
5081
5396
  const stream = yield navigator.mediaDevices.getUserMedia(constraints);
5397
+ if (signal.aborted) {
5398
+ // Jeśli anulowano, zatrzymaj nowo utworzony strumień
5399
+ stream.getTracks().forEach(track => track.stop());
5400
+ return;
5401
+ }
5082
5402
  this._stream = stream;
5083
5403
  this.onCameraStreamSuccess(this._stream);
5084
5404
  } catch (error) {
5405
+ if (signal.aborted) return;
5085
5406
  if (constraints.video) {
5086
5407
  this.onUserMediaError({
5087
5408
  name: error.name || 'Error',
@@ -5101,6 +5422,10 @@
5101
5422
  } catch (error) {
5102
5423
  console.error("Error in startCamera:", error);
5103
5424
  yield this.grabDevices();
5425
+ } finally {
5426
+ if (((_a = this._startCameraAbortController) === null || _a === void 0 ? void 0 : _a.signal) === signal) {
5427
+ this._startCameraAbortController = null;
5428
+ }
5104
5429
  }
5105
5430
  });
5106
5431
  }
@@ -5132,7 +5457,6 @@
5132
5457
  }
5133
5458
  /**
5134
5459
  * This method selects a camera based on previous uses or saved IDs
5135
- *
5136
5460
  * @private
5137
5461
  */
5138
5462
  pickCamera() {
@@ -5202,7 +5526,6 @@
5202
5526
  }
5203
5527
  /**
5204
5528
  * This method selects a microphone based on previous uses or saved IDs
5205
- *
5206
5529
  * @private
5207
5530
  */
5208
5531
  pickMicrophone() {
@@ -5303,7 +5626,6 @@
5303
5626
  }
5304
5627
  /**
5305
5628
  * Applies the microphone state to the actual stream tracks
5306
- *
5307
5629
  * @param enabled true to enable tracks, false to disable
5308
5630
  * @private
5309
5631
  */
@@ -5322,7 +5644,6 @@
5322
5644
  }
5323
5645
  /**
5324
5646
  * This methods is a final check whenever we're ready to publish a stream
5325
- *
5326
5647
  * @param requireVideo - whenever video track is required
5327
5648
  * @param requireAudio - whenever audio track is required
5328
5649
  * @returns {boolean} true if stream is ready for publishing
@@ -5343,6 +5664,47 @@
5343
5664
  this._peerConnection = null;
5344
5665
  }
5345
5666
  }
5667
+ // Asynchroniczna wersja do użycia w destroy
5668
+ closeWebRTCConnectionAsync() {
5669
+ return __awaiter(this, void 0, void 0, function* () {
5670
+ if (this._peerConnection) {
5671
+ try {
5672
+ // Usuń event handlery
5673
+ this._peerConnection.onicecandidate = null;
5674
+ this._peerConnection.onconnectionstatechange = null;
5675
+ this._peerConnection.onnegotiationneeded = null;
5676
+ this._peerConnection.oniceconnectionstatechange = null;
5677
+ this._peerConnection.onicegatheringstatechange = null;
5678
+ this._peerConnection.onsignalingstatechange = null;
5679
+ this._peerConnection.ontrack = null;
5680
+ // Zatrzymaj wszystkie transceivery
5681
+ const transceivers = this._peerConnection.getTransceivers();
5682
+ for (const transceiver of transceivers) {
5683
+ if (transceiver.stop) {
5684
+ transceiver.stop();
5685
+ }
5686
+ }
5687
+ // Usuń wszystkie tracks
5688
+ const senders = this._peerConnection.getSenders();
5689
+ for (const sender of senders) {
5690
+ if (sender.track) {
5691
+ sender.track.enabled = false;
5692
+ sender.track.stop();
5693
+ }
5694
+ this._peerConnection.removeTrack(sender);
5695
+ }
5696
+ // Zamknij połączenie
5697
+ this._peerConnection.close();
5698
+ // Poczekaj na zamknięcie
5699
+ yield new Promise(resolve => setTimeout(resolve, 100));
5700
+ } catch (e) {
5701
+ this._logger.error(this, 'Error closing peer connection: ' + e);
5702
+ } finally {
5703
+ this._peerConnection = null;
5704
+ }
5705
+ }
5706
+ });
5707
+ }
5346
5708
  onDescriptionError(error) {
5347
5709
  this._logger.info(this, "WebRTCStreamer :: onDescriptionError: " + JSON.stringify(error));
5348
5710
  }
@@ -5455,110 +5817,101 @@
5455
5817
  }
5456
5818
  }
5457
5819
  /**
5458
- * Method stops streaming for all streams
5820
+ * Ulepszona metoda czyszczenia strumienia
5821
+ * @private
5822
+ */
5823
+ cleanupMediaStream(stream) {
5824
+ return __awaiter(this, void 0, void 0, function* () {
5825
+ if (!stream) return;
5826
+ const tracks = stream.getTracks();
5827
+ for (const track of tracks) {
5828
+ try {
5829
+ // Usuń wszystkie event listenery
5830
+ track.onended = null;
5831
+ track.onmute = null;
5832
+ track.onunmute = null;
5833
+ // Wyłącz track
5834
+ track.enabled = false;
5835
+ // Zatrzymaj track
5836
+ track.stop();
5837
+ this._logger.info(this, `Track ${track.kind} stopped. ReadyState: ${track.readyState}`);
5838
+ } catch (e) {
5839
+ this._logger.error(this, `Error stopping track: ${e}`);
5840
+ }
5841
+ }
5842
+ // Poczekaj na cleanup
5843
+ yield new Promise(resolve => setTimeout(resolve, 50));
5844
+ });
5845
+ }
5846
+ /**
5847
+ * Method stops streaming for all streams with proper cleanup
5459
5848
  * @private
5460
5849
  */
5461
5850
  forceStopAllStreams() {
5462
5851
  var _a, _b;
5463
- // 1. First, detach the sound meter
5464
- if (this._soundMeter) {
5465
- this._soundMeter.detach();
5466
- }
5467
- // 2. Stop the main stream if it exists
5468
- if (this._stream) {
5469
- try {
5470
- const tracks = this._stream.getTracks();
5471
- this._logger.info(this, `Stopping ${tracks.length} tracks from main stream`);
5472
- tracks.forEach(track => {
5473
- try {
5474
- track.enabled = false;
5475
- track.stop();
5476
- this._logger.info(this, `Stopped ${track.kind} track: ${track.id}`);
5477
- } catch (e) {
5478
- this._logger.error(this, `Error stopping ${track.kind} track: ${e}`);
5479
- }
5480
- });
5481
- this._stream = null;
5482
- } catch (e) {
5483
- this._logger.error(this, 'Error stopping main stream');
5484
- }
5485
- }
5486
- // 3. Clean up video element
5487
- try {
5488
- const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
5489
- if (videoElement && videoElement.srcObject instanceof MediaStream) {
5490
- const videoTracks = videoElement.srcObject.getTracks();
5491
- if (videoTracks.length > 0) {
5492
- this._logger.info(this, `Stopping ${videoTracks.length} tracks from video element`);
5493
- videoTracks.forEach(track => {
5494
- try {
5495
- track.enabled = false;
5496
- track.stop();
5497
- } catch (e) {
5498
- this._logger.error(this, `Error stopping video element track: ${e}`);
5499
- }
5500
- });
5852
+ return __awaiter(this, void 0, void 0, function* () {
5853
+ this._logger.info(this, "Force stopping all streams...");
5854
+ // 1. Odłączenie i zniszczenie SoundMeter
5855
+ if (this._soundMeter) {
5856
+ try {
5857
+ this._soundMeter.destroy();
5858
+ } catch (e) {
5859
+ this._logger.error(this, 'Error destroying SoundMeter: ' + e);
5501
5860
  }
5502
- videoElement.srcObject = null;
5503
- videoElement.removeAttribute('src');
5504
- videoElement.load();
5505
5861
  }
5506
- } catch (e) {
5507
- this._logger.error(this, 'Error cleaning video element');
5508
- }
5509
- // 4. Handle RTCPeerConnection last, with proper state checking
5510
- if (this._peerConnection) {
5862
+ // 2. Zatrzymanie głównego strumienia
5863
+ if (this._stream) {
5864
+ yield this.cleanupMediaStream(this._stream);
5865
+ this._stream = null;
5866
+ }
5867
+ // 3. Czyszczenie elementu video
5511
5868
  try {
5512
- // Only try to remove tracks if the connection isn't already closed
5513
- if (this._peerConnection.signalingState !== 'closed') {
5514
- const senders = this._peerConnection.getSenders();
5515
- this._logger.info(this, `Cleaning up ${senders.length} senders from peer connection`);
5516
- senders.forEach(sender => {
5517
- try {
5518
- if (sender.track) {
5519
- sender.track.enabled = false;
5520
- sender.track.stop();
5521
- // Only try to remove the track if connection is still open
5522
- if (this._peerConnection && this._peerConnection.signalingState !== 'closed') {
5523
- this._peerConnection.removeTrack(sender);
5524
- }
5525
- }
5526
- } catch (e) {
5527
- this._logger.error(this, `Error stopping sender track: ${e}`);
5528
- }
5529
- });
5869
+ const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
5870
+ if (videoElement) {
5871
+ // Zatrzymaj strumień w elemencie video
5872
+ if (videoElement.srcObject instanceof MediaStream) {
5873
+ yield this.cleanupMediaStream(videoElement.srcObject);
5874
+ }
5875
+ // Wyczyść element video
5876
+ videoElement.pause();
5877
+ videoElement.removeAttribute('src');
5878
+ videoElement.srcObject = null;
5879
+ videoElement.load();
5880
+ // Usuń event listenery
5881
+ videoElement.onloadedmetadata = null;
5882
+ videoElement.onloadeddata = null;
5883
+ videoElement.oncanplay = null;
5884
+ videoElement.onerror = null;
5530
5885
  }
5531
- // Now close the peer connection
5532
- this._peerConnection.close();
5533
- this._peerConnection = null;
5534
5886
  } catch (e) {
5535
- this._logger.error(this, 'Error closing peer connection');
5887
+ this._logger.error(this, 'Error cleaning video element: ' + e);
5536
5888
  }
5537
- }
5538
- // 5. Ensure we properly null out our device references
5539
- this._selectedCamera = null;
5540
- this._selectedMicrophone = null;
5541
- // 6. Make a final check for any active tracks at the global level
5542
- try {
5543
- const allTracks = [];
5544
- // Try to find any tracks that might still be active by querying devices
5545
- navigator.mediaDevices.getUserMedia({
5546
- audio: false,
5547
- video: false
5548
- }).then(() => {
5549
- // This is just to trigger a device check
5550
- this._logger.info(this, "Performed final device check");
5551
- }).catch(() => {
5552
- // Ignore errors from this check
5553
- });
5554
- } catch (e) {
5555
- // Ignore errors from final check
5556
- }
5889
+ // 4. Zamknięcie WebRTC
5890
+ yield this.closeWebRTCConnectionAsync();
5891
+ // 5. Resetuj zmienne
5892
+ this._selectedCamera = null;
5893
+ this._selectedMicrophone = null;
5894
+ this._pendingMicrophoneState = null;
5895
+ this._logger.info(this, "Force stop all streams completed");
5896
+ });
5557
5897
  }
5558
5898
  /**
5559
5899
  * Stops all streaming operations and cleans up resources
5560
5900
  */
5561
5901
  stop() {
5902
+ // Anuluj wszystkie aktywne operacje
5903
+ if (this._cameraAbortController) {
5904
+ this._cameraAbortController.abort();
5905
+ this._cameraAbortController = null;
5906
+ }
5907
+ if (this._microphoneAbortController) {
5908
+ this._microphoneAbortController.abort();
5909
+ this._microphoneAbortController = null;
5910
+ }
5911
+ if (this._startCameraAbortController) {
5912
+ this._startCameraAbortController.abort();
5913
+ this._startCameraAbortController = null;
5914
+ }
5562
5915
  // Stop status connection and clear timer
5563
5916
  if (this._statusConnection) {
5564
5917
  this._statusConnection.destroy();
@@ -5617,60 +5970,96 @@
5617
5970
  });
5618
5971
  }
5619
5972
  /**
5620
- * Method used for destroying everything (one-time use)
5973
+ * Method used for destroying everything (one-time use) with proper cleanup
5621
5974
  */
5622
5975
  destroy() {
5623
- // Stop any ongoing timers and intervals first
5624
- if (this._statusTimer != null) {
5625
- clearInterval(this._statusTimer);
5626
- this._statusTimer = null;
5627
- }
5628
- if (this._restartTimer != null) {
5629
- clearInterval(this._restartTimer);
5630
- this._restartTimer = null;
5631
- }
5632
- clearTimeout(this._publishTimer);
5633
- // Remove orientation change listeners
5634
- if (window.screen && window.screen.orientation) {
5635
- window.screen.orientation.removeEventListener('change', this.handleOrientationChange);
5636
- } else {
5637
- window.removeEventListener('orientationchange', this.handleOrientationChange);
5638
- }
5639
- // Remove other event listeners
5640
- try {
5641
- this._main.removeEventListener("serverConnect", this.onServerConnect);
5642
- this._main.removeEventListener("serverDisconnect", this.onServerDisconnect);
5643
- this._main.removeEventListener("streamKeyInUse", this.onStreamKeyTaken);
5644
- this._main.removeEventListener("statusServerConnect", this.onStatusServerConnect);
5645
- this._main.removeEventListener("statusServerDisconnect", this.onStatusServerDisconnect);
5646
- this._main.removeEventListener("streamStatusUpdate", this.onStreamStatsUpdate);
5647
- this._main.removeEventListener("deviceStateChange", this.onDeviceStateChange);
5648
- document.removeEventListener("visibilitychange", this.visibilityChange);
5649
- window.removeEventListener("blur", this.onWindowBlur);
5650
- window.removeEventListener("focus", this.onWindowFocus);
5651
- } catch (e) {
5652
- this._logger.error(this, 'Error removing event listeners');
5653
- }
5654
- // Make sure we destroy the status connection
5655
- if (this._statusConnection) {
5656
- this._statusConnection.destroy();
5657
- this._statusConnection = null;
5658
- }
5659
- // Stop all media streams and clean up the WebRTC connection
5660
- this.forceStopAllStreams();
5661
- // Reset all state variables
5662
- this._pendingMicrophoneState = null;
5663
- this._cameraList = new InputDeviceList();
5664
- this._microphoneList = new InputDeviceList();
5665
- this._permissionChecked = false;
5666
- this._isWindowActive = false;
5667
- this._isMicrophoneMuted = false;
5668
- this._publishState = exports.PublishState.NOT_INITIALIZED;
5669
- this._inputDeviceState = exports.InputDevicesState.NOT_INITIALIZED;
5670
- this._cameraState = exports.DeviceState.NOT_INITIALIZED;
5671
- this._microphoneState = exports.DeviceState.NOT_INITIALIZED;
5672
- // Log completion
5673
- this._logger.success(this, "StreamerController successfully destroyed and all resources released");
5976
+ return __awaiter(this, void 0, void 0, function* () {
5977
+ this._logger.info(this, "Starting StreamerController destroy...");
5978
+ this._isDestroying = true;
5979
+ try {
5980
+ // 1. Anuluj wszystkie aktywne operacje
5981
+ if (this._cameraAbortController) {
5982
+ this._cameraAbortController.abort();
5983
+ this._cameraAbortController = null;
5984
+ }
5985
+ if (this._microphoneAbortController) {
5986
+ this._microphoneAbortController.abort();
5987
+ this._microphoneAbortController = null;
5988
+ }
5989
+ if (this._startCameraAbortController) {
5990
+ this._startCameraAbortController.abort();
5991
+ this._startCameraAbortController = null;
5992
+ }
5993
+ // 2. Zatrzymaj timery
5994
+ if (this._statusTimer != null) {
5995
+ clearInterval(this._statusTimer);
5996
+ this._statusTimer = null;
5997
+ }
5998
+ if (this._restartTimer != null) {
5999
+ clearInterval(this._restartTimer);
6000
+ this._restartTimer = null;
6001
+ }
6002
+ clearTimeout(this._publishTimer);
6003
+ // 3. Usuń event listenery urządzeń
6004
+ if (this._deviceChangeHandler) {
6005
+ navigator.mediaDevices.removeEventListener('devicechange', this._deviceChangeHandler);
6006
+ this._deviceChangeHandler = null;
6007
+ }
6008
+ // 4. Usuń event listenery orientacji
6009
+ if (this._orientationChangeHandler) {
6010
+ if (window.screen && window.screen.orientation) {
6011
+ window.screen.orientation.removeEventListener('change', this._orientationChangeHandler);
6012
+ } else {
6013
+ window.removeEventListener('orientationchange', this._orientationChangeHandler);
6014
+ }
6015
+ this._orientationChangeHandler = null;
6016
+ }
6017
+ // 5. Usuń pozostałe event listenery
6018
+ try {
6019
+ this._main.removeEventListener("serverConnect", this.onServerConnect);
6020
+ this._main.removeEventListener("serverDisconnect", this.onServerDisconnect);
6021
+ this._main.removeEventListener("streamKeyInUse", this.onStreamKeyTaken);
6022
+ this._main.removeEventListener("statusServerConnect", this.onStatusServerConnect);
6023
+ this._main.removeEventListener("statusServerDisconnect", this.onStatusServerDisconnect);
6024
+ this._main.removeEventListener("streamStatusUpdate", this.onStreamStatsUpdate);
6025
+ this._main.removeEventListener("deviceStateChange", this.onDeviceStateChange);
6026
+ document.removeEventListener("visibilitychange", this.visibilityChange);
6027
+ window.removeEventListener("blur", this.onWindowBlur);
6028
+ window.removeEventListener("focus", this.onWindowFocus);
6029
+ } catch (e) {
6030
+ this._logger.error(this, 'Error removing event listeners: ' + e);
6031
+ }
6032
+ // 6. Zniszcz status connection
6033
+ if (this._statusConnection) {
6034
+ this._statusConnection.destroy();
6035
+ this._statusConnection = null;
6036
+ }
6037
+ // 7. Zatrzymaj wszystkie strumienie
6038
+ yield this.forceStopAllStreams();
6039
+ // 8. Resetuj stany
6040
+ this._permissionChecked = false;
6041
+ this._isWindowActive = false;
6042
+ this._isMicrophoneMuted = false;
6043
+ this._publishState = exports.PublishState.NOT_INITIALIZED;
6044
+ this._inputDeviceState = exports.InputDevicesState.NOT_INITIALIZED;
6045
+ this._cameraState = exports.DeviceState.NOT_INITIALIZED;
6046
+ this._microphoneState = exports.DeviceState.NOT_INITIALIZED;
6047
+ this._switchingCamera = false;
6048
+ this._switchingMicrophone = false;
6049
+ // 9. Wyczyść listy urządzeń
6050
+ if (this._cameraList) {
6051
+ this._cameraList = new InputDeviceList();
6052
+ }
6053
+ if (this._microphoneList) {
6054
+ this._microphoneList = new InputDeviceList();
6055
+ }
6056
+ this._logger.success(this, "StreamerController successfully destroyed");
6057
+ } catch (error) {
6058
+ this._logger.error(this, "Error during destroy: " + error);
6059
+ } finally {
6060
+ this._isDestroying = false;
6061
+ }
6062
+ });
5674
6063
  }
5675
6064
  }
5676
6065
 
@@ -6255,7 +6644,6 @@
6255
6644
  this.onStreamStatsUpdate = event => {
6256
6645
  var _a;
6257
6646
  (_a = this._graph) === null || _a === void 0 ? void 0 : _a.addEntry(event.high * 500);
6258
- console.log(event.high * 200);
6259
6647
  };
6260
6648
  this._main = main;
6261
6649
  this._object = container;
@@ -6303,12 +6691,12 @@
6303
6691
  * Version of this streamer in SemVer format (Major.Minor.Patch).
6304
6692
  * @private
6305
6693
  */
6306
- this.STREAMER_VERSION = "0.9.3-beta.0";
6694
+ this.STREAMER_VERSION = "1.0.0-rc.1";
6307
6695
  /**
6308
6696
  * Compile date for this streamer
6309
6697
  * @private
6310
6698
  */
6311
- this.COMPILE_DATE = "3/8/2025, 11:51:35 PM";
6699
+ this.COMPILE_DATE = "11/17/2025, 10:30:23 AM";
6312
6700
  /**
6313
6701
  * Defines from which branch this streamer comes from e.g. "Main", "Experimental"
6314
6702
  * @private
@@ -6370,6 +6758,7 @@
6370
6758
  if (this._configManager == null) {
6371
6759
  this._configManager = new ConfigManager(copiedStreamConfig);
6372
6760
  this._logger = new Logger(this._configManager.getSettingsData().getDebugData(), this);
6761
+ this._logger.info(this, "Storm Streamer :: Storm Streaming Suite");
6373
6762
  this._logger.info(this, "StreamerID: " + this._streamerID);
6374
6763
  this._logger.info(this, "Version: " + this.STREAMER_VERSION + " | Compile Date: " + this.COMPILE_DATE + " | Branch: " + this.STREAMER_BRANCH);
6375
6764
  this._logger.info(this, "UserCapabilities :: Browser: " + UserCapabilities.getBrowserName() + " " + UserCapabilities.getBrowserVersion());
@@ -6573,6 +6962,7 @@
6573
6962
  */
6574
6963
  unpublish() {
6575
6964
  var _a;
6965
+ console.log("kutas 1");
6576
6966
  (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.unpublish();
6577
6967
  }
6578
6968
  /**