243 lines
7.2 KiB
JavaScript
243 lines
7.2 KiB
JavaScript
// --- 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();
|
|
};
|