@stormstreaming/stormstreamer 1.0.0-rc.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
@@ -4,8 +4,8 @@
4
4
  * contact@stormstreaming.com
5
5
  * https://stormstreaming.com
6
6
  *
7
- * Version: 1.0.0-rc.0
8
- * Version: 3/28/2025, 9:18:38 AM
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
  */
@@ -4509,6 +4716,7 @@
4509
4716
  // Initialize device lists
4510
4717
  yield this.grabDevices();
4511
4718
  } catch (error) {
4719
+ console.log(error);
4512
4720
  this._logger.error(this, "Error initializing devices: " + JSON.stringify(error));
4513
4721
  yield this.grabDevices();
4514
4722
  }
@@ -4548,30 +4756,31 @@
4548
4756
  * @private
4549
4757
  */
4550
4758
  setupDeviceChangeListener() {
4551
- navigator.mediaDevices.addEventListener('devicechange', () => __awaiter(this, void 0, void 0, function* () {
4759
+ this._deviceChangeHandler = () => __awaiter(this, void 0, void 0, function* () {
4552
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
+ }
4553
4766
  this._logger.info(this, "Device change detected, restarting streamer");
4554
- // Store current stream key if we're publishing
4555
4767
  const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
4556
- const wasPublishing = this._publishState === exports.PublishState.CONNECTED || this._publishState === exports.PublishState.PUBLISHED;
4768
+ const wasPublishing = this._publishState === exports.PublishState.CONNECTED;
4557
4769
  try {
4558
- // Stop all current operations
4559
4770
  this.stop();
4560
- // Wait a moment for devices to settle
4561
4771
  yield new Promise(resolve => setTimeout(resolve, 500));
4562
- // Restart the streamer
4563
- yield this.start();
4564
- // If we were publishing before, try to resume
4565
- if (wasPublishing && streamKey) {
4566
- this._logger.info(this, "Resuming publishing after device change");
4567
- if (this.isStreamReady(true, true)) {
4568
- this.publish(streamKey);
4569
- } else {
4570
- this._logger.warning(this, "Cannot resume publishing - stream not ready after device change");
4571
- // Używamy ogólnego eventu zamiast nowego
4572
- this._main.dispatchEvent("inputDeviceError", {
4573
- ref: this._main
4574
- });
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
+ }
4575
4784
  }
4576
4785
  }
4577
4786
  this._logger.success(this, "Successfully handled device change");
@@ -4579,7 +4788,8 @@
4579
4788
  this._logger.error(this, "Error handling device change: " + JSON.stringify(error));
4580
4789
  this.setInputDeviceState(exports.InputDevicesState.INVALID);
4581
4790
  }
4582
- }));
4791
+ });
4792
+ navigator.mediaDevices.addEventListener('devicechange', this._deviceChangeHandler);
4583
4793
  }
4584
4794
  handlePermissionChange(device, state) {
4585
4795
  return __awaiter(this, void 0, void 0, function* () {
@@ -4656,12 +4866,14 @@
4656
4866
  this.grabDevices();
4657
4867
  }
4658
4868
  const videoElement = this._main.getStageController().getScreenElement().getVideoElement();
4659
- videoElement.srcObject = stream;
4660
- videoElement.autoplay = true;
4661
- videoElement.playsInline = true;
4662
- videoElement.disableRemotePlayback = true;
4663
- videoElement.controls = false;
4664
- 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
+ }
4665
4877
  this.setPublishState(exports.PublishState.INITIALIZED);
4666
4878
  }
4667
4879
  /**
@@ -4688,25 +4900,24 @@
4688
4900
  * @returns {boolean} - true jeśli udało się rozpocząć publikowanie
4689
4901
  */
