<!DOCTYPE html>
<html>
<head>
<title>Decart Avatar Live - WebRTC</title>
</head>
<body>
<video id="avatarVideo" autoplay playsinline width="640"></video>
<br>
<input type="file" id="imageInput" accept="image/jpeg,image/png,image/webp">
<input type="file" id="audioInput" accept="audio/*">
<button id="connectBtn">Connect</button>
<input id="promptInput" placeholder="Enter avatar behavior...">
<button id="promptBtn">Set Prompt</button>
<script>
let ws;
let peerConnection;
// Convert file to base64
async function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// Connect with avatar image
async function connect() {
const imageFile = document.getElementById('imageInput').files[0];
if (!imageFile) {
alert('Please select an avatar image first');
return;
}
// Connect to WebSocket
ws = new WebSocket('wss://api3.decart.ai/v1/live_avatar/stream?api_key=YOUR_API_KEY');
ws.onopen = async () => {
// Send avatar image first
const imageBase64 = await fileToBase64(imageFile);
ws.send(JSON.stringify({
type: 'set_image',
image_data: imageBase64
}));
};
ws.onmessage = async (event) => {
const message = JSON.parse(event.data);
if (message.type === 'set_image_ack') {
// Image accepted, now set up WebRTC
await setupWebRTC();
} else if (message.type === 'answer' && peerConnection) {
await peerConnection.setRemoteDescription({
type: 'answer',
sdp: message.sdp
});
}
};
}
async function setupWebRTC() {
peerConnection = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
// Send ICE candidates
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
ws.send(JSON.stringify({
type: 'ice-candidate',
candidate: event.candidate
}));
}
};
// Receive animated video stream
peerConnection.ontrack = (event) => {
document.getElementById('avatarVideo').srcObject = event.streams[0];
};
// Add receive-only video transceiver
peerConnection.addTransceiver('video', { direction: 'recvonly' });
// Create silent audio stream for connection
const audioContext = new AudioContext({ sampleRate: 16000 });
const oscillator = audioContext.createOscillator();
const gain = audioContext.createGain();
const destination = audioContext.createMediaStreamDestination();
gain.gain.value = 0;
oscillator.connect(gain);
gain.connect(destination);
oscillator.start();
destination.stream.getTracks().forEach(track => {
peerConnection.addTrack(track, destination.stream);
});
// Create and send offer
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
ws.send(JSON.stringify({
type: 'offer',
sdp: offer.sdp
}));
}
// Send audio to animate avatar
async function sendAudio() {
const audioFile = document.getElementById('audioInput').files[0];
if (!audioFile || !ws) return;
const audioBase64 = await fileToBase64(audioFile);
ws.send(JSON.stringify({
type: 'audio',
audio_data: audioBase64
}));
}
// Send behavior prompt
function sendPrompt() {
const prompt = document.getElementById('promptInput').value.trim();
if (prompt && ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'prompt',
prompt: prompt
}));
}
}
// Event listeners
document.getElementById('connectBtn').onclick = connect;
document.getElementById('audioInput').onchange = sendAudio;
document.getElementById('promptBtn').onclick = sendPrompt;
</script>
</body>
</html>