import { englishPage } from "./language"
export async function handleRequest(request) {
let {pathname, host} = new URL(request.url.toLowerCase())
if (pathname.endsWith('/')) pathname = pathname.substring(0, pathname.length - 1)
console.log('pathname', pathname, 'host', host)
if (pathname === '/voice-agents.html') {
console.log('we find the uri')
let page = englishPage(request) ? HOMEPAGE_EN : HOMEPAGE
page = page.replace('$[{host}]', host)
return new Response(page, {headers: {"Content-Type": "text/html; charset=utf-8"}})
}
console.log('we could not find uri')
return new Response('Not Found', {
status: 404,
statusText: 'Not Found',
headers: {
'Content-Type': 'text/plain',
},
});
}
const HOMEPAGE_EN = `
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>streamAI audio</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
#callButton {
padding: 10px 20px;
font-size: 16px;
margin: 20px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
transition: background-color 0.3s;
}
#callButton:hover {
background-color: #45a049;
}
#audioPlayer {
margin-top: 20px;
}
#dataChannelMessages, .url-input{
margin-top: 20px;
padding: 10px;
width: 80%;
max-width: 500px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
text-align: center;
}
</style>
</head>
<body>
<div class="url-input">
<label for="sURL">offer URL:</label>
<input id="sURL" type="offer addr" value="https://voice.edgecloudapp.com/v2/eca-test/voiceagents/test" size="50"/>
</div>
<button id="callButton" onclick="toggleConnection()">start connection</button>
<div id="dataChannelMessages" >
i am listening
</div>
<script>
let peerConnection;
let isCallActive = false;
let lastMode = "listen"
function toggleConnection() {
if (!isCallActive) {
callButton.textContent = 'close connection';
startConnection().catch(error => {
console.error('Connection failed:', error);
callButton.textContent = 'start connection';
isCallActive = false;
});
isCallActive = true;
} else {
stopConnection();
callButton.textContent = 'start connection';
isCallActive = false;
}
}
function stopConnection() {
if (peerConnection) {
peerConnection.close();
console.log('WebRTC connection closed');
}
}
navigator.mediaDevices.enumerateDevices().then(devices => {
for (const device of devices) {
console.log(device.kind + ": " + device.label + " id = " + device.deviceId);
}
});
async function startMedia() {
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({
video: false,
audio: {
deviceId: { exact: 'default' },
sampleRate: 16000,
channelCount: 1
}
});
return mediaStream;
} catch (error) {
console.error('Error accessing media devices.', error);
}
}
// 创建 WebRTC 连接并发送 offer
async function startConnection() {
peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stunserver.stunprotocol.org:3478' }
]
});
const mediaStream = await startMedia();
mediaStream.getTracks().forEach(track => peerConnection.addTrack(track, mediaStream));
const dataChannel = peerConnection.createDataChannel("data");
dataChannel.onopen = () => {
console.log('DataChannel connection success');
};
dataChannel.onmessage = (event) => {
console.log('datachannel recieve');
console.log('receive message:', event.data);
const messageDiv = document.getElementById('dataChannelMessages');
try {
const data = JSON.parse(event.data);
let current_mode = "listen"
if ('mode' in data) {
current_mode = data.mode
} else if ('type' in data){
current_mode = data.type
}
else{
current_mode = "answer"
}
if (current_mode != lastMode){
messageDiv.innerHTML = '';
}
if (data.mode === "2pass-online") {
messageDiv.innerHTML += data.text;
lastMode = data.mode
} else if (data.mode === "2pass-offline") {
messageDiv.innerHTML = data.text;
lastMode = data.mode
} else if (data.type === "conversation.item.input_audio_transcription.delta") {
messageDiv.innerHTML += data.delta;
lastMode = data.type
} else if (data.type === "conversation.item.input_audio_transcription.completed") {
messageDiv.innerHTML = data.transcript;
lastMode = data.type
} else if ((current_mode==="answer") &&(data.choices.length > 0)) {
const content = data.choices[0].delta.content;
if (typeof content !== 'undefined' && content !== '') {
messageDiv.innerHTML += content;
}
lastMode = "answer";
}
} catch (error) {
console.error('Error parsing JSON:', error);
}
};
peerConnection.onicecandidate = event => {
if (event.candidate) {
console.log('Send ICE Candidate to the server:', event.candidate);
}
};
peerConnection.ontrack = event => {
console.log('received audio stream', event.streams[0]);
const remoteAudio = new Audio();
remoteAudio.srcObject = event.streams[0];
const desiredOutputDeviceId = "default";
if (remoteAudio.setSinkId) {
remoteAudio.setSinkId(desiredOutputDeviceId)
.then(() => {
console.log("audio device set success");
return remoteAudio.play();
})
.catch(err => {
console.error("set audio output fail:", err);
return remoteAudio.play();
});
} else {
console.warn("browser not support setSinkId, using default output device");
remoteAudio.play();
}
};
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
const response = await fetch(sURL.value, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer '+'your token'
},
body: JSON.stringify(peerConnection.localDescription)
});
if (!response.ok) {
throw new Error('HTTP error!');
}
const answer = await response.json();
await peerConnection.setRemoteDescription(new RTCSessionDescription(answer.data));
console.log('WebRTC connection established');
}
</script>
</body>
</html>
`