ep_webrtc 0.1.80 → 0.1.81
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/README.md +2 -1
- package/locales/en.json +2 -0
- package/package.json +1 -1
- package/static/css/styles.css +6 -0
- package/static/js/index.js +99 -27
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# ep_webrtc
|
|
4
4
|
|
|
5
|
-
WebRTC-based audio/video chat with other users visiting the
|
|
5
|
+
WebRTC-based audio/video chat and screen sharing with other users visiting the
|
|
6
|
+
same pad.
|
|
6
7
|
|
|
7
8
|
The audio and video streams are peer-to-peer: every user sends a copy of their
|
|
8
9
|
audio/video streams directly to every other user visiting the same pad. Because
|
package/locales/en.json
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
"ep_webrtc_error_permission_cam": "Failed to get permission to access your camera.",
|
|
6
6
|
"ep_webrtc_error_permission_mic": "Failed to get permission to access your microphone.",
|
|
7
7
|
"ep_webrtc_error_permission_cammic": "Failed to get permission to access your camera and microphone.",
|
|
8
|
+
"ep_webrtc_error_permission_screen": "Failed to get permission to access your screen.",
|
|
8
9
|
"ep_webrtc_error_notFound_cam": "Failed to access your camera.",
|
|
9
10
|
"ep_webrtc_error_notFound_mic": "Failed to access your microphone.",
|
|
10
11
|
"ep_webrtc_error_notFound_cammic": "Failed to access your camera and microphone.",
|
|
12
|
+
"ep_webrtc_error_notFound_screen": "Failed to access your screen.",
|
|
11
13
|
"ep_webrtc_error_notReadable": "Sorry, a hardware error occurred that prevented access to your camera and/or microphone:",
|
|
12
14
|
"ep_webrtc_error_otherCantAccess": "Sorry, the browser doesn't have access to your camera and/or microphone. Please check permissions in your browser's settings. This is most likely not a hardware error:",
|
|
13
15
|
|
package/package.json
CHANGED
package/static/css/styles.css
CHANGED
|
@@ -102,6 +102,12 @@
|
|
|
102
102
|
#rtcbox .interface-btn.video-btn:before { content: '\e83b'; }
|
|
103
103
|
#rtcbox .interface-btn.video-btn.off:before { content: '\e83c'; }
|
|
104
104
|
|
|
105
|
+
#rtcbox .interface-btn.screenshare-btn:before {
|
|
106
|
+
content: "\f108";
|
|
107
|
+
font-family: "fontawesome-ep_webrtc";
|
|
108
|
+
}
|
|
109
|
+
#rtcbox .interface-btn.screenshare-btn.off:before { content: "\f109"; }
|
|
110
|
+
|
|
105
111
|
#rtcbox .interface-btn.enlarge-btn:before { content: '\e840'; }
|
|
106
112
|
#rtcbox .interface-btn.enlarge-btn.large:before { content: '\e83f'; }
|
|
107
113
|
|
package/static/js/index.js
CHANGED
|
@@ -84,6 +84,7 @@ class LocalTracks extends EventTargetPolyfill {
|
|
|
84
84
|
constructor() {
|
|
85
85
|
super();
|
|
86
86
|
Object.defineProperty(this, 'stream', {value: new MediaStream(), writeable: false});
|
|
87
|
+
this.videoIsScreenshare = false;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
_getTracks(kind) {
|
|
@@ -447,7 +448,10 @@ exports.rtc = new class {
|
|
|
447
448
|
if (newTrack != null) return;
|
|
448
449
|
switch (oldTrack.kind) {
|
|
449
450
|
case 'audio': this._selfViewButtons.audio.enabled = false; break;
|
|
450
|
-
case 'video':
|
|
451
|
+
case 'video':
|
|
452
|
+
this._selfViewButtons.video.enabled = false;
|
|
453
|
+
this._selfViewButtons.screenshare.enabled = false;
|
|
454
|
+
break;
|
|
451
455
|
}
|
|
452
456
|
});
|
|
453
457
|
this._pad = null;
|
|
@@ -651,27 +655,52 @@ exports.rtc = new class {
|
|
|
651
655
|
(t) => t !== this._disabledSilence && t.readyState === 'live');
|
|
652
656
|
if (addAudioTrack) devices.push('mic');
|
|
653
657
|
const addVideoTrack = updateVideo && this._selfViewButtons.video.enabled &&
|
|
654
|
-
!this._localTracks.stream.getVideoTracks().some((t) => t.readyState === 'live')
|
|
658
|
+
(!this._localTracks.stream.getVideoTracks().some((t) => t.readyState === 'live') ||
|
|
659
|
+
this._localTracks.videoIsScreenshare);
|
|
655
660
|
if (addVideoTrack) devices.push('cam');
|
|
656
|
-
|
|
661
|
+
const addScreenshareTrack = updateVideo && this._selfViewButtons.screenshare.enabled &&
|
|
662
|
+
!this._selfViewButtons.video.enabled && // Video button overrides screenshare button.
|
|
663
|
+
(!this._localTracks.stream.getVideoTracks().some((t) => t.readyState === 'live') ||
|
|
664
|
+
!this._localTracks.videoIsScreenshare);
|
|
665
|
+
const getUserMedia = async () => {
|
|
666
|
+
if (!addAudioTrack && !addVideoTrack) return new MediaStream();
|
|
657
667
|
debug(`requesting permission to access ${devices.join(' and ')}`);
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
668
|
+
const stream = await window.navigator.mediaDevices.getUserMedia({
|
|
669
|
+
audio: addAudioTrack,
|
|
670
|
+
video: addVideoTrack && {width: {ideal: 320}, height: {ideal: 240}},
|
|
671
|
+
});
|
|
672
|
+
debug('successfully accessed device(s)');
|
|
673
|
+
return stream;
|
|
674
|
+
};
|
|
675
|
+
const getDisplayMedia = async () => {
|
|
676
|
+
if (!addScreenshareTrack) return new MediaStream();
|
|
677
|
+
debug('requesting permission to access screen');
|
|
678
|
+
const stream = await window.navigator.mediaDevices.getDisplayMedia({
|
|
679
|
+
video: {cursor: 'always'},
|
|
680
|
+
});
|
|
681
|
+
debug('successfully accessed screen');
|
|
682
|
+
return new MediaStream(stream.getVideoTracks());
|
|
683
|
+
};
|
|
684
|
+
await Promise.all([[getUserMedia, devices], [getDisplayMedia, ['screen']]].map(
|
|
685
|
+
async ([getMedia, devices]) => {
|
|
686
|
+
let stream;
|
|
687
|
+
try {
|
|
688
|
+
stream = await getMedia();
|
|
689
|
+
} catch (err) {
|
|
690
|
+
// Display but otherwise ignore the error. The button(s) will be toggled back to
|
|
691
|
+
// disabled below if we failed to access the microphone/camera. The user can re-click
|
|
692
|
+
// the button to try again.
|
|
693
|
+
err.devices = devices;
|
|
694
|
+
(async () => this.showUserMediaError(err))();
|
|
695
|
+
stream = new MediaStream();
|
|
696
|
+
}
|
|
697
|
+
for (const track of stream.getTracks()) {
|
|
698
|
+
if (track.kind === 'video') {
|
|
699
|
+
this._localTracks.videoIsScreenshare = devices.includes('screen');
|
|
700
|
+
}
|
|
701
|
+
this._localTracks.setTrack(track.kind, track);
|
|
702
|
+
}
|
|
703
|
+
}));
|
|
675
704
|
if (updateAudio) {
|
|
676
705
|
for (const track of this._localTracks.stream.getAudioTracks()) {
|
|
677
706
|
// Re-check the state of the button because the user might have clicked it while
|
|
@@ -685,12 +714,15 @@ exports.rtc = new class {
|
|
|
685
714
|
if (updateVideo) {
|
|
686
715
|
for (const track of this._localTracks.stream.getVideoTracks()) {
|
|
687
716
|
// Re-check the state of the button because the user might have clicked it while
|
|
688
|
-
// getUserMedia() was running.
|
|
689
|
-
track.enabled = this.
|
|
717
|
+
// getUserMedia() or getDisplayMedia() was running.
|
|
718
|
+
track.enabled = this._localTracks.videoIsScreenshare
|
|
719
|
+
? this._selfViewButtons.screenshare.enabled : this._selfViewButtons.video.enabled;
|
|
690
720
|
}
|
|
691
721
|
const hasVideo = this._localTracks.stream.getVideoTracks().some(
|
|
692
722
|
(t) => t.enabled && t.readyState === 'live');
|
|
693
|
-
this._selfViewButtons.video.enabled = hasVideo;
|
|
723
|
+
this._selfViewButtons.video.enabled = hasVideo && !this._localTracks.videoIsScreenshare;
|
|
724
|
+
this._selfViewButtons.screenshare.enabled =
|
|
725
|
+
hasVideo && this._localTracks.videoIsScreenshare;
|
|
694
726
|
}
|
|
695
727
|
} finally {
|
|
696
728
|
if (updateVideo) this._trackLocks.video.unlock();
|
|
@@ -744,8 +776,15 @@ exports.rtc = new class {
|
|
|
744
776
|
const $rtcbox = $('#rtcbox');
|
|
745
777
|
$rtcbox.empty(); // In case any peer videos didn't get cleaned up for some reason.
|
|
746
778
|
$rtcbox.hide();
|
|
747
|
-
|
|
748
|
-
|
|
779
|
+
await this._trackLocks.audio.lock();
|
|
780
|
+
await this._trackLocks.video.lock();
|
|
781
|
+
try {
|
|
782
|
+
for (const track of this._localTracks.stream.getTracks()) {
|
|
783
|
+
this._localTracks.setTrack(track.kind, null);
|
|
784
|
+
}
|
|
785
|
+
} finally {
|
|
786
|
+
this._trackLocks.video.unlock();
|
|
787
|
+
this._trackLocks.audio.unlock();
|
|
749
788
|
}
|
|
750
789
|
} finally {
|
|
751
790
|
$checkbox.prop('disabled', false);
|
|
@@ -936,10 +975,11 @@ exports.rtc = new class {
|
|
|
936
975
|
});
|
|
937
976
|
|
|
938
977
|
// /////
|
|
939
|
-
//
|
|
978
|
+
// Video and Screen Sharing Buttons
|
|
940
979
|
// /////
|
|
941
980
|
|
|
942
981
|
let $videoBtn;
|
|
982
|
+
let $screenshareBtn;
|
|
943
983
|
if (isLocal) {
|
|
944
984
|
$videoBtn = $('<span>').addClass('interface-btn video-btn buttonicon').appendTo($interface);
|
|
945
985
|
this._selfViewButtons.video = {
|
|
@@ -956,11 +996,43 @@ exports.rtc = new class {
|
|
|
956
996
|
if (videoHardDisabled) {
|
|
957
997
|
$videoBtn.attr('title', 'Video disallowed by admin').addClass('disallowed');
|
|
958
998
|
}
|
|
999
|
+
const {navigator: {mediaDevices: {getDisplayMedia} = {}} = {}} = window;
|
|
1000
|
+
$screenshareBtn = $('<span>')
|
|
1001
|
+
.addClass('interface-btn screenshare-btn buttonicon')
|
|
1002
|
+
.css('display', typeof getDisplayMedia === 'function' ? '' : 'none')
|
|
1003
|
+
.appendTo($interface);
|
|
1004
|
+
this._selfViewButtons.screenshare = {
|
|
1005
|
+
get enabled() { return !$screenshareBtn.hasClass('off'); },
|
|
1006
|
+
set enabled(val) {
|
|
1007
|
+
$screenshareBtn
|
|
1008
|
+
.toggleClass('off', !val)
|
|
1009
|
+
.attr('title', val ? 'Stop screen share' : 'Start screen share');
|
|
1010
|
+
},
|
|
1011
|
+
};
|
|
1012
|
+
this._selfViewButtons.screenshare.enabled = false;
|
|
959
1013
|
addAsyncEventHandlers($videoBtn, videoHardDisabled ? {} : {
|
|
960
1014
|
click: async () => {
|
|
961
1015
|
const videoEnabled = !this._selfViewButtons.video.enabled;
|
|
962
1016
|
_debug(`video button clicked to ${videoEnabled ? 'en' : 'dis'}able video`);
|
|
963
1017
|
this._selfViewButtons.video.enabled = videoEnabled;
|
|
1018
|
+
// Unconditionally disable screen sharing. Either the camera was previously disabled in
|
|
1019
|
+
// which case the user now wants to share camera video, or the camera was previously
|
|
1020
|
+
// enabled in which case the user now wants to shut off all video.
|
|
1021
|
+
this._selfViewButtons.screenshare.enabled = false;
|
|
1022
|
+
await this.updateLocalTracks({updateVideo: true});
|
|
1023
|
+
// Don't use `await` here -- see the comment for the audio button click handler above.
|
|
1024
|
+
this.unmuteAndPlayAll();
|
|
1025
|
+
},
|
|
1026
|
+
});
|
|
1027
|
+
addAsyncEventHandlers($screenshareBtn, {
|
|
1028
|
+
click: async () => {
|
|
1029
|
+
const screenshareEnabled = !this._selfViewButtons.screenshare.enabled;
|
|
1030
|
+
_debug(`button clicked to ${screenshareEnabled ? 'en' : 'dis'}able screen sharing`);
|
|
1031
|
+
// Unconditionally disable the camera. Either screen sharing was previously disabled in
|
|
1032
|
+
// which case the user now wants to share the screen, or screen sharing was previously
|
|
1033
|
+
// enabled in which case the user wants to shut off all video.
|
|
1034
|
+
this._selfViewButtons.video.enabled = false;
|
|
1035
|
+
this._selfViewButtons.screenshare.enabled = screenshareEnabled;
|
|
964
1036
|
await this.updateLocalTracks({updateVideo: true});
|
|
965
1037
|
// Don't use `await` here -- see the comment for the audio button click handler above.
|
|
966
1038
|
this.unmuteAndPlayAll();
|
|
@@ -1047,7 +1119,7 @@ exports.rtc = new class {
|
|
|
1047
1119
|
{
|
|
1048
1120
|
cls: 'videoended-error-btn',
|
|
1049
1121
|
title: 'Video stopped unexpectedly. Click to retry.',
|
|
1050
|
-
click: () => $videoBtn.click(),
|
|
1122
|
+
click: () => (this._localTracks.videoIsScreenshare ? $screenshareBtn : $videoBtn).click(),
|
|
1051
1123
|
},
|
|
1052
1124
|
] : []),
|
|
1053
1125
|
];
|