Spaces:
Running
Running
retroo support one directional audio
Browse files- index.html +31 -23
index.html
CHANGED
|
@@ -638,6 +638,7 @@
|
|
| 638 |
// Microphone state (for speaking through the robot)
|
| 639 |
let localMicStream = null;
|
| 640 |
let isMicMuted = true; // Default mic off
|
|
|
|
| 641 |
|
| 642 |
// Export functions
|
| 643 |
window.loginToHuggingFace = loginToHuggingFace;
|
|
@@ -835,28 +836,24 @@
|
|
| 835 |
isMuted = true;
|
| 836 |
updateMuteButton();
|
| 837 |
|
| 838 |
-
// Capture microphone for
|
|
|
|
|
|
|
| 839 |
try {
|
| 840 |
localMicStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| 841 |
localMicStream.getAudioTracks().forEach(t => t.enabled = false); // Start muted
|
| 842 |
isMicMuted = true;
|
| 843 |
-
updateMicButton();
|
| 844 |
} catch (e) {
|
| 845 |
console.warn('Microphone not available, speak-through-robot disabled:', e);
|
| 846 |
localMicStream = null;
|
| 847 |
}
|
| 848 |
|
|
|
|
|
|
|
| 849 |
peerConnection = new RTCPeerConnection({
|
| 850 |
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
| 851 |
});
|
| 852 |
|
| 853 |
-
// Add mic track to PeerConnection (before SDP exchange)
|
| 854 |
-
if (localMicStream) {
|
| 855 |
-
for (const track of localMicStream.getAudioTracks()) {
|
| 856 |
-
peerConnection.addTrack(track, localMicStream);
|
| 857 |
-
}
|
| 858 |
-
}
|
| 859 |
-
|
| 860 |
peerConnection.ontrack = (e) => {
|
| 861 |
if (e.track.kind === 'video') {
|
| 862 |
const video = document.getElementById('remoteVideo');
|
|
@@ -907,16 +904,26 @@
|
|
| 907 |
if (!peerConnection) return;
|
| 908 |
try {
|
| 909 |
if (msg.sdp) {
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
if (sdp.type === 'offer' && localMicStream) {
|
| 913 |
-
sdp = { ...sdp, sdp: makeAudioBidirectional(sdp.sdp) };
|
| 914 |
-
}
|
| 915 |
-
await peerConnection.setRemoteDescription(new RTCSessionDescription(sdp));
|
| 916 |
if (sdp.type === 'offer') {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 917 |
const answer = await peerConnection.createAnswer();
|
| 918 |
await peerConnection.setLocalDescription(answer);
|
| 919 |
await sendToServer({ type: 'peer', sessionId: currentSessionId, sdp: { type: 'answer', sdp: answer.sdp } });
|
|
|
|
|
|
|
| 920 |
}
|
| 921 |
}
|
| 922 |
if (msg.ice) {
|
|
@@ -963,6 +970,7 @@
|
|
| 963 |
localMicStream = null;
|
| 964 |
}
|
| 965 |
isMicMuted = true;
|
|
|
|
| 966 |
|
| 967 |
if (peerConnection) peerConnection.close();
|
| 968 |
if (dataChannel) dataChannel.close();
|
|
@@ -981,7 +989,7 @@
|
|
| 981 |
function enableControls(enabled) {
|
| 982 |
document.getElementById('btnPlaySound').disabled = !enabled;
|
| 983 |
document.getElementById('muteBtn').disabled = !enabled;
|
| 984 |
-
document.getElementById('micBtn').disabled = !enabled || !localMicStream;
|
| 985 |
}
|
| 986 |
|
| 987 |
function toggleMute() {
|
|
@@ -1035,17 +1043,17 @@
|
|
| 1035 |
}
|
| 1036 |
}
|
| 1037 |
|
| 1038 |
-
function
|
| 1039 |
-
//
|
| 1040 |
-
//
|
| 1041 |
const lines = sdp.split('\r\n');
|
| 1042 |
let inAudioSection = false;
|
| 1043 |
-
|
| 1044 |
if (line.startsWith('m=audio')) inAudioSection = true;
|
| 1045 |
else if (line.startsWith('m=')) inAudioSection = false;
|
| 1046 |
-
if (inAudioSection && line === 'a=
|
| 1047 |
-
|
| 1048 |
-
|
| 1049 |
}
|
| 1050 |
|
| 1051 |
// ===================== Robot State =====================
|
|
|
|
| 638 |
// Microphone state (for speaking through the robot)
|
| 639 |
let localMicStream = null;
|
| 640 |
let isMicMuted = true; // Default mic off
|
| 641 |
+
let robotSupportsMic = false; // Whether robot's SDP offers sendrecv audio
|
| 642 |
|
| 643 |
// Export functions
|
| 644 |
window.loginToHuggingFace = loginToHuggingFace;
|
|
|
|
| 836 |
isMuted = true;
|
| 837 |
updateMuteButton();
|
| 838 |
|
| 839 |
+
// Capture microphone eagerly (for permission prompt), but do NOT add
|
| 840 |
+
// the track to the PeerConnection yet. We only add it after seeing
|
| 841 |
+
// the robot's SDP offer to confirm it supports bidirectional audio.
|
| 842 |
try {
|
| 843 |
localMicStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| 844 |
localMicStream.getAudioTracks().forEach(t => t.enabled = false); // Start muted
|
| 845 |
isMicMuted = true;
|
|
|
|
| 846 |
} catch (e) {
|
| 847 |
console.warn('Microphone not available, speak-through-robot disabled:', e);
|
| 848 |
localMicStream = null;
|
| 849 |
}
|
| 850 |
|
| 851 |
+
robotSupportsMic = false;
|
| 852 |
+
|
| 853 |
peerConnection = new RTCPeerConnection({
|
| 854 |
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
| 855 |
});
|
| 856 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 857 |
peerConnection.ontrack = (e) => {
|
| 858 |
if (e.track.kind === 'video') {
|
| 859 |
const video = document.getElementById('remoteVideo');
|
|
|
|
| 904 |
if (!peerConnection) return;
|
| 905 |
try {
|
| 906 |
if (msg.sdp) {
|
| 907 |
+
const sdp = msg.sdp;
|
| 908 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 909 |
if (sdp.type === 'offer') {
|
| 910 |
+
// Check if robot supports bidirectional audio (sendrecv)
|
| 911 |
+
robotSupportsMic = sdpHasAudioSendRecv(sdp.sdp);
|
| 912 |
+
|
| 913 |
+
// Add mic track BEFORE setRemoteDescription so the
|
| 914 |
+
// answer naturally includes sendrecv for audio.
|
| 915 |
+
if (robotSupportsMic && localMicStream) {
|
| 916 |
+
for (const track of localMicStream.getAudioTracks()) {
|
| 917 |
+
peerConnection.addTrack(track, localMicStream);
|
| 918 |
+
}
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
await peerConnection.setRemoteDescription(new RTCSessionDescription(sdp));
|
| 922 |
const answer = await peerConnection.createAnswer();
|
| 923 |
await peerConnection.setLocalDescription(answer);
|
| 924 |
await sendToServer({ type: 'peer', sessionId: currentSessionId, sdp: { type: 'answer', sdp: answer.sdp } });
|
| 925 |
+
} else {
|
| 926 |
+
await peerConnection.setRemoteDescription(new RTCSessionDescription(sdp));
|
| 927 |
}
|
| 928 |
}
|
| 929 |
if (msg.ice) {
|
|
|
|
| 970 |
localMicStream = null;
|
| 971 |
}
|
| 972 |
isMicMuted = true;
|
| 973 |
+
robotSupportsMic = false;
|
| 974 |
|
| 975 |
if (peerConnection) peerConnection.close();
|
| 976 |
if (dataChannel) dataChannel.close();
|
|
|
|
| 989 |
function enableControls(enabled) {
|
| 990 |
document.getElementById('btnPlaySound').disabled = !enabled;
|
| 991 |
document.getElementById('muteBtn').disabled = !enabled;
|
| 992 |
+
document.getElementById('micBtn').disabled = !enabled || !localMicStream || !robotSupportsMic;
|
| 993 |
}
|
| 994 |
|
| 995 |
function toggleMute() {
|
|
|
|
| 1043 |
}
|
| 1044 |
}
|
| 1045 |
|
| 1046 |
+
function sdpHasAudioSendRecv(sdp) {
|
| 1047 |
+
// Check whether the robot's SDP offer has sendrecv for audio,
|
| 1048 |
+
// meaning it supports bidirectional audio (updated daemon).
|
| 1049 |
const lines = sdp.split('\r\n');
|
| 1050 |
let inAudioSection = false;
|
| 1051 |
+
for (const line of lines) {
|
| 1052 |
if (line.startsWith('m=audio')) inAudioSection = true;
|
| 1053 |
else if (line.startsWith('m=')) inAudioSection = false;
|
| 1054 |
+
if (inAudioSection && line === 'a=sendrecv') return true;
|
| 1055 |
+
}
|
| 1056 |
+
return false;
|
| 1057 |
}
|
| 1058 |
|
| 1059 |
// ===================== Robot State =====================
|