4690
4902
  publish(streamKey) {
4903
+ if (this._debug) this._logger.decoratedLog("Publishing: " + streamKey, "dark-red");
4904
+ this._logger.info(this, "Publish: " + streamKey);
4691
4905
  if (this._statusTimer != null) clearInterval(this._statusTimer);
4692
- if (this._main.getConfigManager().getStreamData().streamKey == streamKey && this._publishState == exports.PublishState.CONNECTED) {
4693
- this._logger.warning(this, "Already published!");
4694
- return false;
4906
+ if (this._main.getConfigManager().getStreamData().streamKey != null && !this._firstPublish) {
4907
+ this.unpublish();
4695
4908
  }
4696
- if (this._main.getConfigManager().getStreamData().streamKey != null) this.unpublish();
4697
4909
  this._main.getConfigManager().getStreamData().streamKey = streamKey;
4698
4910
  if (!this.isStreamReady(true, true)) {
4699
4911
  this._logger.warning(this, "Cannot publish - stream not ready (missing video or audio track)");
4700
4912
  return false;
4701
4913
  }
4702
- if (this._debug) this._logger.decoratedLog("Publishing: " + streamKey, "dark-red");
4703
- this._logger.info(this, "Publish: " + streamKey);
4704
4914
  this.closeWebRTCConnection();
4705
4915
  this._main.dispatchEvent("publish", {
4706
4916
  ref: this._main,
4707
4917
  streamKey: streamKey
4708
4918
  });
4709
4919
  this.initializeWebRTC();
4920
+ this._firstPublish = false;
4710
4921
  return true;
4711
4922
  }
4712
4923
  unpublish() {
@@ -4731,12 +4942,11 @@
4731
4942
  * @private
4732
4943
  */
4733
4944
  setupOrientationListener() {
4734
- // Sprawdzamy czy urządzenie wspiera event orientationchange
4945
+ this._orientationChangeHandler = this.handleOrientationChange;
4735
4946
  if (window.screen && window.screen.orientation) {
4736
- window.screen.orientation.addEventListener('change', this.handleOrientationChange);
4947
+ window.screen.orientation.addEventListener('change', this._orientationChangeHandler);
4737
4948
  } else {
4738
- // Fallback dla starszych urządzeń
4739
- window.addEventListener('orientationchange', this.handleOrientationChange);
4949
+ window.addEventListener('orientationchange', this._orientationChangeHandler);
4740
4950
  }
4741
4951
  }
4742
4952
  //------------------------------------------------------------------------//
@@ -4955,6 +5165,7 @@
4955
5165
  this._selectedMicrophone = this.pickMicrophone();
4956
5166
  }
4957
5167
  } catch (error) {
5168
+ console.log(error);
4958
5169
  this.setInputDeviceState(exports.InputDevicesState.INVALID);
4959
5170
  this._logger.error(this, "Errror on grab devices: " + JSON.stringify(error));
4960
5171
  }
@@ -4981,94 +5192,145 @@
4981
5192
  });
4982
5193
  }
4983
5194
  /**
4984
- * Selects camera based on camera device ID;
5195
+ * Selects camera based on camera device ID with abort support
4985
5196
  * @param cameraID
4986
5197
  */
4987
5198
  selectCamera(cameraID) {
4988
- var _a, _b;
4989
- for (let i = 0; i < this._cameraList.getSize(); i++) {
4990
- this._cameraList.get(i).isSelected = false;
4991
- }
4992
- this._selectedCamera = null;
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;
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();
5004
5204
  }
5005
- }
5006
- this._main.dispatchEvent("deviceListUpdate", {
5007
- ref: this._main,
5008
- cameraList: this._cameraList.getArray(),
5009
- microphoneList: this._microphoneList.getArray()
5010
- });
5011
- this.stopCameraStream();
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);
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;
5022
5211
  }
5023
- });
5024
- } else {
5025
- this.setInputDeviceState(exports.InputDevicesState.INVALID);
5026
- }
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
+ });
5027
5271
  }
5028
5272
  /**
5029
- * 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
5030
5274
  * @param micID
5031
5275
  */
5032
5276
  selectMicrophone(micID) {
5033
- var _a, _b;
5277
+ var _a, _b, _c;
5034
5278
  return __awaiter(this, void 0, void 0, function* () {
5035
- for (let i = 0; i < this._microphoneList.getSize(); i++) {
5036
- this._microphoneList.get(i).isSelected = false;
5279
+ // Anuluj poprzednie przełączanie mikrofonu
5280
+ if (this._microphoneAbortController) {
5281
+ this._microphoneAbortController.abort();
5037
5282
  }
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;
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;
5052
5289
  }
5053
- }
5054
- // Zawsze wysyłamy aktualizację list urządzeń
5055
- this._main.dispatchEvent("deviceListUpdate", {
5056
- ref: this._main,
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();
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()
5068
5311
  });
5069
- this._stream = null;
5070
- }
5071
- 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;
5072
5334
  // Rozpocznij wszystko od nowa
5073
5335
  yield this.startCamera();
5074
5336
  this.setMicrophoneState(exports.DeviceState.ENABLED);
@@ -5078,25 +5340,38 @@
5078
5340
  this.setInputDeviceState(exports.InputDevicesState.INVALID);
5079
5341
  }
5080
5342
  // Jeśli stream był opublikowany, publikujemy ponownie
