[mirotalk] - add possibility to select audio-video devices

This commit is contained in:
Miroslav Pejic
2021-02-12 22:04:04 +01:00
parent bd00ad9192
commit c2a457e2a7
4 changed files with 315 additions and 26 deletions
+1
View File
@@ -37,6 +37,7 @@ Open the app in one of following **supported browser**
- Chat Room
- Full Screen Mode
- Change Theme
- Select Audio Input-Output & Video source (beta)
- Right click on Video elements for more options
- No download required, entirely browser based
- Direct peer to peer connection ensures lowest latency
+238 -24
View File
@@ -41,6 +41,12 @@ var iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; // backup iceServer
var startTime;
var elapsedTime;
// devices audio video
var audioInputSelect = null;
var audioOutputSelect = null;
var videoSelect = null;
var selectors = null;
// =====================================================
// Get peer info using DetecRTC
// =====================================================
@@ -97,6 +103,9 @@ function initPeer() {
return;
}
// setup audio video deovices
setupDevices();
// peer ready for WebRTC! :)
console.log("Connecting to signaling server");
signalingSocket = io(signalingServer);
@@ -360,6 +369,48 @@ function initPeer() {
});
} // end [initPeer]
// =====================================================
// Setup audio - video devices
// =====================================================
function setupDevices() {
// audio - video select box
audioInputSelect = get("audioSource");
audioOutputSelect = get("audioOutput");
videoSelect = get("videoSource");
selectors = [audioInputSelect, audioOutputSelect, videoSelect];
audioOutputSelect.disabled = !("sinkId" in HTMLMediaElement.prototype);
navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
audioInputSelect.onchange = refreshLocalMedia;
audioOutputSelect.onchange = changeAudioDestination;
videoSelect.onchange = refreshLocalMedia;
}
// =====================================================
// Refresh Local media audio video in - out
// =====================================================
function refreshLocalMedia() {
if (window.stream) {
window.stream.getTracks().forEach((track) => {
track.stop();
});
}
const audioSource = audioInputSelect.value;
const videoSource = videoSelect.value;
const constraints = {
audio: { deviceId: audioSource ? { exact: audioSource } : undefined },
video: { deviceId: videoSource ? { exact: videoSource } : undefined },
};
navigator.mediaDevices
.getUserMedia(constraints)
.then(gotStream)
.then(gotDevices)
.catch(handleError);
}
// =====================================================
// Local media stuff
// =====================================================
@@ -370,18 +421,19 @@ function setupLocalMedia(callback, errorback) {
return;
}
// --------------------------------------------------------------------------------
// TODO: add possibility to select audio - video devices before to load local media
// getDevices();
// --------------------------------------------------------------------------------
if (window.stream) {
window.stream.getTracks().forEach((track) => {
track.stop();
});
}
/* Ask user for permission to use the computers microphone and/or camera,
* attach it to an <audio> or <video> tag if they give us access. */
console.log("Requesting access to local audio / video inputs");
const constraints = {
video: useVideo,
audio: useAudio,
video: useVideo,
};
navigator.mediaDevices
@@ -424,6 +476,9 @@ function setupLocalMedia(callback, errorback) {
localMedia.poster = null;
resizeVideos();
// here i have access to audio - video can do it :P
setupDevices();
if (callback) callback();
})
.catch((e) => {
@@ -438,7 +493,133 @@ function setupLocalMedia(callback, errorback) {
} // end [setup_local_stream]
// =====================================================
// TODO: Select audio - video devices
// get Devices and show to select box
// =====================================================
function gotDevices(deviceInfos) {
// https://github.com/webrtc/samples/tree/gh-pages/src/content/devices/input-output
// Handles being called several times to update labels. Preserve values.
const values = selectors.map((select) => select.value);
selectors.forEach((select) => {
while (select.firstChild) {
select.removeChild(select.firstChild);
}
});
// check devices
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
// console.log("device-info ------> ", deviceInfo);
const option = document.createElement("option");
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === "audioinput") {
// audio Input
option.text =
deviceInfo.label || `microphone ${audioInputSelect.length + 1}`;
audioInputSelect.appendChild(option);
} else if (deviceInfo.kind === "audiooutput") {
// audio Output
option.text =
deviceInfo.label || `speaker ${audioOutputSelect.length + 1}`;
audioOutputSelect.appendChild(option);
} else if (deviceInfo.kind === "videoinput") {
// video Input
option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
videoSelect.appendChild(option);
} else {
// something else
console.log("Some other kind of source/device: ", deviceInfo);
}
} // end for devices
selectors.forEach((select, selectorIndex) => {
if (
Array.prototype.slice
.call(select.childNodes)
.some((n) => n.value === values[selectorIndex])
) {
select.value = values[selectorIndex];
}
});
}
// =====================================================
// Attach audio output device to video element using device/sink ID.
// =====================================================
function attachSinkId(element, sinkId) {
if (typeof element.sinkId !== "undefined") {
element
.setSinkId(sinkId)
.then(() => {
console.log(`Success, audio output device attached: ${sinkId}`);
})
.catch((error) => {
let errorMessage = error;
if (error.name === "SecurityError") {
errorMessage = `You need to use HTTPS for selecting audio output device: ${error}`;
}
console.error(errorMessage);
// Jump back to first output device in the list as it's the default.
audioOutputSelect.selectedIndex = 0;
});
} else {
console.warn("Browser does not support output device selection.");
}
}
// =====================================================
// Change audio output
// =====================================================
function changeAudioDestination() {
const audioDestination = audioOutputSelect.value;
attachSinkId(get("myVideo"), audioDestination);
}
// =====================================================
// Got Stream append to local media
// =====================================================
function gotStream(stream) {
window.stream = stream; // make stream available to console
// refresh my video to peers
for (var peer_id in peers) {
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/getSenders
var sender = peers[peer_id]
.getSenders()
.find((s) => (s.track ? s.track.kind === "video" : false));
// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/replaceTrack
sender.replaceTrack(stream.getVideoTracks()[0]);
}
stream.getVideoTracks()[0].enabled = true;
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStream
const newStream = new MediaStream([
stream.getVideoTracks()[0],
localMediaStream.getAudioTracks()[0],
]);
localMediaStream = newStream;
// attachMediaStream is a part of the adapter.js library
attachMediaStream(get("myVideo"), localMediaStream);
get("myVideo").classList.toggle("mirror");
// Refresh button list in case labels have become available
return navigator.mediaDevices.enumerateDevices();
}
// =====================================================
// Handle error
// =====================================================
function handleError(error) {
console.log(
"navigator.MediaDevices.getUserMedia error: ",
error.message,
error.name
);
}
// =====================================================
// Extra not used, print audio - video devices
// =====================================================
function getDevices() {
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
@@ -452,12 +633,12 @@ function getDevices() {
.then(function (devices) {
devices.forEach(function (device) {
myDevices.push({
deviceName: device.kind + ": " + device.label,
deviceKind: device.kind,
deviceName: device.label,
deviceId: device.deviceId,
});
});
console.log("Audio-Video-Devices", myDevices);
// ....
})
.catch(function (err) {
console.log(err.name + ": " + err.message);
@@ -526,8 +707,9 @@ function manageButtons() {
setFullScreenBtn();
setSendMsgBtn();
setChatRoomBtn();
setAboutBtn();
setThemeBtn();
setDevicesBtn();
setAboutBtn();
setLeaveRoomBtn();
showLeftButtons();
}
@@ -588,8 +770,8 @@ function setVideoBtn() {
function setSwapCameraBtn() {
navigator.mediaDevices.enumerateDevices().then((devices) => {
const videoInput = devices.filter((device) => device.kind === "videoinput");
if (videoInput.length > 1) {
// swap camera front - rear button click event
if (videoInput.length > 1 && isMobileDevice) {
// swap camera front - rear button click event for mobile
document
.getElementById("swapCameraBtn")
.addEventListener("click", (e) => {
@@ -715,15 +897,6 @@ function setChatRoomBtn() {
});
}
// =====================================================
// About button click event
// =====================================================
function setAboutBtn() {
get("aboutBtn").addEventListener("click", (e) => {
getAbout();
});
}
// =====================================================
// Theme button click event
// =====================================================
@@ -733,6 +906,31 @@ function setThemeBtn() {
});
}
// =====================================================
// Devices button click event
// =====================================================
function setDevicesBtn() {
get("myDevicesBtn").addEventListener("click", (e) => {
hideShowDevices();
});
get("myDevicesCloseBtn").addEventListener("click", (e) => {
hideShowDevices();
});
if (!isMobileDevice) {
// make chat room draggable for desktop
dragElement(get("myDevices"), get("myDeviceHeader"));
}
}
// =====================================================
// About button click event
// =====================================================
function setAboutBtn() {
get("aboutBtn").addEventListener("click", (e) => {
getAbout();
});
}
// =====================================================
// Leave room button click event
// =====================================================
@@ -751,7 +949,7 @@ function setChatBoxMobile() {
document.documentElement.style.setProperty("--msger-width", "98vw");
} else {
// make chat room draggable for desktop
dragElement(get("msgerDraggable"));
dragElement(get("msgerDraggable"), get("msgerHeader"));
// $("#msgerDraggable").draggable(); https://jqueryui.com/draggable/ declined, can't select chat room texts...
}
}
@@ -759,15 +957,15 @@ function setChatBoxMobile() {
// =====================================================
// Make chat room draggable
// =====================================================
function dragElement(elmnt) {
function dragElement(elmnt, dragObj) {
// https://www.w3schools.com/howto/howto_js_draggable.asp
var pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
if (get("msgerHeader")) {
if (dragObj) {
// if present, the header is where you move the DIV from:
get("msgerHeader").onmousedown = dragMouseDown;
dragObj.onmousedown = dragMouseDown;
} else {
// otherwise, move the DIV from anywhere inside the DIV:
elmnt.onmousedown = dragMouseDown;
@@ -957,6 +1155,22 @@ function checkCountTime() {
}
}
// =====================================================
// Hide - show devices div
// =====================================================
function hideShowDevices() {
if (noPeers()) {
userLog("info", "Can't setup devices, no peer connection detected");
return;
}
let md = get("myDevices");
if (md.style.display == "none") {
md.style.display = "block";
return;
}
md.style.display = "none";
}
// =====================================================
// Append Message to msger chat room
// =====================================================
+28
View File
@@ -93,6 +93,7 @@
class="fas fa-comment"
></button>
<button title="theme" id="themeBtn" class="fas fa-palette"></button>
<button title="devices" id="myDevicesBtn" class="fas fa-cogs"></button>
<button title="about" id="aboutBtn" class="fas fa-question"></button>
<button
title="leave room"
@@ -184,6 +185,33 @@
</section>
<!-- End chat room -->
<section id="myDevices">
<header id="myDeviceHeader">
<button
title="close"
id="myDevicesCloseBtn"
class="fas fa-times"
></button>
</header>
<main>
<br />
<div>
<label for="audioSource">Audio input:&nbsp;</label
><select id="audioSource"></select>
</div>
<br />
<div>
<label for="audioOutput">Audio output:&nbsp;</label
><select id="audioOutput"></select>
</div>
<br />
<div>
<label for="videoSource">Video source:&nbsp;</label
><select id="videoSource"></select>
</div>
</main>
</section>
<!--
the <video> and <audio> tags are all added and removed dynamically
in 'onAddStream', 'setupLocalMedia', and 'removePeer'/'disconnect'
+48 -2
View File
@@ -100,6 +100,7 @@ body {
}
#buttons {
display: none;
position: absolute;
left: var(--btns-left);
top: 50%;
@@ -110,7 +111,6 @@ body {
background: transparent;
box-shadow: var(--box-shadow);
padding: 15px;
display: none;
flex-direction: column;
justify-content: space-around;
grid-gap: 0.3rem;
@@ -151,8 +151,9 @@ button:hover {
#fullScreenBtn,
#sendMsgBtn,
#chatRoomBtn,
#aboutBtn,
#themeBtn,
#myDevicesBtn,
#aboutBtn,
#leaveRoomBtn {
font-size: 2rem;
opacity: var(--btn-opc);
@@ -439,3 +440,48 @@ video:fullscreen {
#chat-msg a {
color: white;
}
/* devices */
#myDevices {
display: none;
z-index: 9991;
/* center */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/* gui */
background-color: rgba(255, 255, 255, 0.5);
border-radius: 5px;
margin: auto;
box-shadow: var(--box-shadow);
padding: 10px;
/* fade in */
-webkit-animation: fadeIn ease-in 1;
-moz-animation: fadeIn ease-in 1;
animation: fadeIn ease-in 1;
-webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-duration: 1s;
-moz-animation-duration: 1s;
animation-duration: 1s;
overflow: hidden;
}
#myDeviceHeader {
cursor: move;
}
label {
width: 7em;
display: inline-block;
color: black;
}
select {
width: 25em;
height: 30px;
color: white;
background-color: #2b2b2b;
border-radius: 5px;
}