mood_play / index.html
PVK-Varma's picture
updated everything
08cbd2c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Emotion Based Music Player</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/face_mesh.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js"></script>
<script src="https://sdk.scdn.co/spotify-player.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
}
.loader {
border-top-color: #3498db;
-webkit-animation: spin 1s linear infinite;
animation: spin 1s linear infinite;
}
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-900 text-white flex flex-col items-center justify-center min-h-screen p-4">
<div class="w-full max-w-4xl mx-auto bg-gray-800 rounded-2xl shadow-2xl p-6 md:p-8">
<div class="text-center mb-6">
<h1 class="text-3xl md:text-4xl font-bold text-white">Emotion-Powered Music</h1>
<p class="text-gray-400 mt-2">Let's find the perfect song for your mood.</p>
</div>
<!-- Spotify Login and Control Section -->
<div id="spotify-login-section" class="text-center mb-6">
<p class="mb-4 text-gray-300">Please connect your Spotify account to get started.</p>
<button id="spotify-login-btn" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg transition duration-300">
Login with Spotify
</button>
</div>
<div id="main-content" class="hidden">
<!-- Video and Emotion Display -->
<div class="relative w-full aspect-video bg-black rounded-lg overflow-hidden mb-6 shadow-lg">
<video id="webcam" class="w-full h-full object-cover" autoplay playsinline></video>
<canvas id="overlay" class="absolute top-0 left-0 w-full h-full"></canvas>
<div id="loading" class="absolute inset-0 bg-black bg-opacity-75 flex flex-col items-center justify-center">
<div class="loader ease-linear rounded-full border-8 border-t-8 border-gray-200 h-24 w-24 mb-4"></div>
<p class="text-lg text-white">Initializing Camera & AI Model...</p>
</div>
<div id="emotion-display" class="absolute bottom-4 left-4 bg-black bg-opacity-60 text-white text-xl font-semibold px-4 py-2 rounded-lg hidden">
Emotion: <span id="emotion-text">...</span>
</div>
</div>
<!-- Controls -->
<div class="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-4 mb-6">
<button id="start-btn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition duration-300 w-full sm:w-auto">Start Emotion Detection</button>
<button id="stop-btn" class="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg transition duration-300 w-full sm:w-auto" disabled>Stop Detection</button>
</div>
<!-- Spotify Player -->
<div id="spotify-player" class="bg-gray-700 p-4 rounded-lg shadow-md hidden">
<h2 class="text-xl font-semibold mb-2 text-center">Now Playing</h2>
<div id="spotify-widget" class="text-center">
<p>Music will appear here once an emotion is detected.</p>
</div>
</div>
</div>
</div>
<!-- Modal for errors -->
<div id="error-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white text-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
<h2 class="text-xl font-bold mb-4">Error</h2>
<p id="error-message"></p>
<button id="close-modal-btn" class="mt-4 bg-red-500 text-white py-2 px-4 rounded-lg">Close</button>
</div>
</div>
<script>
const videoElement = document.getElementById('webcam');
const canvasElement = document.getElementById('overlay');
const canvasCtx = canvasElement.getContext('2d');
const loadingDiv = document.getElementById('loading');
const emotionText = document.getElementById('emotion-text');
const emotionDisplay = document.getElementById('emotion-display');
const startBtn = document.getElementById('start-btn');
const stopBtn = document.getElementById('stop-btn');
const spotifyLoginBtn = document.getElementById('spotify-login-btn');
const spotifyLoginSection = document.getElementById('spotify-login-section');
const mainContent = document.getElementById('main-content');
const spotifyPlayerDiv = document.getElementById('spotify-player');
const spotifyWidget = document.getElementById('spotify-widget');
const errorModal = document.getElementById('error-modal');
const errorMessage = document.getElementById('error-message');
const closeModalBtn = document.getElementById('close-modal-btn');
let faceMesh;
let ortSession;
let scalerMean;
let scalerScale;
let spotifyAccessToken = '';
let spotifyPlayer;
let deviceId;
let isDetecting = false;
const emotions = ['happy', 'sad', 'neutral', 'angry', 'disgust', 'surprise'];
const SPOTIFY_CLIENT_ID = 'a20b1a54a9314c38a3973d50c9324ca0'; // IMPORTANT: Replace with your Spotify Client ID
const REDIRECT_URI = window.location.origin + window.location.pathname;
const HUGGINGFACE_REPO_URL = "https://huggingface.co/PVK-VARMA/mood_play/resolve/main/"; // IMPORTANT: Replace with your Hugging Face repo URL
// --- Error Handling ---
function showError(message) {
errorMessage.textContent = message;
errorModal.classList.remove('hidden');
}
closeModalBtn.addEventListener('click', () => errorModal.classList.add('hidden'));
// --- Spotify Authentication & Setup ---
spotifyLoginBtn.addEventListener('click', () => {
const scopes = 'streaming user-read-email user-read-private user-modify-playback-state';
const authUrl = `https://accounts.spotify.com/authorize?response_type=token&client_id=${SPOTIFY_CLIENT_ID}&scope=${encodeURIComponent(scopes)}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;
window.location = authUrl;
});
// This callback is required by the Spotify SDK. It must be in the global scope.
window.onSpotifyWebPlaybackSDKReady = () => {
if (spotifyAccessToken) {
initializeSpotifyPlayer();
}
};
window.addEventListener('load', () => {
const hash = window.location.hash;
if (hash) {
const params = new URLSearchParams(hash.substring(1));
const token = params.get('access_token');
if (token) {
spotifyAccessToken = token;
window.location.hash = ''; // Clean the URL
spotifyLoginSection.classList.add('hidden');
mainContent.classList.remove('hidden');
// If SDK is already loaded, initialize player. Otherwise, the callback above will.
if (window.Spotify) {
initializeSpotifyPlayer();
}
}
}
});
function initializeSpotifyPlayer() {
if (spotifyPlayer) return; // Prevent re-initialization
spotifyPlayer = new Spotify.Player({
name: 'Emotion Music Player',
getOAuthToken: cb => { cb(spotifyAccessToken); },
volume: 0.5
});
spotifyPlayer.addListener('ready', ({ device_id }) => {
console.log('Ready with Device ID', device_id);
deviceId = device_id;
});
spotifyPlayer.addListener('not_ready', ({ device_id }) => console.log('Device ID has gone offline', device_id));
spotifyPlayer.addListener('initialization_error', ({ message }) => showError(`Spotify Player Init Error: ${message}`));
spotifyPlayer.addListener('authentication_error', ({ message }) => showError(`Spotify Auth Error: ${message}`));
spotifyPlayer.addListener('account_error', ({ message }) => showError(`Spotify Account Error: ${message}`));
spotifyPlayer.connect().then(success => {
if (success) console.log('The Spotify Player has connected successfully!');
});
}
async function playSongForEmotion(emotion) {
if (!deviceId) {
showError("Spotify player is not ready.");
return;
}
let searchQuery;
switch (emotion) {
case 'happy': searchQuery = 'genre:pop happy'; break;
case 'sad': searchQuery = 'genre:acoustic sad'; break;
case 'angry': searchQuery = 'genre:rock angry'; break;
case 'neutral': searchQuery = 'genre:ambient focus'; break;
case 'surprise': searchQuery = 'genre:electronic energetic'; break;
case 'disgust': searchQuery = 'genre:metal industrial'; break;
default: searchQuery = 'genre:chill';
}
try {
const response = await fetch(`https://api.spotify.com/v1/search?q=${encodeURIComponent(searchQuery)}&type=track&limit=1`, {
headers: { 'Authorization': `Bearer ${spotifyAccessToken}` }
});
if (!response.ok) throw new Error(`Spotify API error: ${response.statusText}`);
const data = await response.json();
const track = data.tracks.items[0];
if (track) {
await fetch(`https://api.spotify.com/v1/me/player/play?device_id=${deviceId}`, {
method: 'PUT',
body: JSON.stringify({ uris: [track.uri] }),
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${spotifyAccessToken}`
},
});
spotifyWidget.innerHTML = `
<img src="${track.album.images[0].url}" alt="Album Art" class="w-24 h-24 mx-auto rounded-md mb-2">
<p class="font-bold">${track.name}</p>
<p class="text-gray-400">${track.artists.map(a => a.name).join(', ')}</p>
`;
spotifyPlayerDiv.classList.remove('hidden');
} else {
spotifyWidget.innerHTML = `<p>Could not find a song for "${emotion}".</p>`;
}
} catch (error) {
console.error('Error playing song:', error);
showError(`Failed to play song. Please check console for details.`);
}
}
// --- Model and Feature Extraction ---
async function loadModelAndScaler() {
try {
const modelUrl = `${HUGGINGFACE_REPO_URL}rf_emotion.onnx`;
ortSession = await ort.InferenceSession.create(modelUrl);
const scalerUrl = `${HUGGINGFACE_REPO_URL}scaler.json`;
const response = await fetch(scalerUrl);
const scalerData = await response.json();
scalerMean = scalerData.mean_;
scalerScale = scalerData.scale_;
console.log("Model and scaler loaded successfully.");
} catch (error) {
console.error("Failed to load model or scaler:", error);
showError("Could not load the AI model from Hugging Face. Please check the repository URL and file names.");
throw error; // Propagate error to stop initialization
}
}
function buildFeatures(landmarks, imageWidth, imageHeight) {
const features = [];
for (const landmark of landmarks) {
features.push(landmark.x * imageWidth, landmark.y * imageHeight);
}
return features;
}
function scaleFeatures(features) {
if (!scalerMean || !scalerScale) return features;
return features.map((val, i) => (val - scalerMean[i]) / scalerScale[i]);
}
// --- Main Application Logic ---
async function onResults(results) {
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
if (results.multiFaceLandmarks && results.multiFaceLandmarks.length > 0 && ortSession) {
const landmarks = results.multiFaceLandmarks[0];
const w = videoElement.videoWidth;
const h = videoElement.videoHeight;
if (w === 0 || h === 0) return;
const features = buildFeatures(landmarks, w, h);
const scaledFeatures = scaleFeatures(features);
try {
const tensor = new ort.Tensor('float32', scaledFeatures, [1, scaledFeatures.length]);
const feeds = { 'float_input': tensor };
const modelResults = await ortSession.run(feeds);
const prediction = modelResults.output_label.data[0];
const predictedEmotion = emotions[prediction];
emotionText.textContent = predictedEmotion;
if (predictedEmotion !== window.lastPlayedEmotion) {
playSongForEmotion(predictedEmotion);
window.lastPlayedEmotion = predictedEmotion;
}
} catch(e) {
console.error("Error during model inference:", e);
}
}
}
async function initializeDetection() {
await loadModelAndScaler();
faceMesh = new FaceMesh({locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`});
faceMesh.setOptions({ maxNumFaces: 1, refineLandmarks: true, minDetectionConfidence: 0.5, minTrackingConfidence: 0.5 });
faceMesh.onResults(onResults);
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 1280, height: 720 } });
videoElement.srcObject = stream;
return new Promise((resolve) => {
videoElement.onloadedmetadata = () => {
videoElement.play();
canvasElement.width = videoElement.videoWidth;
canvasElement.height = videoElement.videoHeight;
loadingDiv.classList.add('hidden');
emotionDisplay.classList.remove('hidden');
resolve();
};
});
}
async function detectionLoop() {
if (!isDetecting) return;
await faceMesh.send({image: videoElement});
requestAnimationFrame(detectionLoop);
}
// --- Event Listeners ---
startBtn.addEventListener('click', async () => {
if (isDetecting) return;
startBtn.disabled = true;
startBtn.textContent = 'Initializing...';
try {
await initializeDetection();
isDetecting = true;
detectionLoop();
stopBtn.disabled = false;
} catch (error) {
showError("Failed to initialize. Check permissions and model URL.");
startBtn.disabled = false;
} finally {
startBtn.textContent = 'Start Emotion Detection';
}
});
stopBtn.addEventListener('click', () => {
isDetecting = false;
const stream = videoElement.srcObject;
if (stream) {
stream.getTracks().forEach(track => track.stop());
videoElement.srcObject = null;
}
startBtn.disabled = false;
stopBtn.disabled = true;
emotionText.textContent = '...';
if (spotifyPlayer) spotifyPlayer.pause();
});
</script>
</body>
</html>