5081
- if (wasPublished && streamKey) {
5343
+ if (wasPublished && streamKey && !signal.aborted) {
5082
5344
  this.publish(streamKey);
5083
5345
  }
5084
5346
  } catch (error) {
5085
- console.error("Error changing microphone:", error);
5086
- this._main.dispatchEvent("inputDeviceError", {
5087
- ref: this._main
5088
- });
5089
- 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
+ }
5090
5359
  }
5091
5360
  });
5092
5361
  }
5093
5362
  /**
5094
- * This method tries to start a camera.
5095
- *
5363
+ * This method tries to start a camera with abort support
5096
5364
  * @private
5097
5365
  */
5098
5366
  startCamera() {
5367
+ var _a;
5099
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;
5100
5375
  if (this._stream) {
5101
5376
  this._stream.getTracks().forEach(track => {
5102
5377
  track.stop();
@@ -5116,11 +5391,18 @@
5116
5391
  }
5117
5392
  } : false
5118
5393
  };
5394
+ if (signal.aborted) return;
5119
5395
  try {
5120
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
+ }
5121
5402
  this._stream = stream;
5122
5403
  this.onCameraStreamSuccess(this._stream);
5123
5404
  } catch (error) {
5405
+ if (signal.aborted) return;
5124
5406
  if (constraints.video) {
5125
5407
  this.onUserMediaError({
5126
5408
  name: error.name || 'Error',
@@ -5140,6 +5422,10 @@
5140
5422
  } catch (error) {
5141
5423
  console.error("Error in startCamera:", error);
5142
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
+ }
5143
5429
  }
5144
5430
  });
5145
5431
  }
@@ -5171,7 +5457,6 @@
5171
5457
  }
5172
5458
  /**
5173
5459
  * This method selects a camera based on previous uses or saved IDs
5174
- *
5175
5460
  * @private
5176
5461
  */
5177
5462
  pickCamera() {
@@ -5241,7 +5526,6 @@
5241
5526
  }
5242
5527
  /**
5243
5528
  * This method selects a microphone based on previous uses or saved IDs
5244
- *
5245
5529
  * @private
5246
5530
  */
5247
5531
  pickMicrophone() {
@@ -5342,7 +5626,6 @@
5342
5626
  }
5343
5627
  /**
5344
5628
  * Applies the microphone state to the actual stream tracks
5345
- *
5346
5629
  * @param enabled true to enable tracks, false to disable
5347
5630
  * @private
5348
5631
  */
@@ -5361,7 +5644,6 @@
5361
5644
  }
5362
5645
  /**
5363
5646
  * This methods is a final check whenever we're ready to publish a stream
5364
- *
5365
5647
  * @param requireVideo - whenever video track is required
5366
5648
  * @param requireAudio - whenever audio track is required
5367
5649
  * @returns {boolean} true if stream is ready for publishing
@@ -5382,6 +5664,47 @@
5382
5664
  this._peerConnection = null;
5383
5665
  }
5384
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
+ }
5385
5708
  onDescriptionError(error) {
5386
5709
  this._logger.info(this, "WebRTCStreamer :: onDescriptionError: " + JSON.stringify(error));
5387
5710
  }
@@ -5494,110 +5817,101 @@
5494
5817
  }
5495
5818
  }
5496
5819
  /**
5497
- * 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
5498
5848
  * @private
5499
5849
  */
5500
5850
  forceStopAllStreams() {
5501
5851
  var _a, _b;
5502
- // 1. First, detach the sound meter
5503
- if (this._soundMeter) {
5504
- this._soundMeter.detach();
5505
- }
5506
- // 2. Stop the main stream if it exists
5507
- if (this._stream) {
5508
- try {
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
- }
5524
- }
5525
- // 3. Clean up video element
5526
- try {
5527
- const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
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
- });
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);
5540
5860
  }
5541
- videoElement.srcObject = null;
5542
- videoElement.removeAttribute('src');
5543
- videoElement.load();
5544
5861
  }
5545
- } catch (e) {
5546
- this._logger.error(this, 'Error cleaning video element');
5547
- }
5548
- // 4. Handle RTCPeerConnection last, with proper state checking
5549
- 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
5550
5868
  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
- });
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;
5569
5885
  }
5570
- // Now close the peer connection
5571
- this._peerConnection.close();
5572
- this._peerConnection = null;
5573
5886
  } catch (e) {
5574
- this._logger.error(this, 'Error closing peer connection');
5887
+ this._logger.error(this, 'Error cleaning video element: ' + e);
5575
5888
  }
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
- }
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
+ });
5596
5897
  }
5597
5898
  /**
5598
5899
  * Stops all streaming operations and cleans up resources
5599
5900
  */
5600
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
+ }
5601
5915
  // Stop status connection and clear timer
