@stormstreaming/stormstreamer 1.0.0-rc.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/amd/index.js +655 -979
- package/dist/cjs/index.js +3 -3
- package/dist/esm/index.js +8 -8
- package/dist/iife/index.js +8 -8
- package/dist/types/StormStreamer.d.ts +6 -384
- package/dist/types/playback/SoundMeter.d.ts +1 -0
- package/dist/types/playback/StreamerController.d.ts +69 -110
- package/dist/umd/index.js +8 -8
- package/package.json +1 -1
package/dist/amd/index.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* contact@stormstreaming.com
|
|
5
5
|
* https://stormstreaming.com
|
|
6
6
|
*
|
|
7
|
-
* Version: 1.0.0
|
|
8
|
-
* Version:
|
|
7
|
+
* Version: 1.0.0
|
|
8
|
+
* Version: 2/7/2026, 6:37:17 PM
|
|
9
9
|
*
|
|
10
10
|
* LEGAL NOTICE:
|
|
11
11
|
* This software is subject to the terms and conditions defined in
|
|
@@ -3672,13 +3672,13 @@
|
|
|
3672
3672
|
this._lastEventTime = 0;
|
|
3673
3673
|
this.THROTTLE_INTERVAL = 100; // ms between updates
|
|
3674
3674
|
this._isMonitoring = false;
|
|
3675
|
-
// POPRAWKA: Dodane pole do przechowywania ID animacji
|
|
3676
3675
|
this._animationFrameId = null;
|
|
3677
|
-
//
|
|
3678
|
-
this._instant = 0.0;
|
|
3679
|
-
this._slow = 0.0;
|
|
3676
|
+
// Peak levels for VU meter
|
|
3677
|
+
this._instant = 0.0; // Current peak (fast)
|
|
3678
|
+
this._slow = 0.0; // Peak hold with decay
|
|
3679
|
+
// Decay rate for peak hold (0.95 = slow decay, 0.8 = fast decay)
|
|
3680
|
+
this.PEAK_DECAY = 0.92;
|
|
3680
3681
|
this._main = main;
|
|
3681
|
-
console.log('SoundMeter: Created new instance');
|
|
3682
3682
|
}
|
|
3683
3683
|
attach(stream) {
|
|
3684
3684
|
if (!stream.getAudioTracks().length) {
|
|
@@ -3693,30 +3693,24 @@
|
|
|
3693
3693
|
this._microphone = this._audioContext.createMediaStreamSource(stream);
|
|
3694
3694
|
this._analyser = this._audioContext.createAnalyser();
|
|
3695
3695
|
this._analyser.fftSize = 2048;
|
|
3696
|
-
this._analyser.smoothingTimeConstant = 0.3;
|
|
3697
3696
|
// Connect nodes
|
|
3698
3697
|
this._microphone.connect(this._analyser);
|
|
3699
3698
|
// Start monitoring
|
|
3700
3699
|
this.startMonitoring();
|
|
3701
3700
|
} catch (error) {
|
|
3702
3701
|
console.error('SoundMeter: Error during attach:', error);
|
|
3703
|
-
this.detach();
|
|
3702
|
+
this.detach();
|
|
3704
3703
|
}
|
|
3705
3704
|
}
|
|
3706
|
-
|
|
3707
3705
|
detach() {
|
|
3708
|
-
// POPRAWKA: Zabezpieczenie przed wielokrotnym wywołaniem
|
|
3709
3706
|
if (!this._audioContext && !this._analyser && !this._microphone) {
|
|
3710
|
-
return;
|
|
3707
|
+
return;
|
|
3711
3708
|
}
|
|
3712
|
-
// Stop monitoring first
|
|
3713
3709
|
this._isMonitoring = false;
|
|
3714
|
-
// POPRAWKA: Anulowanie requestAnimationFrame
|
|
3715
3710
|
if (this._animationFrameId !== null) {
|
|
3716
3711
|
cancelAnimationFrame(this._animationFrameId);
|
|
3717
3712
|
this._animationFrameId = null;
|
|
3718
3713
|
}
|
|
3719
|
-
// Disconnect and cleanup nodes in reverse order
|
|
3720
3714
|
if (this._microphone) {
|
|
3721
3715
|
try {
|
|
3722
3716
|
this._microphone.disconnect();
|
|
@@ -3735,13 +3729,9 @@
|
|
|
3735
3729
|
}
|
|
3736
3730
|
if (this._audioContext) {
|
|
3737
3731
|
try {
|
|
3738
|
-
// POPRAWKA: Sprawdzenie przed użyciem w Promise
|
|
3739
3732
|
if (this._audioContext.state !== 'closed') {
|
|
3740
|
-
// Zapisz referencję przed asynchroniczną operacją
|
|
3741
3733
|
const audioContextRef = this._audioContext;
|
|
3742
|
-
// Najpierw ustaw na null, żeby uniknąć podwójnego wywołania
|
|
3743
3734
|
this._audioContext = null;
|
|
3744
|
-
// Teraz bezpiecznie zamknij
|
|
3745
3735
|
audioContextRef.suspend().then(() => audioContextRef.close()).catch(e => {
|
|
3746
3736
|
console.warn('SoundMeter: Error closing audio context:', e);
|
|
3747
3737
|
});
|
|
@@ -3765,28 +3755,29 @@
|
|
|
3765
3755
|
this._isMonitoring = true;
|
|
3766
3756
|
const dataArray = new Float32Array(this._analyser.frequencyBinCount);
|
|
3767
3757
|
const analyze = () => {
|
|
3768
|
-
// POPRAWKA: Dodatkowe sprawdzenie przed kontynuacją
|
|
3769
3758
|
if (!this._analyser || !this._isMonitoring || !this._audioContext) {
|
|
3770
3759
|
this._animationFrameId = null;
|
|
3771
3760
|
return;
|
|
3772
3761
|
}
|
|
3773
3762
|
const now = Date.now();
|
|
3774
3763
|
try {
|
|
3775
|
-
// Read time-domain data
|
|
3776
3764
|
this._analyser.getFloatTimeDomainData(dataArray);
|
|
3777
|
-
//
|
|
3778
|
-
let
|
|
3779
|
-
let clipcount = 0;
|
|
3765
|
+
// Peak detection - find maximum amplitude
|
|
3766
|
+
let peak = 0.0;
|
|
3780
3767
|
for (let i = 0; i < dataArray.length; ++i) {
|
|
3781
|
-
const amplitude = dataArray[i];
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
clipcount += 1;
|
|
3768
|
+
const amplitude = Math.abs(dataArray[i]);
|
|
3769
|
+
if (amplitude > peak) {
|
|
3770
|
+
peak = amplitude;
|
|
3785
3771
|
}
|
|
3786
3772
|
}
|
|
3787
|
-
//
|
|
3788
|
-
this._instant = Math.
|
|
3789
|
-
|
|
3773
|
+
// Instant = current peak (clamped to 1.0)
|
|
3774
|
+
this._instant = Math.min(1.0, peak);
|
|
3775
|
+
// Slow = peak hold with decay (classic VU meter behavior)
|
|
3776
|
+
if (this._instant > this._slow) {
|
|
3777
|
+
this._slow = this._instant;
|
|
3778
|
+
} else {
|
|
3779
|
+
this._slow *= this.PEAK_DECAY;
|
|
3780
|
+
}
|
|
3790
3781
|
// Throttle event dispatch
|
|
3791
3782
|
if (now - this._lastEventTime >= this.THROTTLE_INTERVAL) {
|
|
3792
3783
|
this._lastEventTime = now;
|
|
@@ -3802,17 +3793,13 @@
|
|
|
3802
3793
|
this._animationFrameId = null;
|
|
3803
3794
|
return;
|
|
3804
3795
|
}
|
|
3805
|
-
// Schedule next analysis only if still monitoring
|
|
3806
3796
|
if (this._isMonitoring) {
|
|
3807
3797
|
this._animationFrameId = requestAnimationFrame(analyze);
|
|
3808
3798
|
}
|
|
3809
3799
|
};
|
|
3810
|
-
// Start analysis loop
|
|
3811
3800
|
this._animationFrameId = requestAnimationFrame(analyze);
|
|
3812
3801
|
}
|
|
3813
|
-
// POPRAWKA: Dodana metoda destroy
|
|
3814
3802
|
destroy() {
|
|
3815
|
-
console.log('SoundMeter: Destroying instance');
|
|
3816
3803
|
this.detach();
|
|
3817
3804
|
}
|
|
3818
3805
|
}
|
|
@@ -4331,7 +4318,7 @@
|
|
|
4331
4318
|
* @param config
|
|
4332
4319
|
*/
|
|
4333
4320
|
constructor(main) {
|
|
4334
|
-
var _a, _b, _c;
|
|
4321
|
+
var _a, _b, _c, _d;
|
|
4335
4322
|
/**
|
|
4336
4323
|
* Whenever current window is active or not
|
|
4337
4324
|
* @private
|
|
@@ -4345,7 +4332,7 @@
|
|
|
4345
4332
|
'iceServers': []
|
|
4346
4333
|
};
|
|
4347
4334
|
/**
|
|
4348
|
-
* Whenever microphone is
|
|
4335
|
+
* Whenever microphone is currently muted
|
|
4349
4336
|
* @private
|
|
4350
4337
|
*/
|
|
4351
4338
|
this._isMicrophoneMuted = false;
|
|
@@ -4355,7 +4342,7 @@
|
|
|
4355
4342
|
*/
|
|
4356
4343
|
this._pendingMicrophoneState = null;
|
|
4357
4344
|
/**
|
|
4358
|
-
* Whenever we have
|
|
4345
|
+
* Whenever we have checked for permissions
|
|
4359
4346
|
* @private
|
|
4360
4347
|
*/
|
|
4361
4348
|
this._permissionChecked = false;
|
|
@@ -4418,58 +4405,83 @@
|
|
|
4418
4405
|
this._currentOrientation = ((_a = window.screen.orientation) === null || _a === void 0 ? void 0 : _a.type) || '';
|
|
4419
4406
|
this._statusTimer = null;
|
|
4420
4407
|
this._debug = false;
|
|
4421
|
-
//
|
|
4408
|
+
// FIELDS FOR OPERATION CANCELLATION
|
|
4422
4409
|
this._deviceChangeHandler = null;
|
|
4423
4410
|
this._orientationChangeHandler = null;
|
|
4424
|
-
|
|
4411
|
+
/**
|
|
4412
|
+
* CRITICAL: This flag is checked after EVERY async operation
|
|
4413
|
+
* Set to true immediately when destroy() is called
|
|
4414
|
+
* @private
|
|
4415
|
+
*/
|
|
4416
|
+
this._isDestroyed = false;
|
|
4425
4417
|
this._cameraAbortController = null;
|
|
4426
4418
|
this._microphoneAbortController = null;
|
|
4427
4419
|
this._startCameraAbortController = null;
|
|
4428
4420
|
this._switchingCamera = false;
|
|
4429
4421
|
this._switchingMicrophone = false;
|
|
4430
4422
|
this._firstPublish = true;
|
|
4423
|
+
/**
|
|
4424
|
+
* Counter for tracking active media streams (for debugging)
|
|
4425
|
+
* @private
|
|
4426
|
+
*/
|
|
4427
|
+
this._activeStreamCount = 0;
|
|
4428
|
+
/**
|
|
4429
|
+
* Permission status objects that need cleanup
|
|
4430
|
+
* @private
|
|
4431
|
+
*/
|
|
4432
|
+
this._cameraPermissionStatus = null;
|
|
4433
|
+
this._microphonePermissionStatus = null;
|
|
4434
|
+
/**
|
|
4435
|
+
* Bound handlers for permission changes (needed for removal)
|
|
4436
|
+
* @private
|
|
4437
|
+
*/
|
|
4438
|
+
this._boundCameraPermissionHandler = null;
|
|
4439
|
+
this._boundMicrophonePermissionHandler = null;
|
|
4431
4440
|
/**
|
|
4432
4441
|
* Handles device state changes and initiates publishing if appropriate
|
|
4433
4442
|
* @private
|
|
4434
4443
|
*/
|
|
4435
4444
|
this.onDeviceStateChange = event => {
|
|
4436
4445
|
var _a;
|
|
4446
|
+
if (this._isDestroyed) return;
|
|
4437
4447
|
const usedStreamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
4438
4448
|
if (event.state == exports.InputDevicesState.READY && usedStreamKey != null) {
|
|
4439
4449
|
this.publish(usedStreamKey);
|
|
4440
4450
|
}
|
|
4441
4451
|
};
|
|
4442
4452
|
/**
|
|
4443
|
-
*
|
|
4453
|
+
* Handles orientation changes for mobile devices
|
|
4444
4454
|
*/
|
|
4445
4455
|
this.handleOrientationChange = () => __awaiter(this, void 0, void 0, function* () {
|
|
4446
|
-
var
|
|
4447
|
-
if (this.
|
|
4448
|
-
// Dajemy chwilę na ustabilizowanie się orientacji
|
|
4456
|
+
var _e, _f;
|
|
4457
|
+
if (this._isDestroyed) return;
|
|
4449
4458
|
yield new Promise(resolve => setTimeout(resolve, 500));
|
|
4450
|
-
|
|
4459
|
+
if (this._isDestroyed) return;
|
|
4460
|
+
const newOrientation = ((_e = window.screen.orientation) === null || _e === void 0 ? void 0 : _e.type) || '';
|
|
4451
4461
|
if (this._currentOrientation !== newOrientation) {
|
|
4452
4462
|
this._logger.info(this, `Orientation changed from ${this._currentOrientation} to ${newOrientation}`);
|
|
4453
4463
|
this._currentOrientation = newOrientation;
|
|
4454
|
-
|
|
4455
|
-
const streamKey = (_e = this._main.getConfigManager()) === null || _e === void 0 ? void 0 : _e.getStreamData().streamKey;
|
|
4464
|
+
const streamKey = (_f = this._main.getConfigManager()) === null || _f === void 0 ? void 0 : _f.getStreamData().streamKey;
|
|
4456
4465
|
this._publishState === exports.PublishState.PUBLISHED;
|
|
4457
|
-
// Zamknij istniejące połączenie i stream
|
|
4458
4466
|
this.closeWebRTCConnection();
|
|
4459
4467
|
if (this._stream) {
|
|
4468
|
+
this._logger.info(this, "📹 [RELEASE] handleOrientationChange() - releasing stream for orientation change");
|
|
4460
4469
|
this._stream.getTracks().forEach(track => {
|
|
4461
4470
|
track.stop();
|
|
4471
|
+
this._logger.info(this, `📹 [RELEASE] handleOrientationChange() - stopped track: ${track.kind}`);
|
|
4462
4472
|
});
|
|
4463
4473
|
this._stream = null;
|
|
4474
|
+
this._activeStreamCount--;
|
|
4464
4475
|
}
|
|
4465
|
-
|
|
4476
|
+
if (this._isDestroyed) return;
|
|
4466
4477
|
try {
|
|
4467
4478
|
yield this.startCamera();
|
|
4468
|
-
|
|
4469
|
-
if (streamKey
|
|
4479
|
+
if (this._isDestroyed) return;
|
|
4480
|
+
if (streamKey) {
|
|
4470
4481
|
this.publish(streamKey);
|
|
4471
4482
|
}
|
|
4472
4483
|
} catch (error) {
|
|
4484
|
+
if (this._isDestroyed) return;
|
|
4473
4485
|
this._logger.error(this, "Error restarting stream after orientation change: " + JSON.stringify(error));
|
|
4474
4486
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4475
4487
|
}
|
|
@@ -4478,10 +4490,8 @@
|
|
|
4478
4490
|
this.onServerDisconnect = () => {
|
|
4479
4491
|
// Implementation
|
|
4480
4492
|
};
|
|
4481
|
-
/**
|
|
4482
|
-
* Method for handling a situation when a given streamKey is already in use.
|
|
4483
|
-
*/
|
|
4484
4493
|
this.onStreamKeyTaken = () => {
|
|
4494
|
+
if (this._isDestroyed) return;
|
|
4485
4495
|
if (this._restartTimer != null) {
|
|
4486
4496
|
clearInterval(this._restartTimer);
|
|
4487
4497
|
this._restartTimerCount = 0;
|
|
@@ -4490,6 +4500,10 @@
|
|
|
4490
4500
|
this.setPublishState(exports.PublishState.ERROR);
|
|
4491
4501
|
this._restartTimer = setInterval(() => {
|
|
4492
4502
|
var _a, _b;
|
|
4503
|
+
if (this._isDestroyed) {
|
|
4504
|
+
if (this._restartTimer) clearInterval(this._restartTimer);
|
|
4505
|
+
return;
|
|
4506
|
+
}
|
|
4493
4507
|
if (this._restartTimer != null) {
|
|
4494
4508
|
if (this._restartTimerCount < this._restartTimerMaxCount) {
|
|
4495
4509
|
this._logger.info(this, "WebRTCStreamer :: StreamKeyTaken Interval: " + this._restartTimerCount + "/" + this._restartTimerMaxCount);
|
|
@@ -4500,41 +4514,37 @@
|
|
|
4500
4514
|
this._restartTimerCount = 0;
|
|
4501
4515
|
const streamData = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData();
|
|
4502
4516
|
const streamKey = streamData === null || streamData === void 0 ? void 0 : streamData.streamKey;
|
|
4503
|
-
if (streamKey != null) {
|
|
4504
|
-
|
|
4505
|
-
this.publish(prevStreamKey);
|
|
4517
|
+
if (streamKey != null && !this._isDestroyed) {
|
|
4518
|
+
this.publish(streamKey);
|
|
4506
4519
|
}
|
|
4507
4520
|
}
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4521
|
+
if (!this._isDestroyed) {
|
|
4522
|
+
const usedStreamKey = (_b = this._main.getConfigManager().getStreamData().streamKey) !== null && _b !== void 0 ? _b : "unknown";
|
|
4523
|
+
this._main.dispatchEvent("streamKeyInUseInterval", {
|
|
4524
|
+
ref: this._main,
|
|
4525
|
+
streamKey: usedStreamKey,
|
|
4526
|
+
count: this._restartTimerCount,
|
|
4527
|
+
maxCount: this._restartTimerMaxCount
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4515
4530
|
}
|
|
4516
4531
|
}, 1000);
|
|
4517
4532
|
};
|
|
4518
4533
|
//------------------------------------------------------------------------//
|
|
4519
4534
|
// NETWORK AND RTC
|
|
4520
4535
|
//------------------------------------------------------------------------//
|
|
4521
|
-
/**
|
|
4522
|
-
* Method fires once a connection with wowza is established. It's the main connection where we exchange
|
|
4523
|
-
* ice-candidates and SDP. We'll start setting up peer connections.
|
|
4524
|
-
*/
|
|
4525
4536
|
this.onServerConnect = () => {
|
|
4537
|
+
if (this._isDestroyed) return;
|
|
4526
4538
|
if (this._peerConnection) {
|
|
4527
4539
|
this.closeWebRTCConnection();
|
|
4528
4540
|
}
|
|
4529
4541
|
this._peerConnection = new RTCPeerConnection(this._peerConnectionConfig);
|
|
4530
|
-
// Najpierw dodaj tracki
|
|
4531
4542
|
if (this._stream) {
|
|
4532
4543
|
let localTracks = this._stream.getTracks();
|
|
4533
4544
|
for (let localTrack in localTracks) {
|
|
4534
4545
|
this._peerConnection.addTrack(localTracks[localTrack], this._stream);
|
|
4535
4546
|
}
|
|
4536
4547
|
}
|
|
4537
|
-
// Potem dodaj event handlery
|
|
4538
4548
|
this._peerConnection.onicecandidate = event => {
|
|
4539
4549
|
this.onIceCandidate(event);
|
|
4540
4550
|
};
|
|
@@ -4542,26 +4552,29 @@
|
|
|
4542
4552
|
this.onConnectionStateChange(event);
|
|
4543
4553
|
};
|
|
4544
4554
|
this._peerConnection.onnegotiationneeded = event => __awaiter(this, void 0, void 0, function* () {
|
|
4545
|
-
if (this._peerConnection) {
|
|
4555
|
+
if (this._peerConnection && !this._isDestroyed) {
|
|
4546
4556
|
try {
|
|
4547
4557
|
const description = yield this._peerConnection.createOffer();
|
|
4548
|
-
|
|
4558
|
+
if (!this._isDestroyed) {
|
|
4559
|
+
yield this.onDescriptionSuccess(description);
|
|
4560
|
+
}
|
|
4549
4561
|
} catch (error) {
|
|
4550
4562
|
this.onDescriptionError(error);
|
|
4551
|
-
console.error('Error creating offer:', error);
|
|
4552
4563
|
}
|
|
4553
4564
|
}
|
|
4554
4565
|
});
|
|
4555
4566
|
this.createStatusConnection();
|
|
4556
4567
|
};
|
|
4557
|
-
/**
|
|
4558
|
-
* Method fires once a status connection is established. We'll set an interval for monitoring stream status.
|
|
4559
|
-
*/
|
|
4560
4568
|
this.onStatusServerConnect = () => {
|
|
4569
|
+
if (this._isDestroyed) return;
|
|
4561
4570
|
const usedStreamKey = this._main.getConfigManager().getStreamData().streamKey;
|
|
4562
4571
|
if (this._statusTimer == null) {
|
|
4563
4572
|
if (this._statusConnection != null && usedStreamKey != null) {
|
|
4564
4573
|
this._statusTimer = setInterval(() => {
|
|
4574
|
+
if (this._isDestroyed) {
|
|
4575
|
+
if (this._statusTimer) clearInterval(this._statusTimer);
|
|
4576
|
+
return;
|
|
4577
|
+
}
|
|
4565
4578
|
this.requestStatusData();
|
|
4566
4579
|
}, 1000);
|
|
4567
4580
|
}
|
|
@@ -4569,27 +4582,22 @@
|
|
|
4569
4582
|
};
|
|
4570
4583
|
this.requestStatusData = () => {
|
|
4571
4584
|
var _a;
|
|
4585
|
+
if (this._isDestroyed) return;
|
|
4572
4586
|
(_a = this._statusConnection) === null || _a === void 0 ? void 0 : _a.sendData('{"packetID":"STREAM_STATUS", "streamKey": "' + this._fullStreamName + '"}');
|
|
4573
4587
|
};
|
|
4574
|
-
/**
|
|
4575
|
-
* If for some reason the status connection is disconnected we have to clean the interval
|
|
4576
|
-
*/
|
|
4577
4588
|
this.onStatusServerDisconnect = () => {
|
|
4578
4589
|
if (this._statusTimer != null) clearInterval(this._statusTimer);
|
|
4579
4590
|
this._statusTimer = null;
|
|
4580
4591
|
};
|
|
4581
|
-
/**
|
|
4582
|
-
* This event fires whenever "STREAM_STATUS_RESPONSE" packet form status connection reports stream status along some stream data. This gives
|
|
4583
|
-
* us an insight into whenever our stream is ok (works) or not.
|
|
4584
|
-
* @param event
|
|
4585
|
-
*/
|
|
4586
4592
|
this.onStreamStatsUpdate = event => {
|
|
4593
|
+
if (this._isDestroyed) return;
|
|
4587
4594
|
const update = event.streamStatus;
|
|
4588
4595
|
if (this._publishState == exports.PublishState.PUBLISHED && update.publishState != exports.PublishState.PUBLISHED) this.setPublishState(exports.PublishState.UNPUBLISHED);
|
|
4589
4596
|
if (this._publishState == exports.PublishState.CONNECTED && update.publishState == exports.PublishState.PUBLISHED) this.setPublishState(exports.PublishState.PUBLISHED);
|
|
4590
4597
|
};
|
|
4591
4598
|
this.onDescriptionSuccess = description => {
|
|
4592
4599
|
var _a, _b, _c;
|
|
4600
|
+
if (this._isDestroyed) return;
|
|
4593
4601
|
this._fullStreamName = ((_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey) + "_" + new Date().getTime();
|
|
4594
4602
|
const streamInfo = {
|
|
4595
4603
|
applicationName: (_c = (_b = this._main.getNetworkController()) === null || _b === void 0 ? void 0 : _b.getConnection().getCurrentServer()) === null || _c === void 0 ? void 0 : _c.getApplication(),
|
|
@@ -4603,22 +4611,19 @@
|
|
|
4603
4611
|
videoCodec: "42e01f",
|
|
4604
4612
|
audioCodec: "opus"
|
|
4605
4613
|
});
|
|
4606
|
-
if (this._peerConnection) {
|
|
4614
|
+
if (this._peerConnection && !this._isDestroyed) {
|
|
4607
4615
|
this._peerConnection.setLocalDescription(description).then(() => {
|
|
4608
4616
|
var _a;
|
|
4617
|
+
if (this._isDestroyed) return;
|
|
4609
4618
|
(_a = this._main.getNetworkController()) === null || _a === void 0 ? void 0 : _a.sendMessage('{"direction":"publish", "command":"sendOffer", "streamInfo":' + JSON.stringify(streamInfo) + ', "sdp":' + JSON.stringify(description) + '}');
|
|
4610
4619
|
}).catch(error => {
|
|
4611
4620
|
console.log(error);
|
|
4612
|
-
//this.onWebRTCError(error, self);
|
|
4613
4621
|
});
|
|
4614
4622
|
}
|
|
4615
4623
|
};
|
|
4616
4624
|
//------------------------------------------------------------------------//
|
|
4617
4625
|
// BLUR & FOCUS
|
|
4618
4626
|
//------------------------------------------------------------------------//
|
|
4619
|
-
/**
|
|
4620
|
-
* Methods handles visibility change events
|
|
4621
|
-
*/
|
|
4622
4627
|
this.visibilityChange = () => {
|
|
4623
4628
|
if (document.visibilityState === 'hidden') {
|
|
4624
4629
|
this.onWindowBlur();
|
|
@@ -4626,18 +4631,12 @@
|
|
|
4626
4631
|
this.onWindowFocus();
|
|
4627
4632
|
}
|
|
4628
4633
|
};
|
|
4629
|
-
/**
|
|
4630
|
-
* Reacts to browser changing visibility of the document (or blur)
|
|
4631
|
-
*/
|
|
4632
4634
|
this.onWindowBlur = () => {
|
|
4633
4635
|
if (this._isWindowActive) {
|
|
4634
4636
|
this._logger.warning(this, "Player window is no longer in focus!");
|
|
4635
4637
|
}
|
|
4636
4638
|
this._isWindowActive = false;
|
|
4637
4639
|
};
|
|
4638
|
-
/**
|
|
4639
|
-
* Reacts to browser changing visibility of the document (or focus)
|
|
4640
|
-
*/
|
|
4641
4640
|
this.onWindowFocus = () => {
|
|
4642
4641
|
if (!this._isWindowActive) {
|
|
4643
4642
|
this._logger.info(this, "Player window is focused again!");
|
|
@@ -4649,38 +4648,76 @@
|
|
|
4649
4648
|
this._mungeSDP = new MungeSDP();
|
|
4650
4649
|
this._soundMeter = new SoundMeter(this._main);
|
|
4651
4650
|
this._debug = (_c = (_b = this._main.getConfigManager()) === null || _b === void 0 ? void 0 : _b.getSettingsData().getDebugData().streamerControllerDebug) !== null && _c !== void 0 ? _c : this._debug;
|
|
4651
|
+
// Restore saved microphone mute state
|
|
4652
|
+
const savedMuteState = (_d = this._main.getStorageManager()) === null || _d === void 0 ? void 0 : _d.getField("microphoneMuted");
|
|
4653
|
+
if (savedMuteState !== null) {
|
|
4654
|
+
this._isMicrophoneMuted = savedMuteState === "true";
|
|
4655
|
+
this._logger.info(this, `📹 [INIT] Restored microphone mute state: ${this._isMicrophoneMuted}`);
|
|
4656
|
+
}
|
|
4652
4657
|
// Start initialization process
|
|
4653
4658
|
this.initialize();
|
|
4654
4659
|
}
|
|
4655
4660
|
//------------------------------------------------------------------------//
|
|
4661
|
+
// HELPER: Check if destroyed
|
|
4662
|
+
//------------------------------------------------------------------------//
|
|
4663
|
+
/**
|
|
4664
|
+
* Helper method to check if instance is destroyed
|
|
4665
|
+
* Call this after EVERY await!
|
|
4666
|
+
* @private
|
|
4667
|
+
*/
|
|
4668
|
+
isDestroyedCheck(context) {
|
|
4669
|
+
var _a;
|
|
4670
|
+
if (this._isDestroyed) {
|
|
4671
|
+
if (context) {
|
|
4672
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.info(this, `📹 [ABORT] ${context} - instance destroyed, aborting`);
|
|
4673
|
+
}
|
|
4674
|
+
return true;
|
|
4675
|
+
}
|
|
4676
|
+
return false;
|
|
4677
|
+
}
|
|
4678
|
+
//------------------------------------------------------------------------//
|
|
4656
4679
|
// MAIN METHODS
|
|
4657
4680
|
//------------------------------------------------------------------------//
|
|
4658
4681
|
/**
|
|
4659
4682
|
* Initializes the PlaybackController by setting up event listeners and device handling
|
|
4660
|
-
* This method orchestrates the initialization process by first checking device availability
|
|
4661
|
-
* and permissions, then setting up necessary event listeners and configurations
|
|
4662
4683
|
* @private
|
|
4663
4684
|
*/
|
|
4664
4685
|
initialize() {
|
|
4665
4686
|
var _a, _b;
|
|
4666
4687
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4688
|
+
this._logger.info(this, "📹 [INIT] initialize() - starting initialization");
|
|
4667
4689
|
try {
|
|
4690
|
+
if (this.isDestroyedCheck("initialize start")) return;
|
|
4668
4691
|
yield this.initializeDevices();
|
|
4692
|
+
if (this.isDestroyedCheck("after initializeDevices")) return;
|
|
4669
4693
|
this.setupEventListeners();
|
|
4694
|
+
if (this.isDestroyedCheck("after setupEventListeners")) return;
|
|
4670
4695
|
this.initializeStream();
|
|
4696
|
+
if (this.isDestroyedCheck("after initializeStream")) return;
|
|
4671
4697
|
this.setupOrientationListener();
|
|
4672
|
-
this.setupPermissionListeners();
|
|
4698
|
+
yield this.setupPermissionListeners();
|
|
4699
|
+
if (this.isDestroyedCheck("after setupPermissionListeners")) return;
|
|
4673
4700
|
this.setupDeviceChangeListener();
|
|
4701
|
+
if (this.isDestroyedCheck("before network init")) return;
|
|
4674
4702
|
if ((_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getSettingsData().autoConnect) {
|
|
4675
4703
|
this._logger.info(this, "Initializing NetworkController (autoConnect is true)");
|
|
4676
4704
|
(_b = this._main.getNetworkController()) === null || _b === void 0 ? void 0 : _b.initialize();
|
|
4677
4705
|
} else {
|
|
4678
4706
|
this._logger.warning(this, "Warning - autoConnect is set to false, switching to standby mode!");
|
|
4679
4707
|
}
|
|
4708
|
+
this._logger.success(this, "📹 [INIT] initialize() - completed successfully");
|
|
4680
4709
|
} catch (error) {
|
|
4710
|
+
if (this._isDestroyed) {
|
|
4711
|
+
this._logger.warning(this, "📹 [INIT] initialize() - aborted by destroy()");
|
|
4712
|
+
return;
|
|
4713
|
+
}
|
|
4681
4714
|
this._logger.error(this, "Initialization failed: " + JSON.stringify(error));
|
|
4682
4715
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4683
4716
|
}
|
|
4717
|
+
this._main.dispatchEvent("microphoneStateChange", {
|
|
4718
|
+
ref: this._main,
|
|
4719
|
+
isMuted: this._isMicrophoneMuted
|
|
4720
|
+
});
|
|
4684
4721
|
});
|
|
4685
4722
|
}
|
|
4686
4723
|
/**
|
|
@@ -4688,6 +4725,7 @@
|
|
|
4688
4725
|
* @private
|
|
4689
4726
|
*/
|
|
4690
4727
|
setupEventListeners() {
|
|
4728
|
+
if (this._isDestroyed) return;
|
|
4691
4729
|
this._main.addEventListener("serverConnect", this.onServerConnect, false);
|
|
4692
4730
|
this._main.addEventListener("serverDisconnect", this.onServerDisconnect, false);
|
|
4693
4731
|
this._main.addEventListener("streamKeyInUse", this.onStreamKeyTaken, false);
|
|
@@ -4706,19 +4744,38 @@
|
|
|
4706
4744
|
initializeDevices() {
|
|
4707
4745
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4708
4746
|
try {
|
|
4747
|
+
this._logger.info(this, "📹 [ACQUIRE] initializeDevices() - requesting test getUserMedia for permissions");
|
|
4748
|
+
if (this.isDestroyedCheck("initializeDevices before getUserMedia")) return;
|
|
4709
4749
|
// Request initial device permissions
|
|
4710
4750
|
const stream = yield navigator.mediaDevices.getUserMedia({
|
|
4711
4751
|
video: true,
|
|
4712
4752
|
audio: true
|
|
4713
4753
|
});
|
|
4754
|
+
// CRITICAL: Check IMMEDIATELY after getUserMedia returns
|
|
4755
|
+
if (this._isDestroyed) {
|
|
4756
|
+
this._logger.warning(this, "📹 [RELEASE] initializeDevices() - destroyed during getUserMedia, releasing orphan stream");
|
|
4757
|
+
stream.getTracks().forEach(track => {
|
|
4758
|
+
track.stop();
|
|
4759
|
+
this._logger.info(this, `📹 [RELEASE] initializeDevices() - stopped orphan track: ${track.kind}, id: ${track.id}`);
|
|
4760
|
+
});
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4763
|
+
this._logger.info(this, "📹 [RELEASE] initializeDevices() - stopping test stream immediately");
|
|
4714
4764
|
// Stop the test stream
|
|
4715
|
-
stream.getTracks().forEach(track =>
|
|
4765
|
+
stream.getTracks().forEach(track => {
|
|
4766
|
+
track.stop();
|
|
4767
|
+
this._logger.info(this, `📹 [RELEASE] initializeDevices() - stopped track: ${track.kind}, id: ${track.id}`);
|
|
4768
|
+
});
|
|
4769
|
+
if (this.isDestroyedCheck("initializeDevices before grabDevices")) return;
|
|
4716
4770
|
// Initialize device lists
|
|
4717
4771
|
yield this.grabDevices();
|
|
4718
4772
|
} catch (error) {
|
|
4773
|
+
if (this._isDestroyed) return;
|
|
4719
4774
|
console.log(error);
|
|
4720
4775
|
this._logger.error(this, "Error initializing devices: " + JSON.stringify(error));
|
|
4721
|
-
|
|
4776
|
+
if (!this._isDestroyed) {
|
|
4777
|
+
yield this.grabDevices();
|
|
4778
|
+
}
|
|
4722
4779
|
}
|
|
4723
4780
|
});
|
|
4724
4781
|
}
|
|
@@ -4727,38 +4784,73 @@
|
|
|
4727
4784
|
* @private
|
|
4728
4785
|
*/
|
|
4729
4786
|
initializeStream() {
|
|
4787
|
+
if (this._isDestroyed) return;
|
|
4730
4788
|
if (this._selectedCamera || this._selectedMicrophone) {
|
|
4731
4789
|
this.startCamera();
|
|
4732
4790
|
}
|
|
4733
4791
|
}
|
|
4792
|
+
/**
|
|
4793
|
+
* Sets up permission listeners with proper cleanup tracking
|
|
4794
|
+
* @private
|
|
4795
|
+
*/
|
|
4734
4796
|
setupPermissionListeners() {
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4797
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
4798
|
+
if (this._isDestroyed) return;
|
|
4799
|
+
try {
|
|
4800
|
+
const cameraQuery = {
|
|
4801
|
+
name: 'camera'
|
|
4802
|
+
};
|
|
4803
|
+
const microphoneQuery = {
|
|
4804
|
+
name: 'microphone'
|
|
4805
|
+
};
|
|
4806
|
+
// Camera permission
|
|
4807
|
+
this._cameraPermissionStatus = yield navigator.permissions.query(cameraQuery);
|
|
4808
|
+
if (this._isDestroyed) return;
|
|
4809
|
+
this._boundCameraPermissionHandler = () => {
|
|
4810
|
+
if (this._isDestroyed) return;
|
|
4811
|
+
this._permissionChecked = false;
|
|
4812
|
+
this.handlePermissionChange('camera', this._cameraPermissionStatus.state);
|
|
4813
|
+
};
|
|
4814
|
+
this._cameraPermissionStatus.addEventListener('change', this._boundCameraPermissionHandler);
|
|
4815
|
+
// Microphone permission
|
|
4816
|
+
this._microphonePermissionStatus = yield navigator.permissions.query(microphoneQuery);
|
|
4817
|
+
if (this._isDestroyed) return;
|
|
4818
|
+
this._boundMicrophonePermissionHandler = () => {
|
|
4819
|
+
if (this._isDestroyed) return;
|
|
4820
|
+
this._permissionChecked = false;
|
|
4821
|
+
this.handlePermissionChange('microphone', this._microphonePermissionStatus.state);
|
|
4822
|
+
};
|
|
4823
|
+
this._microphonePermissionStatus.addEventListener('change', this._boundMicrophonePermissionHandler);
|
|
4824
|
+
} catch (error) {
|
|
4825
|
+
this._logger.warning(this, "Could not set up permission listeners: " + error);
|
|
4826
|
+
}
|
|
4752
4827
|
});
|
|
4753
4828
|
}
|
|
4829
|
+
/**
|
|
4830
|
+
* Removes permission listeners
|
|
4831
|
+
* @private
|
|
4832
|
+
*/
|
|
4833
|
+
removePermissionListeners() {
|
|
4834
|
+
if (this._cameraPermissionStatus && this._boundCameraPermissionHandler) {
|
|
4835
|
+
this._cameraPermissionStatus.removeEventListener('change', this._boundCameraPermissionHandler);
|
|
4836
|
+
this._cameraPermissionStatus = null;
|
|
4837
|
+
this._boundCameraPermissionHandler = null;
|
|
4838
|
+
}
|
|
4839
|
+
if (this._microphonePermissionStatus && this._boundMicrophonePermissionHandler) {
|
|
4840
|
+
this._microphonePermissionStatus.removeEventListener('change', this._boundMicrophonePermissionHandler);
|
|
4841
|
+
this._microphonePermissionStatus = null;
|
|
4842
|
+
this._boundMicrophonePermissionHandler = null;
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4754
4845
|
/**
|
|
4755
4846
|
* Sets up event listener for device changes and handles them appropriately
|
|
4756
4847
|
* @private
|
|
4757
4848
|
*/
|
|
4758
4849
|
setupDeviceChangeListener() {
|
|
4850
|
+
if (this._isDestroyed) return;
|
|
4759
4851
|
this._deviceChangeHandler = () => __awaiter(this, void 0, void 0, function* () {
|
|
4760
4852
|
var _a;
|
|
4761
|
-
if (this.
|
|
4853
|
+
if (this._isDestroyed) return;
|
|
4762
4854
|
if (this._publishState === exports.PublishState.PUBLISHED) {
|
|
4763
4855
|
this._logger.info(this, "Device change detected, but already publish - no restarting streamer");
|
|
4764
4856
|
return;
|
|
@@ -4769,37 +4861,49 @@
|
|
|
4769
4861
|
try {
|
|
4770
4862
|
this.stop();
|
|
4771
4863
|
yield new Promise(resolve => setTimeout(resolve, 500));
|
|
4772
|
-
if (
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
}
|
|
4864
|
+
if (this._isDestroyed) return;
|
|
4865
|
+
yield this.start();
|
|
4866
|
+
if (this._isDestroyed) return;
|
|
4867
|
+
if (wasPublishing && streamKey) {
|
|
4868
|
+
this._logger.info(this, "Resuming publishing after device change");
|
|
4869
|
+
if (this.isStreamReady(true, true)) {
|
|
4870
|
+
this.publish(streamKey);
|
|
4871
|
+
} else {
|
|
4872
|
+
this._logger.warning(this, "Cannot resume publishing - stream not ready after device change");
|
|
4873
|
+
this._main.dispatchEvent("inputDeviceError", {
|
|
4874
|
+
ref: this._main
|
|
4875
|
+
});
|
|
4784
4876
|
}
|
|
4785
4877
|
}
|
|
4786
4878
|
this._logger.success(this, "Successfully handled device change");
|
|
4787
4879
|
} catch (error) {
|
|
4880
|
+
if (this._isDestroyed) return;
|
|
4788
4881
|
this._logger.error(this, "Error handling device change: " + JSON.stringify(error));
|
|
4789
4882
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4790
4883
|
}
|
|
4791
4884
|
});
|
|
4792
4885
|
navigator.mediaDevices.addEventListener('devicechange', this._deviceChangeHandler);
|
|
4793
4886
|
}
|
|
4887
|
+
/**
|
|
4888
|
+
* Handles permission changes for camera or microphone
|
|
4889
|
+
* @private
|
|
4890
|
+
*/
|
|
4794
4891
|
handlePermissionChange(device, state) {
|
|
4795
4892
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4893
|
+
if (this._isDestroyed) return;
|
|
4894
|
+
this._logger.info(this, `📹 [PERMISSION] handlePermissionChange() - device: ${device}, state: ${state}`);
|
|
4796
4895
|
if (state === 'denied') {
|
|
4797
4896
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
4798
|
-
|
|
4799
|
-
|
|
4897
|
+
// Stop the media stream AND WebRTC
|
|
4898
|
+
if (this._publishState == exports.PublishState.CONNECTED || this._stream) {
|
|
4899
|
+
this._logger.info(this, "📹 [RELEASE] handlePermissionChange() - permission denied, stopping all streams");
|
|
4900
|
+
this.stopCameraStream();
|
|
4901
|
+
this.closeWebRTCConnection();
|
|
4800
4902
|
}
|
|
4801
4903
|
}
|
|
4904
|
+
if (this._isDestroyed) return;
|
|
4802
4905
|
yield this.grabDevices();
|
|
4906
|
+
if (this._isDestroyed) return;
|
|
4803
4907
|
if (state === 'granted') {
|
|
4804
4908
|
yield this.startCamera();
|
|
4805
4909
|
}
|
|
@@ -4809,7 +4913,17 @@
|
|
|
4809
4913
|
* Handles successful camera stream initialization
|
|
4810
4914
|
*/
|
|
4811
4915
|
onCameraStreamSuccess(stream) {
|
|
4812
|
-
|
|
4916
|
+
var _a, _b;
|
|
4917
|
+
// CRITICAL: Check if we were destroyed while waiting
|
|
4918
|
+
if (this._isDestroyed) {
|
|
4919
|
+
this._logger.warning(this, "📹 [RELEASE] onCameraStreamSuccess() - destroyed, releasing stream immediately");
|
|
4920
|
+
stream.getTracks().forEach(track => {
|
|
4921
|
+
track.stop();
|
|
4922
|
+
});
|
|
4923
|
+
return;
|
|
4924
|
+
}
|
|
4925
|
+
this._activeStreamCount++;
|
|
4926
|
+
this._logger.success(this, `📹 [ACQUIRED] onCameraStreamSuccess() - stream acquired, id: ${stream.id}, active streams: ${this._activeStreamCount}`);
|
|
4813
4927
|
// Get actual stream dimensions
|
|
4814
4928
|
const videoTrack = stream.getVideoTracks()[0];
|
|
4815
4929
|
const audioTrack = stream.getAudioTracks()[0];
|
|
@@ -4818,7 +4932,6 @@
|
|
|
4818
4932
|
const settings = videoTrack.getSettings();
|
|
4819
4933
|
let width = settings.width;
|
|
4820
4934
|
let height = settings.height;
|
|
4821
|
-
// Na urządzeniach mobilnych potrzebujemy skorygować wymiary
|
|
4822
4935
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
4823
4936
|
if (isMobile) {
|
|
4824
4937
|
if (width > height && window.innerWidth < window.innerHeight || width < height && window.innerWidth > window.innerHeight) {
|
|
@@ -4828,12 +4941,10 @@
|
|
|
4828
4941
|
streamData.streamWidth = width;
|
|
4829
4942
|
streamData.streamHeight = height;
|
|
4830
4943
|
streamData.videoTrackPresent = true;
|
|
4831
|
-
// Obliczanie proporcji i konwersja na string
|
|
4832
4944
|
const gcd = (a, b) => b ? gcd(b, a % b) : a;
|
|
4833
4945
|
const divisor = gcd(width, height);
|
|
4834
4946
|
const widthRatio = width / divisor;
|
|
4835
4947
|
const heightRatio = height / divisor;
|
|
4836
|
-
// Sprawdzamy typowe proporcje z pewną tolerancją
|
|
4837
4948
|
const ratio = width / height;
|
|
4838
4949
|
let aspectRatioString = `${widthRatio}:${heightRatio}`;
|
|
4839
4950
|
if (Math.abs(ratio - 16 / 9) < 0.1) {
|
|
@@ -4842,11 +4953,22 @@
|
|
|
4842
4953
|
aspectRatioString = width > height ? "4:3" : "3:4";
|
|
4843
4954
|
}
|
|
4844
4955
|
streamData.aspectRatio = aspectRatioString;
|
|
4956
|
+
this._logger.info(this, `📹 [INFO] Video track - id: ${videoTrack.id}, enabled: ${videoTrack.enabled}, readyState: ${videoTrack.readyState}`);
|
|
4845
4957
|
} else {
|
|
4846
4958
|
streamData.videoTrackPresent = false;
|
|
4847
4959
|
}
|
|
4960
|
+
if (audioTrack) {
|
|
4961
|
+
this._logger.info(this, `📹 [INFO] Audio track - id: ${audioTrack.id}, enabled: ${audioTrack.enabled}, readyState: ${audioTrack.readyState}`);
|
|
4962
|
+
}
|
|
4848
4963
|
streamData.audioTrackPresent = !!audioTrack;
|
|
4849
4964
|
this._logger.info(this, `Publish MetaData :: Resolution: ${streamData.streamWidth}x${streamData.streamHeight} | ` + `Aspect ratio: ${streamData.aspectRatio}, ` + `Video track: ${streamData.videoTrackPresent} | Audio track: ${streamData.audioTrackPresent}`);
|
|
4965
|
+
// Double-check we weren't destroyed during setup
|
|
4966
|
+
if (this._isDestroyed) {
|
|
4967
|
+
this._logger.warning(this, "📹 [RELEASE] onCameraStreamSuccess() - destroyed during setup, releasing");
|
|
4968
|
+
stream.getTracks().forEach(track => track.stop());
|
|
4969
|
+
this._activeStreamCount--;
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4850
4972
|
// Dispatch event with stream data
|
|
4851
4973
|
this._main.dispatchEvent("publishMetadataUpdate", {
|
|
4852
4974
|
ref: this._main,
|
|
@@ -4865,7 +4987,7 @@
|
|
|
4865
4987
|
if (this._cameraList == null || this._microphoneList == null) {
|
|
4866
4988
|
this.grabDevices();
|
|
4867
4989
|
}
|
|
4868
|
-
const videoElement = this._main.getStageController().getScreenElement().getVideoElement();
|
|
4990
|
+
const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
|
|
4869
4991
|
if (videoElement) {
|
|
4870
4992
|
videoElement.srcObject = stream;
|
|
4871
4993
|
videoElement.autoplay = true;
|
|
@@ -4881,6 +5003,7 @@
|
|
|
4881
5003
|
*/
|
|
4882
5004
|
initializeWebRTC() {
|
|
4883
5005
|
var _a;
|
|
5006
|
+
if (this._isDestroyed) return;
|
|
4884
5007
|
if (!this._stream) {
|
|
4885
5008
|
this._logger.error(this, "Cannot initialize WebRTC - no camera stream available");
|
|
4886
5009
|
return;
|
|
@@ -4894,12 +5017,10 @@
|
|
|
4894
5017
|
}
|
|
4895
5018
|
}
|
|
4896
5019
|
/**
|
|
4897
|
-
*
|
|
4898
|
-
*
|
|
4899
|
-
* @param streamKey - klucz streamu
|
|
4900
|
-
* @returns {boolean} - true jeśli udało się rozpocząć publikowanie
|
|
5020
|
+
* Publish method
|
|
4901
5021
|
*/
|
|
4902
5022
|
publish(streamKey) {
|
|
5023
|
+
if (this._isDestroyed) return false;
|
|
4903
5024
|
if (this._debug) this._logger.decoratedLog("Publishing: " + streamKey, "dark-red");
|
|
4904
5025
|
this._logger.info(this, "Publish: " + streamKey);
|
|
4905
5026
|
if (this._statusTimer != null) clearInterval(this._statusTimer);
|
|
@@ -4920,8 +5041,12 @@
|
|
|
4920
5041
|
this._firstPublish = false;
|
|
4921
5042
|
return true;
|
|
4922
5043
|
}
|
|
5044
|
+
/**
|
|
5045
|
+
* Stops publishing and cleans up WebRTC connection
|
|
5046
|
+
*/
|
|
4923
5047
|
unpublish() {
|
|
4924
5048
|
if (this._debug) this._logger.decoratedLog("Unpublish", "dark-red");
|
|
5049
|
+
this._logger.info(this, "📹 [UNPUBLISH] unpublish() - stopping WebRTC but keeping camera preview");
|
|
4925
5050
|
if (this._statusConnection != null) {
|
|
4926
5051
|
this._statusConnection.destroy();
|
|
4927
5052
|
this._statusConnection = null;
|
|
@@ -4935,13 +5060,23 @@
|
|
|
4935
5060
|
this._main.dispatchEvent("unpublish", {
|
|
4936
5061
|
ref: this._main
|
|
4937
5062
|
});
|
|
4938
|
-
this.
|
|
5063
|
+
this.closeWebRTCConnection();
|
|
5064
|
+
this.setPublishState(exports.PublishState.UNPUBLISHED);
|
|
5065
|
+
}
|
|
5066
|
+
/**
|
|
5067
|
+
* Stops publishing AND releases camera/microphone
|
|
5068
|
+
*/
|
|
5069
|
+
unpublishAndRelease() {
|
|
5070
|
+
this._logger.info(this, "📹 [UNPUBLISH+RELEASE] unpublishAndRelease() - stopping everything");
|
|
5071
|
+
this.unpublish();
|
|
5072
|
+
this.stopCameraStream();
|
|
4939
5073
|
}
|
|
4940
5074
|
/**
|
|
4941
|
-
*
|
|
5075
|
+
* Sets up orientation listener
|
|
4942
5076
|
* @private
|
|
4943
5077
|
*/
|
|
4944
5078
|
setupOrientationListener() {
|
|
5079
|
+
if (this._isDestroyed) return;
|
|
4945
5080
|
this._orientationChangeHandler = this.handleOrientationChange;
|
|
4946
5081
|
if (window.screen && window.screen.orientation) {
|
|
4947
5082
|
window.screen.orientation.addEventListener('change', this._orientationChangeHandler);
|
|
@@ -4953,21 +5088,20 @@
|
|
|
4953
5088
|
// USER MEDIA
|
|
4954
5089
|
//------------------------------------------------------------------------//
|
|
4955
5090
|
/**
|
|
4956
|
-
* Error
|
|
4957
|
-
*
|
|
4958
|
-
* @param error
|
|
5091
|
+
* Error handler for getUserMedia
|
|
4959
5092
|
*/
|
|
4960
5093
|
onUserMediaError(error) {
|
|
4961
5094
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5095
|
+
if (this._isDestroyed) return;
|
|
5096
|
+
this._logger.error(this, `📹 [ERROR] onUserMediaError() - ${error.name}: ${error.message}`);
|
|
4962
5097
|
yield this.grabDevices();
|
|
4963
|
-
// Dodatkowa obsługa specyficznych błędów getUserMedia, jeśli potrzebna
|
|
4964
5098
|
if (error.name === "OverconstrainedError") {
|
|
4965
5099
|
this._logger.warning(this, "Device constraints not satisfied");
|
|
4966
5100
|
}
|
|
4967
5101
|
});
|
|
4968
5102
|
}
|
|
4969
5103
|
/**
|
|
4970
|
-
*
|
|
5104
|
+
* Checks individual device access
|
|
4971
5105
|
* @private
|
|
4972
5106
|
*/
|
|
4973
5107
|
checkIndividualDeviceAccess() {
|
|
@@ -4982,44 +5116,47 @@
|
|
|
4982
5116
|
available: false
|
|
4983
5117
|
}
|
|
4984
5118
|
};
|
|
4985
|
-
|
|
5119
|
+
if (this._isDestroyed) return results;
|
|
4986
5120
|
try {
|
|
4987
5121
|
const devices = yield navigator.mediaDevices.enumerateDevices();
|
|
5122
|
+
if (this._isDestroyed) return results;
|
|
4988
5123
|
results.camera.available = devices.some(device => device.kind === 'videoinput');
|
|
4989
5124
|
results.microphone.available = devices.some(device => device.kind === 'audioinput');
|
|
4990
|
-
// Sprawdzamy czy mamy etykiety urządzeń - ich brak może oznaczać brak uprawnień
|
|
4991
5125
|
const hasLabels = devices.some(device => device.label !== '');
|
|
4992
5126
|
if (!hasLabels) {
|
|
4993
|
-
|
|
5127
|
+
this._logger.info(this, "📹 [ACQUIRE] checkIndividualDeviceAccess() - no labels, requesting permissions");
|
|
5128
|
+
if (this._isDestroyed) return results;
|
|
4994
5129
|
try {
|
|
4995
5130
|
const stream = yield navigator.mediaDevices.getUserMedia({
|
|
4996
5131
|
video: results.camera.available,
|
|
4997
5132
|
audio: results.microphone.available
|
|
4998
5133
|
});
|
|
4999
|
-
//
|
|
5134
|
+
// CRITICAL: Check IMMEDIATELY after getUserMedia
|
|
5135
|
+
if (this._isDestroyed) {
|
|
5136
|
+
this._logger.warning(this, "📹 [RELEASE] checkIndividualDeviceAccess() - destroyed, releasing orphan stream");
|
|
5137
|
+
stream.getTracks().forEach(track => track.stop());
|
|
5138
|
+
return results;
|
|
5139
|
+
}
|
|
5000
5140
|
results.camera.allowed = stream.getVideoTracks().length > 0;
|
|
5001
5141
|
results.microphone.allowed = stream.getAudioTracks().length > 0;
|
|
5002
|
-
|
|
5003
|
-
stream.getTracks().forEach(track =>
|
|
5004
|
-
|
|
5142
|
+
this._logger.info(this, "📹 [RELEASE] checkIndividualDeviceAccess() - stopping test stream");
|
|
5143
|
+
stream.getTracks().forEach(track => {
|
|
5144
|
+
track.stop();
|
|
5145
|
+
this._logger.info(this, `📹 [RELEASE] checkIndividualDeviceAccess() - stopped track: ${track.kind}`);
|
|
5146
|
+
});
|
|
5147
|
+
if (this._isDestroyed) return results;
|
|
5005
5148
|
yield this.grabDevices();
|
|
5006
5149
|
} catch (error) {
|
|
5007
5150
|
console.error('Error requesting permissions:', error);
|
|
5008
|
-
// Nie udało się uzyskać uprawnień
|
|
5009
5151
|
results.camera.allowed = false;
|
|
5010
5152
|
results.microphone.allowed = false;
|
|
5011
5153
|
}
|
|
5012
5154
|
} else {
|
|
5013
|
-
// Jeśli mamy etykiety, prawdopodobnie mamy już uprawnienia
|
|
5014
5155
|
results.camera.allowed = devices.some(device => device.kind === 'videoinput' && device.label !== '');
|
|
5015
5156
|
results.microphone.allowed = devices.some(device => device.kind === 'audioinput' && device.label !== '');
|
|
5016
5157
|
}
|
|
5017
5158
|
} catch (error) {
|
|
5018
5159
|
console.error('Error checking devices:', error);
|
|
5019
|
-
results.camera.available = false;
|
|
5020
|
-
results.microphone.available = false;
|
|
5021
|
-
results.camera.allowed = false;
|
|
5022
|
-
results.microphone.allowed = false;
|
|
5023
5160
|
}
|
|
5024
5161
|
return results;
|
|
5025
5162
|
});
|
|
@@ -5028,21 +5165,18 @@
|
|
|
5028
5165
|
// SOCKETS & SDP
|
|
5029
5166
|
//------------------------------------------------------------------------//
|
|
5030
5167
|
/**
|
|
5031
|
-
*
|
|
5032
|
-
*
|
|
5033
|
-
* @param data
|
|
5168
|
+
* Handles SDP/ICE-Candidate exchange
|
|
5034
5169
|
*/
|
|
5035
5170
|
onSocketMessage(data) {
|
|
5036
5171
|
var _a;
|
|
5172
|
+
if (this._isDestroyed) return;
|
|
5037
5173
|
let msgJSON = JSON.parse(data);
|
|
5038
5174
|
let msgStatus = Number(msgJSON["status"]);
|
|
5039
5175
|
switch (msgStatus) {
|
|
5040
5176
|
case 200:
|
|
5041
|
-
// OK
|
|
5042
5177
|
this._logger.info(this, "SDP Exchange Successful");
|
|
5043
5178
|
let sdpData = msgJSON['sdp'];
|
|
5044
|
-
if (sdpData !== undefined) {
|
|
5045
|
-
// @ts-ignore
|
|
5179
|
+
if (sdpData !== undefined && this._peerConnection) {
|
|
5046
5180
|
this._peerConnection.setRemoteDescription(new RTCSessionDescription(sdpData), () => {}, () => {});
|
|
5047
5181
|
}
|
|
5048
5182
|
let iceCandidates = msgJSON['iceCandidates'];
|
|
@@ -5053,7 +5187,6 @@
|
|
|
5053
5187
|
}
|
|
5054
5188
|
break;
|
|
5055
5189
|
case 503:
|
|
5056
|
-
// NOT OK
|
|
5057
5190
|
this._logger.error(this, "StreamKey already use");
|
|
5058
5191
|
const usedStreamKey = (_a = this._main.getConfigManager().getStreamData().streamKey) !== null && _a !== void 0 ? _a : "unknown";
|
|
5059
5192
|
this._main.dispatchEvent("streamKeyInUse", {
|
|
@@ -5067,14 +5200,8 @@
|
|
|
5067
5200
|
//------------------------------------------------------------------------//
|
|
5068
5201
|
// EVENTS
|
|
5069
5202
|
//------------------------------------------------------------------------//
|
|
5070
|
-
/**
|
|
5071
|
-
* Recives events related to peerConnection (change of state)
|
|
5072
|
-
*
|
|
5073
|
-
* @param event event with its data
|
|
5074
|
-
* @param thisRef reference to player classonConnectionStateChange
|
|
5075
|
-
* @private
|
|
5076
|
-
*/
|
|
5077
5203
|
onConnectionStateChange(event) {
|
|
5204
|
+
if (this._isDestroyed) return;
|
|
5078
5205
|
this._logger.info(this, "Connection State Change: " + JSON.stringify(event));
|
|
5079
5206
|
if (event !== null) {
|
|
5080
5207
|
switch (event.currentTarget.connectionState) {
|
|
@@ -5105,16 +5232,16 @@
|
|
|
5105
5232
|
// DEVICES
|
|
5106
5233
|
//------------------------------------------------------------------------//
|
|
5107
5234
|
/**
|
|
5108
|
-
*
|
|
5235
|
+
* Grabs available devices
|
|
5109
5236
|
*/
|
|
5110
5237
|
grabDevices() {
|
|
5111
5238
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5239
|
+
if (this._isDestroyed) return;
|
|
5112
5240
|
try {
|
|
5113
5241
|
const deviceAccess = yield this.checkIndividualDeviceAccess();
|
|
5242
|
+
if (this._isDestroyed) return;
|
|
5114
5243
|
this._cameraList = new InputDeviceList();
|
|
5115
5244
|
this._microphoneList = new InputDeviceList();
|
|
5116
|
-
// Wysyłamy eventy tylko jeśli nie sprawdzaliśmy wcześniej uprawnień
|
|
5117
|
-
// lub jeśli zmienił się stan uprawnień (resetowana flaga)
|
|
5118
5245
|
if (!this._permissionChecked) {
|
|
5119
5246
|
if (!deviceAccess.camera.allowed) {
|
|
5120
5247
|
this._main.dispatchEvent("cameraAccessDenied", {
|
|
@@ -5143,8 +5270,9 @@
|
|
|
5143
5270
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5144
5271
|
}
|
|
5145
5272
|
}
|
|
5146
|
-
|
|
5273
|
+
if (this._isDestroyed) return;
|
|
5147
5274
|
const devices = yield navigator.mediaDevices.enumerateDevices();
|
|
5275
|
+
if (this._isDestroyed) return;
|
|
5148
5276
|
for (const device of devices) {
|
|
5149
5277
|
if (device.deviceId && device.label) {
|
|
5150
5278
|
if (device.kind === 'videoinput' && deviceAccess.camera.allowed) {
|
|
@@ -5156,8 +5284,8 @@
|
|
|
5156
5284
|
}
|
|
5157
5285
|
}
|
|
5158
5286
|
}
|
|
5287
|
+
if (this._isDestroyed) return;
|
|
5159
5288
|
try {
|
|
5160
|
-
// Aktualizacja wybranych urządzeń
|
|
5161
5289
|
if (deviceAccess.camera.allowed) {
|
|
5162
5290
|
this._selectedCamera = this.pickCamera();
|
|
5163
5291
|
}
|
|
@@ -5167,38 +5295,41 @@
|
|
|
5167
5295
|
} catch (error) {
|
|
5168
5296
|
console.log(error);
|
|
5169
5297
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5170
|
-
this._logger.error(this, "
|
|
5298
|
+
this._logger.error(this, "Error on grab devices: " + JSON.stringify(error));
|
|
5299
|
+
}
|
|
5300
|
+
if (!this._isDestroyed) {
|
|
5301
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5302
|
+
ref: this._main,
|
|
5303
|
+
cameraList: this._cameraList.getArray(),
|
|
5304
|
+
microphoneList: this._microphoneList.getArray()
|
|
5305
|
+
});
|
|
5171
5306
|
}
|
|
5172
|
-
// Zawsze wysyłamy aktualizację list urządzeń
|
|
5173
|
-
this._main.dispatchEvent("deviceListUpdate", {
|
|
5174
|
-
ref: this._main,
|
|
5175
|
-
cameraList: this._cameraList.getArray(),
|
|
5176
|
-
microphoneList: this._microphoneList.getArray()
|
|
5177
|
-
});
|
|
5178
5307
|
this._permissionChecked = true;
|
|
5179
5308
|
} catch (error) {
|
|
5309
|
+
if (this._isDestroyed) return;
|
|
5180
5310
|
console.error("Error in grabDevices:", error);
|
|
5181
5311
|
this._cameraList = new InputDeviceList();
|
|
5182
5312
|
this._microphoneList = new InputDeviceList();
|
|
5183
|
-
this.
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5313
|
+
if (!this._isDestroyed) {
|
|
5314
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5315
|
+
ref: this._main,
|
|
5316
|
+
cameraList: this._cameraList.getArray(),
|
|
5317
|
+
microphoneList: this._microphoneList.getArray()
|
|
5318
|
+
});
|
|
5319
|
+
this._main.dispatchEvent("inputDeviceError", {
|
|
5320
|
+
ref: this._main
|
|
5321
|
+
});
|
|
5322
|
+
}
|
|
5191
5323
|
}
|
|
5192
5324
|
});
|
|
5193
5325
|
}
|
|
5194
5326
|
/**
|
|
5195
|
-
* Selects camera
|
|
5196
|
-
* @param cameraID
|
|
5327
|
+
* Selects camera by ID
|
|
5197
5328
|
*/
|
|
5198
5329
|
selectCamera(cameraID) {
|
|
5199
5330
|
var _a, _b, _c;
|
|
5200
5331
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5201
|
-
|
|
5332
|
+
if (this._isDestroyed) return;
|
|
5202
5333
|
if (this._cameraAbortController) {
|
|
5203
5334
|
this._cameraAbortController.abort();
|
|
5204
5335
|
}
|
|
@@ -5206,13 +5337,13 @@
|
|
|
5206
5337
|
const signal = this._cameraAbortController.signal;
|
|
5207
5338
|
try {
|
|
5208
5339
|
this._switchingCamera = true;
|
|
5340
|
+
this._logger.info(this, `📹 [SWITCH] selectCamera() - switching to camera: ${cameraID}`);
|
|
5209
5341
|
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5210
5342
|
this._cameraList.get(i).isSelected = false;
|
|
5211
5343
|
}
|
|
5212
5344
|
this._selectedCamera = null;
|
|
5213
5345
|
this.setInputDeviceState(exports.InputDevicesState.UPDATING);
|
|
5214
5346
|
this.setCameraState(exports.DeviceState.NOT_INITIALIZED);
|
|
5215
|
-
// Zapamiętaj aktualny stream key i stan publikacji
|
|
5216
5347
|
const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
5217
5348
|
const wasPublished = this._publishState === exports.PublishState.CONNECTED;
|
|
5218
5349
|
let found = false;
|
|
@@ -5230,14 +5361,11 @@
|
|
|
5230
5361
|
cameraList: this._cameraList.getArray(),
|
|
5231
5362
|
microphoneList: this._microphoneList.getArray()
|
|
5232
5363
|
});
|
|
5233
|
-
if (signal.aborted) return;
|
|
5364
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5234
5365
|
this.stopCameraStream();
|
|
5235
5366
|
if (this._selectedCamera != null) {
|
|
5236
|
-
// Update constraints with new device
|
|
5237
5367
|
this._constraints.video.deviceId = this._selectedCamera.id;
|
|
5238
|
-
|
|
5239
|
-
if (signal.aborted) return;
|
|
5240
|
-
// Poczekaj z możliwością anulowania
|
|
5368
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5241
5369
|
yield new Promise((resolve, reject) => {
|
|
5242
5370
|
const timeout = setTimeout(resolve, 500);
|
|
5243
5371
|
signal.addEventListener('abort', () => {
|
|
@@ -5245,20 +5373,20 @@
|
|
|
5245
5373
|
reject(new Error('Aborted'));
|
|
5246
5374
|
});
|
|
5247
5375
|
});
|
|
5248
|
-
if (signal.aborted) return;
|
|
5249
|
-
// Restart camera stream
|
|
5376
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5250
5377
|
yield this.startCamera();
|
|
5378
|
+
if (this._isDestroyed) return;
|
|
5251
5379
|
this.setCameraState(exports.DeviceState.ENABLED);
|
|
5252
5380
|
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) {
|
|
5381
|
+
if (wasPublished && streamKey && !signal.aborted && !this._isDestroyed) {
|
|
5254
5382
|
this.publish(streamKey);
|
|
5255
5383
|
}
|
|
5256
5384
|
} else {
|
|
5257
5385
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5258
5386
|
}
|
|
5259
5387
|
} catch (error) {
|
|
5260
|
-
if (error.message !== 'Aborted') {
|
|
5261
|
-
this._logger.error(this, 'Error switching camera: ' + error);
|
|
5388
|
+
if (error.message !== 'Aborted' && !this._isDestroyed) {
|
|
5389
|
+
this._logger.error(this, '📹 [ERROR] selectCamera() - Error switching camera: ' + error);
|
|
5262
5390
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5263
5391
|
}
|
|
5264
5392
|
} finally {
|
|
@@ -5270,13 +5398,12 @@
|
|
|
5270
5398
|
});
|
|
5271
5399
|
}
|
|
5272
5400
|
/**
|
|
5273
|
-
*
|
|
5274
|
-
* @param micID
|
|
5401
|
+
* Selects microphone by ID
|
|
5275
5402
|
*/
|
|
5276
5403
|
selectMicrophone(micID) {
|
|
5277
5404
|
var _a, _b, _c;
|
|
5278
5405
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5279
|
-
|
|
5406
|
+
if (this._isDestroyed) return;
|
|
5280
5407
|
if (this._microphoneAbortController) {
|
|
5281
5408
|
this._microphoneAbortController.abort();
|
|
5282
5409
|
}
|
|
@@ -5284,17 +5411,15 @@
|
|
|
5284
5411
|
const signal = this._microphoneAbortController.signal;
|
|
5285
5412
|
try {
|
|
5286
5413
|
this._switchingMicrophone = true;
|
|
5414
|
+
this._logger.info(this, `📹 [SWITCH] selectMicrophone() - switching to microphone: ${micID}`);
|
|
5287
5415
|
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5288
5416
|
this._microphoneList.get(i).isSelected = false;
|
|
5289
5417
|
}
|
|
5290
5418
|
this._selectedMicrophone = null;
|
|
5291
5419
|
this.setInputDeviceState(exports.InputDevicesState.UPDATING);
|
|
5292
5420
|
this.setMicrophoneState(exports.DeviceState.NOT_INITIALIZED);
|
|
5293
|
-
this._logger.info(this, "Selecting microphone: " + micID);
|
|
5294
|
-
// Zapamiętaj aktualny stream key i stan publikacji
|
|
5295
5421
|
const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
5296
5422
|
const wasPublished = this._publishState === exports.PublishState.CONNECTED;
|
|
5297
|
-
// Znajdź i zapisz wybrany mikrofon
|
|
5298
5423
|
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5299
5424
|
if (this._microphoneList.get(i).id == micID) {
|
|
5300
5425
|
this._selectedMicrophone = this._microphoneList.get(i);
|
|
@@ -5303,26 +5428,23 @@
|
|
|
5303
5428
|
break;
|
|
5304
5429
|
}
|
|
5305
5430
|
}
|
|
5306
|
-
// Zawsze wysyłamy aktualizację list urządzeń
|
|
5307
5431
|
this._main.dispatchEvent("deviceListUpdate", {
|
|
5308
5432
|
ref: this._main,
|
|
5309
5433
|
cameraList: this._cameraList.getArray(),
|
|
5310
5434
|
microphoneList: this._microphoneList.getArray()
|
|
5311
5435
|
});
|
|
5312
|
-
if (signal.aborted) return;
|
|
5313
|
-
// Odłącz SoundMeter przed zmianą strumienia
|
|
5436
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5314
5437
|
this._soundMeter.detach();
|
|
5315
|
-
// Zamknij istniejące połączenie WebRTC
|
|
5316
5438
|
this.closeWebRTCConnection();
|
|
5317
|
-
// Zatrzymaj obecny strumień
|
|
5318
5439
|
if (this._stream) {
|
|
5440
|
+
this._logger.info(this, "📹 [RELEASE] selectMicrophone() - stopping current stream");
|
|
5319
5441
|
this._stream.getTracks().forEach(track => {
|
|
5320
5442
|
track.stop();
|
|
5321
5443
|
});
|
|
5322
5444
|
this._stream = null;
|
|
5445
|
+
this._activeStreamCount--;
|
|
5323
5446
|
}
|
|
5324
|
-
if (signal.aborted) return;
|
|
5325
|
-
// Poczekaj z możliwością anulowania
|
|
5447
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5326
5448
|
yield new Promise((resolve, reject) => {
|
|
5327
5449
|
const timeout = setTimeout(resolve, 500);
|
|
5328
5450
|
signal.addEventListener('abort', () => {
|
|
@@ -5330,21 +5452,20 @@
|
|
|
5330
5452
|
reject(new Error('Aborted'));
|
|
5331
5453
|
});
|
|
5332
5454
|
});
|
|
5333
|
-
if (signal.aborted) return;
|
|
5334
|
-
// Rozpocznij wszystko od nowa
|
|
5455
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5335
5456
|
yield this.startCamera();
|
|
5457
|
+
if (this._isDestroyed) return;
|
|
5336
5458
|
this.setMicrophoneState(exports.DeviceState.ENABLED);
|
|
5337
5459
|
if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) {
|
|
5338
5460
|
this.setInputDeviceState(exports.InputDevicesState.READY);
|
|
5339
5461
|
} else {
|
|
5340
5462
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5341
5463
|
}
|
|
5342
|
-
|
|
5343
|
-
if (wasPublished && streamKey && !signal.aborted) {
|
|
5464
|
+
if (wasPublished && streamKey && !signal.aborted && !this._isDestroyed) {
|
|
5344
5465
|
this.publish(streamKey);
|
|
5345
5466
|
}
|
|
5346
5467
|
} catch (error) {
|
|
5347
|
-
if (error.message !== 'Aborted') {
|
|
5468
|
+
if (error.message !== 'Aborted' && !this._isDestroyed) {
|
|
5348
5469
|
console.error("Error changing microphone:", error);
|
|
5349
5470
|
this._main.dispatchEvent("inputDeviceError", {
|
|
5350
5471
|
ref: this._main
|
|
@@ -5360,23 +5481,29 @@
|
|
|
5360
5481
|
});
|
|
5361
5482
|
}
|
|
5362
5483
|
/**
|
|
5363
|
-
*
|
|
5484
|
+
* Starts camera with abort support
|
|
5364
5485
|
* @private
|
|
5365
5486
|
*/
|
|
5366
5487
|
startCamera() {
|
|
5367
5488
|
var _a;
|
|
5368
5489
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5369
|
-
|
|
5490
|
+
if (this._isDestroyed) {
|
|
5491
|
+
this._logger.warning(this, "📹 [ACQUIRE] startCamera() - aborted, instance is destroyed");
|
|
5492
|
+
return;
|
|
5493
|
+
}
|
|
5370
5494
|
if (this._startCameraAbortController) {
|
|
5371
5495
|
this._startCameraAbortController.abort();
|
|
5372
5496
|
}
|
|
5373
5497
|
this._startCameraAbortController = new AbortController();
|
|
5374
5498
|
const signal = this._startCameraAbortController.signal;
|
|
5499
|
+
// Release existing stream first
|
|
5375
5500
|
if (this._stream) {
|
|
5501
|
+
this._logger.info(this, "📹 [RELEASE] startCamera() - releasing existing stream before acquiring new one");
|
|
5376
5502
|
this._stream.getTracks().forEach(track => {
|
|
5377
5503
|
track.stop();
|
|
5378
5504
|
});
|
|
5379
5505
|
this._stream = null;
|
|
5506
|
+
this._activeStreamCount--;
|
|
5380
5507
|
}
|
|
5381
5508
|
try {
|
|
5382
5509
|
const constraints = {
|
|
@@ -5391,18 +5518,24 @@
|
|
|
5391
5518
|
}
|
|
5392
5519
|
} : false
|
|
5393
5520
|
};
|
|
5394
|
-
if (signal.aborted) return;
|
|
5521
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5522
|
+
this._logger.info(this, `📹 [ACQUIRE] startCamera() - requesting getUserMedia`);
|
|
5395
5523
|
try {
|
|
5396
5524
|
const stream = yield navigator.mediaDevices.getUserMedia(constraints);
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5525
|
+
// CRITICAL: Check IMMEDIATELY after getUserMedia returns
|
|
5526
|
+
if (signal.aborted || this._isDestroyed) {
|
|
5527
|
+
this._logger.warning(this, "📹 [RELEASE] startCamera() - destroyed during getUserMedia, releasing orphan stream");
|
|
5528
|
+
stream.getTracks().forEach(track => {
|
|
5529
|
+
track.stop();
|
|
5530
|
+
this._logger.info(this, `📹 [RELEASE] startCamera() - stopped orphan track: ${track.kind}, id: ${track.id}`);
|
|
5531
|
+
});
|
|
5400
5532
|
return;
|
|
5401
5533
|
}
|
|
5402
5534
|
this._stream = stream;
|
|
5403
5535
|
this.onCameraStreamSuccess(this._stream);
|
|
5404
5536
|
} catch (error) {
|
|
5405
|
-
if (signal.aborted) return;
|
|
5537
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5538
|
+
this._logger.error(this, `📹 [ERROR] startCamera() - getUserMedia failed: ${error.name}: ${error.message}`);
|
|
5406
5539
|
if (constraints.video) {
|
|
5407
5540
|
this.onUserMediaError({
|
|
5408
5541
|
name: error.name || 'Error',
|
|
@@ -5420,6 +5553,7 @@
|
|
|
5420
5553
|
}
|
|
5421
5554
|
if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) this.setInputDeviceState(exports.InputDevicesState.READY);
|
|
5422
5555
|
} catch (error) {
|
|
5556
|
+
if (this._isDestroyed) return;
|
|
5423
5557
|
console.error("Error in startCamera:", error);
|
|
5424
5558
|
yield this.grabDevices();
|
|
5425
5559
|
} finally {
|
|
@@ -5433,15 +5567,14 @@
|
|
|
5433
5567
|
* Updates WebRTC connection with new stream
|
|
5434
5568
|
*/
|
|
5435
5569
|
updateWebRTCStream() {
|
|
5570
|
+
if (this._isDestroyed) return;
|
|
5436
5571
|
if (!this._peerConnection || !this._stream) {
|
|
5437
5572
|
return;
|
|
5438
5573
|
}
|
|
5439
|
-
// Remove all existing tracks from the peer connection
|
|
5440
5574
|
const senders = this._peerConnection.getSenders();
|
|
5441
5575
|
senders.forEach(sender => {
|
|
5442
5576
|
if (this._peerConnection) this._peerConnection.removeTrack(sender);
|
|
5443
5577
|
});
|
|
5444
|
-
// Add new tracks
|
|
5445
5578
|
this._stream.getTracks().forEach(track => {
|
|
5446
5579
|
if (this._stream != null && this._peerConnection) {
|
|
5447
5580
|
this._peerConnection.addTrack(track, this._stream);
|
|
@@ -5449,32 +5582,24 @@
|
|
|
5449
5582
|
});
|
|
5450
5583
|
}
|
|
5451
5584
|
/**
|
|
5452
|
-
*
|
|
5453
|
-
*/
|
|
5454
|
-
closeStream() {
|
|
5455
|
-
if (this._peerConnection !== undefined && this._peerConnection !== null) this._peerConnection.close();
|
|
5456
|
-
this.setPublishState(exports.PublishState.UNPUBLISHED);
|
|
5457
|
-
}
|
|
5458
|
-
/**
|
|
5459
|
-
* This method selects a camera based on previous uses or saved IDs
|
|
5585
|
+
* Picks camera based on saved ID or defaults
|
|
5460
5586
|
* @private
|
|
5461
5587
|
*/
|
|
5462
5588
|
pickCamera() {
|
|
5463
5589
|
var _a, _b, _c, _d, _e, _f;
|
|
5590
|
+
if (this._isDestroyed) return null;
|
|
5464
5591
|
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5465
5592
|
this._cameraList.get(i).isSelected = false;
|
|
5466
5593
|
}
|
|
5467
5594
|
let savedCameraID = (_b = (_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.getField("cameraID")) !== null && _b !== void 0 ? _b : null;
|
|
5468
5595
|
if (this._cameraList.getSize() > 0) {
|
|
5469
5596
|
if (savedCameraID) {
|
|
5470
|
-
// Szukamy zapisanej kamery
|
|
5471
5597
|
let found = false;
|
|
5472
5598
|
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5473
5599
|
if (this._cameraList.get(i).id === savedCameraID) {
|
|
5474
5600
|
this._selectedCamera = this._cameraList.get(i);
|
|
5475
5601
|
this._selectedCamera.isSelected = true;
|
|
5476
5602
|
this.setCameraState(exports.DeviceState.ENABLED);
|
|
5477
|
-
// Ustaw deviceId w constraints
|
|
5478
5603
|
found = true;
|
|
5479
5604
|
this._constraints.video.deviceId = this._selectedCamera.id;
|
|
5480
5605
|
break;
|
|
@@ -5491,10 +5616,8 @@
|
|
|
5491
5616
|
this._logger.info(this, "Canceling Publish!");
|
|
5492
5617
|
this._main.getConfigManager().getStreamData().streamKey = null;
|
|
5493
5618
|
}
|
|
5494
|
-
return null;
|
|
5495
5619
|
}
|
|
5496
5620
|
}
|
|
5497
|
-
// Jeśli nie znaleziono zapisanej kamery, używamy pierwszej
|
|
5498
5621
|
if (!this._selectedCamera) {
|
|
5499
5622
|
this._main.dispatchEvent("savedCameraNotFound", {
|
|
5500
5623
|
ref: this._main,
|
|
@@ -5517,19 +5640,22 @@
|
|
|
5517
5640
|
}
|
|
5518
5641
|
}
|
|
5519
5642
|
}
|
|
5520
|
-
this.
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5643
|
+
if (!this._isDestroyed) {
|
|
5644
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5645
|
+
ref: this._main,
|
|
5646
|
+
cameraList: this._cameraList.getArray(),
|
|
5647
|
+
microphoneList: this._microphoneList.getArray()
|
|
5648
|
+
});
|
|
5649
|
+
}
|
|
5525
5650
|
return this._selectedCamera;
|
|
5526
5651
|
}
|
|
5527
5652
|
/**
|
|
5528
|
-
*
|
|
5653
|
+
* Picks microphone based on saved ID or defaults
|
|
5529
5654
|
* @private
|
|
5530
5655
|
*/
|
|
5531
5656
|
pickMicrophone() {
|
|
5532
5657
|
var _a, _b, _c, _d, _e, _f;
|
|
5658
|
+
if (this._isDestroyed) return null;
|
|
5533
5659
|
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5534
5660
|
this._microphoneList.get(i).isSelected = false;
|
|
5535
5661
|
}
|
|
@@ -5556,7 +5682,6 @@
|
|
|
5556
5682
|
this._logger.info(this, "Canceling Publish!");
|
|
5557
5683
|
this._main.getConfigManager().getStreamData().streamKey = null;
|
|
5558
5684
|
}
|
|
5559
|
-
return null;
|
|
5560
5685
|
}
|
|
5561
5686
|
}
|
|
5562
5687
|
if (!this._selectedMicrophone) {
|
|
@@ -5585,73 +5710,43 @@
|
|
|
5585
5710
|
}
|
|
5586
5711
|
return this._selectedMicrophone;
|
|
5587
5712
|
}
|
|
5588
|
-
/**
|
|
5589
|
-
* Cleans all saved cameras and microphones IDs.
|
|
5590
|
-
*/
|
|
5591
5713
|
clearSavedDevices() {
|
|
5592
5714
|
var _a, _b;
|
|
5593
5715
|
(_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.removeField("cameraID");
|
|
5594
5716
|
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.removeField("microphoneID");
|
|
5595
5717
|
}
|
|
5596
|
-
/**
|
|
5597
|
-
* Messes up camera's and microphone's id (for testing only)
|
|
5598
|
-
*/
|
|
5599
5718
|
messSavedDevices() {
|
|
5600
5719
|
var _a, _b;
|
|
5601
5720
|
(_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.saveField("cameraID", "a");
|
|
5602
5721
|
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("microphoneID", "b");
|
|
5603
5722
|
}
|
|
5604
|
-
/**
|
|
5605
|
-
* Handles microphone muting state
|
|
5606
|
-
* @param microphoneState true to unmute, false to mute
|
|
5607
|
-
*/
|
|
5608
5723
|
muteMicrophone(shouldMute) {
|
|
5724
|
+
var _a;
|
|
5725
|
+
if (this._isDestroyed) return;
|
|
5609
5726
|
if (this._isMicrophoneMuted === shouldMute) {
|
|
5610
|
-
// State hasn't changed, no need to do anything
|
|
5611
5727
|
return;
|
|
5612
5728
|
}
|
|
5613
5729
|
this._isMicrophoneMuted = shouldMute;
|
|
5730
|
+
(_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.saveField("microphoneMuted", shouldMute ? "true" : "false");
|
|
5614
5731
|
if (this._stream) {
|
|
5615
|
-
this.applyMicrophoneState(!shouldMute);
|
|
5732
|
+
this.applyMicrophoneState(!shouldMute);
|
|
5616
5733
|
} else {
|
|
5617
|
-
|
|
5618
|
-
this._pendingMicrophoneState = !shouldMute; // Odwracamy wartość dla przyszłego track.enabled
|
|
5619
|
-
this._logger.info(this, `WebRTCStreamer :: Stream not yet available, storing microphone state (muted: ${shouldMute})`);
|
|
5734
|
+
this._pendingMicrophoneState = !shouldMute;
|
|
5620
5735
|
}
|
|
5621
|
-
// Always dispatch the event to keep UI in sync
|
|
5622
5736
|
this._main.dispatchEvent("microphoneStateChange", {
|
|
5623
5737
|
ref: this._main,
|
|
5624
5738
|
isMuted: this._isMicrophoneMuted
|
|
5625
5739
|
});
|
|
5626
5740
|
}
|
|
5627
|
-
/**
|
|
5628
|
-
* Applies the microphone state to the actual stream tracks
|
|
5629
|
-
* @param enabled true to enable tracks, false to disable
|
|
5630
|
-
* @private
|
|
5631
|
-
*/
|
|
5632
5741
|
applyMicrophoneState(enabled) {
|
|
5633
|
-
if (!this._stream)
|
|
5634
|
-
this._logger.warning(this, "WebRTCStreamer :: Cannot apply microphone state - stream not available");
|
|
5635
|
-
return;
|
|
5636
|
-
}
|
|
5742
|
+
if (!this._stream) return;
|
|
5637
5743
|
const audioTracks = this._stream.getAudioTracks();
|
|
5638
5744
|
if (audioTracks && audioTracks.length > 0) {
|
|
5639
|
-
this._logger.success(this, `WebRTCStreamer :: ${enabled ? 'Unmuting' : 'Muting'} microphone`);
|
|
5640
5745
|
audioTracks.forEach(track => track.enabled = enabled);
|
|
5641
|
-
} else {
|
|
5642
|
-
this._logger.warning(this, "WebRTCStreamer :: No audio tracks found in stream");
|
|
5643
5746
|
}
|
|
5644
5747
|
}
|
|
5645
|
-
/**
|
|
5646
|
-
* This methods is a final check whenever we're ready to publish a stream
|
|
5647
|
-
* @param requireVideo - whenever video track is required
|
|
5648
|
-
* @param requireAudio - whenever audio track is required
|
|
5649
|
-
* @returns {boolean} true if stream is ready for publishing
|
|
5650
|
-
*/
|
|
5651
5748
|
isStreamReady(requireVideo = true, requireAudio = true) {
|
|
5652
|
-
if (!this._stream)
|
|
5653
|
-
return false;
|
|
5654
|
-
}
|
|
5749
|
+
if (!this._stream) return false;
|
|
5655
5750
|
const videoTracks = this._stream.getVideoTracks();
|
|
5656
5751
|
const audioTracks = this._stream.getAudioTracks();
|
|
5657
5752
|
const videoReady = !requireVideo || videoTracks.length > 0 && videoTracks[0].readyState === 'live';
|
|
@@ -5660,51 +5755,18 @@
|
|
|
5660
5755
|
}
|
|
5661
5756
|
closeWebRTCConnection() {
|
|
5662
5757
|
if (this._peerConnection) {
|
|
5663
|
-
this.
|
|
5758
|
+
this._logger.info(this, "📡 [WEBRTC] closeWebRTCConnection() - closing peer connection");
|
|
5759
|
+
this._peerConnection.onicecandidate = null;
|
|
5760
|
+
this._peerConnection.onconnectionstatechange = null;
|
|
5761
|
+
this._peerConnection.onnegotiationneeded = null;
|
|
5762
|
+
try {
|
|
5763
|
+
this._peerConnection.close();
|
|
5764
|
+
} catch (e) {
|
|
5765
|
+
// Ignore
|
|
5766
|
+
}
|
|
5664
5767
|
this._peerConnection = null;
|
|
5665
5768
|
}
|
|
5666
5769
|
}
|
|
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
|
-
}
|
|
5708
5770
|
onDescriptionError(error) {
|
|
5709
5771
|
this._logger.info(this, "WebRTCStreamer :: onDescriptionError: " + JSON.stringify(error));
|
|
5710
5772
|
}
|
|
@@ -5716,6 +5778,7 @@
|
|
|
5716
5778
|
//------------------------------------------------------------------------//
|
|
5717
5779
|
createStatusConnection() {
|
|
5718
5780
|
var _a, _b, _c;
|
|
5781
|
+
if (this._isDestroyed) return;
|
|
5719
5782
|
const serverItem = (_c = (_b = (_a = this._main) === null || _a === void 0 ? void 0 : _a.getNetworkController()) === null || _b === void 0 ? void 0 : _b.getConnection()) === null || _c === void 0 ? void 0 : _c.getCurrentServer();
|
|
5720
5783
|
if (!serverItem) return;
|
|
5721
5784
|
if (this._statusConnection) {
|
|
@@ -5736,6 +5799,7 @@
|
|
|
5736
5799
|
}
|
|
5737
5800
|
setPublishState(newState) {
|
|
5738
5801
|
if (this._publishState == newState) return;
|
|
5802
|
+
if (this._isDestroyed) return;
|
|
5739
5803
|
if (this._debug) this._logger.decoratedLog("Publish State: " + newState, "dark-blue");
|
|
5740
5804
|
this._logger.info(this, "Publish State: " + newState);
|
|
5741
5805
|
if (newState == exports.PublishState.PUBLISHED) this._publishTime = new Date().getTime();
|
|
@@ -5748,9 +5812,9 @@
|
|
|
5748
5812
|
getPublishTime() {
|
|
5749
5813
|
return this._publishState == exports.PublishState.PUBLISHED ? this._publishTime : 0;
|
|
5750
5814
|
}
|
|
5751
|
-
// DEVICE STATE
|
|
5752
5815
|
setInputDeviceState(newState) {
|
|
5753
5816
|
if (this._inputDeviceState == newState) return;
|
|
5817
|
+
if (this._isDestroyed) return;
|
|
5754
5818
|
this._inputDeviceState = newState;
|
|
5755
5819
|
this._main.dispatchEvent("deviceStateChange", {
|
|
5756
5820
|
ref: this._main,
|
|
@@ -5762,9 +5826,9 @@
|
|
|
5762
5826
|
getInputDeviceState() {
|
|
5763
5827
|
return this._inputDeviceState;
|
|
5764
5828
|
}
|
|
5765
|
-
// CAMERA STATE
|
|
5766
5829
|
setCameraState(newState) {
|
|
5767
5830
|
if (this._cameraState == newState) return;
|
|
5831
|
+
if (this._isDestroyed) return;
|
|
5768
5832
|
this._cameraState = newState;
|
|
5769
5833
|
this._main.dispatchEvent("cameraDeviceStateChange", {
|
|
5770
5834
|
ref: this._main,
|
|
@@ -5775,9 +5839,9 @@
|
|
|
5775
5839
|
getCameraState() {
|
|
5776
5840
|
return this._cameraState;
|
|
5777
5841
|
}
|
|
5778
|
-
// MICROPHONE STATE
|
|
5779
5842
|
setMicrophoneState(newState) {
|
|
5780
5843
|
if (this._microphoneState == newState) return;
|
|
5844
|
+
if (this._isDestroyed) return;
|
|
5781
5845
|
this._microphoneState = newState;
|
|
5782
5846
|
this._main.dispatchEvent("microphoneDeviceStateChange", {
|
|
5783
5847
|
ref: this._main,
|
|
@@ -5798,108 +5862,70 @@
|
|
|
5798
5862
|
return this._publishState;
|
|
5799
5863
|
}
|
|
5800
5864
|
//------------------------------------------------------------------------//
|
|
5865
|
+
// DEBUG
|
|
5866
|
+
//------------------------------------------------------------------------//
|
|
5867
|
+
debugMediaState() {
|
|
5868
|
+
var _a, _b;
|
|
5869
|
+
console.group("🎥 Media Debug State");
|
|
5870
|
+
console.log("=== STREAM INFO ===");
|
|
5871
|
+
console.log("this._stream:", this._stream);
|
|
5872
|
+
console.log("Active stream count:", this._activeStreamCount);
|
|
5873
|
+
console.log("Is destroyed:", this._isDestroyed);
|
|
5874
|
+
if (this._stream) {
|
|
5875
|
+
console.log("Stream ID:", this._stream.id);
|
|
5876
|
+
console.log("Stream active:", this._stream.active);
|
|
5877
|
+
console.log("--- Video Tracks ---");
|
|
5878
|
+
this._stream.getVideoTracks().forEach((track, index) => {
|
|
5879
|
+
console.log(` Track ${index}:`, {
|
|
5880
|
+
id: track.id,
|
|
5881
|
+
enabled: track.enabled,
|
|
5882
|
+
readyState: track.readyState,
|
|
5883
|
+
muted: track.muted
|
|
5884
|
+
});
|
|
5885
|
+
});
|
|
5886
|
+
console.log("--- Audio Tracks ---");
|
|
5887
|
+
this._stream.getAudioTracks().forEach((track, index) => {
|
|
5888
|
+
console.log(` Track ${index}:`, {
|
|
5889
|
+
id: track.id,
|
|
5890
|
+
enabled: track.enabled,
|
|
5891
|
+
readyState: track.readyState,
|
|
5892
|
+
muted: track.muted
|
|
5893
|
+
});
|
|
5894
|
+
});
|
|
5895
|
+
}
|
|
5896
|
+
console.log("=== VIDEO ELEMENT ===");
|
|
5897
|
+
const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
|
|
5898
|
+
if ((videoElement === null || videoElement === void 0 ? void 0 : videoElement.srcObject) instanceof MediaStream) {
|
|
5899
|
+
const srcStream = videoElement.srcObject;
|
|
5900
|
+
console.log("Video srcObject stream ID:", srcStream.id);
|
|
5901
|
+
console.log("Video srcObject active:", srcStream.active);
|
|
5902
|
+
console.log("Same as this._stream:", srcStream === this._stream);
|
|
5903
|
+
}
|
|
5904
|
+
console.groupEnd();
|
|
5905
|
+
}
|
|
5906
|
+
//------------------------------------------------------------------------//
|
|
5801
5907
|
// DESTROY & DELETE
|
|
5802
5908
|
//------------------------------------------------------------------------//
|
|
5803
|
-
/**
|
|
5804
|
-
* Method used to stop camera from streaming
|
|
5805
|
-
* @private
|
|
5806
|
-
*/
|
|
5807
5909
|
stopCameraStream() {
|
|
5808
5910
|
var _a, _b;
|
|
5809
5911
|
if (this._stream) {
|
|
5810
|
-
this.
|
|
5912
|
+
this._logger.info(this, `📹 [RELEASE] stopCameraStream() - stopping stream, id: ${this._stream.id}`);
|
|
5913
|
+
this._stream.getTracks().forEach(track => {
|
|
5914
|
+
this._logger.info(this, `📹 [RELEASE] stopCameraStream() - stopping track: ${track.kind}, id: ${track.id}`);
|
|
5915
|
+
track.stop();
|
|
5916
|
+
});
|
|
5811
5917
|
const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
|
|
5812
5918
|
if (videoElement) {
|
|
5813
5919
|
videoElement.srcObject = null;
|
|
5814
5920
|
}
|
|
5815
5921
|
this._soundMeter.detach();
|
|
5816
5922
|
this._stream = null;
|
|
5923
|
+
this._activeStreamCount--;
|
|
5924
|
+
this._logger.info(this, `📹 [RELEASE] stopCameraStream() - complete, active streams: ${this._activeStreamCount}`);
|
|
5817
5925
|
}
|
|
5818
5926
|
}
|
|
5819
|
-
/**
|
|
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
|
|
5848
|
-
* @private
|
|
5849
|
-
*/
|
|
5850
|
-
forceStopAllStreams() {
|
|
5851
|
-
var _a, _b;
|
|
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);
|
|
5860
|
-
}
|
|
5861
|
-
}
|
|
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
|
|
5868
|
-
try {
|
|
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;
|
|
5885
|
-
}
|
|
5886
|
-
} catch (e) {
|
|
5887
|
-
this._logger.error(this, 'Error cleaning video element: ' + e);
|
|
5888
|
-
}
|
|
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
|
-
});
|
|
5897
|
-
}
|
|
5898
|
-
/**
|
|
5899
|
-
* Stops all streaming operations and cleans up resources
|
|
5900
|
-
*/
|
|
5901
5927
|
stop() {
|
|
5902
|
-
|
|
5928
|
+
this._logger.info(this, "📹 [STOP] stop() - stopping all operations");
|
|
5903
5929
|
if (this._cameraAbortController) {
|
|
5904
5930
|
this._cameraAbortController.abort();
|
|
5905
5931
|
this._cameraAbortController = null;
|
|
@@ -5912,7 +5938,6 @@
|
|
|
5912
5938
|
this._startCameraAbortController.abort();
|
|
5913
5939
|
this._startCameraAbortController = null;
|
|
5914
5940
|
}
|
|
5915
|
-
// Stop status connection and clear timer
|
|
5916
5941
|
if (this._statusConnection) {
|
|
5917
5942
|
this._statusConnection.destroy();
|
|
5918
5943
|
this._statusConnection = null;
|
|
@@ -5922,16 +5947,12 @@
|
|
|
5922
5947
|
this._statusTimer = null;
|
|
5923
5948
|
}
|
|
5924
5949
|
this._main.getConfigManager().getStreamData().streamKey = null;
|
|
5925
|
-
// Close WebRTC connection
|
|
5926
5950
|
this.closeWebRTCConnection();
|
|
5927
|
-
// Stop all media streams
|
|
5928
5951
|
this.stopCameraStream();
|
|
5929
|
-
// Reset states
|
|
5930
5952
|
this.setPublishState(exports.PublishState.STOPPED);
|
|
5931
5953
|
this.setInputDeviceState(exports.InputDevicesState.STOPPED);
|
|
5932
5954
|
this.setCameraState(exports.DeviceState.STOPPED);
|
|
5933
5955
|
this.setMicrophoneState(exports.DeviceState.STOPPED);
|
|
5934
|
-
// Clear restart timer if exists
|
|
5935
5956
|
if (this._restartTimer) {
|
|
5936
5957
|
clearInterval(this._restartTimer);
|
|
5937
5958
|
this._restartTimer = null;
|
|
@@ -5939,127 +5960,140 @@
|
|
|
5939
5960
|
this._restartTimerCount = 0;
|
|
5940
5961
|
clearTimeout(this._publishTimer);
|
|
5941
5962
|
}
|
|
5942
|
-
/**
|
|
5943
|
-
* Reinitializes the streaming setup
|
|
5944
|
-
*/
|
|
5945
5963
|
start() {
|
|
5946
5964
|
var _a, _b, _c;
|
|
5947
5965
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5966
|
+
if (this._isDestroyed) return;
|
|
5967
|
+
this._logger.info(this, "📹 [START] start() - reinitializing streaming");
|
|
5948
5968
|
try {
|
|
5949
|
-
// Reset states
|
|
5950
5969
|
this._publishState = exports.PublishState.NOT_INITIALIZED;
|
|
5951
5970
|
this._inputDeviceState = exports.InputDevicesState.NOT_INITIALIZED;
|
|
5952
5971
|
this._cameraState = exports.DeviceState.NOT_INITIALIZED;
|
|
5953
5972
|
this._microphoneState = exports.DeviceState.NOT_INITIALIZED;
|
|
5954
|
-
// Reinitialize devices and stream
|
|
5955
5973
|
yield this.initializeDevices();
|
|
5974
|
+
if (this._isDestroyed) return;
|
|
5956
5975
|
yield this.startCamera();
|
|
5957
|
-
|
|
5976
|
+
if (this._isDestroyed) return;
|
|
5958
5977
|
if ((_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getSettingsData().autoConnect) {
|
|
5959
5978
|
(_b = this._main.getNetworkController()) === null || _b === void 0 ? void 0 : _b.initialize();
|
|
5960
5979
|
}
|
|
5961
|
-
// Reinitialize status connection if needed
|
|
5962
5980
|
if ((_c = this._main.getConfigManager()) === null || _c === void 0 ? void 0 : _c.getStreamData().streamKey) {
|
|
5963
5981
|
this.createStatusConnection();
|
|
5964
5982
|
}
|
|
5965
5983
|
} catch (error) {
|
|
5966
|
-
|
|
5984
|
+
if (this._isDestroyed) return;
|
|
5985
|
+
this._logger.error(this, "📹 [START] start() - failed: " + JSON.stringify(error));
|
|
5967
5986
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5968
5987
|
throw error;
|
|
5969
5988
|
}
|
|
5970
5989
|
});
|
|
5971
5990
|
}
|
|
5972
5991
|
/**
|
|
5973
|
-
*
|
|
5992
|
+
* SYNCHRONOUS destroy - sets flag immediately, cleanup happens in background
|
|
5993
|
+
* This ensures that even if called without await, all async operations will abort
|
|
5974
5994
|
*/
|
|
5975
5995
|
destroy() {
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
5996
|
+
var _a, _b, _c, _d;
|
|
5997
|
+
// Prevent double destroy
|
|
5998
|
+
if (this._isDestroyed) {
|
|
5999
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.warning(this, "🔴 [DESTROY] Already destroyed, skipping");
|
|
6000
|
+
return;
|
|
6001
|
+
}
|
|
6002
|
+
this._logger.info(this, "🔴 [DESTROY] Starting StreamerController destroy (sync)...");
|
|
6003
|
+
// CRITICAL: Set flag IMMEDIATELY - this will cause all pending async operations to abort
|
|
6004
|
+
this._isDestroyed = true;
|
|
6005
|
+
// Cancel all abort controllers immediately
|
|
6006
|
+
if (this._cameraAbortController) {
|
|
6007
|
+
this._cameraAbortController.abort();
|
|
6008
|
+
this._cameraAbortController = null;
|
|
6009
|
+
}
|
|
6010
|
+
if (this._microphoneAbortController) {
|
|
6011
|
+
this._microphoneAbortController.abort();
|
|
6012
|
+
this._microphoneAbortController = null;
|
|
6013
|
+
}
|
|
6014
|
+
if (this._startCameraAbortController) {
|
|
6015
|
+
this._startCameraAbortController.abort();
|
|
6016
|
+
this._startCameraAbortController = null;
|
|
6017
|
+
}
|
|
6018
|
+
// Stop all timers immediately
|
|
6019
|
+
if (this._statusTimer != null) {
|
|
6020
|
+
clearInterval(this._statusTimer);
|
|
6021
|
+
this._statusTimer = null;
|
|
6022
|
+
}
|
|
6023
|
+
if (this._restartTimer != null) {
|
|
6024
|
+
clearInterval(this._restartTimer);
|
|
6025
|
+
this._restartTimer = null;
|
|
6026
|
+
}
|
|
6027
|
+
clearTimeout(this._publishTimer);
|
|
6028
|
+
// Remove permission listeners
|
|
6029
|
+
this.removePermissionListeners();
|
|
6030
|
+
// Remove device change listener
|
|
6031
|
+
if (this._deviceChangeHandler) {
|
|
6032
|
+
navigator.mediaDevices.removeEventListener('devicechange', this._deviceChangeHandler);
|
|
6033
|
+
this._deviceChangeHandler = null;
|
|
6034
|
+
}
|
|
6035
|
+
// Remove orientation listener
|
|
6036
|
+
if (this._orientationChangeHandler) {
|
|
6037
|
+
if (window.screen && window.screen.orientation) {
|
|
6038
|
+
window.screen.orientation.removeEventListener('change', this._orientationChangeHandler);
|
|
6039
|
+
} else {
|
|
6040
|
+
window.removeEventListener('orientationchange', this._orientationChangeHandler);
|
|
6041
|
+
}
|
|
6042
|
+
this._orientationChangeHandler = null;
|
|
6043
|
+
}
|
|
6044
|
+
// Remove event listeners
|
|
6045
|
+
try {
|
|
6046
|
+
this._main.removeEventListener("serverConnect", this.onServerConnect);
|
|
6047
|
+
this._main.removeEventListener("serverDisconnect", this.onServerDisconnect);
|
|
6048
|
+
this._main.removeEventListener("streamKeyInUse", this.onStreamKeyTaken);
|
|
6049
|
+
this._main.removeEventListener("statusServerConnect", this.onStatusServerConnect);
|
|
6050
|
+
this._main.removeEventListener("statusServerDisconnect", this.onStatusServerDisconnect);
|
|
6051
|
+
this._main.removeEventListener("streamStatusUpdate", this.onStreamStatsUpdate);
|
|
6052
|
+
this._main.removeEventListener("deviceStateChange", this.onDeviceStateChange);
|
|
6053
|
+
document.removeEventListener("visibilitychange", this.visibilityChange);
|
|
6054
|
+
window.removeEventListener("blur", this.onWindowBlur);
|
|
6055
|
+
window.removeEventListener("focus", this.onWindowFocus);
|
|
6056
|
+
} catch (e) {
|
|
6057
|
+
// Ignore errors
|
|
6058
|
+
}
|
|
6059
|
+
// Destroy status connection
|
|
6060
|
+
if (this._statusConnection) {
|
|
6061
|
+
this._statusConnection.destroy();
|
|
6062
|
+
this._statusConnection = null;
|
|
6063
|
+
}
|
|
6064
|
+
// Close WebRTC
|
|
6065
|
+
this.closeWebRTCConnection();
|
|
6066
|
+
// Stop camera stream
|
|
6067
|
+
if (this._stream) {
|
|
6068
|
+
this._logger.info(this, "📹 [FORCE_STOP] Stopping main stream");
|
|
6069
|
+
this._stream.getTracks().forEach(track => {
|
|
6070
|
+
track.stop();
|
|
6071
|
+
});
|
|
6072
|
+
this._stream = null;
|
|
6073
|
+
}
|
|
6074
|
+
// Clean video element
|
|
6075
|
+
try {
|
|
6076
|
+
const videoElement = (_c = (_b = this._main.getStageController()) === null || _b === void 0 ? void 0 : _b.getScreenElement()) === null || _c === void 0 ? void 0 : _c.getVideoElement();
|
|
6077
|
+
if (videoElement) {
|
|
6078
|
+
if (videoElement.srcObject instanceof MediaStream) {
|
|
6079
|
+
videoElement.srcObject.getTracks().forEach(track => track.stop());
|
|
6055
6080
|
}
|
|
6056
|
-
|
|
6057
|
-
} catch (error) {
|
|
6058
|
-
this._logger.error(this, "Error during destroy: " + error);
|
|
6059
|
-
} finally {
|
|
6060
|
-
this._isDestroying = false;
|
|
6081
|
+
videoElement.srcObject = null;
|
|
6061
6082
|
}
|
|
6062
|
-
})
|
|
6083
|
+
} catch (e) {
|
|
6084
|
+
// Ignore
|
|
6085
|
+
}
|
|
6086
|
+
// Destroy sound meter
|
|
6087
|
+
try {
|
|
6088
|
+
(_d = this._soundMeter) === null || _d === void 0 ? void 0 : _d.destroy();
|
|
6089
|
+
} catch (e) {
|
|
6090
|
+
// Ignore
|
|
6091
|
+
}
|
|
6092
|
+
// Reset variables
|
|
6093
|
+
this._selectedCamera = null;
|
|
6094
|
+
this._selectedMicrophone = null;
|
|
6095
|
+
this._activeStreamCount = 0;
|
|
6096
|
+
this._logger.success(this, "🔴 [DESTROY] StreamerController destroyed (sync)");
|
|
6063
6097
|
}
|
|
6064
6098
|
}
|
|
6065
6099
|
|
|
@@ -6671,52 +6705,19 @@
|
|
|
6671
6705
|
* Main class of the player. The player itself has no GUI, but can be controlled via provided API.
|
|
6672
6706
|
*/
|
|
6673
6707
|
class StormStreamer extends EventDispatcher {
|
|
6674
|
-
//------------------------------------------------------------------------//
|
|
6675
|
-
// CONSTRUCTOR
|
|
6676
|
-
//------------------------------------------------------------------------//
|
|
6677
|
-
/**
|
|
6678
|
-
* Constructor - creates a new StormStreamer instance
|
|
6679
|
-
*
|
|
6680
|
-
* @param streamConfig - Configuration object for the streamer
|
|
6681
|
-
* @param autoInitialize - Whether to automatically initialize the streamer after creation
|
|
6682
|
-
*/
|
|
6683
6708
|
constructor(streamConfig, autoInitialize = false) {
|
|
6684
6709
|
super();
|
|
6685
|
-
/**
|
|
6686
|
-
* Indicates whether the streamer object is in development mode (provides more debug options)
|
|
6687
|
-
* @private
|
|
6688
|
-
*/
|
|
6689
6710
|
this.DEV_MODE = true;
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
* @private
|
|
6693
|
-
*/
|
|
6694
|
-
this.STREAMER_VERSION = "1.0.0-rc.1";
|
|
6695
|
-
/**
|
|
6696
|
-
* Compile date for this streamer
|
|
6697
|
-
* @private
|
|
6698
|
-
*/
|
|
6699
|
-
this.COMPILE_DATE = "11/17/2025, 10:30:23 AM";
|
|
6700
|
-
/**
|
|
6701
|
-
* Defines from which branch this streamer comes from e.g. "Main", "Experimental"
|
|
6702
|
-
* @private
|
|
6703
|
-
*/
|
|
6711
|
+
this.STREAMER_VERSION = "1.0.0";
|
|
6712
|
+
this.COMPILE_DATE = "2/7/2026, 6:37:16 PM";
|
|
6704
6713
|
this.STREAMER_BRANCH = "Experimental";
|
|
6705
|
-
/**
|
|
6706
|
-
* Defines number of streamer protocol that is required on server-side
|
|
6707
|
-
* @private
|
|
6708
|
-
*/
|
|
6709
6714
|
this.STREAMER_PROTOCOL_VERSION = 1;
|
|
6710
|
-
/**
|
|
6711
|
-
* Indicates whether streamer was initialized or not
|
|
6712
|
-
* @private
|
|
6713
|
-
*/
|
|
6714
6715
|
this._initialized = false;
|
|
6716
|
+
this._isDestroyed = false;
|
|
6715
6717
|
if (typeof window === 'undefined' || !window.document || !window.document.createElement) {
|
|
6716
6718
|
console.error(`StormStreamer Creation Error - No "window" element in the provided context!`);
|
|
6717
6719
|
return;
|
|
6718
6720
|
}
|
|
6719
|
-
// WINDOW.StormStreamerArray
|
|
6720
6721
|
if (this.DEV_MODE && !('StormStreamerArray' in window)) {
|
|
6721
6722
|
window.StormStreamerArray = [];
|
|
6722
6723
|
}
|
|
@@ -6726,17 +6727,13 @@
|
|
|
6726
6727
|
this.setStreamConfig(streamConfig);
|
|
6727
6728
|
if (autoInitialize) this.initialize();
|
|
6728
6729
|
}
|
|
6729
|
-
/**
|
|
6730
|
-
* Initializes the streamer object. From this point, a connection to the server is established and authentication occurs.
|
|
6731
|
-
* It is recommended to add all event listeners before calling this method to ensure they can be properly captured.
|
|
6732
|
-
*/
|
|
6733
6730
|
initialize() {
|
|
6734
|
-
if (this._isRemoved) return;
|
|
6735
|
-
if (this._configManager == null) throw Error("Stream Config was not provided for this streamer!
|
|
6736
|
-
this._storageManager = new StorageManager(this);
|
|
6737
|
-
this._stageController = new StageController(this);
|
|
6738
|
-
this._networkController = new NetworkController(this);
|
|
6739
|
-
this._streamerController = new StreamerController(this);
|
|
6731
|
+
if (this._isRemoved || this._isDestroyed) return;
|
|
6732
|
+
if (this._configManager == null) throw Error("Stream Config was not provided for this streamer!");
|
|
6733
|
+
this._storageManager = new StorageManager(this);
|
|
6734
|
+
this._stageController = new StageController(this);
|
|
6735
|
+
this._networkController = new NetworkController(this);
|
|
6736
|
+
this._streamerController = new StreamerController(this);
|
|
6740
6737
|
this._statsController = new StatsController(this);
|
|
6741
6738
|
this._graphs = [];
|
|
6742
6739
|
this._initialized = true;
|
|
@@ -6744,16 +6741,8 @@
|
|
|
6744
6741
|
ref: this
|
|
6745
6742
|
});
|
|
6746
6743
|
}
|
|
6747
|
-
/**
|
|
6748
|
-
* Sets stream config for the streamer (or overwrites an existing one).
|
|
6749
|
-
*
|
|
6750
|
-
* @param streamConfig - New configuration object for the streamer
|
|
6751
|
-
*/
|
|
6752
6744
|
setStreamConfig(streamConfig) {
|
|
6753
|
-
if (this._isRemoved) return;
|
|
6754
|
-
/**
|
|
6755
|
-
* In case the original streamConfig is modified elsewhere we have to create a separate copy and store it ourselves
|
|
6756
|
-
*/
|
|
6745
|
+
if (this._isRemoved || this._isDestroyed) return;
|
|
6757
6746
|
const copiedStreamConfig = JSON.parse(JSON.stringify(streamConfig));
|
|
6758
6747
|
if (this._configManager == null) {
|
|
6759
6748
|
this._configManager = new ConfigManager(copiedStreamConfig);
|
|
@@ -6778,290 +6767,133 @@
|
|
|
6778
6767
|
});
|
|
6779
6768
|
}
|
|
6780
6769
|
}
|
|
6781
|
-
//------------------------------------------------------------------------//
|
|
6782
6770
|
// PLAYBACK / STREAMING
|
|
6783
|
-
//------------------------------------------------------------------------//
|
|
6784
|
-
/**
|
|
6785
|
-
* Returns true if this streamer instance is currently connected to a Storm Server/Cloud instance.
|
|
6786
|
-
*
|
|
6787
|
-
* @returns Boolean indicating connection status
|
|
6788
|
-
*/
|
|
6789
6771
|
isConnected() {
|
|
6790
6772
|
var _a, _b;
|
|
6791
6773
|
return (_b = (_a = this._networkController) === null || _a === void 0 ? void 0 : _a.getConnection().isConnectionActive()) !== null && _b !== void 0 ? _b : false;
|
|
6792
6774
|
}
|
|
6793
|
-
/**
|
|
6794
|
-
* Mutes the streamer's video object. Audio output will be silenced.
|
|
6795
|
-
*/
|
|
6796
6775
|
mute() {
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
}
|
|
6776
|
+
var _a;
|
|
6777
|
+
if (((_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) != null) {
|
|
6778
|
+
this._stageController.getScreenElement().setMuted(true);
|
|
6779
|
+
return;
|
|
6802
6780
|
}
|
|
6803
6781
|
this._configManager.getSettingsData().getAudioData().muted = true;
|
|
6804
6782
|
}
|
|
6805
|
-
/**
|
|
6806
|
-
* Unmutes the streamer's video object. Audio output will be restored.
|
|
6807
|
-
*/
|
|
6808
6783
|
unmute() {
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
}
|
|
6784
|
+
var _a;
|
|
6785
|
+
if (((_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) != null) {
|
|
6786
|
+
this._stageController.getScreenElement().setMuted(false);
|
|
6787
|
+
return;
|
|
6814
6788
|
}
|
|
6815
6789
|
this._configManager.getSettingsData().getAudioData().muted = false;
|
|
6816
6790
|
}
|
|
6817
|
-
/**
|
|
6818
|
-
* Checks whether the streamer audio is currently muted.
|
|
6819
|
-
*
|
|
6820
|
-
* @returns Boolean indicating mute status
|
|
6821
|
-
*/
|
|
6822
6791
|
isMute() {
|
|
6823
6792
|
var _a, _b, _c, _d;
|
|
6824
6793
|
return (_d = (_c = (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getIfMuted()) !== null && _c !== void 0 ? _c : this._configManager.getSettingsData().getAudioData().muted) !== null && _d !== void 0 ? _d : false;
|
|
6825
6794
|
}
|
|
6826
|
-
/**
|
|
6827
|
-
* Toggles between mute and unmute states. Returns the new mute state.
|
|
6828
|
-
*
|
|
6829
|
-
* @returns New mute state (true = muted, false = unmuted)
|
|
6830
|
-
*/
|
|
6831
6795
|
toggleMute() {
|
|
6832
6796
|
const isMuted = this.isMute();
|
|
6833
|
-
if (isMuted)
|
|
6834
|
-
this.unmute();
|
|
6835
|
-
} else {
|
|
6836
|
-
this.mute();
|
|
6837
|
-
}
|
|
6797
|
+
if (isMuted) this.unmute();else this.mute();
|
|
6838
6798
|
return !isMuted;
|
|
6839
6799
|
}
|
|
6840
|
-
/**
|
|
6841
|
-
* Sets new volume for the streamer (0-100). Once the method is performed, the volumeChange event will be triggered.
|
|
6842
|
-
* If the video was muted prior to the volume change, it will be automatically unmuted.
|
|
6843
|
-
*
|
|
6844
|
-
* @param newVolume - Volume level (0-100)
|
|
6845
|
-
*/
|
|
6846
6800
|
setVolume(newVolume) {
|
|
6847
6801
|
var _a, _b;
|
|
6848
|
-
if (((_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.setVolume(newVolume)) !== undefined)
|
|
6849
|
-
return;
|
|
6850
|
-
}
|
|
6802
|
+
if (((_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.setVolume(newVolume)) !== undefined) return;
|
|
6851
6803
|
this._configManager.getSettingsData().getAudioData().startVolume = newVolume;
|
|
6852
6804
|
}
|
|
6853
|
-
/**
|
|
6854
|
-
* Returns current streamer volume (0-100).
|
|
6855
|
-
*
|
|
6856
|
-
* @returns Current volume level
|
|
6857
|
-
*/
|
|
6858
6805
|
getVolume() {
|
|
6859
6806
|
var _a, _b, _c;
|
|
6860
6807
|
return (_c = (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVolume()) !== null && _c !== void 0 ? _c : this._configManager.getSettingsData().getAudioData().startVolume;
|
|
6861
6808
|
}
|
|
6862
|
-
/**
|
|
6863
|
-
* Returns the list of available camera devices.
|
|
6864
|
-
*
|
|
6865
|
-
* @returns Array of camera input devices
|
|
6866
|
-
*/
|
|
6867
6809
|
getCameraList() {
|
|
6868
6810
|
var _a, _b;
|
|
6869
6811
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCameraList()) !== null && _b !== void 0 ? _b : [];
|
|
6870
6812
|
}
|
|
6871
|
-
/**
|
|
6872
|
-
* Returns the list of available microphone devices.
|
|
6873
|
-
*
|
|
6874
|
-
* @returns Array of microphone input devices
|
|
6875
|
-
*/
|
|
6876
6813
|
getMicrophoneList() {
|
|
6877
6814
|
var _a, _b;
|
|
6878
6815
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getMicrophoneList()) !== null && _b !== void 0 ? _b : [];
|
|
6879
6816
|
}
|
|
6880
|
-
/**
|
|
6881
|
-
* Sets the active camera device by ID.
|
|
6882
|
-
*
|
|
6883
|
-
* @param cameraID - ID of the camera device to use
|
|
6884
|
-
*/
|
|
6885
6817
|
setCamera(cameraID) {
|
|
6886
6818
|
var _a;
|
|
6887
6819
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.selectCamera(cameraID);
|
|
6888
6820
|
}
|
|
6889
|
-
/**
|
|
6890
|
-
* Sets the active microphone device by ID.
|
|
6891
|
-
*
|
|
6892
|
-
* @param microphoneID - ID of the microphone device to use
|
|
6893
|
-
*/
|
|
6894
6821
|
setMicrophone(microphoneID) {
|
|
6895
6822
|
var _a;
|
|
6896
6823
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.selectMicrophone(microphoneID);
|
|
6897
6824
|
}
|
|
6898
|
-
/**
|
|
6899
|
-
* Returns the currently active camera device.
|
|
6900
|
-
*
|
|
6901
|
-
* @returns Current camera device or null if none is active
|
|
6902
|
-
*/
|
|
6903
6825
|
getCurrentCamera() {
|
|
6904
|
-
|
|
6826
|
+
var _a, _b;
|
|
6827
|
+
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCurrentCamera()) !== null && _b !== void 0 ? _b : null;
|
|
6905
6828
|
}
|
|
6906
|
-
/**
|
|
6907
|
-
* Returns the currently active microphone device.
|
|
6908
|
-
*
|
|
6909
|
-
* @returns Current microphone device or null if none is active
|
|
6910
|
-
*/
|
|
6911
6829
|
getCurrentMicrophone() {
|
|
6912
|
-
|
|
6830
|
+
var _a, _b;
|
|
6831
|
+
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCurrentMicrophone()) !== null && _b !== void 0 ? _b : null;
|
|
6913
6832
|
}
|
|
6914
|
-
/**
|
|
6915
|
-
* Mutes or unmutes the microphone.
|
|
6916
|
-
*
|
|
6917
|
-
* @param microphoneState - True to mute, false to unmute
|
|
6918
|
-
*/
|
|
6919
6833
|
muteMicrophone(microphoneState) {
|
|
6920
6834
|
var _a;
|
|
6921
6835
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.muteMicrophone(microphoneState);
|
|
6922
6836
|
}
|
|
6923
|
-
/**
|
|
6924
|
-
* Checks if the microphone is currently muted.
|
|
6925
|
-
*
|
|
6926
|
-
* @returns Boolean indicating if microphone is muted
|
|
6927
|
-
*/
|
|
6928
6837
|
isMicrophoneMuted() {
|
|
6929
6838
|
var _a, _b;
|
|
6930
6839
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.isMicrophoneMuted()) !== null && _b !== void 0 ? _b : false;
|
|
6931
6840
|
}
|
|
6932
|
-
/**
|
|
6933
|
-
* Returns the current publishing state of the streamer.
|
|
6934
|
-
*
|
|
6935
|
-
* @returns Current publishing state
|
|
6936
|
-
*/
|
|
6937
6841
|
getPublishState() {
|
|
6938
6842
|
var _a, _b;
|
|
6939
6843
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getPublishState()) !== null && _b !== void 0 ? _b : exports.PublishState.NOT_INITIALIZED;
|
|
6940
6844
|
}
|
|
6941
|
-
/**
|
|
6942
|
-
* Returns the total time the stream has been publishing in milliseconds.
|
|
6943
|
-
*
|
|
6944
|
-
* @returns Publishing time in milliseconds
|
|
6945
|
-
*/
|
|
6946
6845
|
getPublishTime() {
|
|
6947
6846
|
var _a, _b;
|
|
6948
6847
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getPublishTime()) !== null && _b !== void 0 ? _b : 0;
|
|
6949
6848
|
}
|
|
6950
|
-
/**
|
|
6951
|
-
* Starts publishing a stream with the given stream key.
|
|
6952
|
-
*
|
|
6953
|
-
* @param streamKey - Key identifying the stream to publish
|
|
6954
|
-
* @returns Boolean indicating if publishing was successfully initiated
|
|
6955
|
-
*/
|
|
6956
6849
|
publish(streamKey) {
|
|
6957
6850
|
var _a, _b;
|
|
6958
6851
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.publish(streamKey)) !== null && _b !== void 0 ? _b : false;
|
|
6959
6852
|
}
|
|
6960
|
-
/**
|
|
6961
|
-
* Stops publishing the current stream.
|
|
6962
|
-
*/
|
|
6963
6853
|
unpublish() {
|
|
6964
6854
|
var _a;
|
|
6965
|
-
console.log("kutas 1");
|
|
6966
6855
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.unpublish();
|
|
6967
6856
|
}
|
|
6968
|
-
/**
|
|
6969
|
-
* Returns the current state of input devices (camera and microphone).
|
|
6970
|
-
*
|
|
6971
|
-
* @returns Current state of input devices
|
|
6972
|
-
*/
|
|
6973
6857
|
getInputDevicesState() {
|
|
6974
6858
|
var _a, _b;
|
|
6975
6859
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getInputDeviceState()) !== null && _b !== void 0 ? _b : exports.InputDevicesState.NOT_INITIALIZED;
|
|
6976
6860
|
}
|
|
6977
|
-
/**
|
|
6978
|
-
* Returns the current state of the camera device.
|
|
6979
|
-
*
|
|
6980
|
-
* @returns Current camera device state
|
|
6981
|
-
*/
|
|
6982
6861
|
getCameraState() {
|
|
6983
6862
|
var _a, _b;
|
|
6984
6863
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCameraState()) !== null && _b !== void 0 ? _b : exports.DeviceState.NOT_INITIALIZED;
|
|
6985
6864
|
}
|
|
6986
|
-
/**
|
|
6987
|
-
* Returns the current state of the microphone device.
|
|
6988
|
-
*
|
|
6989
|
-
* @returns Current microphone device state
|
|
6990
|
-
*/
|
|
6991
6865
|
getMicrophoneState() {
|
|
6992
6866
|
var _a, _b;
|
|
6993
6867
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getMicrophoneState()) !== null && _b !== void 0 ? _b : exports.DeviceState.NOT_INITIALIZED;
|
|
6994
6868
|
}
|
|
6995
|
-
/**
|
|
6996
|
-
* Clears saved device preferences from storage.
|
|
6997
|
-
*/
|
|
6998
6869
|
clearSavedDevices() {
|
|
6999
6870
|
var _a;
|
|
7000
|
-
|
|
6871
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.clearSavedDevices();
|
|
7001
6872
|
}
|
|
7002
|
-
/**
|
|
7003
|
-
* Randomizes saved device preferences (for testing purposes).
|
|
7004
|
-
*/
|
|
7005
6873
|
messSavedDevices() {
|
|
7006
6874
|
var _a;
|
|
7007
|
-
|
|
6875
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.messSavedDevices();
|
|
7008
6876
|
}
|
|
7009
|
-
/**
|
|
7010
|
-
* Checks if the stream is ready for publishing.
|
|
7011
|
-
*
|
|
7012
|
-
* @returns Boolean indicating if stream is ready
|
|
7013
|
-
*/
|
|
7014
6877
|
isStreamReady() {
|
|
7015
6878
|
var _a, _b;
|
|
7016
6879
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.isStreamReady()) !== null && _b !== void 0 ? _b : false;
|
|
7017
6880
|
}
|
|
7018
|
-
//------------------------------------------------------------------------//
|
|
7019
6881
|
// CONTAINER
|
|
7020
|
-
//------------------------------------------------------------------------//
|
|
7021
|
-
/**
|
|
7022
|
-
* Attaches the streamer to a new parent container using either a container ID (string) or a reference to an HTMLElement.
|
|
7023
|
-
* If the instance is already attached, it will be moved to a new parent.
|
|
7024
|
-
*
|
|
7025
|
-
* @param container - Container ID (string) or HTMLElement reference
|
|
7026
|
-
* @returns Boolean indicating if attachment was successful
|
|
7027
|
-
*/
|
|
7028
6882
|
attachToContainer(container) {
|
|
7029
6883
|
var _a, _b;
|
|
7030
|
-
let result = false;
|
|
7031
6884
|
if (this._initialized) return (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.attachToParent(container)) !== null && _b !== void 0 ? _b : false;
|
|
7032
|
-
return
|
|
6885
|
+
return false;
|
|
7033
6886
|
}
|
|
7034
|
-
/**
|
|
7035
|
-
* Detaches the streamer from the current parent element, if possible.
|
|
7036
|
-
*
|
|
7037
|
-
* @returns Boolean indicating if detachment was successful
|
|
7038
|
-
*/
|
|
7039
6887
|
detachFromContainer() {
|
|
7040
6888
|
var _a, _b;
|
|
7041
|
-
let result = false;
|
|
7042
6889
|
if (this._initialized) return (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.detachFromParent()) !== null && _b !== void 0 ? _b : false;
|
|
7043
|
-
return
|
|
6890
|
+
return false;
|
|
7044
6891
|
}
|
|
7045
|
-
/**
|
|
7046
|
-
* Returns the current parent element of the streamer, or null if none exists.
|
|
7047
|
-
*
|
|
7048
|
-
* @returns Current container element or null
|
|
7049
|
-
*/
|
|
7050
6892
|
getContainer() {
|
|
7051
6893
|
var _a, _b;
|
|
7052
6894
|
return (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getParentElement()) !== null && _b !== void 0 ? _b : null;
|
|
7053
6895
|
}
|
|
7054
|
-
//------------------------------------------------------------------------//
|
|
7055
6896
|
// SIZE & RESIZE
|
|
7056
|
-
//------------------------------------------------------------------------//
|
|
7057
|
-
/**
|
|
7058
|
-
* Sets a new width and height for the streamer. The values can be given as a number (in which case they are
|
|
7059
|
-
* treated as the number of pixels), or as a string ending with "px" (this will also be the number of pixels) or "%",
|
|
7060
|
-
* where the number is treated as a percentage of the parent container's value.
|
|
7061
|
-
*
|
|
7062
|
-
* @param width - Can be provided as number or a string with "%" or "px" suffix
|
|
7063
|
-
* @param height - Can be provided as number or a string with "%" or "px" suffix
|
|
7064
|
-
*/
|
|
7065
6897
|
setSize(width, height) {
|
|
7066
6898
|
if (this._initialized) this._stageController.setSize(width, height);else {
|
|
7067
6899
|
const parsedWidth = NumberUtilities.parseValue(width);
|
|
@@ -7072,13 +6904,6 @@
|
|
|
7072
6904
|
this._configManager.getSettingsData().getVideoData().videoHeightInPixels = parsedHeight.isPixels;
|
|
7073
6905
|
}
|
|
7074
6906
|
}
|
|
7075
|
-
/**
|
|
7076
|
-
* Sets a new width for the streamer. The value can be given as a number (in which case it is treated as the
|
|
7077
|
-
* number of pixels), or as a string ending with "px" (this will also be the number of pixels) or "%", where the
|
|
7078
|
-
* number is treated as a percentage of the parent container's value.
|
|
7079
|
-
*
|
|
7080
|
-
* @param width - Can be provided as number or a string with "%" or "px" suffix
|
|
7081
|
-
*/
|
|
7082
6907
|
setWidth(width) {
|
|
7083
6908
|
if (this._initialized) this._stageController.setWidth(width);else {
|
|
7084
6909
|
const parsedWidth = NumberUtilities.parseValue(width);
|
|
@@ -7086,13 +6911,6 @@
|
|
|
7086
6911
|
this._configManager.getSettingsData().getVideoData().videoWidthInPixels = parsedWidth.isPixels;
|
|
7087
6912
|
}
|
|
7088
6913
|
}
|
|
7089
|
-
/**
|
|
7090
|
-
* Sets a new height for the streamer. The value can be given as a number (in which case it is treated as the
|
|
7091
|
-
* number of pixels), or as a string ending with "px" (this will also be the number of pixels) or "%", where the
|
|
7092
|
-
* number is treated as a percentage of the parent container's value.
|
|
7093
|
-
*
|
|
7094
|
-
* @param height - Can be provided as number or a string with "%" or "px" suffix
|
|
7095
|
-
*/
|
|
7096
6914
|
setHeight(height) {
|
|
7097
6915
|
if (this._initialized) this._stageController.setHeight(height);else {
|
|
7098
6916
|
const parsedHeight = NumberUtilities.parseValue(height);
|
|
@@ -7100,66 +6918,26 @@
|
|
|
7100
6918
|
this._configManager.getSettingsData().getVideoData().videoHeightInPixels = parsedHeight.isPixels;
|
|
7101
6919
|
}
|
|
7102
6920
|
}
|
|
7103
|
-
/**
|
|
7104
|
-
* Returns current streamer width in pixels.
|
|
7105
|
-
*
|
|
7106
|
-
* @returns Current width in pixels
|
|
7107
|
-
*/
|
|
7108
6921
|
getWidth() {
|
|
7109
|
-
if (this._initialized) return this._stageController.getContainerWidth();
|
|
7110
|
-
|
|
7111
|
-
}
|
|
6922
|
+
if (this._initialized) return this._stageController.getContainerWidth();
|
|
6923
|
+
if (this._configManager.getSettingsData().getVideoData().videoWidthInPixels) return this._configManager.getSettingsData().getVideoData().videoWidthValue;
|
|
7112
6924
|
return 0;
|
|
7113
6925
|
}
|
|
7114
|
-
/**
|
|
7115
|
-
* Returns current streamer height in pixels.
|
|
7116
|
-
*
|
|
7117
|
-
* @returns Current height in pixels
|
|
7118
|
-
*/
|
|
7119
6926
|
getHeight() {
|
|
7120
|
-
if (this._initialized) return this._stageController.getContainerHeight();
|
|
7121
|
-
|
|
7122
|
-
}
|
|
6927
|
+
if (this._initialized) return this._stageController.getContainerHeight();
|
|
6928
|
+
if (this._configManager.getSettingsData().getVideoData().videoHeightInPixels) return this._configManager.getSettingsData().getVideoData().videoHeightValue;
|
|
7123
6929
|
return 0;
|
|
7124
6930
|
}
|
|
7125
|
-
/**
|
|
7126
|
-
* Changes the streamer scaling mode. Available modes include fill, letterbox, original, and crop.
|
|
7127
|
-
*
|
|
7128
|
-
* @param newMode - New scaling mode name (fill, letterbox, original, crop)
|
|
7129
|
-
*/
|
|
7130
6931
|
setScalingMode(newMode) {
|
|
7131
|
-
if (this._stageController)
|
|
7132
|
-
this._stageController.setScalingMode(newMode);
|
|
7133
|
-
} else {
|
|
7134
|
-
this._configManager.getSettingsData().getVideoData().scalingMode = newMode;
|
|
7135
|
-
}
|
|
6932
|
+
if (this._stageController) this._stageController.setScalingMode(newMode);else this._configManager.getSettingsData().getVideoData().scalingMode = newMode;
|
|
7136
6933
|
}
|
|
7137
|
-
/**
|
|
7138
|
-
* Returns the current streamer scaling mode.
|
|
7139
|
-
*
|
|
7140
|
-
* @returns Current scaling mode
|
|
7141
|
-
*/
|
|
7142
6934
|
getScalingMode() {
|
|
7143
|
-
if (this._stageController)
|
|
7144
|
-
|
|
7145
|
-
} else {
|
|
7146
|
-
return this._configManager.getSettingsData().getVideoData().scalingMode;
|
|
7147
|
-
}
|
|
6935
|
+
if (this._stageController) return this._stageController.getScalingMode();
|
|
6936
|
+
return this._configManager.getSettingsData().getVideoData().scalingMode;
|
|
7148
6937
|
}
|
|
7149
|
-
/**
|
|
7150
|
-
* Forces the streamer to recalculate its size based on parent internal dimensions.
|
|
7151
|
-
*/
|
|
7152
6938
|
updateToSize() {
|
|
7153
|
-
if (this._initialized)
|
|
7154
|
-
this._stageController.handleResize();
|
|
7155
|
-
}
|
|
6939
|
+
if (this._initialized) this._stageController.handleResize();
|
|
7156
6940
|
}
|
|
7157
|
-
/**
|
|
7158
|
-
* Returns a promise that resolves with a screenshot of the video element as a blob, or null if taking the
|
|
7159
|
-
* screenshot was not possible.
|
|
7160
|
-
*
|
|
7161
|
-
* @returns Promise resolving to a Blob containing the screenshot or null
|
|
7162
|
-
*/
|
|
7163
6941
|
makeScreenshot() {
|
|
7164
6942
|
let canvas = document.createElement('canvas');
|
|
7165
6943
|
let context = canvas.getContext('2d');
|
|
@@ -7170,64 +6948,24 @@
|
|
|
7170
6948
|
let element = this._stageController.getScreenElement().getVideoElement();
|
|
7171
6949
|
if (context) {
|
|
7172
6950
|
context.drawImage(element, 0, 0, canvas.width, canvas.height);
|
|
7173
|
-
canvas.toBlob(blob =>
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
} else {
|
|
7177
|
-
resolve(null);
|
|
7178
|
-
}
|
|
7179
|
-
} else {
|
|
7180
|
-
resolve(null);
|
|
7181
|
-
}
|
|
6951
|
+
canvas.toBlob(blob => resolve(blob), 'image/png');
|
|
6952
|
+
} else resolve(null);
|
|
6953
|
+
} else resolve(null);
|
|
7182
6954
|
});
|
|
7183
6955
|
}
|
|
7184
|
-
//------------------------------------------------------------------------//
|
|
7185
6956
|
// GRAPHS
|
|
7186
|
-
//------------------------------------------------------------------------//
|
|
7187
|
-
/**
|
|
7188
|
-
* Creates a FPS performance graph in the specified location (container ID or reference). The graph is a
|
|
7189
|
-
* separate object that must be started using its start() method and stopped using its stop() method. The dimensions
|
|
7190
|
-
* of the graph depend on the dimensions of the specified container.
|
|
7191
|
-
*
|
|
7192
|
-
* @param container - Element ID or reference to HTMLElement
|
|
7193
|
-
* @returns FPS graph instance
|
|
7194
|
-
*/
|
|
7195
6957
|
createFPSGraph(container) {
|
|
7196
6958
|
return new FPSGraph(this, container);
|
|
7197
6959
|
}
|
|
7198
|
-
/**
|
|
7199
|
-
* Creates a bitrate performance graph in the specified location (container ID or reference). The graph is a
|
|
7200
|
-
* separate object that must be started using its start() method and stopped using its stop() method. The dimensions
|
|
7201
|
-
* of the graph depend on the dimensions of the specified container.
|
|
7202
|
-
*
|
|
7203
|
-
* @param container - Element ID or reference to HTMLElement
|
|
7204
|
-
* @returns Bitrate graph instance
|
|
7205
|
-
*/
|
|
7206
6960
|
createBitrateGraph(container) {
|
|
7207
6961
|
return new BitrateGraph(this, container);
|
|
7208
6962
|
}
|
|
7209
|
-
/**
|
|
7210
|
-
* Creates a microphone graph in the specified location (container ID or reference). The graph is a
|
|
7211
|
-
* separate object that must be started using its start() method and stopped using its stop() method. The dimensions
|
|
7212
|
-
* of the graph depend on the dimensions of the specified container.
|
|
7213
|
-
*
|
|
7214
|
-
* @param container - Element ID or reference to HTMLElement
|
|
7215
|
-
* @returns Bitrate graph instance
|
|
7216
|
-
*/
|
|
7217
6963
|
createMicrophoneGraph(container) {
|
|
7218
6964
|
return new MicrophoneGraph(this, container);
|
|
7219
6965
|
}
|
|
7220
|
-
/**
|
|
7221
|
-
* Adds new graph to the internal collection of active graphs.
|
|
7222
|
-
*
|
|
7223
|
-
* @param newGraph - Graph instance to add
|
|
7224
|
-
*/
|
|
7225
6966
|
addGraph(newGraph) {
|
|
7226
6967
|
if (this._graphs != null) this._graphs.push(newGraph);
|
|
7227
6968
|
}
|
|
7228
|
-
/**
|
|
7229
|
-
* Stops all active performance graphs.
|
|
7230
|
-
*/
|
|
7231
6969
|
stopAllGraphs() {
|
|
7232
6970
|
if (this._graphs != null && this._graphs.length > 0) {
|
|
7233
6971
|
for (let i = 0; i < this._graphs.length; i++) {
|
|
@@ -7235,198 +6973,136 @@
|
|
|
7235
6973
|
}
|
|
7236
6974
|
}
|
|
7237
6975
|
}
|
|
7238
|
-
//------------------------------------------------------------------------//
|
|
7239
6976
|
// FULLSCREEN
|
|
7240
|
-
//------------------------------------------------------------------------//
|
|
7241
|
-
/**
|
|
7242
|
-
* Enters fullscreen mode for the streamer container.
|
|
7243
|
-
*/
|
|
7244
6977
|
enterFullScreen() {
|
|
7245
6978
|
if (this._initialized && this._stageController) this._stageController.enterFullScreen();
|
|
7246
6979
|
}
|
|
7247
|
-
/**
|
|
7248
|
-
* Exits fullscreen mode.
|
|
7249
|
-
*/
|
|
7250
6980
|
exitFullScreen() {
|
|
7251
6981
|
if (this._initialized && this._stageController) this._stageController.exitFullScreen();
|
|
7252
6982
|
}
|
|
7253
|
-
/**
|
|
7254
|
-
* Returns true if the streamer is currently in fullscreen mode.
|
|
7255
|
-
*
|
|
7256
|
-
* @returns Boolean indicating fullscreen status
|
|
7257
|
-
*/
|
|
7258
6983
|
isFullScreenMode() {
|
|
7259
6984
|
if (this._initialized && this._stageController) return this._stageController.isFullScreenMode();
|
|
7260
6985
|
return false;
|
|
7261
6986
|
}
|
|
7262
|
-
|
|
7263
|
-
// SIMPLE GETS & SETS
|
|
7264
|
-
//------------------------------------------------------------------------//
|
|
7265
|
-
/**
|
|
7266
|
-
* Returns the current stream key or null if none is set.
|
|
7267
|
-
*
|
|
7268
|
-
* @returns Current stream key or null
|
|
7269
|
-
*/
|
|
6987
|
+
// GETTERS
|
|
7270
6988
|
getStreamKey() {
|
|
7271
6989
|
var _a, _b, _c;
|
|
7272
6990
|
return (_c = (_b = (_a = this.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData()) === null || _b === void 0 ? void 0 : _b.streamKey) !== null && _c !== void 0 ? _c : null;
|
|
7273
6991
|
}
|
|
7274
|
-
/**
|
|
7275
|
-
* Returns the Stats Controller instance which contains statistical data about streaming performance.
|
|
7276
|
-
*
|
|
7277
|
-
* @returns Stats controller instance or null
|
|
7278
|
-
*/
|
|
7279
6992
|
getStatsController() {
|
|
7280
6993
|
return this._statsController;
|
|
7281
6994
|
}
|
|
7282
|
-
/**
|
|
7283
|
-
* Returns the unique ID of this streamer instance. Each subsequent instance has a higher number.
|
|
7284
|
-
*
|
|
7285
|
-
* @returns Streamer instance ID
|
|
7286
|
-
*/
|
|
7287
6995
|
getStreamerID() {
|
|
7288
6996
|
return this._streamerID;
|
|
7289
6997
|
}
|
|
7290
|
-
/**
|
|
7291
|
-
* Returns the logger instance used by this streamer.
|
|
7292
|
-
*
|
|
7293
|
-
* @returns Logger instance
|
|
7294
|
-
*/
|
|
7295
6998
|
getLogger() {
|
|
7296
6999
|
return this._logger;
|
|
7297
7000
|
}
|
|
7298
|
-
/**
|
|
7299
|
-
* Returns the configuration manager for this streamer.
|
|
7300
|
-
*
|
|
7301
|
-
* @returns Config manager instance or null
|
|
7302
|
-
*/
|
|
7303
7001
|
getConfigManager() {
|
|
7304
7002
|
return this._configManager;
|
|
7305
7003
|
}
|
|
7306
|
-
/**
|
|
7307
|
-
* Returns the network controller which manages all server communication.
|
|
7308
|
-
*
|
|
7309
|
-
* @returns Network controller instance or null
|
|
7310
|
-
*/
|
|
7311
7004
|
getNetworkController() {
|
|
7312
7005
|
return this._networkController;
|
|
7313
7006
|
}
|
|
7314
|
-
/**
|
|
7315
|
-
* Returns the streamer controller which manages media stream operations.
|
|
7316
|
-
*
|
|
7317
|
-
* @returns Streamer controller instance or null
|
|
7318
|
-
*/
|
|
7319
7007
|
getStreamerController() {
|
|
7320
7008
|
return this._streamerController;
|
|
7321
7009
|
}
|
|
7322
|
-
/**
|
|
7323
|
-
* Returns the stage controller which manages visual presentation.
|
|
7324
|
-
*
|
|
7325
|
-
* @returns Stage controller instance or null
|
|
7326
|
-
*/
|
|
7327
7010
|
getStageController() {
|
|
7328
7011
|
return this._stageController;
|
|
7329
7012
|
}
|
|
7330
|
-
/**
|
|
7331
|
-
* Returns the storage manager which handles persistent data storage.
|
|
7332
|
-
*
|
|
7333
|
-
* @returns Storage manager instance or null
|
|
7334
|
-
*/
|
|
7335
7013
|
getStorageManager() {
|
|
7336
7014
|
return this._storageManager;
|
|
7337
7015
|
}
|
|
7338
|
-
/**
|
|
7339
|
-
* Returns the HTML video element used by this streamer instance.
|
|
7340
|
-
*
|
|
7341
|
-
* @returns Video element or null
|
|
7342
|
-
*/
|
|
7343
7016
|
getVideoElement() {
|
|
7344
7017
|
var _a, _b, _c;
|
|
7345
7018
|
return (_c = (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement()) !== null && _c !== void 0 ? _c : null;
|
|
7346
7019
|
}
|
|
7347
|
-
/**
|
|
7348
|
-
* Returns true if this streamer instance has already been initialized.
|
|
7349
|
-
*
|
|
7350
|
-
* @returns Boolean indicating initialization status
|
|
7351
|
-
*/
|
|
7352
7020
|
isInitialized() {
|
|
7353
7021
|
return this._initialized;
|
|
7354
7022
|
}
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
* @returns Streamer version string
|
|
7359
|
-
*/
|
|
7023
|
+
isDestroyed() {
|
|
7024
|
+
return this._isDestroyed;
|
|
7025
|
+
}
|
|
7360
7026
|
getVersion() {
|
|
7361
7027
|
return this.STREAMER_VERSION;
|
|
7362
7028
|
}
|
|
7363
|
-
/**
|
|
7364
|
-
* Returns the development branch of this streamer (e.g., main, experimental).
|
|
7365
|
-
*
|
|
7366
|
-
* @returns Branch name string
|
|
7367
|
-
*/
|
|
7368
7029
|
getBranch() {
|
|
7369
7030
|
return this.STREAMER_BRANCH;
|
|
7370
7031
|
}
|
|
7371
|
-
|
|
7372
|
-
// EVENT
|
|
7373
|
-
//------------------------------------------------------------------------//
|
|
7374
|
-
/**
|
|
7375
|
-
* Dispatches an event with the specified name and data.
|
|
7376
|
-
*
|
|
7377
|
-
* @param eventName - Name of the event to dispatch
|
|
7378
|
-
* @param event - Object containing event data
|
|
7379
|
-
*/
|
|
7032
|
+
// EVENTS
|
|
7380
7033
|
dispatchEvent(eventName, event) {
|
|
7381
7034
|
super.dispatchEvent(eventName, event);
|
|
7382
7035
|
}
|
|
7383
|
-
|
|
7384
|
-
// CLEAN UP
|
|
7385
|
-
//------------------------------------------------------------------------//
|
|
7386
|
-
/**
|
|
7387
|
-
* Starts the streaming process.
|
|
7388
|
-
*
|
|
7389
|
-
* @returns Promise that resolves when streaming has started
|
|
7390
|
-
*/
|
|
7036
|
+
// START / STOP
|
|
7391
7037
|
start() {
|
|
7392
7038
|
var _a;
|
|
7393
7039
|
return __awaiter(this, void 0, void 0, function* () {
|
|
7394
7040
|
return (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.start();
|
|
7395
7041
|
});
|
|
7396
7042
|
}
|
|
7397
|
-
/**
|
|
7398
|
-
* Stops the streaming process.
|
|
7399
|
-
*/
|
|
7400
7043
|
stop() {
|
|
7401
7044
|
var _a;
|
|
7402
|
-
|
|
7045
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.stop();
|
|
7046
|
+
}
|
|
7047
|
+
// DEBUG
|
|
7048
|
+
debugMediaState() {
|
|
7049
|
+
var _a;
|
|
7050
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.debugMediaState();
|
|
7403
7051
|
}
|
|
7404
|
-
//------------------------------------------------------------------------//
|
|
7405
|
-
// CLEAN UP
|
|
7406
|
-
//------------------------------------------------------------------------//
|
|
7407
7052
|
/**
|
|
7408
|
-
* Destroys this instance of StormStreamer, releasing all resources
|
|
7409
|
-
*
|
|
7053
|
+
* Destroys this instance of StormStreamer, releasing all resources.
|
|
7054
|
+
* This method is SYNCHRONOUS - it sets flags immediately to prevent race conditions.
|
|
7055
|
+
* All pending async operations will detect the destroyed state and abort.
|
|
7410
7056
|
*/
|
|
7411
7057
|
destroy() {
|
|
7412
|
-
var _a, _b, _c, _d;
|
|
7413
|
-
|
|
7414
|
-
if (this.
|
|
7415
|
-
|
|
7058
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
7059
|
+
// Prevent double destroy
|
|
7060
|
+
if (this._isDestroyed) {
|
|
7061
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.warning(this, "🔴 [DESTROY] Already destroyed, skipping");
|
|
7062
|
+
return;
|
|
7063
|
+
}
|
|
7064
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.warning(this, "🔴 [DESTROY] Starting streamer instance destruction (sync)...");
|
|
7065
|
+
// CRITICAL: Set flag IMMEDIATELY
|
|
7066
|
+
this._isDestroyed = true;
|
|
7416
7067
|
this._initialized = false;
|
|
7417
7068
|
this._isRemoved = true;
|
|
7418
|
-
//
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
//
|
|
7069
|
+
// Remove from global array
|
|
7070
|
+
if (this.DEV_MODE && 'StormStreamerArray' in window) {
|
|
7071
|
+
window.StormStreamerArray[this._streamerID] = null;
|
|
7072
|
+
}
|
|
7073
|
+
// Stop all graphs
|
|
7074
|
+
this.stopAllGraphs();
|
|
7075
|
+
this._graphs = [];
|
|
7076
|
+
// Destroy network controller
|
|
7077
|
+
if (this._networkController) {
|
|
7078
|
+
(_c = this._logger) === null || _c === void 0 ? void 0 : _c.info(this, "🔴 [DESTROY] Destroying NetworkController...");
|
|
7079
|
+
try {
|
|
7080
|
+
(_d = this._networkController.getConnection()) === null || _d === void 0 ? void 0 : _d.destroy();
|
|
7081
|
+
} catch (e) {/* ignore */}
|
|
7082
|
+
this._networkController = null;
|
|
7083
|
+
}
|
|
7084
|
+
// Destroy streamer controller (this is also sync now)
|
|
7085
|
+
if (this._streamerController) {
|
|
7086
|
+
(_e = this._logger) === null || _e === void 0 ? void 0 : _e.info(this, "🔴 [DESTROY] Destroying StreamerController...");
|
|
7087
|
+
this._streamerController.destroy();
|
|
7088
|
+
this._streamerController = null;
|
|
7089
|
+
}
|
|
7090
|
+
// Destroy stage controller
|
|
7091
|
+
if (this._stageController) {
|
|
7092
|
+
(_f = this._logger) === null || _f === void 0 ? void 0 : _f.info(this, "🔴 [DESTROY] Destroying StageController...");
|
|
7093
|
+
try {
|
|
7094
|
+
this._stageController.destroy();
|
|
7095
|
+
} catch (e) {/* ignore */}
|
|
7096
|
+
this._stageController = null;
|
|
7097
|
+
}
|
|
7098
|
+
// Clear other references
|
|
7099
|
+
this._storageManager = null;
|
|
7100
|
+
this._statsController = null;
|
|
7101
|
+
// Remove all event listeners
|
|
7423
7102
|
this.removeAllEventListeners();
|
|
7103
|
+
(_g = this._logger) === null || _g === void 0 ? void 0 : _g.success(this, "🔴 [DESTROY] Streamer instance destroyed successfully (sync)");
|
|
7424
7104
|
}
|
|
7425
7105
|
}
|
|
7426
|
-
/**
|
|
7427
|
-
* Next ID for the streamer instance. Each subsequent instance has a higher number.
|
|
7428
|
-
* @private
|
|
7429
|
-
*/
|
|
7430
7106
|
StormStreamer.NEXT_STREAMER_ID = 0;
|
|
7431
7107
|
|
|
7432
7108
|
function create(config) {
|