@stormstreaming/stormstreamer 1.0.0-rc.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/amd/index.js +662 -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.
|
|
8
|
-
* Version:
|
|
7
|
+
* Version: 1.0.1
|
|
8
|
+
* Version: 2/18/2026, 5:26:15 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,16 +5017,20 @@
|
|
|
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);
|
|
5027
|
+
if (this._main.getConfigManager().getStreamData().streamKey == streamKey) {
|
|
5028
|
+
if (this._debug) this._logger.decoratedLog("Canceling publishing, already published " + streamKey, "dark-red");
|
|
5029
|
+
this._logger.info(this, "Canceling publishing, already published " + streamKey);
|
|
5030
|
+
return false;
|
|
5031
|
+
}
|
|
4906
5032
|
if (this._main.getConfigManager().getStreamData().streamKey != null && !this._firstPublish) {
|
|
5033
|
+
if (this._debug) this._logger.decoratedLog("Unpublishing active session: " + this._main.getConfigManager().getStreamData().streamKey, "dark-red");
|
|
4907
5034
|
this.unpublish();
|
|
4908
5035
|
}
|
|
4909
5036
|
this._main.getConfigManager().getStreamData().streamKey = streamKey;
|
|
@@ -4920,8 +5047,12 @@
|
|
|
4920
5047
|
this._firstPublish = false;
|
|
4921
5048
|
return true;
|
|
4922
5049
|
}
|
|
5050
|
+
/**
|
|
5051
|
+
* Stops publishing and cleans up WebRTC connection
|
|
5052
|
+
*/
|
|
4923
5053
|
unpublish() {
|
|
4924
5054
|
if (this._debug) this._logger.decoratedLog("Unpublish", "dark-red");
|
|
5055
|
+
this._logger.info(this, "📹 [UNPUBLISH] unpublish() - stopping WebRTC but keeping camera preview");
|
|
4925
5056
|
if (this._statusConnection != null) {
|
|
4926
5057
|
this._statusConnection.destroy();
|
|
4927
5058
|
this._statusConnection = null;
|
|
@@ -4935,13 +5066,24 @@
|
|
|
4935
5066
|
this._main.dispatchEvent("unpublish", {
|
|
4936
5067
|
ref: this._main
|
|
4937
5068
|
});
|
|
4938
|
-
this.
|
|
5069
|
+
this.closeWebRTCConnection();
|
|
5070
|
+
this.setPublishState(exports.PublishState.UNPUBLISHED);
|
|
4939
5071
|
}
|
|
4940
5072
|
/**
|
|
4941
|
-
*
|
|
5073
|
+
* Stops publishing AND releases camera/microphone
|
|
5074
|
+
*/
|
|
5075
|
+
unpublishAndRelease() {
|
|
5076
|
+
this._logger.info(this, "📹 [UNPUBLISH+RELEASE] unpublishAndRelease() - stopping everything");
|
|
5077
|
+
if (this._debug) this._logger.decoratedLog("Unpublish + Release", "dark-red");
|
|
5078
|
+
this.unpublish();
|
|
5079
|
+
this.stopCameraStream();
|
|
5080
|
+
}
|
|
5081
|
+
/**
|
|
5082
|
+
* Sets up orientation listener
|
|
4942
5083
|
* @private
|
|
4943
5084
|
*/
|
|
4944
5085
|
setupOrientationListener() {
|
|
5086
|
+
if (this._isDestroyed) return;
|
|
4945
5087
|
this._orientationChangeHandler = this.handleOrientationChange;
|
|
4946
5088
|
if (window.screen && window.screen.orientation) {
|
|
4947
5089
|
window.screen.orientation.addEventListener('change', this._orientationChangeHandler);
|
|
@@ -4953,21 +5095,20 @@
|
|
|
4953
5095
|
// USER MEDIA
|
|
4954
5096
|
//------------------------------------------------------------------------//
|
|
4955
5097
|
/**
|
|
4956
|
-
* Error
|
|
4957
|
-
*
|
|
4958
|
-
* @param error
|
|
5098
|
+
* Error handler for getUserMedia
|
|
4959
5099
|
*/
|
|
4960
5100
|
onUserMediaError(error) {
|
|
4961
5101
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5102
|
+
if (this._isDestroyed) return;
|
|
5103
|
+
this._logger.error(this, `📹 [ERROR] onUserMediaError() - ${error.name}: ${error.message}`);
|
|
4962
5104
|
yield this.grabDevices();
|
|
4963
|
-
// Dodatkowa obsługa specyficznych błędów getUserMedia, jeśli potrzebna
|
|
4964
5105
|
if (error.name === "OverconstrainedError") {
|
|
4965
5106
|
this._logger.warning(this, "Device constraints not satisfied");
|
|
4966
5107
|
}
|
|
4967
5108
|
});
|
|
4968
5109
|
}
|
|
4969
5110
|
/**
|
|
4970
|
-
*
|
|
5111
|
+
* Checks individual device access
|
|
4971
5112
|
* @private
|
|
4972
5113
|
*/
|
|
4973
5114
|
checkIndividualDeviceAccess() {
|
|
@@ -4982,44 +5123,47 @@
|
|
|
4982
5123
|
available: false
|
|
4983
5124
|
}
|
|
4984
5125
|
};
|
|
4985
|
-
|
|
5126
|
+
if (this._isDestroyed) return results;
|
|
4986
5127
|
try {
|
|
4987
5128
|
const devices = yield navigator.mediaDevices.enumerateDevices();
|
|
5129
|
+
if (this._isDestroyed) return results;
|
|
4988
5130
|
results.camera.available = devices.some(device => device.kind === 'videoinput');
|
|
4989
5131
|
results.microphone.available = devices.some(device => device.kind === 'audioinput');
|
|
4990
|
-
// Sprawdzamy czy mamy etykiety urządzeń - ich brak może oznaczać brak uprawnień
|
|
4991
5132
|
const hasLabels = devices.some(device => device.label !== '');
|
|
4992
5133
|
if (!hasLabels) {
|
|
4993
|
-
|
|
5134
|
+
this._logger.info(this, "📹 [ACQUIRE] checkIndividualDeviceAccess() - no labels, requesting permissions");
|
|
5135
|
+
if (this._isDestroyed) return results;
|
|
4994
5136
|
try {
|
|
4995
5137
|
const stream = yield navigator.mediaDevices.getUserMedia({
|
|
4996
5138
|
video: results.camera.available,
|
|
4997
5139
|
audio: results.microphone.available
|
|
4998
5140
|
});
|
|
4999
|
-
//
|
|
5141
|
+
// CRITICAL: Check IMMEDIATELY after getUserMedia
|
|
5142
|
+
if (this._isDestroyed) {
|
|
5143
|
+
this._logger.warning(this, "📹 [RELEASE] checkIndividualDeviceAccess() - destroyed, releasing orphan stream");
|
|
5144
|
+
stream.getTracks().forEach(track => track.stop());
|
|
5145
|
+
return results;
|
|
5146
|
+
}
|
|
5000
5147
|
results.camera.allowed = stream.getVideoTracks().length > 0;
|
|
5001
5148
|
results.microphone.allowed = stream.getAudioTracks().length > 0;
|
|
5002
|
-
|
|
5003
|
-
stream.getTracks().forEach(track =>
|
|
5004
|
-
|
|
5149
|
+
this._logger.info(this, "📹 [RELEASE] checkIndividualDeviceAccess() - stopping test stream");
|
|
5150
|
+
stream.getTracks().forEach(track => {
|
|
5151
|
+
track.stop();
|
|
5152
|
+
this._logger.info(this, `📹 [RELEASE] checkIndividualDeviceAccess() - stopped track: ${track.kind}`);
|
|
5153
|
+
});
|
|
5154
|
+
if (this._isDestroyed) return results;
|
|
5005
5155
|
yield this.grabDevices();
|
|
5006
5156
|
} catch (error) {
|
|
5007
5157
|
console.error('Error requesting permissions:', error);
|
|
5008
|
-
// Nie udało się uzyskać uprawnień
|
|
5009
5158
|
results.camera.allowed = false;
|
|
5010
5159
|
results.microphone.allowed = false;
|
|
5011
5160
|
}
|
|
5012
5161
|
} else {
|
|
5013
|
-
// Jeśli mamy etykiety, prawdopodobnie mamy już uprawnienia
|
|
5014
5162
|
results.camera.allowed = devices.some(device => device.kind === 'videoinput' && device.label !== '');
|
|
5015
5163
|
results.microphone.allowed = devices.some(device => device.kind === 'audioinput' && device.label !== '');
|
|
5016
5164
|
}
|
|
5017
5165
|
} catch (error) {
|
|
5018
5166
|
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
5167
|
}
|
|
5024
5168
|
return results;
|
|
5025
5169
|
});
|
|
@@ -5028,21 +5172,18 @@
|
|
|
5028
5172
|
// SOCKETS & SDP
|
|
5029
5173
|
//------------------------------------------------------------------------//
|
|
5030
5174
|
/**
|
|
5031
|
-
*
|
|
5032
|
-
*
|
|
5033
|
-
* @param data
|
|
5175
|
+
* Handles SDP/ICE-Candidate exchange
|
|
5034
5176
|
*/
|
|
5035
5177
|
onSocketMessage(data) {
|
|
5036
5178
|
var _a;
|
|
5179
|
+
if (this._isDestroyed) return;
|
|
5037
5180
|
let msgJSON = JSON.parse(data);
|
|
5038
5181
|
let msgStatus = Number(msgJSON["status"]);
|
|
5039
5182
|
switch (msgStatus) {
|
|
5040
5183
|
case 200:
|
|
5041
|
-
// OK
|
|
5042
5184
|
this._logger.info(this, "SDP Exchange Successful");
|
|
5043
5185
|
let sdpData = msgJSON['sdp'];
|
|
5044
|
-
if (sdpData !== undefined) {
|
|
5045
|
-
// @ts-ignore
|
|
5186
|
+
if (sdpData !== undefined && this._peerConnection) {
|
|
5046
5187
|
this._peerConnection.setRemoteDescription(new RTCSessionDescription(sdpData), () => {}, () => {});
|
|
5047
5188
|
}
|
|
5048
5189
|
let iceCandidates = msgJSON['iceCandidates'];
|
|
@@ -5053,7 +5194,6 @@
|
|
|
5053
5194
|
}
|
|
5054
5195
|
break;
|
|
5055
5196
|
case 503:
|
|
5056
|
-
// NOT OK
|
|
5057
5197
|
this._logger.error(this, "StreamKey already use");
|
|
5058
5198
|
const usedStreamKey = (_a = this._main.getConfigManager().getStreamData().streamKey) !== null && _a !== void 0 ? _a : "unknown";
|
|
5059
5199
|
this._main.dispatchEvent("streamKeyInUse", {
|
|
@@ -5067,14 +5207,8 @@
|
|
|
5067
5207
|
//------------------------------------------------------------------------//
|
|
5068
5208
|
// EVENTS
|
|
5069
5209
|
//------------------------------------------------------------------------//
|
|
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
5210
|
onConnectionStateChange(event) {
|
|
5211
|
+
if (this._isDestroyed) return;
|
|
5078
5212
|
this._logger.info(this, "Connection State Change: " + JSON.stringify(event));
|
|
5079
5213
|
if (event !== null) {
|
|
5080
5214
|
switch (event.currentTarget.connectionState) {
|
|
@@ -5105,16 +5239,16 @@
|
|
|
5105
5239
|
// DEVICES
|
|
5106
5240
|
//------------------------------------------------------------------------//
|
|
5107
5241
|
/**
|
|
5108
|
-
*
|
|
5242
|
+
* Grabs available devices
|
|
5109
5243
|
*/
|
|
5110
5244
|
grabDevices() {
|
|
5111
5245
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5246
|
+
if (this._isDestroyed) return;
|
|
5112
5247
|
try {
|
|
5113
5248
|
const deviceAccess = yield this.checkIndividualDeviceAccess();
|
|
5249
|
+
if (this._isDestroyed) return;
|
|
5114
5250
|
this._cameraList = new InputDeviceList();
|
|
5115
5251
|
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
5252
|
if (!this._permissionChecked) {
|
|
5119
5253
|
if (!deviceAccess.camera.allowed) {
|
|
5120
5254
|
this._main.dispatchEvent("cameraAccessDenied", {
|
|
@@ -5143,8 +5277,9 @@
|
|
|
5143
5277
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5144
5278
|
}
|
|
5145
5279
|
}
|
|
5146
|
-
|
|
5280
|
+
if (this._isDestroyed) return;
|
|
5147
5281
|
const devices = yield navigator.mediaDevices.enumerateDevices();
|
|
5282
|
+
if (this._isDestroyed) return;
|
|
5148
5283
|
for (const device of devices) {
|
|
5149
5284
|
if (device.deviceId && device.label) {
|
|
5150
5285
|
if (device.kind === 'videoinput' && deviceAccess.camera.allowed) {
|
|
@@ -5156,8 +5291,8 @@
|
|
|
5156
5291
|
}
|
|
5157
5292
|
}
|
|
5158
5293
|
}
|
|
5294
|
+
if (this._isDestroyed) return;
|
|
5159
5295
|
try {
|
|
5160
|
-
// Aktualizacja wybranych urządzeń
|
|
5161
5296
|
if (deviceAccess.camera.allowed) {
|
|
5162
5297
|
this._selectedCamera = this.pickCamera();
|
|
5163
5298
|
}
|
|
@@ -5167,38 +5302,41 @@
|
|
|
5167
5302
|
} catch (error) {
|
|
5168
5303
|
console.log(error);
|
|
5169
5304
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5170
|
-
this._logger.error(this, "
|
|
5305
|
+
this._logger.error(this, "Error on grab devices: " + JSON.stringify(error));
|
|
5306
|
+
}
|
|
5307
|
+
if (!this._isDestroyed) {
|
|
5308
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5309
|
+
ref: this._main,
|
|
5310
|
+
cameraList: this._cameraList.getArray(),
|
|
5311
|
+
microphoneList: this._microphoneList.getArray()
|
|
5312
|
+
});
|
|
5171
5313
|
}
|
|
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
5314
|
this._permissionChecked = true;
|
|
5179
5315
|
} catch (error) {
|
|
5316
|
+
if (this._isDestroyed) return;
|
|
5180
5317
|
console.error("Error in grabDevices:", error);
|
|
5181
5318
|
this._cameraList = new InputDeviceList();
|
|
5182
5319
|
this._microphoneList = new InputDeviceList();
|
|
5183
|
-
this.
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5320
|
+
if (!this._isDestroyed) {
|
|
5321
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5322
|
+
ref: this._main,
|
|
5323
|
+
cameraList: this._cameraList.getArray(),
|
|
5324
|
+
microphoneList: this._microphoneList.getArray()
|
|
5325
|
+
});
|
|
5326
|
+
this._main.dispatchEvent("inputDeviceError", {
|
|
5327
|
+
ref: this._main
|
|
5328
|
+
});
|
|
5329
|
+
}
|
|
5191
5330
|
}
|
|
5192
5331
|
});
|
|
5193
5332
|
}
|
|
5194
5333
|
/**
|
|
5195
|
-
* Selects camera
|
|
5196
|
-
* @param cameraID
|
|
5334
|
+
* Selects camera by ID
|
|
5197
5335
|
*/
|
|
5198
5336
|
selectCamera(cameraID) {
|
|
5199
5337
|
var _a, _b, _c;
|
|
5200
5338
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5201
|
-
|
|
5339
|
+
if (this._isDestroyed) return;
|
|
5202
5340
|
if (this._cameraAbortController) {
|
|
5203
5341
|
this._cameraAbortController.abort();
|
|
5204
5342
|
}
|
|
@@ -5206,13 +5344,13 @@
|
|
|
5206
5344
|
const signal = this._cameraAbortController.signal;
|
|
5207
5345
|
try {
|
|
5208
5346
|
this._switchingCamera = true;
|
|
5347
|
+
this._logger.info(this, `📹 [SWITCH] selectCamera() - switching to camera: ${cameraID}`);
|
|
5209
5348
|
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5210
5349
|
this._cameraList.get(i).isSelected = false;
|
|
5211
5350
|
}
|
|
5212
5351
|
this._selectedCamera = null;
|
|
5213
5352
|
this.setInputDeviceState(exports.InputDevicesState.UPDATING);
|
|
5214
5353
|
this.setCameraState(exports.DeviceState.NOT_INITIALIZED);
|
|
5215
|
-
// Zapamiętaj aktualny stream key i stan publikacji
|
|
5216
5354
|
const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
5217
5355
|
const wasPublished = this._publishState === exports.PublishState.CONNECTED;
|
|
5218
5356
|
let found = false;
|
|
@@ -5230,14 +5368,11 @@
|
|
|
5230
5368
|
cameraList: this._cameraList.getArray(),
|
|
5231
5369
|
microphoneList: this._microphoneList.getArray()
|
|
5232
5370
|
});
|
|
5233
|
-
if (signal.aborted) return;
|
|
5371
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5234
5372
|
this.stopCameraStream();
|
|
5235
5373
|
if (this._selectedCamera != null) {
|
|
5236
|
-
// Update constraints with new device
|
|
5237
5374
|
this._constraints.video.deviceId = this._selectedCamera.id;
|
|
5238
|
-
|
|
5239
|
-
if (signal.aborted) return;
|
|
5240
|
-
// Poczekaj z możliwością anulowania
|
|
5375
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5241
5376
|
yield new Promise((resolve, reject) => {
|
|
5242
5377
|
const timeout = setTimeout(resolve, 500);
|
|
5243
5378
|
signal.addEventListener('abort', () => {
|
|
@@ -5245,20 +5380,20 @@
|
|
|
5245
5380
|
reject(new Error('Aborted'));
|
|
5246
5381
|
});
|
|
5247
5382
|
});
|
|
5248
|
-
if (signal.aborted) return;
|
|
5249
|
-
// Restart camera stream
|
|
5383
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5250
5384
|
yield this.startCamera();
|
|
5385
|
+
if (this._isDestroyed) return;
|
|
5251
5386
|
this.setCameraState(exports.DeviceState.ENABLED);
|
|
5252
5387
|
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) {
|
|
5388
|
+
if (wasPublished && streamKey && !signal.aborted && !this._isDestroyed) {
|
|
5254
5389
|
this.publish(streamKey);
|
|
5255
5390
|
}
|
|
5256
5391
|
} else {
|
|
5257
5392
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5258
5393
|
}
|
|
5259
5394
|
} catch (error) {
|
|
5260
|
-
if (error.message !== 'Aborted') {
|
|
5261
|
-
this._logger.error(this, 'Error switching camera: ' + error);
|
|
5395
|
+
if (error.message !== 'Aborted' && !this._isDestroyed) {
|
|
5396
|
+
this._logger.error(this, '📹 [ERROR] selectCamera() - Error switching camera: ' + error);
|
|
5262
5397
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5263
5398
|
}
|
|
5264
5399
|
} finally {
|
|
@@ -5270,13 +5405,12 @@
|
|
|
5270
5405
|
});
|
|
5271
5406
|
}
|
|
5272
5407
|
/**
|
|
5273
|
-
*
|
|
5274
|
-
* @param micID
|
|
5408
|
+
* Selects microphone by ID
|
|
5275
5409
|
*/
|
|
5276
5410
|
selectMicrophone(micID) {
|
|
5277
5411
|
var _a, _b, _c;
|
|
5278
5412
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5279
|
-
|
|
5413
|
+
if (this._isDestroyed) return;
|
|
5280
5414
|
if (this._microphoneAbortController) {
|
|
5281
5415
|
this._microphoneAbortController.abort();
|
|
5282
5416
|
}
|
|
@@ -5284,17 +5418,15 @@
|
|
|
5284
5418
|
const signal = this._microphoneAbortController.signal;
|
|
5285
5419
|
try {
|
|
5286
5420
|
this._switchingMicrophone = true;
|
|
5421
|
+
this._logger.info(this, `📹 [SWITCH] selectMicrophone() - switching to microphone: ${micID}`);
|
|
5287
5422
|
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5288
5423
|
this._microphoneList.get(i).isSelected = false;
|
|
5289
5424
|
}
|
|
5290
5425
|
this._selectedMicrophone = null;
|
|
5291
5426
|
this.setInputDeviceState(exports.InputDevicesState.UPDATING);
|
|
5292
5427
|
this.setMicrophoneState(exports.DeviceState.NOT_INITIALIZED);
|
|
5293
|
-
this._logger.info(this, "Selecting microphone: " + micID);
|
|
5294
|
-
// Zapamiętaj aktualny stream key i stan publikacji
|
|
5295
5428
|
const streamKey = (_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getStreamData().streamKey;
|
|
5296
5429
|
const wasPublished = this._publishState === exports.PublishState.CONNECTED;
|
|
5297
|
-
// Znajdź i zapisz wybrany mikrofon
|
|
5298
5430
|
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5299
5431
|
if (this._microphoneList.get(i).id == micID) {
|
|
5300
5432
|
this._selectedMicrophone = this._microphoneList.get(i);
|
|
@@ -5303,26 +5435,23 @@
|
|
|
5303
5435
|
break;
|
|
5304
5436
|
}
|
|
5305
5437
|
}
|
|
5306
|
-
// Zawsze wysyłamy aktualizację list urządzeń
|
|
5307
5438
|
this._main.dispatchEvent("deviceListUpdate", {
|
|
5308
5439
|
ref: this._main,
|
|
5309
5440
|
cameraList: this._cameraList.getArray(),
|
|
5310
5441
|
microphoneList: this._microphoneList.getArray()
|
|
5311
5442
|
});
|
|
5312
|
-
if (signal.aborted) return;
|
|
5313
|
-
// Odłącz SoundMeter przed zmianą strumienia
|
|
5443
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5314
5444
|
this._soundMeter.detach();
|
|
5315
|
-
// Zamknij istniejące połączenie WebRTC
|
|
5316
5445
|
this.closeWebRTCConnection();
|
|
5317
|
-
// Zatrzymaj obecny strumień
|
|
5318
5446
|
if (this._stream) {
|
|
5447
|
+
this._logger.info(this, "📹 [RELEASE] selectMicrophone() - stopping current stream");
|
|
5319
5448
|
this._stream.getTracks().forEach(track => {
|
|
5320
5449
|
track.stop();
|
|
5321
5450
|
});
|
|
5322
5451
|
this._stream = null;
|
|
5452
|
+
this._activeStreamCount--;
|
|
5323
5453
|
}
|
|
5324
|
-
if (signal.aborted) return;
|
|
5325
|
-
// Poczekaj z możliwością anulowania
|
|
5454
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5326
5455
|
yield new Promise((resolve, reject) => {
|
|
5327
5456
|
const timeout = setTimeout(resolve, 500);
|
|
5328
5457
|
signal.addEventListener('abort', () => {
|
|
@@ -5330,21 +5459,20 @@
|
|
|
5330
5459
|
reject(new Error('Aborted'));
|
|
5331
5460
|
});
|
|
5332
5461
|
});
|
|
5333
|
-
if (signal.aborted) return;
|
|
5334
|
-
// Rozpocznij wszystko od nowa
|
|
5462
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5335
5463
|
yield this.startCamera();
|
|
5464
|
+
if (this._isDestroyed) return;
|
|
5336
5465
|
this.setMicrophoneState(exports.DeviceState.ENABLED);
|
|
5337
5466
|
if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) {
|
|
5338
5467
|
this.setInputDeviceState(exports.InputDevicesState.READY);
|
|
5339
5468
|
} else {
|
|
5340
5469
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5341
5470
|
}
|
|
5342
|
-
|
|
5343
|
-
if (wasPublished && streamKey && !signal.aborted) {
|
|
5471
|
+
if (wasPublished && streamKey && !signal.aborted && !this._isDestroyed) {
|
|
5344
5472
|
this.publish(streamKey);
|
|
5345
5473
|
}
|
|
5346
5474
|
} catch (error) {
|
|
5347
|
-
if (error.message !== 'Aborted') {
|
|
5475
|
+
if (error.message !== 'Aborted' && !this._isDestroyed) {
|
|
5348
5476
|
console.error("Error changing microphone:", error);
|
|
5349
5477
|
this._main.dispatchEvent("inputDeviceError", {
|
|
5350
5478
|
ref: this._main
|
|
@@ -5360,23 +5488,29 @@
|
|
|
5360
5488
|
});
|
|
5361
5489
|
}
|
|
5362
5490
|
/**
|
|
5363
|
-
*
|
|
5491
|
+
* Starts camera with abort support
|
|
5364
5492
|
* @private
|
|
5365
5493
|
*/
|
|
5366
5494
|
startCamera() {
|
|
5367
5495
|
var _a;
|
|
5368
5496
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5369
|
-
|
|
5497
|
+
if (this._isDestroyed) {
|
|
5498
|
+
this._logger.warning(this, "📹 [ACQUIRE] startCamera() - aborted, instance is destroyed");
|
|
5499
|
+
return;
|
|
5500
|
+
}
|
|
5370
5501
|
if (this._startCameraAbortController) {
|
|
5371
5502
|
this._startCameraAbortController.abort();
|
|
5372
5503
|
}
|
|
5373
5504
|
this._startCameraAbortController = new AbortController();
|
|
5374
5505
|
const signal = this._startCameraAbortController.signal;
|
|
5506
|
+
// Release existing stream first
|
|
5375
5507
|
if (this._stream) {
|
|
5508
|
+
this._logger.info(this, "📹 [RELEASE] startCamera() - releasing existing stream before acquiring new one");
|
|
5376
5509
|
this._stream.getTracks().forEach(track => {
|
|
5377
5510
|
track.stop();
|
|
5378
5511
|
});
|
|
5379
5512
|
this._stream = null;
|
|
5513
|
+
this._activeStreamCount--;
|
|
5380
5514
|
}
|
|
5381
5515
|
try {
|
|
5382
5516
|
const constraints = {
|
|
@@ -5391,18 +5525,24 @@
|
|
|
5391
5525
|
}
|
|
5392
5526
|
} : false
|
|
5393
5527
|
};
|
|
5394
|
-
if (signal.aborted) return;
|
|
5528
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5529
|
+
this._logger.info(this, `📹 [ACQUIRE] startCamera() - requesting getUserMedia`);
|
|
5395
5530
|
try {
|
|
5396
5531
|
const stream = yield navigator.mediaDevices.getUserMedia(constraints);
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5532
|
+
// CRITICAL: Check IMMEDIATELY after getUserMedia returns
|
|
5533
|
+
if (signal.aborted || this._isDestroyed) {
|
|
5534
|
+
this._logger.warning(this, "📹 [RELEASE] startCamera() - destroyed during getUserMedia, releasing orphan stream");
|
|
5535
|
+
stream.getTracks().forEach(track => {
|
|
5536
|
+
track.stop();
|
|
5537
|
+
this._logger.info(this, `📹 [RELEASE] startCamera() - stopped orphan track: ${track.kind}, id: ${track.id}`);
|
|
5538
|
+
});
|
|
5400
5539
|
return;
|
|
5401
5540
|
}
|
|
5402
5541
|
this._stream = stream;
|
|
5403
5542
|
this.onCameraStreamSuccess(this._stream);
|
|
5404
5543
|
} catch (error) {
|
|
5405
|
-
if (signal.aborted) return;
|
|
5544
|
+
if (signal.aborted || this._isDestroyed) return;
|
|
5545
|
+
this._logger.error(this, `📹 [ERROR] startCamera() - getUserMedia failed: ${error.name}: ${error.message}`);
|
|
5406
5546
|
if (constraints.video) {
|
|
5407
5547
|
this.onUserMediaError({
|
|
5408
5548
|
name: error.name || 'Error',
|
|
@@ -5420,6 +5560,7 @@
|
|
|
5420
5560
|
}
|
|
5421
5561
|
if (this._cameraState == exports.DeviceState.ENABLED && this._microphoneState == exports.DeviceState.ENABLED) this.setInputDeviceState(exports.InputDevicesState.READY);
|
|
5422
5562
|
} catch (error) {
|
|
5563
|
+
if (this._isDestroyed) return;
|
|
5423
5564
|
console.error("Error in startCamera:", error);
|
|
5424
5565
|
yield this.grabDevices();
|
|
5425
5566
|
} finally {
|
|
@@ -5433,15 +5574,14 @@
|
|
|
5433
5574
|
* Updates WebRTC connection with new stream
|
|
5434
5575
|
*/
|
|
5435
5576
|
updateWebRTCStream() {
|
|
5577
|
+
if (this._isDestroyed) return;
|
|
5436
5578
|
if (!this._peerConnection || !this._stream) {
|
|
5437
5579
|
return;
|
|
5438
5580
|
}
|
|
5439
|
-
// Remove all existing tracks from the peer connection
|
|
5440
5581
|
const senders = this._peerConnection.getSenders();
|
|
5441
5582
|
senders.forEach(sender => {
|
|
5442
5583
|
if (this._peerConnection) this._peerConnection.removeTrack(sender);
|
|
5443
5584
|
});
|
|
5444
|
-
// Add new tracks
|
|
5445
5585
|
this._stream.getTracks().forEach(track => {
|
|
5446
5586
|
if (this._stream != null && this._peerConnection) {
|
|
5447
5587
|
this._peerConnection.addTrack(track, this._stream);
|
|
@@ -5449,32 +5589,24 @@
|
|
|
5449
5589
|
});
|
|
5450
5590
|
}
|
|
5451
5591
|
/**
|
|
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
|
|
5592
|
+
* Picks camera based on saved ID or defaults
|
|
5460
5593
|
* @private
|
|
5461
5594
|
*/
|
|
5462
5595
|
pickCamera() {
|
|
5463
5596
|
var _a, _b, _c, _d, _e, _f;
|
|
5597
|
+
if (this._isDestroyed) return null;
|
|
5464
5598
|
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5465
5599
|
this._cameraList.get(i).isSelected = false;
|
|
5466
5600
|
}
|
|
5467
5601
|
let savedCameraID = (_b = (_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.getField("cameraID")) !== null && _b !== void 0 ? _b : null;
|
|
5468
5602
|
if (this._cameraList.getSize() > 0) {
|
|
5469
5603
|
if (savedCameraID) {
|
|
5470
|
-
// Szukamy zapisanej kamery
|
|
5471
5604
|
let found = false;
|
|
5472
5605
|
for (let i = 0; i < this._cameraList.getSize(); i++) {
|
|
5473
5606
|
if (this._cameraList.get(i).id === savedCameraID) {
|
|
5474
5607
|
this._selectedCamera = this._cameraList.get(i);
|
|
5475
5608
|
this._selectedCamera.isSelected = true;
|
|
5476
5609
|
this.setCameraState(exports.DeviceState.ENABLED);
|
|
5477
|
-
// Ustaw deviceId w constraints
|
|
5478
5610
|
found = true;
|
|
5479
5611
|
this._constraints.video.deviceId = this._selectedCamera.id;
|
|
5480
5612
|
break;
|
|
@@ -5491,10 +5623,8 @@
|
|
|
5491
5623
|
this._logger.info(this, "Canceling Publish!");
|
|
5492
5624
|
this._main.getConfigManager().getStreamData().streamKey = null;
|
|
5493
5625
|
}
|
|
5494
|
-
return null;
|
|
5495
5626
|
}
|
|
5496
5627
|
}
|
|
5497
|
-
// Jeśli nie znaleziono zapisanej kamery, używamy pierwszej
|
|
5498
5628
|
if (!this._selectedCamera) {
|
|
5499
5629
|
this._main.dispatchEvent("savedCameraNotFound", {
|
|
5500
5630
|
ref: this._main,
|
|
@@ -5517,19 +5647,22 @@
|
|
|
5517
5647
|
}
|
|
5518
5648
|
}
|
|
5519
5649
|
}
|
|
5520
|
-
this.
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5650
|
+
if (!this._isDestroyed) {
|
|
5651
|
+
this._main.dispatchEvent("deviceListUpdate", {
|
|
5652
|
+
ref: this._main,
|
|
5653
|
+
cameraList: this._cameraList.getArray(),
|
|
5654
|
+
microphoneList: this._microphoneList.getArray()
|
|
5655
|
+
});
|
|
5656
|
+
}
|
|
5525
5657
|
return this._selectedCamera;
|
|
5526
5658
|
}
|
|
5527
5659
|
/**
|
|
5528
|
-
*
|
|
5660
|
+
* Picks microphone based on saved ID or defaults
|
|
5529
5661
|
* @private
|
|
5530
5662
|
*/
|
|
5531
5663
|
pickMicrophone() {
|
|
5532
5664
|
var _a, _b, _c, _d, _e, _f;
|
|
5665
|
+
if (this._isDestroyed) return null;
|
|
5533
5666
|
for (let i = 0; i < this._microphoneList.getSize(); i++) {
|
|
5534
5667
|
this._microphoneList.get(i).isSelected = false;
|
|
5535
5668
|
}
|
|
@@ -5556,7 +5689,6 @@
|
|
|
5556
5689
|
this._logger.info(this, "Canceling Publish!");
|
|
5557
5690
|
this._main.getConfigManager().getStreamData().streamKey = null;
|
|
5558
5691
|
}
|
|
5559
|
-
return null;
|
|
5560
5692
|
}
|
|
5561
5693
|
}
|
|
5562
5694
|
if (!this._selectedMicrophone) {
|
|
@@ -5585,73 +5717,43 @@
|
|
|
5585
5717
|
}
|
|
5586
5718
|
return this._selectedMicrophone;
|
|
5587
5719
|
}
|
|
5588
|
-
/**
|
|
5589
|
-
* Cleans all saved cameras and microphones IDs.
|
|
5590
|
-
*/
|
|
5591
5720
|
clearSavedDevices() {
|
|
5592
5721
|
var _a, _b;
|
|
5593
5722
|
(_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.removeField("cameraID");
|
|
5594
5723
|
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.removeField("microphoneID");
|
|
5595
5724
|
}
|
|
5596
|
-
/**
|
|
5597
|
-
* Messes up camera's and microphone's id (for testing only)
|
|
5598
|
-
*/
|
|
5599
5725
|
messSavedDevices() {
|
|
5600
5726
|
var _a, _b;
|
|
5601
5727
|
(_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.saveField("cameraID", "a");
|
|
5602
5728
|
(_b = this._main.getStorageManager()) === null || _b === void 0 ? void 0 : _b.saveField("microphoneID", "b");
|
|
5603
5729
|
}
|
|
5604
|
-
/**
|
|
5605
|
-
* Handles microphone muting state
|
|
5606
|
-
* @param microphoneState true to unmute, false to mute
|
|
5607
|
-
*/
|
|
5608
5730
|
muteMicrophone(shouldMute) {
|
|
5731
|
+
var _a;
|
|
5732
|
+
if (this._isDestroyed) return;
|
|
5609
5733
|
if (this._isMicrophoneMuted === shouldMute) {
|
|
5610
|
-
// State hasn't changed, no need to do anything
|
|
5611
5734
|
return;
|
|
5612
5735
|
}
|
|
5613
5736
|
this._isMicrophoneMuted = shouldMute;
|
|
5737
|
+
(_a = this._main.getStorageManager()) === null || _a === void 0 ? void 0 : _a.saveField("microphoneMuted", shouldMute ? "true" : "false");
|
|
5614
5738
|
if (this._stream) {
|
|
5615
|
-
this.applyMicrophoneState(!shouldMute);
|
|
5739
|
+
this.applyMicrophoneState(!shouldMute);
|
|
5616
5740
|
} 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})`);
|
|
5741
|
+
this._pendingMicrophoneState = !shouldMute;
|
|
5620
5742
|
}
|
|
5621
|
-
// Always dispatch the event to keep UI in sync
|
|
5622
5743
|
this._main.dispatchEvent("microphoneStateChange", {
|
|
5623
5744
|
ref: this._main,
|
|
5624
5745
|
isMuted: this._isMicrophoneMuted
|
|
5625
5746
|
});
|
|
5626
5747
|
}
|
|
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
5748
|
applyMicrophoneState(enabled) {
|
|
5633
|
-
if (!this._stream)
|
|
5634
|
-
this._logger.warning(this, "WebRTCStreamer :: Cannot apply microphone state - stream not available");
|
|
5635
|
-
return;
|
|
5636
|
-
}
|
|
5749
|
+
if (!this._stream) return;
|
|
5637
5750
|
const audioTracks = this._stream.getAudioTracks();
|
|
5638
5751
|
if (audioTracks && audioTracks.length > 0) {
|
|
5639
|
-
this._logger.success(this, `WebRTCStreamer :: ${enabled ? 'Unmuting' : 'Muting'} microphone`);
|
|
5640
5752
|
audioTracks.forEach(track => track.enabled = enabled);
|
|
5641
|
-
} else {
|
|
5642
|
-
this._logger.warning(this, "WebRTCStreamer :: No audio tracks found in stream");
|
|
5643
5753
|
}
|
|
5644
5754
|
}
|
|
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
5755
|
isStreamReady(requireVideo = true, requireAudio = true) {
|
|
5652
|
-
if (!this._stream)
|
|
5653
|
-
return false;
|
|
5654
|
-
}
|
|
5756
|
+
if (!this._stream) return false;
|
|
5655
5757
|
const videoTracks = this._stream.getVideoTracks();
|
|
5656
5758
|
const audioTracks = this._stream.getAudioTracks();
|
|
5657
5759
|
const videoReady = !requireVideo || videoTracks.length > 0 && videoTracks[0].readyState === 'live';
|
|
@@ -5660,51 +5762,18 @@
|
|
|
5660
5762
|
}
|
|
5661
5763
|
closeWebRTCConnection() {
|
|
5662
5764
|
if (this._peerConnection) {
|
|
5663
|
-
this.
|
|
5765
|
+
this._logger.info(this, "📡 [WEBRTC] closeWebRTCConnection() - closing peer connection");
|
|
5766
|
+
this._peerConnection.onicecandidate = null;
|
|
5767
|
+
this._peerConnection.onconnectionstatechange = null;
|
|
5768
|
+
this._peerConnection.onnegotiationneeded = null;
|
|
5769
|
+
try {
|
|
5770
|
+
this._peerConnection.close();
|
|
5771
|
+
} catch (e) {
|
|
5772
|
+
// Ignore
|
|
5773
|
+
}
|
|
5664
5774
|
this._peerConnection = null;
|
|
5665
5775
|
}
|
|
5666
5776
|
}
|
|
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
5777
|
onDescriptionError(error) {
|
|
5709
5778
|
this._logger.info(this, "WebRTCStreamer :: onDescriptionError: " + JSON.stringify(error));
|
|
5710
5779
|
}
|
|
@@ -5716,6 +5785,7 @@
|
|
|
5716
5785
|
//------------------------------------------------------------------------//
|
|
5717
5786
|
createStatusConnection() {
|
|
5718
5787
|
var _a, _b, _c;
|
|
5788
|
+
if (this._isDestroyed) return;
|
|
5719
5789
|
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
5790
|
if (!serverItem) return;
|
|
5721
5791
|
if (this._statusConnection) {
|
|
@@ -5736,6 +5806,7 @@
|
|
|
5736
5806
|
}
|
|
5737
5807
|
setPublishState(newState) {
|
|
5738
5808
|
if (this._publishState == newState) return;
|
|
5809
|
+
if (this._isDestroyed) return;
|
|
5739
5810
|
if (this._debug) this._logger.decoratedLog("Publish State: " + newState, "dark-blue");
|
|
5740
5811
|
this._logger.info(this, "Publish State: " + newState);
|
|
5741
5812
|
if (newState == exports.PublishState.PUBLISHED) this._publishTime = new Date().getTime();
|
|
@@ -5748,9 +5819,9 @@
|
|
|
5748
5819
|
getPublishTime() {
|
|
5749
5820
|
return this._publishState == exports.PublishState.PUBLISHED ? this._publishTime : 0;
|
|
5750
5821
|
}
|
|
5751
|
-
// DEVICE STATE
|
|
5752
5822
|
setInputDeviceState(newState) {
|
|
5753
5823
|
if (this._inputDeviceState == newState) return;
|
|
5824
|
+
if (this._isDestroyed) return;
|
|
5754
5825
|
this._inputDeviceState = newState;
|
|
5755
5826
|
this._main.dispatchEvent("deviceStateChange", {
|
|
5756
5827
|
ref: this._main,
|
|
@@ -5762,9 +5833,9 @@
|
|
|
5762
5833
|
getInputDeviceState() {
|
|
5763
5834
|
return this._inputDeviceState;
|
|
5764
5835
|
}
|
|
5765
|
-
// CAMERA STATE
|
|
5766
5836
|
setCameraState(newState) {
|
|
5767
5837
|
if (this._cameraState == newState) return;
|
|
5838
|
+
if (this._isDestroyed) return;
|
|
5768
5839
|
this._cameraState = newState;
|
|
5769
5840
|
this._main.dispatchEvent("cameraDeviceStateChange", {
|
|
5770
5841
|
ref: this._main,
|
|
@@ -5775,9 +5846,9 @@
|
|
|
5775
5846
|
getCameraState() {
|
|
5776
5847
|
return this._cameraState;
|
|
5777
5848
|
}
|
|
5778
|
-
// MICROPHONE STATE
|
|
5779
5849
|
setMicrophoneState(newState) {
|
|
5780
5850
|
if (this._microphoneState == newState) return;
|
|
5851
|
+
if (this._isDestroyed) return;
|
|
5781
5852
|
this._microphoneState = newState;
|
|
5782
5853
|
this._main.dispatchEvent("microphoneDeviceStateChange", {
|
|
5783
5854
|
ref: this._main,
|
|
@@ -5798,108 +5869,70 @@
|
|
|
5798
5869
|
return this._publishState;
|
|
5799
5870
|
}
|
|
5800
5871
|
//------------------------------------------------------------------------//
|
|
5872
|
+
// DEBUG
|
|
5873
|
+
//------------------------------------------------------------------------//
|
|
5874
|
+
debugMediaState() {
|
|
5875
|
+
var _a, _b;
|
|
5876
|
+
console.group("🎥 Media Debug State");
|
|
5877
|
+
console.log("=== STREAM INFO ===");
|
|
5878
|
+
console.log("this._stream:", this._stream);
|
|
5879
|
+
console.log("Active stream count:", this._activeStreamCount);
|
|
5880
|
+
console.log("Is destroyed:", this._isDestroyed);
|
|
5881
|
+
if (this._stream) {
|
|
5882
|
+
console.log("Stream ID:", this._stream.id);
|
|
5883
|
+
console.log("Stream active:", this._stream.active);
|
|
5884
|
+
console.log("--- Video Tracks ---");
|
|
5885
|
+
this._stream.getVideoTracks().forEach((track, index) => {
|
|
5886
|
+
console.log(` Track ${index}:`, {
|
|
5887
|
+
id: track.id,
|
|
5888
|
+
enabled: track.enabled,
|
|
5889
|
+
readyState: track.readyState,
|
|
5890
|
+
muted: track.muted
|
|
5891
|
+
});
|
|
5892
|
+
});
|
|
5893
|
+
console.log("--- Audio Tracks ---");
|
|
5894
|
+
this._stream.getAudioTracks().forEach((track, index) => {
|
|
5895
|
+
console.log(` Track ${index}:`, {
|
|
5896
|
+
id: track.id,
|
|
5897
|
+
enabled: track.enabled,
|
|
5898
|
+
readyState: track.readyState,
|
|
5899
|
+
muted: track.muted
|
|
5900
|
+
});
|
|
5901
|
+
});
|
|
5902
|
+
}
|
|
5903
|
+
console.log("=== VIDEO ELEMENT ===");
|
|
5904
|
+
const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
|
|
5905
|
+
if ((videoElement === null || videoElement === void 0 ? void 0 : videoElement.srcObject) instanceof MediaStream) {
|
|
5906
|
+
const srcStream = videoElement.srcObject;
|
|
5907
|
+
console.log("Video srcObject stream ID:", srcStream.id);
|
|
5908
|
+
console.log("Video srcObject active:", srcStream.active);
|
|
5909
|
+
console.log("Same as this._stream:", srcStream === this._stream);
|
|
5910
|
+
}
|
|
5911
|
+
console.groupEnd();
|
|
5912
|
+
}
|
|
5913
|
+
//------------------------------------------------------------------------//
|
|
5801
5914
|
// DESTROY & DELETE
|
|
5802
5915
|
//------------------------------------------------------------------------//
|
|
5803
|
-
/**
|
|
5804
|
-
* Method used to stop camera from streaming
|
|
5805
|
-
* @private
|
|
5806
|
-
*/
|
|
5807
5916
|
stopCameraStream() {
|
|
5808
5917
|
var _a, _b;
|
|
5809
5918
|
if (this._stream) {
|
|
5810
|
-
this.
|
|
5919
|
+
this._logger.info(this, `📹 [RELEASE] stopCameraStream() - stopping stream, id: ${this._stream.id}`);
|
|
5920
|
+
this._stream.getTracks().forEach(track => {
|
|
5921
|
+
this._logger.info(this, `📹 [RELEASE] stopCameraStream() - stopping track: ${track.kind}, id: ${track.id}`);
|
|
5922
|
+
track.stop();
|
|
5923
|
+
});
|
|
5811
5924
|
const videoElement = (_b = (_a = this._main.getStageController()) === null || _a === void 0 ? void 0 : _a.getScreenElement()) === null || _b === void 0 ? void 0 : _b.getVideoElement();
|
|
5812
5925
|
if (videoElement) {
|
|
5813
5926
|
videoElement.srcObject = null;
|
|
5814
5927
|
}
|
|
5815
5928
|
this._soundMeter.detach();
|
|
5816
5929
|
this._stream = null;
|
|
5930
|
+
this._activeStreamCount--;
|
|
5931
|
+
this._logger.info(this, `📹 [RELEASE] stopCameraStream() - complete, active streams: ${this._activeStreamCount}`);
|
|
5817
5932
|
}
|
|
5818
5933
|
}
|
|
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
5934
|
stop() {
|
|
5902
|
-
|
|
5935
|
+
this._logger.info(this, "📹 [STOP] stop() - stopping all operations");
|
|
5903
5936
|
if (this._cameraAbortController) {
|
|
5904
5937
|
this._cameraAbortController.abort();
|
|
5905
5938
|
this._cameraAbortController = null;
|
|
@@ -5912,7 +5945,6 @@
|
|
|
5912
5945
|
this._startCameraAbortController.abort();
|
|
5913
5946
|
this._startCameraAbortController = null;
|
|
5914
5947
|
}
|
|
5915
|
-
// Stop status connection and clear timer
|
|
5916
5948
|
if (this._statusConnection) {
|
|
5917
5949
|
this._statusConnection.destroy();
|
|
5918
5950
|
this._statusConnection = null;
|
|
@@ -5922,16 +5954,12 @@
|
|
|
5922
5954
|
this._statusTimer = null;
|
|
5923
5955
|
}
|
|
5924
5956
|
this._main.getConfigManager().getStreamData().streamKey = null;
|
|
5925
|
-
// Close WebRTC connection
|
|
5926
5957
|
this.closeWebRTCConnection();
|
|
5927
|
-
// Stop all media streams
|
|
5928
5958
|
this.stopCameraStream();
|
|
5929
|
-
// Reset states
|
|
5930
5959
|
this.setPublishState(exports.PublishState.STOPPED);
|
|
5931
5960
|
this.setInputDeviceState(exports.InputDevicesState.STOPPED);
|
|
5932
5961
|
this.setCameraState(exports.DeviceState.STOPPED);
|
|
5933
5962
|
this.setMicrophoneState(exports.DeviceState.STOPPED);
|
|
5934
|
-
// Clear restart timer if exists
|
|
5935
5963
|
if (this._restartTimer) {
|
|
5936
5964
|
clearInterval(this._restartTimer);
|
|
5937
5965
|
this._restartTimer = null;
|
|
@@ -5939,127 +5967,140 @@
|
|
|
5939
5967
|
this._restartTimerCount = 0;
|
|
5940
5968
|
clearTimeout(this._publishTimer);
|
|
5941
5969
|
}
|
|
5942
|
-
/**
|
|
5943
|
-
* Reinitializes the streaming setup
|
|
5944
|
-
*/
|
|
5945
5970
|
start() {
|
|
5946
5971
|
var _a, _b, _c;
|
|
5947
5972
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5973
|
+
if (this._isDestroyed) return;
|
|
5974
|
+
this._logger.info(this, "📹 [START] start() - reinitializing streaming");
|
|
5948
5975
|
try {
|
|
5949
|
-
// Reset states
|
|
5950
5976
|
this._publishState = exports.PublishState.NOT_INITIALIZED;
|
|
5951
5977
|
this._inputDeviceState = exports.InputDevicesState.NOT_INITIALIZED;
|
|
5952
5978
|
this._cameraState = exports.DeviceState.NOT_INITIALIZED;
|
|
5953
5979
|
this._microphoneState = exports.DeviceState.NOT_INITIALIZED;
|
|
5954
|
-
// Reinitialize devices and stream
|
|
5955
5980
|
yield this.initializeDevices();
|
|
5981
|
+
if (this._isDestroyed) return;
|
|
5956
5982
|
yield this.startCamera();
|
|
5957
|
-
|
|
5983
|
+
if (this._isDestroyed) return;
|
|
5958
5984
|
if ((_a = this._main.getConfigManager()) === null || _a === void 0 ? void 0 : _a.getSettingsData().autoConnect) {
|
|
5959
5985
|
(_b = this._main.getNetworkController()) === null || _b === void 0 ? void 0 : _b.initialize();
|
|
5960
5986
|
}
|
|
5961
|
-
// Reinitialize status connection if needed
|
|
5962
5987
|
if ((_c = this._main.getConfigManager()) === null || _c === void 0 ? void 0 : _c.getStreamData().streamKey) {
|
|
5963
5988
|
this.createStatusConnection();
|
|
5964
5989
|
}
|
|
5965
5990
|
} catch (error) {
|
|
5966
|
-
|
|
5991
|
+
if (this._isDestroyed) return;
|
|
5992
|
+
this._logger.error(this, "📹 [START] start() - failed: " + JSON.stringify(error));
|
|
5967
5993
|
this.setInputDeviceState(exports.InputDevicesState.INVALID);
|
|
5968
5994
|
throw error;
|
|
5969
5995
|
}
|
|
5970
5996
|
});
|
|
5971
5997
|
}
|
|
5972
5998
|
/**
|
|
5973
|
-
*
|
|
5999
|
+
* SYNCHRONOUS destroy - sets flag immediately, cleanup happens in background
|
|
6000
|
+
* This ensures that even if called without await, all async operations will abort
|
|
5974
6001
|
*/
|
|
5975
6002
|
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
|
-
|
|
6003
|
+
var _a, _b, _c, _d;
|
|
6004
|
+
// Prevent double destroy
|
|
6005
|
+
if (this._isDestroyed) {
|
|
6006
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.warning(this, "🔴 [DESTROY] Already destroyed, skipping");
|
|
6007
|
+
return;
|
|
6008
|
+
}
|
|
6009
|
+
this._logger.info(this, "🔴 [DESTROY] Starting StreamerController destroy (sync)...");
|
|
6010
|
+
// CRITICAL: Set flag IMMEDIATELY - this will cause all pending async operations to abort
|
|
6011
|
+
this._isDestroyed = true;
|
|
6012
|
+
// Cancel all abort controllers immediately
|
|
6013
|
+
if (this._cameraAbortController) {
|
|
6014
|
+
this._cameraAbortController.abort();
|
|
6015
|
+
this._cameraAbortController = null;
|
|
6016
|
+
}
|
|
6017
|
+
if (this._microphoneAbortController) {
|
|
6018
|
+
this._microphoneAbortController.abort();
|
|
6019
|
+
this._microphoneAbortController = null;
|
|
6020
|
+
}
|
|
6021
|
+
if (this._startCameraAbortController) {
|
|
6022
|
+
this._startCameraAbortController.abort();
|
|
6023
|
+
this._startCameraAbortController = null;
|
|
6024
|
+
}
|
|
6025
|
+
// Stop all timers immediately
|
|
6026
|
+
if (this._statusTimer != null) {
|
|
6027
|
+
clearInterval(this._statusTimer);
|
|
6028
|
+
this._statusTimer = null;
|
|
6029
|
+
}
|
|
6030
|
+
if (this._restartTimer != null) {
|
|
6031
|
+
clearInterval(this._restartTimer);
|
|
6032
|
+
this._restartTimer = null;
|
|
6033
|
+
}
|
|
6034
|
+
clearTimeout(this._publishTimer);
|
|
6035
|
+
// Remove permission listeners
|
|
6036
|
+
this.removePermissionListeners();
|
|
6037
|
+
// Remove device change listener
|
|
6038
|
+
if (this._deviceChangeHandler) {
|
|
6039
|
+
navigator.mediaDevices.removeEventListener('devicechange', this._deviceChangeHandler);
|
|
6040
|
+
this._deviceChangeHandler = null;
|
|
6041
|
+
}
|
|
6042
|
+
// Remove orientation listener
|
|
6043
|
+
if (this._orientationChangeHandler) {
|
|
6044
|
+
if (window.screen && window.screen.orientation) {
|
|
6045
|
+
window.screen.orientation.removeEventListener('change', this._orientationChangeHandler);
|
|
6046
|
+
} else {
|
|
6047
|
+
window.removeEventListener('orientationchange', this._orientationChangeHandler);
|
|
6048
|
+
}
|
|
6049
|
+
this._orientationChangeHandler = null;
|
|
6050
|
+
}
|
|
6051
|
+
// Remove event listeners
|
|
6052
|
+
try {
|
|
6053
|
+
this._main.removeEventListener("serverConnect", this.onServerConnect);
|
|
6054
|
+
this._main.removeEventListener("serverDisconnect", this.onServerDisconnect);
|
|
6055
|
+
this._main.removeEventListener("streamKeyInUse", this.onStreamKeyTaken);
|
|
6056
|
+
this._main.removeEventListener("statusServerConnect", this.onStatusServerConnect);
|
|
6057
|
+
this._main.removeEventListener("statusServerDisconnect", this.onStatusServerDisconnect);
|
|
6058
|
+
this._main.removeEventListener("streamStatusUpdate", this.onStreamStatsUpdate);
|
|
6059
|
+
this._main.removeEventListener("deviceStateChange", this.onDeviceStateChange);
|
|
6060
|
+
document.removeEventListener("visibilitychange", this.visibilityChange);
|
|
6061
|
+
window.removeEventListener("blur", this.onWindowBlur);
|
|
6062
|
+
window.removeEventListener("focus", this.onWindowFocus);
|
|
6063
|
+
} catch (e) {
|
|
6064
|
+
// Ignore errors
|
|
6065
|
+
}
|
|
6066
|
+
// Destroy status connection
|
|
6067
|
+
if (this._statusConnection) {
|
|
6068
|
+
this._statusConnection.destroy();
|
|
6069
|
+
this._statusConnection = null;
|
|
6070
|
+
}
|
|
6071
|
+
// Close WebRTC
|
|
6072
|
+
this.closeWebRTCConnection();
|
|
6073
|
+
// Stop camera stream
|
|
6074
|
+
if (this._stream) {
|
|
6075
|
+
this._logger.info(this, "📹 [FORCE_STOP] Stopping main stream");
|
|
6076
|
+
this._stream.getTracks().forEach(track => {
|
|
6077
|
+
track.stop();
|
|
6078
|
+
});
|
|
6079
|
+
this._stream = null;
|
|
6080
|
+
}
|
|
6081
|
+
// Clean video element
|
|
6082
|
+
try {
|
|
6083
|
+
const videoElement = (_c = (_b = this._main.getStageController()) === null || _b === void 0 ? void 0 : _b.getScreenElement()) === null || _c === void 0 ? void 0 : _c.getVideoElement();
|
|
6084
|
+
if (videoElement) {
|
|
6085
|
+
if (videoElement.srcObject instanceof MediaStream) {
|
|
6086
|
+
videoElement.srcObject.getTracks().forEach(track => track.stop());
|
|
6055
6087
|
}
|
|
6056
|
-
|
|
6057
|
-
} catch (error) {
|
|
6058
|
-
this._logger.error(this, "Error during destroy: " + error);
|
|
6059
|
-
} finally {
|
|
6060
|
-
this._isDestroying = false;
|
|
6088
|
+
videoElement.srcObject = null;
|
|
6061
6089
|
}
|
|
6062
|
-
})
|
|
6090
|
+
} catch (e) {
|
|
6091
|
+
// Ignore
|
|
6092
|
+
}
|
|
6093
|
+
// Destroy sound meter
|
|
6094
|
+
try {
|
|
6095
|
+
(_d = this._soundMeter) === null || _d === void 0 ? void 0 : _d.destroy();
|
|
6096
|
+
} catch (e) {
|
|
6097
|
+
// Ignore
|
|
6098
|
+
}
|
|
6099
|
+
// Reset variables
|
|
6100
|
+
this._selectedCamera = null;
|
|
6101
|
+
this._selectedMicrophone = null;
|
|
6102
|
+
this._activeStreamCount = 0;
|
|
6103
|
+
this._logger.success(this, "🔴 [DESTROY] StreamerController destroyed (sync)");
|
|
6063
6104
|
}
|
|
6064
6105
|
}
|
|
6065
6106
|
|
|
@@ -6671,52 +6712,19 @@
|
|
|
6671
6712
|
* Main class of the player. The player itself has no GUI, but can be controlled via provided API.
|
|
6672
6713
|
*/
|
|
6673
6714
|
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
6715
|
constructor(streamConfig, autoInitialize = false) {
|
|
6684
6716
|
super();
|
|
6685
|
-
/**
|
|
6686
|
-
* Indicates whether the streamer object is in development mode (provides more debug options)
|
|
6687
|
-
* @private
|
|
6688
|
-
*/
|
|
6689
6717
|
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
|
-
*/
|
|
6718
|
+
this.STREAMER_VERSION = "1.0.1";
|
|
6719
|
+
this.COMPILE_DATE = "2/18/2026, 5:26:13 PM";
|
|
6704
6720
|
this.STREAMER_BRANCH = "Experimental";
|
|
6705
|
-
/**
|
|
6706
|
-
* Defines number of streamer protocol that is required on server-side
|
|
6707
|
-
* @private
|
|
6708
|
-
*/
|
|
6709
6721
|
this.STREAMER_PROTOCOL_VERSION = 1;
|
|
6710
|
-
/**
|
|
6711
|
-
* Indicates whether streamer was initialized or not
|
|
6712
|
-
* @private
|
|
6713
|
-
*/
|
|
6714
6722
|
this._initialized = false;
|
|
6723
|
+
this._isDestroyed = false;
|
|
6715
6724
|
if (typeof window === 'undefined' || !window.document || !window.document.createElement) {
|
|
6716
6725
|
console.error(`StormStreamer Creation Error - No "window" element in the provided context!`);
|
|
6717
6726
|
return;
|
|
6718
6727
|
}
|
|
6719
|
-
// WINDOW.StormStreamerArray
|
|
6720
6728
|
if (this.DEV_MODE && !('StormStreamerArray' in window)) {
|
|
6721
6729
|
window.StormStreamerArray = [];
|
|
6722
6730
|
}
|
|
@@ -6726,17 +6734,13 @@
|
|
|
6726
6734
|
this.setStreamConfig(streamConfig);
|
|
6727
6735
|
if (autoInitialize) this.initialize();
|
|
6728
6736
|
}
|
|
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
6737
|
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);
|
|
6738
|
+
if (this._isRemoved || this._isDestroyed) return;
|
|
6739
|
+
if (this._configManager == null) throw Error("Stream Config was not provided for this streamer!");
|
|
6740
|
+
this._storageManager = new StorageManager(this);
|
|
6741
|
+
this._stageController = new StageController(this);
|
|
6742
|
+
this._networkController = new NetworkController(this);
|
|
6743
|
+
this._streamerController = new StreamerController(this);
|
|
6740
6744
|
this._statsController = new StatsController(this);
|
|
6741
6745
|
this._graphs = [];
|
|
6742
6746
|
this._initialized = true;
|
|
@@ -6744,16 +6748,8 @@
|
|
|
6744
6748
|
ref: this
|
|
6745
6749
|
});
|
|
6746
6750
|
}
|
|
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
6751
|
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
|
-
*/
|
|
6752
|
+
if (this._isRemoved || this._isDestroyed) return;
|
|
6757
6753
|
const copiedStreamConfig = JSON.parse(JSON.stringify(streamConfig));
|
|
6758
6754
|
if (this._configManager == null) {
|
|
6759
6755
|
this._configManager = new ConfigManager(copiedStreamConfig);
|
|
@@ -6778,290 +6774,133 @@
|
|
|
6778
6774
|
});
|
|
6779
6775
|
}
|
|
6780
6776
|
}
|
|
6781
|
-
//------------------------------------------------------------------------//
|
|
6782
6777
|
// 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
6778
|
isConnected() {
|
|
6790
6779
|
var _a, _b;
|
|
6791
6780
|
return (_b = (_a = this._networkController) === null || _a === void 0 ? void 0 : _a.getConnection().isConnectionActive()) !== null && _b !== void 0 ? _b : false;
|
|
6792
6781
|
}
|
|
6793
|
-
/**
|
|
6794
|
-
* Mutes the streamer's video object. Audio output will be silenced.
|
|
6795
|
-
*/
|
|
6796
6782
|
mute() {
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
}
|
|
6783
|
+
var _a;
|
|
6784
|
+
if (((_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) != null) {
|
|
6785
|
+
this._stageController.getScreenElement().setMuted(true);
|
|
6786
|
+
return;
|
|
6802
6787
|
}
|
|
6803
6788
|
this._configManager.getSettingsData().getAudioData().muted = true;
|
|
6804
6789
|
}
|
|
6805
|
-
/**
|
|
6806
|
-
* Unmutes the streamer's video object. Audio output will be restored.
|
|
6807
|
-
*/
|
|
6808
6790
|
unmute() {
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
}
|
|
6791
|
+
var _a;
|
|
6792
|
+
if (((_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getScreenElement()) != null) {
|
|
6793
|
+
this._stageController.getScreenElement().setMuted(false);
|
|
6794
|
+
return;
|
|
6814
6795
|
}
|
|
6815
6796
|
this._configManager.getSettingsData().getAudioData().muted = false;
|
|
6816
6797
|
}
|
|
6817
|
-
/**
|
|
6818
|
-
* Checks whether the streamer audio is currently muted.
|
|
6819
|
-
*
|
|
6820
|
-
* @returns Boolean indicating mute status
|
|
6821
|
-
*/
|
|
6822
6798
|
isMute() {
|
|
6823
6799
|
var _a, _b, _c, _d;
|
|
6824
6800
|
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
6801
|
}
|
|
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
6802
|
toggleMute() {
|
|
6832
6803
|
const isMuted = this.isMute();
|
|
6833
|
-
if (isMuted)
|
|
6834
|
-
this.unmute();
|
|
6835
|
-
} else {
|
|
6836
|
-
this.mute();
|
|
6837
|
-
}
|
|
6804
|
+
if (isMuted) this.unmute();else this.mute();
|
|
6838
6805
|
return !isMuted;
|
|
6839
6806
|
}
|
|
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
6807
|
setVolume(newVolume) {
|
|
6847
6808
|
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
|
-
}
|
|
6809
|
+
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
6810
|
this._configManager.getSettingsData().getAudioData().startVolume = newVolume;
|
|
6852
6811
|
}
|
|
6853
|
-
/**
|
|
6854
|
-
* Returns current streamer volume (0-100).
|
|
6855
|
-
*
|
|
6856
|
-
* @returns Current volume level
|
|
6857
|
-
*/
|
|
6858
6812
|
getVolume() {
|
|
6859
6813
|
var _a, _b, _c;
|
|
6860
6814
|
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
6815
|
}
|
|
6862
|
-
/**
|
|
6863
|
-
* Returns the list of available camera devices.
|
|
6864
|
-
*
|
|
6865
|
-
* @returns Array of camera input devices
|
|
6866
|
-
*/
|
|
6867
6816
|
getCameraList() {
|
|
6868
6817
|
var _a, _b;
|
|
6869
6818
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCameraList()) !== null && _b !== void 0 ? _b : [];
|
|
6870
6819
|
}
|
|
6871
|
-
/**
|
|
6872
|
-
* Returns the list of available microphone devices.
|
|
6873
|
-
*
|
|
6874
|
-
* @returns Array of microphone input devices
|
|
6875
|
-
*/
|
|
6876
6820
|
getMicrophoneList() {
|
|
6877
6821
|
var _a, _b;
|
|
6878
6822
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getMicrophoneList()) !== null && _b !== void 0 ? _b : [];
|
|
6879
6823
|
}
|
|
6880
|
-
/**
|
|
6881
|
-
* Sets the active camera device by ID.
|
|
6882
|
-
*
|
|
6883
|
-
* @param cameraID - ID of the camera device to use
|
|
6884
|
-
*/
|
|
6885
6824
|
setCamera(cameraID) {
|
|
6886
6825
|
var _a;
|
|
6887
6826
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.selectCamera(cameraID);
|
|
6888
6827
|
}
|
|
6889
|
-
/**
|
|
6890
|
-
* Sets the active microphone device by ID.
|
|
6891
|
-
*
|
|
6892
|
-
* @param microphoneID - ID of the microphone device to use
|
|
6893
|
-
*/
|
|
6894
6828
|
setMicrophone(microphoneID) {
|
|
6895
6829
|
var _a;
|
|
6896
6830
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.selectMicrophone(microphoneID);
|
|
6897
6831
|
}
|
|
6898
|
-
/**
|
|
6899
|
-
* Returns the currently active camera device.
|
|
6900
|
-
*
|
|
6901
|
-
* @returns Current camera device or null if none is active
|
|
6902
|
-
*/
|
|
6903
6832
|
getCurrentCamera() {
|
|
6904
|
-
|
|
6833
|
+
var _a, _b;
|
|
6834
|
+
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCurrentCamera()) !== null && _b !== void 0 ? _b : null;
|
|
6905
6835
|
}
|
|
6906
|
-
/**
|
|
6907
|
-
* Returns the currently active microphone device.
|
|
6908
|
-
*
|
|
6909
|
-
* @returns Current microphone device or null if none is active
|
|
6910
|
-
*/
|
|
6911
6836
|
getCurrentMicrophone() {
|
|
6912
|
-
|
|
6837
|
+
var _a, _b;
|
|
6838
|
+
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCurrentMicrophone()) !== null && _b !== void 0 ? _b : null;
|
|
6913
6839
|
}
|
|
6914
|
-
/**
|
|
6915
|
-
* Mutes or unmutes the microphone.
|
|
6916
|
-
*
|
|
6917
|
-
* @param microphoneState - True to mute, false to unmute
|
|
6918
|
-
*/
|
|
6919
6840
|
muteMicrophone(microphoneState) {
|
|
6920
6841
|
var _a;
|
|
6921
6842
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.muteMicrophone(microphoneState);
|
|
6922
6843
|
}
|
|
6923
|
-
/**
|
|
6924
|
-
* Checks if the microphone is currently muted.
|
|
6925
|
-
*
|
|
6926
|
-
* @returns Boolean indicating if microphone is muted
|
|
6927
|
-
*/
|
|
6928
6844
|
isMicrophoneMuted() {
|
|
6929
6845
|
var _a, _b;
|
|
6930
6846
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.isMicrophoneMuted()) !== null && _b !== void 0 ? _b : false;
|
|
6931
6847
|
}
|
|
6932
|
-
/**
|
|
6933
|
-
* Returns the current publishing state of the streamer.
|
|
6934
|
-
*
|
|
6935
|
-
* @returns Current publishing state
|
|
6936
|
-
*/
|
|
6937
6848
|
getPublishState() {
|
|
6938
6849
|
var _a, _b;
|
|
6939
6850
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getPublishState()) !== null && _b !== void 0 ? _b : exports.PublishState.NOT_INITIALIZED;
|
|
6940
6851
|
}
|
|
6941
|
-
/**
|
|
6942
|
-
* Returns the total time the stream has been publishing in milliseconds.
|
|
6943
|
-
*
|
|
6944
|
-
* @returns Publishing time in milliseconds
|
|
6945
|
-
*/
|
|
6946
6852
|
getPublishTime() {
|
|
6947
6853
|
var _a, _b;
|
|
6948
6854
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getPublishTime()) !== null && _b !== void 0 ? _b : 0;
|
|
6949
6855
|
}
|
|
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
6856
|
publish(streamKey) {
|
|
6957
6857
|
var _a, _b;
|
|
6958
6858
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.publish(streamKey)) !== null && _b !== void 0 ? _b : false;
|
|
6959
6859
|
}
|
|
6960
|
-
/**
|
|
6961
|
-
* Stops publishing the current stream.
|
|
6962
|
-
*/
|
|
6963
6860
|
unpublish() {
|
|
6964
6861
|
var _a;
|
|
6965
|
-
console.log("kutas 1");
|
|
6966
6862
|
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.unpublish();
|
|
6967
6863
|
}
|
|
6968
|
-
/**
|
|
6969
|
-
* Returns the current state of input devices (camera and microphone).
|
|
6970
|
-
*
|
|
6971
|
-
* @returns Current state of input devices
|
|
6972
|
-
*/
|
|
6973
6864
|
getInputDevicesState() {
|
|
6974
6865
|
var _a, _b;
|
|
6975
6866
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getInputDeviceState()) !== null && _b !== void 0 ? _b : exports.InputDevicesState.NOT_INITIALIZED;
|
|
6976
6867
|
}
|
|
6977
|
-
/**
|
|
6978
|
-
* Returns the current state of the camera device.
|
|
6979
|
-
*
|
|
6980
|
-
* @returns Current camera device state
|
|
6981
|
-
*/
|
|
6982
6868
|
getCameraState() {
|
|
6983
6869
|
var _a, _b;
|
|
6984
6870
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getCameraState()) !== null && _b !== void 0 ? _b : exports.DeviceState.NOT_INITIALIZED;
|
|
6985
6871
|
}
|
|
6986
|
-
/**
|
|
6987
|
-
* Returns the current state of the microphone device.
|
|
6988
|
-
*
|
|
6989
|
-
* @returns Current microphone device state
|
|
6990
|
-
*/
|
|
6991
6872
|
getMicrophoneState() {
|
|
6992
6873
|
var _a, _b;
|
|
6993
6874
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.getMicrophoneState()) !== null && _b !== void 0 ? _b : exports.DeviceState.NOT_INITIALIZED;
|
|
6994
6875
|
}
|
|
6995
|
-
/**
|
|
6996
|
-
* Clears saved device preferences from storage.
|
|
6997
|
-
*/
|
|
6998
6876
|
clearSavedDevices() {
|
|
6999
6877
|
var _a;
|
|
7000
|
-
|
|
6878
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.clearSavedDevices();
|
|
7001
6879
|
}
|
|
7002
|
-
/**
|
|
7003
|
-
* Randomizes saved device preferences (for testing purposes).
|
|
7004
|
-
*/
|
|
7005
6880
|
messSavedDevices() {
|
|
7006
6881
|
var _a;
|
|
7007
|
-
|
|
6882
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.messSavedDevices();
|
|
7008
6883
|
}
|
|
7009
|
-
/**
|
|
7010
|
-
* Checks if the stream is ready for publishing.
|
|
7011
|
-
*
|
|
7012
|
-
* @returns Boolean indicating if stream is ready
|
|
7013
|
-
*/
|
|
7014
6884
|
isStreamReady() {
|
|
7015
6885
|
var _a, _b;
|
|
7016
6886
|
return (_b = (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.isStreamReady()) !== null && _b !== void 0 ? _b : false;
|
|
7017
6887
|
}
|
|
7018
|
-
//------------------------------------------------------------------------//
|
|
7019
6888
|
// 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
6889
|
attachToContainer(container) {
|
|
7029
6890
|
var _a, _b;
|
|
7030
|
-
let result = false;
|
|
7031
6891
|
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
|
|
6892
|
+
return false;
|
|
7033
6893
|
}
|
|
7034
|
-
/**
|
|
7035
|
-
* Detaches the streamer from the current parent element, if possible.
|
|
7036
|
-
*
|
|
7037
|
-
* @returns Boolean indicating if detachment was successful
|
|
7038
|
-
*/
|
|
7039
6894
|
detachFromContainer() {
|
|
7040
6895
|
var _a, _b;
|
|
7041
|
-
let result = false;
|
|
7042
6896
|
if (this._initialized) return (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.detachFromParent()) !== null && _b !== void 0 ? _b : false;
|
|
7043
|
-
return
|
|
6897
|
+
return false;
|
|
7044
6898
|
}
|
|
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
6899
|
getContainer() {
|
|
7051
6900
|
var _a, _b;
|
|
7052
6901
|
return (_b = (_a = this._stageController) === null || _a === void 0 ? void 0 : _a.getParentElement()) !== null && _b !== void 0 ? _b : null;
|
|
7053
6902
|
}
|
|
7054
|
-
//------------------------------------------------------------------------//
|
|
7055
6903
|
// 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
6904
|
setSize(width, height) {
|
|
7066
6905
|
if (this._initialized) this._stageController.setSize(width, height);else {
|
|
7067
6906
|
const parsedWidth = NumberUtilities.parseValue(width);
|
|
@@ -7072,13 +6911,6 @@
|
|
|
7072
6911
|
this._configManager.getSettingsData().getVideoData().videoHeightInPixels = parsedHeight.isPixels;
|
|
7073
6912
|
}
|
|
7074
6913
|
}
|
|
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
6914
|
setWidth(width) {
|
|
7083
6915
|
if (this._initialized) this._stageController.setWidth(width);else {
|
|
7084
6916
|
const parsedWidth = NumberUtilities.parseValue(width);
|
|
@@ -7086,13 +6918,6 @@
|
|
|
7086
6918
|
this._configManager.getSettingsData().getVideoData().videoWidthInPixels = parsedWidth.isPixels;
|
|
7087
6919
|
}
|
|
7088
6920
|
}
|
|
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
6921
|
setHeight(height) {
|
|
7097
6922
|
if (this._initialized) this._stageController.setHeight(height);else {
|
|
7098
6923
|
const parsedHeight = NumberUtilities.parseValue(height);
|
|
@@ -7100,66 +6925,26 @@
|
|
|
7100
6925
|
this._configManager.getSettingsData().getVideoData().videoHeightInPixels = parsedHeight.isPixels;
|
|
7101
6926
|
}
|
|
7102
6927
|
}
|
|
7103
|
-
/**
|
|
7104
|
-
* Returns current streamer width in pixels.
|
|
7105
|
-
*
|
|
7106
|
-
* @returns Current width in pixels
|
|
7107
|
-
*/
|
|
7108
6928
|
getWidth() {
|
|
7109
|
-
if (this._initialized) return this._stageController.getContainerWidth();
|
|
7110
|
-
|
|
7111
|
-
}
|
|
6929
|
+
if (this._initialized) return this._stageController.getContainerWidth();
|
|
6930
|
+
if (this._configManager.getSettingsData().getVideoData().videoWidthInPixels) return this._configManager.getSettingsData().getVideoData().videoWidthValue;
|
|
7112
6931
|
return 0;
|
|
7113
6932
|
}
|
|
7114
|
-
/**
|
|
7115
|
-
* Returns current streamer height in pixels.
|
|
7116
|
-
*
|
|
7117
|
-
* @returns Current height in pixels
|
|
7118
|
-
*/
|
|
7119
6933
|
getHeight() {
|
|
7120
|
-
if (this._initialized) return this._stageController.getContainerHeight();
|
|
7121
|
-
|
|
7122
|
-
}
|
|
6934
|
+
if (this._initialized) return this._stageController.getContainerHeight();
|
|
6935
|
+
if (this._configManager.getSettingsData().getVideoData().videoHeightInPixels) return this._configManager.getSettingsData().getVideoData().videoHeightValue;
|
|
7123
6936
|
return 0;
|
|
7124
6937
|
}
|
|
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
6938
|
setScalingMode(newMode) {
|
|
7131
|
-
if (this._stageController)
|
|
7132
|
-
this._stageController.setScalingMode(newMode);
|
|
7133
|
-
} else {
|
|
7134
|
-
this._configManager.getSettingsData().getVideoData().scalingMode = newMode;
|
|
7135
|
-
}
|
|
6939
|
+
if (this._stageController) this._stageController.setScalingMode(newMode);else this._configManager.getSettingsData().getVideoData().scalingMode = newMode;
|
|
7136
6940
|
}
|
|
7137
|
-
/**
|
|
7138
|
-
* Returns the current streamer scaling mode.
|
|
7139
|
-
*
|
|
7140
|
-
* @returns Current scaling mode
|
|
7141
|
-
*/
|
|
7142
6941
|
getScalingMode() {
|
|
7143
|
-
if (this._stageController)
|
|
7144
|
-
|
|
7145
|
-
} else {
|
|
7146
|
-
return this._configManager.getSettingsData().getVideoData().scalingMode;
|
|
7147
|
-
}
|
|
6942
|
+
if (this._stageController) return this._stageController.getScalingMode();
|
|
6943
|
+
return this._configManager.getSettingsData().getVideoData().scalingMode;
|
|
7148
6944
|
}
|
|
7149
|
-
/**
|
|
7150
|
-
* Forces the streamer to recalculate its size based on parent internal dimensions.
|
|
7151
|
-
*/
|
|
7152
6945
|
updateToSize() {
|
|
7153
|
-
if (this._initialized)
|
|
7154
|
-
this._stageController.handleResize();
|
|
7155
|
-
}
|
|
6946
|
+
if (this._initialized) this._stageController.handleResize();
|
|
7156
6947
|
}
|
|
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
6948
|
makeScreenshot() {
|
|
7164
6949
|
let canvas = document.createElement('canvas');
|
|
7165
6950
|
let context = canvas.getContext('2d');
|
|
@@ -7170,64 +6955,24 @@
|
|
|
7170
6955
|
let element = this._stageController.getScreenElement().getVideoElement();
|
|
7171
6956
|
if (context) {
|
|
7172
6957
|
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
|
-
}
|
|
6958
|
+
canvas.toBlob(blob => resolve(blob), 'image/png');
|
|
6959
|
+
} else resolve(null);
|
|
6960
|
+
} else resolve(null);
|
|
7182
6961
|
});
|
|
7183
6962
|
}
|
|
7184
|
-
//------------------------------------------------------------------------//
|
|
7185
6963
|
// 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
6964
|
createFPSGraph(container) {
|
|
7196
6965
|
return new FPSGraph(this, container);
|
|
7197
6966
|
}
|
|
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
6967
|
createBitrateGraph(container) {
|
|
7207
6968
|
return new BitrateGraph(this, container);
|
|
7208
6969
|
}
|
|
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
6970
|
createMicrophoneGraph(container) {
|
|
7218
6971
|
return new MicrophoneGraph(this, container);
|
|
7219
6972
|
}
|
|
7220
|
-
/**
|
|
7221
|
-
* Adds new graph to the internal collection of active graphs.
|
|
7222
|
-
*
|
|
7223
|
-
* @param newGraph - Graph instance to add
|
|
7224
|
-
*/
|
|
7225
6973
|
addGraph(newGraph) {
|
|
7226
6974
|
if (this._graphs != null) this._graphs.push(newGraph);
|
|
7227
6975
|
}
|
|
7228
|
-
/**
|
|
7229
|
-
* Stops all active performance graphs.
|
|
7230
|
-
*/
|
|
7231
6976
|
stopAllGraphs() {
|
|
7232
6977
|
if (this._graphs != null && this._graphs.length > 0) {
|
|
7233
6978
|
for (let i = 0; i < this._graphs.length; i++) {
|
|
@@ -7235,198 +6980,136 @@
|
|
|
7235
6980
|
}
|
|
7236
6981
|
}
|
|
7237
6982
|
}
|
|
7238
|
-
//------------------------------------------------------------------------//
|
|
7239
6983
|
// FULLSCREEN
|
|
7240
|
-
//------------------------------------------------------------------------//
|
|
7241
|
-
/**
|
|
7242
|
-
* Enters fullscreen mode for the streamer container.
|
|
7243
|
-
*/
|
|
7244
6984
|
enterFullScreen() {
|
|
7245
6985
|
if (this._initialized && this._stageController) this._stageController.enterFullScreen();
|
|
7246
6986
|
}
|
|
7247
|
-
/**
|
|
7248
|
-
* Exits fullscreen mode.
|
|
7249
|
-
*/
|
|
7250
6987
|
exitFullScreen() {
|
|
7251
6988
|
if (this._initialized && this._stageController) this._stageController.exitFullScreen();
|
|
7252
6989
|
}
|
|
7253
|
-
/**
|
|
7254
|
-
* Returns true if the streamer is currently in fullscreen mode.
|
|
7255
|
-
*
|
|
7256
|
-
* @returns Boolean indicating fullscreen status
|
|
7257
|
-
*/
|
|
7258
6990
|
isFullScreenMode() {
|
|
7259
6991
|
if (this._initialized && this._stageController) return this._stageController.isFullScreenMode();
|
|
7260
6992
|
return false;
|
|
7261
6993
|
}
|
|
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
|
-
*/
|
|
6994
|
+
// GETTERS
|
|
7270
6995
|
getStreamKey() {
|
|
7271
6996
|
var _a, _b, _c;
|
|
7272
6997
|
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
6998
|
}
|
|
7274
|
-
/**
|
|
7275
|
-
* Returns the Stats Controller instance which contains statistical data about streaming performance.
|
|
7276
|
-
*
|
|
7277
|
-
* @returns Stats controller instance or null
|
|
7278
|
-
*/
|
|
7279
6999
|
getStatsController() {
|
|
7280
7000
|
return this._statsController;
|
|
7281
7001
|
}
|
|
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
7002
|
getStreamerID() {
|
|
7288
7003
|
return this._streamerID;
|
|
7289
7004
|
}
|
|
7290
|
-
/**
|
|
7291
|
-
* Returns the logger instance used by this streamer.
|
|
7292
|
-
*
|
|
7293
|
-
* @returns Logger instance
|
|
7294
|
-
*/
|
|
7295
7005
|
getLogger() {
|
|
7296
7006
|
return this._logger;
|
|
7297
7007
|
}
|
|
7298
|
-
/**
|
|
7299
|
-
* Returns the configuration manager for this streamer.
|
|
7300
|
-
*
|
|
7301
|
-
* @returns Config manager instance or null
|
|
7302
|
-
*/
|
|
7303
7008
|
getConfigManager() {
|
|
7304
7009
|
return this._configManager;
|
|
7305
7010
|
}
|
|
7306
|
-
/**
|
|
7307
|
-
* Returns the network controller which manages all server communication.
|
|
7308
|
-
*
|
|
7309
|
-
* @returns Network controller instance or null
|
|
7310
|
-
*/
|
|
7311
7011
|
getNetworkController() {
|
|
7312
7012
|
return this._networkController;
|
|
7313
7013
|
}
|
|
7314
|
-
/**
|
|
7315
|
-
* Returns the streamer controller which manages media stream operations.
|
|
7316
|
-
*
|
|
7317
|
-
* @returns Streamer controller instance or null
|
|
7318
|
-
*/
|
|
7319
7014
|
getStreamerController() {
|
|
7320
7015
|
return this._streamerController;
|
|
7321
7016
|
}
|
|
7322
|
-
/**
|
|
7323
|
-
* Returns the stage controller which manages visual presentation.
|
|
7324
|
-
*
|
|
7325
|
-
* @returns Stage controller instance or null
|
|
7326
|
-
*/
|
|
7327
7017
|
getStageController() {
|
|
7328
7018
|
return this._stageController;
|
|
7329
7019
|
}
|
|
7330
|
-
/**
|
|
7331
|
-
* Returns the storage manager which handles persistent data storage.
|
|
7332
|
-
*
|
|
7333
|
-
* @returns Storage manager instance or null
|
|
7334
|
-
*/
|
|
7335
7020
|
getStorageManager() {
|
|
7336
7021
|
return this._storageManager;
|
|
7337
7022
|
}
|
|
7338
|
-
/**
|
|
7339
|
-
* Returns the HTML video element used by this streamer instance.
|
|
7340
|
-
*
|
|
7341
|
-
* @returns Video element or null
|
|
7342
|
-
*/
|
|
7343
7023
|
getVideoElement() {
|
|
7344
7024
|
var _a, _b, _c;
|
|
7345
7025
|
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
7026
|
}
|
|
7347
|
-
/**
|
|
7348
|
-
* Returns true if this streamer instance has already been initialized.
|
|
7349
|
-
*
|
|
7350
|
-
* @returns Boolean indicating initialization status
|
|
7351
|
-
*/
|
|
7352
7027
|
isInitialized() {
|
|
7353
7028
|
return this._initialized;
|
|
7354
7029
|
}
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
* @returns Streamer version string
|
|
7359
|
-
*/
|
|
7030
|
+
isDestroyed() {
|
|
7031
|
+
return this._isDestroyed;
|
|
7032
|
+
}
|
|
7360
7033
|
getVersion() {
|
|
7361
7034
|
return this.STREAMER_VERSION;
|
|
7362
7035
|
}
|
|
7363
|
-
/**
|
|
7364
|
-
* Returns the development branch of this streamer (e.g., main, experimental).
|
|
7365
|
-
*
|
|
7366
|
-
* @returns Branch name string
|
|
7367
|
-
*/
|
|
7368
7036
|
getBranch() {
|
|
7369
7037
|
return this.STREAMER_BRANCH;
|
|
7370
7038
|
}
|
|
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
|
-
*/
|
|
7039
|
+
// EVENTS
|
|
7380
7040
|
dispatchEvent(eventName, event) {
|
|
7381
7041
|
super.dispatchEvent(eventName, event);
|
|
7382
7042
|
}
|
|
7383
|
-
|
|
7384
|
-
// CLEAN UP
|
|
7385
|
-
//------------------------------------------------------------------------//
|
|
7386
|
-
/**
|
|
7387
|
-
* Starts the streaming process.
|
|
7388
|
-
*
|
|
7389
|
-
* @returns Promise that resolves when streaming has started
|
|
7390
|
-
*/
|
|
7043
|
+
// START / STOP
|
|
7391
7044
|
start() {
|
|
7392
7045
|
var _a;
|
|
7393
7046
|
return __awaiter(this, void 0, void 0, function* () {
|
|
7394
7047
|
return (_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.start();
|
|
7395
7048
|
});
|
|
7396
7049
|
}
|
|
7397
|
-
/**
|
|
7398
|
-
* Stops the streaming process.
|
|
7399
|
-
*/
|
|
7400
7050
|
stop() {
|
|
7401
7051
|
var _a;
|
|
7402
|
-
|
|
7052
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.stop();
|
|
7053
|
+
}
|
|
7054
|
+
// DEBUG
|
|
7055
|
+
debugMediaState() {
|
|
7056
|
+
var _a;
|
|
7057
|
+
(_a = this._streamerController) === null || _a === void 0 ? void 0 : _a.debugMediaState();
|
|
7403
7058
|
}
|
|
7404
|
-
//------------------------------------------------------------------------//
|
|
7405
|
-
// CLEAN UP
|
|
7406
|
-
//------------------------------------------------------------------------//
|
|
7407
7059
|
/**
|
|
7408
|
-
* Destroys this instance of StormStreamer, releasing all resources
|
|
7409
|
-
*
|
|
7060
|
+
* Destroys this instance of StormStreamer, releasing all resources.
|
|
7061
|
+
* This method is SYNCHRONOUS - it sets flags immediately to prevent race conditions.
|
|
7062
|
+
* All pending async operations will detect the destroyed state and abort.
|
|
7410
7063
|
*/
|
|
7411
7064
|
destroy() {
|
|
7412
|
-
var _a, _b, _c, _d;
|
|
7413
|
-
|
|
7414
|
-
if (this.
|
|
7415
|
-
|
|
7065
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
7066
|
+
// Prevent double destroy
|
|
7067
|
+
if (this._isDestroyed) {
|
|
7068
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.warning(this, "🔴 [DESTROY] Already destroyed, skipping");
|
|
7069
|
+
return;
|
|
7070
|
+
}
|
|
7071
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.warning(this, "🔴 [DESTROY] Starting streamer instance destruction (sync)...");
|
|
7072
|
+
// CRITICAL: Set flag IMMEDIATELY
|
|
7073
|
+
this._isDestroyed = true;
|
|
7416
7074
|
this._initialized = false;
|
|
7417
7075
|
this._isRemoved = true;
|
|
7418
|
-
//
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
//
|
|
7076
|
+
// Remove from global array
|
|
7077
|
+
if (this.DEV_MODE && 'StormStreamerArray' in window) {
|
|
7078
|
+
window.StormStreamerArray[this._streamerID] = null;
|
|
7079
|
+
}
|
|
7080
|
+
// Stop all graphs
|
|
7081
|
+
this.stopAllGraphs();
|
|
7082
|
+
this._graphs = [];
|
|
7083
|
+
// Destroy network controller
|
|
7084
|
+
if (this._networkController) {
|
|
7085
|
+
(_c = this._logger) === null || _c === void 0 ? void 0 : _c.info(this, "🔴 [DESTROY] Destroying NetworkController...");
|
|
7086
|
+
try {
|
|
7087
|
+
(_d = this._networkController.getConnection()) === null || _d === void 0 ? void 0 : _d.destroy();
|
|
7088
|
+
} catch (e) {/* ignore */}
|
|
7089
|
+
this._networkController = null;
|
|
7090
|
+
}
|
|
7091
|
+
// Destroy streamer controller (this is also sync now)
|
|
7092
|
+
if (this._streamerController) {
|
|
7093
|
+
(_e = this._logger) === null || _e === void 0 ? void 0 : _e.info(this, "🔴 [DESTROY] Destroying StreamerController...");
|
|
7094
|
+
this._streamerController.destroy();
|
|
7095
|
+
this._streamerController = null;
|
|
7096
|
+
}
|
|
7097
|
+
// Destroy stage controller
|
|
7098
|
+
if (this._stageController) {
|
|
7099
|
+
(_f = this._logger) === null || _f === void 0 ? void 0 : _f.info(this, "🔴 [DESTROY] Destroying StageController...");
|
|
7100
|
+
try {
|
|
7101
|
+
this._stageController.destroy();
|
|
7102
|
+
} catch (e) {/* ignore */}
|
|
7103
|
+
this._stageController = null;
|
|
7104
|
+
}
|
|
7105
|
+
// Clear other references
|
|
7106
|
+
this._storageManager = null;
|
|
7107
|
+
this._statsController = null;
|
|
7108
|
+
// Remove all event listeners
|
|
7423
7109
|
this.removeAllEventListeners();
|
|
7110
|
+
(_g = this._logger) === null || _g === void 0 ? void 0 : _g.success(this, "🔴 [DESTROY] Streamer instance destroyed successfully (sync)");
|
|
7424
7111
|
}
|
|
7425
7112
|
}
|
|
7426
|
-
/**
|
|
7427
|
-
* Next ID for the streamer instance. Each subsequent instance has a higher number.
|
|
7428
|
-
* @private
|
|
7429
|
-
*/
|
|
7430
7113
|
StormStreamer.NEXT_STREAMER_ID = 0;
|
|
7431
7114
|
|
|
7432
7115
|
function create(config) {
|