[mirotalk] - add possibility to select audio-video devices
This commit is contained in:
@@ -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
@@ -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
|
||||
// =====================================================
|
||||
|
||||
@@ -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: </label
|
||||
><select id="audioSource"></select>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<label for="audioOutput">Audio output: </label
|
||||
><select id="audioOutput"></select>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<label for="videoSource">Video source: </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
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user