// --- Main Application Logic --- const statusDiv = document.getElementById("status"); const authSection = document.getElementById("auth-section"); const appSection = document.getElementById("app-section"); const sessionEndSection = document.getElementById("session-end-section"); const restartBtn = document.getElementById("restartBtn"); const micBtn = document.getElementById("micBtn"); const cameraBtn = document.getElementById("cameraBtn"); const screenBtn = document.getElementById("screenBtn"); const disconnectBtn = document.getElementById("disconnectBtn"); const textInput = document.getElementById("textInput"); const sendBtn = document.getElementById("sendBtn"); const videoPreview = document.getElementById("video-preview"); const videoPlaceholder = document.getElementById("video-placeholder"); const connectBtn = document.getElementById("connectBtn"); const chatLog = document.getElementById("chat-log"); let currentGeminiMessageDiv = null; let currentUserMessageDiv = null; const mediaHandler = new MediaHandler(); const geminiClient = new GeminiClient({ onOpen: () => { statusDiv.textContent = "Connected"; statusDiv.className = "status connected"; authSection.classList.add("hidden"); appSection.classList.remove("hidden"); // Send hidden instruction geminiClient.sendText( `System: Introduce yourself as a demo of the Gemini Live API. Suggest playing with features like the native audio for accents, multilingual support, proactive audio by asking you not to speak until I say something specific, or the affective audio capabilities by changing the emotion in your voice to match the tone of the conversation. Keep the intro concise and friendly.` ); }, onMessage: (event) => { if (typeof event.data === "string") { try { const msg = JSON.parse(event.data); handleJsonMessage(msg); } catch (e) { console.error("Parse error:", e); } } else { mediaHandler.playAudio(event.data); } }, onClose: (e) => { console.log("WS Closed:", e); statusDiv.textContent = "Disconnected"; statusDiv.className = "status disconnected"; showSessionEnd(); }, onError: (e) => { console.error("WS Error:", e); statusDiv.textContent = "Connection Error"; statusDiv.className = "status error"; }, }); function handleJsonMessage(msg) { if (msg.type === "interrupted") { mediaHandler.stopAudioPlayback(); currentGeminiMessageDiv = null; currentUserMessageDiv = null; } else if (msg.type === "turn_complete") { currentGeminiMessageDiv = null; currentUserMessageDiv = null; } else if (msg.type === "user") { if (currentUserMessageDiv) { currentUserMessageDiv.textContent += msg.text; chatLog.scrollTop = chatLog.scrollHeight; } else { currentUserMessageDiv = appendMessage("user", msg.text); } } else if (msg.type === "gemini") { if (currentGeminiMessageDiv) { currentGeminiMessageDiv.textContent += msg.text; chatLog.scrollTop = chatLog.scrollHeight; } else { currentGeminiMessageDiv = appendMessage("gemini", msg.text); } } } function appendMessage(type, text) { const msgDiv = document.createElement("div"); msgDiv.className = `message ${type}`; msgDiv.textContent = text; chatLog.appendChild(msgDiv); chatLog.scrollTop = chatLog.scrollHeight; return msgDiv; } // Connect Button Handler connectBtn.onclick = async () => { statusDiv.textContent = "Connecting..."; connectBtn.disabled = true; try { // Initialize audio context on user gesture await mediaHandler.initializeAudio(); geminiClient.connect(); } catch (error) { console.error("Connection error:", error); statusDiv.textContent = "Connection Failed: " + error.message; statusDiv.className = "status error"; connectBtn.disabled = false; } }; // UI Controls disconnectBtn.onclick = () => { geminiClient.disconnect(); }; micBtn.onclick = async () => { if (mediaHandler.isRecording) { mediaHandler.stopAudio(); micBtn.textContent = "Start Mic"; } else { try { await mediaHandler.startAudio((data) => { if (geminiClient.isConnected()) { geminiClient.send(data); } }); micBtn.textContent = "Stop Mic"; } catch (e) { alert("Could not start audio capture"); } } }; cameraBtn.onclick = async () => { if (cameraBtn.textContent === "Stop Camera") { mediaHandler.stopVideo(videoPreview); cameraBtn.textContent = "Start Camera"; screenBtn.textContent = "Share Screen"; videoPlaceholder.classList.remove("hidden"); } else { // If another stream is active (e.g. Screen), stop it first if (mediaHandler.videoStream) { mediaHandler.stopVideo(videoPreview); screenBtn.textContent = "Share Screen"; } try { await mediaHandler.startVideo(videoPreview, (base64Data) => { if (geminiClient.isConnected()) { geminiClient.sendImage(base64Data); } }); cameraBtn.textContent = "Stop Camera"; screenBtn.textContent = "Share Screen"; videoPlaceholder.classList.add("hidden"); } catch (e) { alert("Could not access camera"); } } }; screenBtn.onclick = async () => { if (screenBtn.textContent === "Stop Sharing") { mediaHandler.stopVideo(videoPreview); screenBtn.textContent = "Share Screen"; cameraBtn.textContent = "Start Camera"; videoPlaceholder.classList.remove("hidden"); } else { // If another stream is active (e.g. Camera), stop it first if (mediaHandler.videoStream) { mediaHandler.stopVideo(videoPreview); cameraBtn.textContent = "Start Camera"; } try { await mediaHandler.startScreen( videoPreview, (base64Data) => { if (geminiClient.isConnected()) { geminiClient.sendImage(base64Data); } }, () => { // onEnded callback (e.g. user stopped sharing from browser) screenBtn.textContent = "Share Screen"; videoPlaceholder.classList.remove("hidden"); } ); screenBtn.textContent = "Stop Sharing"; cameraBtn.textContent = "Start Camera"; videoPlaceholder.classList.add("hidden"); } catch (e) { alert("Could not share screen"); } } }; sendBtn.onclick = sendText; textInput.onkeypress = (e) => { if (e.key === "Enter") sendText(); }; function sendText() { const text = textInput.value; if (text && geminiClient.isConnected()) { geminiClient.sendText(text); appendMessage("user", text); textInput.value = ""; } } function resetUI() { authSection.classList.remove("hidden"); appSection.classList.add("hidden"); sessionEndSection.classList.add("hidden"); mediaHandler.stopAudio(); mediaHandler.stopVideo(videoPreview); videoPlaceholder.classList.remove("hidden"); micBtn.textContent = "Start Mic"; cameraBtn.textContent = "Start Camera"; screenBtn.textContent = "Share Screen"; chatLog.innerHTML = ""; connectBtn.disabled = false; } function showSessionEnd() { appSection.classList.add("hidden"); sessionEndSection.classList.remove("hidden"); mediaHandler.stopAudio(); mediaHandler.stopVideo(videoPreview); } restartBtn.onclick = () => { resetUI(); };