5602
5916
  if (this._statusConnection) {
5603
5917
  this._statusConnection.destroy();
@@ -5656,60 +5970,96 @@
5656
5970
  });
5657
5971
  }
5658
5972
  /**
5659
- * Method used for destroying everything (one-time use)
5973
+ * Method used for destroying everything (one-time use) with proper cleanup
5660
5974
  */
5661
5975
  destroy() {
5662
- // Stop any ongoing timers and intervals first
5663
- if (this._statusTimer != null) {
5664
- clearInterval(this._statusTimer);
5665
- this._statusTimer = null;
5666
- }
5667
- if (this._restartTimer != null) {
5668
- clearInterval(this._restartTimer);
5669
- this._restartTimer = null;
5670
- }
5671
- clearTimeout(this._publishTimer);
5672
- // Remove orientation change listeners
5673
- if (window.screen && window.screen.orientation) {
5674
- window.screen.orientation.removeEventListener('change', this.handleOrientationChange);
5675
- } else {
5676
- window.removeEventListener('orientationchange', this.handleOrientationChange);
5677
- }
5678
- // Remove other event listeners
5679
- try {
5680
- this._main.removeEventListener("serverConnect", this.onServerConnect);
5681
- this._main.removeEventListener("serverDisconnect", this.onServerDisconnect);
5682
- this._main.removeEventListener("streamKeyInUse", this.onStreamKeyTaken);
5683
- this._main.removeEventListener("statusServerConnect", this.onStatusServerConnect);
5684
- this._main.removeEventListener("statusServerDisconnect", this.onStatusServerDisconnect);
5685
- this._main.removeEventListener("streamStatusUpdate", this.onStreamStatsUpdate);
5686
- this._main.removeEventListener("deviceStateChange", this.onDeviceStateChange);
5687
- document.removeEventListener("visibilitychange", this.visibilityChange);
5688
- window.removeEventListener("blur", this.onWindowBlur);
5689
- window.removeEventListener("focus", this.onWindowFocus);
5690
- } catch (e) {
5691
- this._logger.error(this, 'Error removing event listeners');
5692
- }
5693
- // Make sure we destroy the status connection
5694
- if (this._statusConnection) {
5695
- this._statusConnection.destroy();
5696
- this._statusConnection = null;
5697
- }
5698
- // Stop all media streams and clean up the WebRTC connection
5699
- this.forceStopAllStreams();
5700
- // Reset all state variables
5701
- this._pendingMicrophoneState = null;
5702
- this._cameraList = new InputDeviceList();
5703
- this._microphoneList = new InputDeviceList();
5704
- this._permissionChecked = false;
5705
- this._isWindowActive = false;
5706
- this._isMicrophoneMuted = false;
5707
- this._publishState = exports.PublishState.NOT_INITIALIZED;
5708
- this._inputDeviceState = exports.InputDevicesState.NOT_INITIALIZED;
5709
- this._cameraState = exports.DeviceState.NOT_INITIALIZED;
5710
- this._microphoneState = exports.DeviceState.NOT_INITIALIZED;
5711
- // Log completion
5712
- 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
+ });
5713
6063
  }
5714
6064
  }
5715
6065
 
@@ -6294,7 +6644,6 @@
6294
6644
  this.onStreamStatsUpdate = event => {
6295
6645
  var _a;
6296
6646
  (_a = this._graph) === null || _a === void 0 ? void 0 : _a.addEntry(event.high * 500);
6297
- console.log(event.high * 200);
6298
6647
  };
6299
6648
  this._main = main;
6300
6649
  this._object = container;
@@ -6342,12 +6691,12 @@
6342
6691
  * Version of this streamer in SemVer format (Major.Minor.Patch).
6343
6692
  * @private
6344
6693
  */
6345
- this.STREAMER_VERSION = "1.0.0-rc.0";
6694
+ this.STREAMER_VERSION = "1.0.0-rc.1";
6346
6695
  /**
6347
6696
  * Compile date for this streamer
6348
6697
  * @private
6349
6698
  */
6350
- this.COMPILE_DATE = "3/28/2025, 9:18:36 AM";
6699
+ this.COMPILE_DATE = "11/17/2025, 10:30:23 AM";
6351
6700
  /**
6352
6701
  * Defines from which branch this streamer comes from e.g. "Main", "Experimental"
6353
6702
  * @private
@@ -6613,6 +6962,7 @@
6613
6962
  */
6614
6963
  unpublish() {
6615
6964
  var _a;
6965
+ console.log("kutas 1");
6616
6966
  (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.unpublish();
6617
6967
  }
6618
6968
  /**