[mirotalksfu] - fix setSinkId now require user gesture
هذا الالتزام موجود في:
@@ -64,7 +64,7 @@ dev dependencies: {
|
|||||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||||
* @version 1.9.35
|
* @version 1.9.36
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
4
package-lock.json
مولّد
4
package-lock.json
مولّد
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalksfu",
|
"name": "mirotalksfu",
|
||||||
"version": "1.9.35",
|
"version": "1.9.36",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mirotalksfu",
|
"name": "mirotalksfu",
|
||||||
"version": "1.9.35",
|
"version": "1.9.36",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.864.0",
|
"@aws-sdk/client-s3": "^3.864.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalksfu",
|
"name": "mirotalksfu",
|
||||||
"version": "1.9.35",
|
"version": "1.9.36",
|
||||||
"description": "WebRTC SFU browser-based video calls",
|
"description": "WebRTC SFU browser-based video calls",
|
||||||
"main": "Server.js",
|
"main": "Server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ let BRAND = {
|
|||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
imageUrl: '../images/mirotalk-logo.gif',
|
imageUrl: '../images/mirotalk-logo.gif',
|
||||||
title: '<strong>WebRTC SFU v1.9.35</strong>',
|
title: '<strong>WebRTC SFU v1.9.36</strong>',
|
||||||
html: `
|
html: `
|
||||||
<button
|
<button
|
||||||
id="support-button"
|
id="support-button"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h
|
|||||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||||
* @version 1.9.35
|
* @version 1.9.36
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -5543,7 +5543,7 @@ function showAbout() {
|
|||||||
position: 'center',
|
position: 'center',
|
||||||
imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' ? BRAND.about.imageUrl : image.about,
|
imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' ? BRAND.about.imageUrl : image.about,
|
||||||
customClass: { image: 'img-about' },
|
customClass: { image: 'img-about' },
|
||||||
title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.9.35',
|
title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.9.36',
|
||||||
html: `
|
html: `
|
||||||
<br />
|
<br />
|
||||||
<div id="about">
|
<div id="about">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||||
* @version 1.9.35
|
* @version 1.9.36
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -237,6 +237,8 @@ class RoomClient {
|
|||||||
this.isMobileDevice = peer_info.is_mobile_device;
|
this.isMobileDevice = peer_info.is_mobile_device;
|
||||||
this.isMobileSafari = this.isMobileDevice && peer_info.browser_name.toLowerCase().includes('safari');
|
this.isMobileSafari = this.isMobileDevice && peer_info.browser_name.toLowerCase().includes('safari');
|
||||||
|
|
||||||
|
this.pendingSinkId = null; // store desired sink id until next user gesture
|
||||||
|
|
||||||
this.localAudioEl = localAudioEl;
|
this.localAudioEl = localAudioEl;
|
||||||
this.remoteAudioEl = remoteAudioEl;
|
this.remoteAudioEl = remoteAudioEl;
|
||||||
this.videoMediaContainer = videoMediaContainer;
|
this.videoMediaContainer = videoMediaContainer;
|
||||||
@@ -3562,39 +3564,88 @@ class RoomClient {
|
|||||||
console.log(who + ' Success attached media ' + type);
|
console.log(who + ' Success attached media ' + type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasUserActivation() {
|
||||||
|
if (navigator.userActivation) return !!navigator.userActivation.isActive;
|
||||||
|
if ('hasTransientUserActivation' in document) return !!document.hasTransientUserActivation;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
runOnNextUserActivation(callback) {
|
||||||
|
const fire = (e) => {
|
||||||
|
cleanup();
|
||||||
|
try {
|
||||||
|
// Call synchronously to keep the user-activation
|
||||||
|
callback(e);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('runOnNextUserActivation callback error:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const cleanup = () => {
|
||||||
|
window.removeEventListener('pointerdown', fire, true);
|
||||||
|
window.removeEventListener('click', fire, true);
|
||||||
|
window.removeEventListener('mousedown', fire, true);
|
||||||
|
window.removeEventListener('touchstart', fire, true);
|
||||||
|
window.removeEventListener('keydown', fire, true);
|
||||||
|
};
|
||||||
|
const opts = { capture: true, once: true, passive: true };
|
||||||
|
window.addEventListener('pointerdown', fire, opts);
|
||||||
|
window.addEventListener('click', fire, opts);
|
||||||
|
window.addEventListener('mousedown', fire, opts);
|
||||||
|
window.addEventListener('touchstart', fire, opts);
|
||||||
|
window.addEventListener('keydown', fire, opts);
|
||||||
|
}
|
||||||
|
|
||||||
async changeAudioDestination(audioElement = false) {
|
async changeAudioDestination(audioElement = false) {
|
||||||
const audioDestination = speakerSelect.value;
|
const sinkId = speakerSelect?.value;
|
||||||
if (audioElement) {
|
if (!sinkId) return;
|
||||||
await this.attachSinkId(audioElement, audioDestination);
|
|
||||||
} else {
|
// Defer until a user gesture if needed
|
||||||
const audioElements = this.remoteAudioEl.querySelectorAll('audio');
|
if (!this.hasUserActivation()) {
|
||||||
audioElements.forEach(async (audioElement) => {
|
this.pendingSinkId = sinkId;
|
||||||
await this.attachSinkId(audioElement, audioDestination);
|
this.userLog('info', 'Click once to apply the selected speaker', 'top-end', 3000);
|
||||||
|
this.runOnNextUserActivation(() => {
|
||||||
|
const els = audioElement ? [audioElement] : this.remoteAudioEl.querySelectorAll('audio');
|
||||||
|
els.forEach((el) => this.attachSinkId(el, this.pendingSinkId));
|
||||||
|
this.pendingSinkId = null;
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const els = audioElement ? [audioElement] : this.remoteAudioEl.querySelectorAll('audio');
|
||||||
|
for (const el of els) {
|
||||||
|
await this.attachSinkId(el, sinkId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async attachSinkId(elem, sinkId) {
|
async attachSinkId(elem, sinkId) {
|
||||||
if (typeof elem.sinkId !== 'undefined') {
|
if (typeof elem.setSinkId !== 'function') {
|
||||||
elem.setSinkId(sinkId)
|
const error = `Browser doesn't support output device selection.`;
|
||||||
.then(() => {
|
|
||||||
console.log(`Success, audio output device attached: ${sinkId}`);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let errorMessage = err;
|
|
||||||
let speakerSelect = this.getId('speakerSelect');
|
|
||||||
if (err.name === 'SecurityError')
|
|
||||||
errorMessage = `You need to use HTTPS for selecting audio output device: ${err}`;
|
|
||||||
console.error('Attach SinkId error: ', errorMessage);
|
|
||||||
this.userLog('error', errorMessage, 'top-end', 6000);
|
|
||||||
speakerSelect.selectedIndex = 0;
|
|
||||||
refreshLsDevices();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const error = `Browser seems doesn't support output device selection.`;
|
|
||||||
console.warn(error);
|
console.warn(error);
|
||||||
this.userLog('error', error, 'top-end', 6000);
|
this.userLog('error', error, 'top-end', 6000);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return elem
|
||||||
|
.setSinkId(sinkId)
|
||||||
|
.then(() => console.log(`Success, audio output device attached: ${sinkId}`))
|
||||||
|
.catch((err) => {
|
||||||
|
const speakerSel = this.getId('speakerSelect');
|
||||||
|
if (err?.name === 'SecurityError') {
|
||||||
|
const msg = `Use HTTPS to select audio output device: ${err.message || err}`;
|
||||||
|
console.error('Attach SinkId error: ', msg);
|
||||||
|
this.userLog('error', msg, 'top-end', 6000);
|
||||||
|
} else if (err?.name === 'NotAllowedError' || /user gesture/i.test(err?.message || '')) {
|
||||||
|
// Retry on next user gesture
|
||||||
|
this.userLog('info', 'Click once to allow changing the speaker', 'top-end', 4000);
|
||||||
|
this.pendingSinkId = sinkId;
|
||||||
|
this.runOnNextUserActivation(() => this.attachSinkId(elem, this.pendingSinkId));
|
||||||
|
} else {
|
||||||
|
console.error('Attach SinkId error: ', err);
|
||||||
|
this.userLog('error', err, 'top-end', 6000);
|
||||||
|
}
|
||||||
|
if (speakerSel) speakerSel.selectedIndex = 0;
|
||||||
|
refreshLsDevices();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
event(evt) {
|
event(evt) {
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم