99 * @license For private project or commercial purposes contact us at: license.mirotalk@gmail.com or purchase it directly via Code Canyon:
1010 * @license https://codecanyon.net/item/mirotalk-c2c-webrtc-real-time-cam-2-cam-video-conferences-and-screen-sharing/43383005
1111 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
12- * @version 1.3.09
12+ * @version 1.3.10
1313 */
1414
1515const roomId = new URLSearchParams ( window . location . search ) . get ( 'room' ) ;
@@ -37,6 +37,9 @@ const leaveBtn = document.getElementById('leaveBtn');
3737const settings = document . getElementById ( 'settings' ) ;
3838const settingsCloseBtn = document . getElementById ( 'settingsCloseBtn' ) ;
3939const audioSource = document . getElementById ( 'audioSource' ) ;
40+ const audioOutputDiv = document . getElementById ( 'audioOutputDiv' ) ;
41+ const audioOutputSource = document . getElementById ( 'audioOutputSource' ) ;
42+ const testSpeakerBtn = document . getElementById ( 'testSpeakerBtn' ) ;
4043const videoSource = document . getElementById ( 'videoSource' ) ;
4144const videoQualitySelect = document . getElementById ( 'videoQualitySelect' ) ;
4245const videoFpsDiv = document . getElementById ( 'videoFpsDiv' ) ;
@@ -635,10 +638,15 @@ async function enumerateDevices() {
635638 const devices = await navigator . mediaDevices . enumerateDevices ( ) ;
636639 const videoDevices = devices . filter ( ( device ) => device . kind === 'videoinput' && device . deviceId !== 'default' ) ;
637640 const audioDevices = devices . filter ( ( device ) => device . kind === 'audioinput' && device . deviceId !== 'default' ) ;
641+ const speakerDevices = devices . filter (
642+ ( device ) => device . kind === 'audiooutput' && device . deviceId !== 'default'
643+ ) ;
638644 removeAllChildren ( audioSource ) ;
639645 removeAllChildren ( videoSource ) ;
646+ removeAllChildren ( audioOutputSource ) ;
640647 LS . DEVICES_COUNT . audio = 0 ;
641648 LS . DEVICES_COUNT . video = 0 ;
649+ LS . DEVICES_COUNT . speaker = 0 ;
642650 for ( const device of audioDevices ) {
643651 await addChild ( audioSource , device ) ;
644652 LS . DEVICES_COUNT . audio ++ ;
@@ -647,6 +655,16 @@ async function enumerateDevices() {
647655 await addChild ( videoSource , device ) ;
648656 LS . DEVICES_COUNT . video ++ ;
649657 }
658+ // Speaker selection is only available where setSinkId is supported
659+ if ( isAudioOutputSupported ( ) ) {
660+ for ( const device of speakerDevices ) {
661+ await addChild ( audioOutputSource , device ) ;
662+ LS . DEVICES_COUNT . speaker ++ ;
663+ }
664+ elemDisplay ( audioOutputDiv , LS . DEVICES_COUNT . speaker > 0 ) ;
665+ } else {
666+ elemDisplay ( audioOutputDiv , false ) ;
667+ }
650668 } catch ( err ) {
651669 playSound ( 'error' ) ;
652670 console . error ( '[Error] enumerate devices audio/video' , err ) ;
@@ -670,6 +688,7 @@ function createVideoElement(id, stream, muted = false) {
670688 video . muted = muted ;
671689 video . volume = muted ? 0 : 1 ;
672690 attachMediaStream ( video , stream ) ;
691+ if ( ! muted ) applySinkId ( video , getSelectedSpeakerId ( ) ) ;
673692 video . play ( ) . catch ( ( ) => { } ) ;
674693 return video ;
675694}
@@ -690,6 +709,7 @@ function createAudioElement(id, stream, muted = false) {
690709 audio . muted = muted ;
691710 audio . volume = muted ? 0 : 1 ;
692711 attachMediaStream ( audio , stream ) ;
712+ if ( ! muted ) applySinkId ( audio , getSelectedSpeakerId ( ) ) ;
693713 audio . play ( ) . catch ( ( ) => { } ) ;
694714 return audio ;
695715}
@@ -1101,6 +1121,14 @@ function handleEvents() {
11011121 saveLocalStorageConfig ( ) ;
11021122 changeMicrophone ( e . target . value ) ;
11031123 } ;
1124+ audioOutputSource . onchange = ( e ) => {
1125+ setLocalStorageSpeakerConfig ( e . target ) ;
1126+ saveLocalStorageConfig ( ) ;
1127+ changeSpeaker ( e . target . value ) ;
1128+ } ;
1129+ testSpeakerBtn . onclick = ( ) => {
1130+ playTestSound ( ) ;
1131+ } ;
11041132 videoSource . onchange = ( e ) => {
11051133 resetVideoConstraints ( ) ;
11061134 setLocalStorageCameraConfig ( e . target ) ;
@@ -1421,6 +1449,44 @@ function getAudioConstraints(deviceId = null) {
14211449 return audioConstraints ;
14221450}
14231451
1452+ function isAudioOutputSupported ( ) {
1453+ return typeof HTMLMediaElement !== 'undefined' && 'setSinkId' in HTMLMediaElement . prototype ;
1454+ }
1455+
1456+ function getSelectedSpeakerId ( ) {
1457+ return localStorageConfig ?. speaker ?. devices ?. select ?. id || null ;
1458+ }
1459+
1460+ async function applySinkId ( element , sinkId ) {
1461+ if ( ! element || ! isAudioOutputSupported ( ) || typeof element . setSinkId !== 'function' ) return ;
1462+ if ( ! sinkId ) return ;
1463+ try {
1464+ await element . setSinkId ( sinkId ) ;
1465+ } catch ( err ) {
1466+ console . error ( '[Error] applySinkId' , err ) ;
1467+ }
1468+ }
1469+
1470+ function changeSpeaker ( sinkId = false ) {
1471+ if ( ! sinkId || ! isAudioOutputSupported ( ) ) return ;
1472+ const mediaElements = document . querySelectorAll ( 'audio, video' ) ;
1473+ mediaElements . forEach ( ( element ) => applySinkId ( element , sinkId ) ) ;
1474+ console . log ( 'Speaker (audio output) changed to' , sinkId ) ;
1475+ }
1476+
1477+ async function playTestSound ( ) {
1478+ const audioToPlay = new Audio ( '../sounds/join.mp3' ) ;
1479+ audioToPlay . volume = 0.5 ;
1480+ const sinkId = getSelectedSpeakerId ( ) ;
1481+ if ( sinkId ) await applySinkId ( audioToPlay , sinkId ) ;
1482+ try {
1483+ await audioToPlay . play ( ) ;
1484+ } catch ( err ) {
1485+ console . error ( '[Error] playTestSound' , err ) ;
1486+ popupMessage ( 'toast' , 'Test speaker' , 'Unable to play test sound' , 'top' ) ;
1487+ }
1488+ }
1489+
14241490function getVideoConstraints ( deviceId = false ) {
14251491 let videoConstraints = true ;
14261492
@@ -1513,9 +1579,16 @@ function setLocalStorageAudioConfig(target) {
15131579 localStorageConfig . audio . devices . select . id = target . value ;
15141580}
15151581
1582+ function setLocalStorageSpeakerConfig ( target ) {
1583+ localStorageConfig . speaker . devices . select . index = target . selectedIndex ;
1584+ localStorageConfig . speaker . devices . select . label = target . options [ target . selectedIndex ] . text ;
1585+ localStorageConfig . speaker . devices . select . id = target . value ;
1586+ }
1587+
15161588function saveLocalStorageConfig ( ) {
15171589 localStorageConfig . video . devices . count = LS . DEVICES_COUNT . video ;
15181590 localStorageConfig . audio . devices . count = LS . DEVICES_COUNT . audio ;
1591+ localStorageConfig . speaker . devices . count = LS . DEVICES_COUNT . speaker ;
15191592 LS . setConfig ( localStorageConfig ) ;
15201593}
15211594
@@ -1524,6 +1597,9 @@ function loadLocalStorageConfig() {
15241597 videoQualitySelect . selectedIndex = localStorageConfig . video . settings . quality_index ;
15251598 videoFpsSelect . selectedIndex = localStorageConfig . video . settings . fps_index ;
15261599 audioSource . selectedIndex = localStorageConfig . audio . devices . select . index ;
1600+ if ( isAudioOutputSupported ( ) && audioOutputSource . options . length > 0 ) {
1601+ audioOutputSource . selectedIndex = localStorageConfig ?. speaker ?. devices ?. select ?. index ?? 0 ;
1602+ }
15271603 switchNoiseSuppression . checked = localStorageConfig ?. audio ?. settings ?. noise_suppression ?? false ;
15281604 switchMaxVideoQuality . checked = localStorageConfig . video . settings . best_quality ;
15291605 switchKeepAspectRatio . checked = localStorageConfig . video . settings . aspect_ratio ;
0 commit comments