audio-recorder / index.html
NeoPy's picture
Make my project more dynamic, looks like new youtube UI, and more features, add made by https://huggingface.co/terastudio too
53a985d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio Studio Pro | YouTube-Style Recorder</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--yt-red: #ff0000;
--yt-dark: #0f0f0f;
--yt-gray: #272727;
--yt-light-gray: #3f3f3f;
}
.waveform {
background: linear-gradient(90deg, #ff0000 0%, #cc0000 50%, #990000 100%);
height: 100px;
border-radius: 0.75rem;
position: relative;
overflow: hidden;
box-shadow: 0 4px 20px rgba(255, 0, 0, 0.3);
}
.waveform::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg,
rgba(255,255,255,0.15) 0%,
rgba(255,255,255,0.25) 50%,
rgba(255,255,255,0.15) 100%);
animation: wave 1.8s linear infinite;
transform: translateX(-100%);
}
@keyframes wave {
100% {
transform: translateX(100%);
}
}
.recording-animation {
animation: youtube-pulse 1.2s infinite;
}
@keyframes youtube-pulse {
0% {
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.8);
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
box-shadow: 0 0 0 15px rgba(255, 0, 0, 0);
transform: scale(1);
}
}
.format-option:hover {
transform: translateY(-5px);
box-shadow: 0 15px 30px rgba(255, 0, 0, 0.3);
background: var(--yt-gray) !important;
}
.format-option {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid var(--yt-light-gray);
}
.device-option:hover {
background-color: var(--yt-gray) !important;
}
.device-option {
transition: all 0.3s ease;
}
.visualizer {
width: 100%;
height: 100px;
background: var(--yt-dark);
border-radius: 0.75rem;
position: relative;
overflow: hidden;
box-shadow: inset 0 2px 10px rgba(0,0,0,0.5);
}
.bar {
position: absolute;
bottom: 0;
width: 6px;
background: linear-gradient(to top, #ff0000, #cc0000);
border-radius: 3px 3px 0 0;
transition: height 0.08s ease;
}
.youtube-nav {
background: var(--yt-dark);
border-bottom: 1px solid var(--yt-light-gray);
}
.youtube-sidebar {
background: var(--yt-dark);
border-right: 1px solid var(--yt-light-gray);
}
.youtube-card {
background: var(--yt-gray);
border-radius: 1rem;
border: 1px solid var(--yt-light-gray);
box-shadow: 0 4px 25px rgba(0,0,0,0.4);
transition: all 0.3s ease;
}
.youtube-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 35px rgba(255, 0, 0, 0.2);
}
.gradient-text {
background: linear-gradient(135deg, #ff0000, #cc0000);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.live-indicator {
background: var(--yt-red);
color: white;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: bold;
animation: live-pulse 2s infinite;
}
@keyframes live-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.yt-button {
background: var(--yt-gray);
border: 1px solid var(--yt-light-gray);
transition: all 0.3s ease;
}
.yt-button:hover {
background: var(--yt-light-gray);
}
.yt-button-primary {
background: var(--yt-red);
border: 1px solid var(--yt-red);
}
.yt-button-primary:hover {
background: #cc0000;
border-color: #cc0000;
}
.progress-bar {
height: 4px;
background: var(--yt-light-gray);
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: var(--yt-red);
border-radius: 2px;
transition: width 0.1s linear;
}
.recording-item {
background: var(--yt-gray);
border: 1px solid var(--yt-light-gray);
border-radius: 0.75rem;
transition: all 0.3s ease;
}
.recording-item:hover {
background: #2a2a2a;
border-color: #4a4a4a;
}
.sidebar-item {
transition: all 0.2s ease;
}
.sidebar-item:hover {
background: var(--yt-light-gray);
}
</style>
</head>
<body class="bg-[#0f0f0f] min-h-screen text-white">
<!-- YouTube-style Navigation -->
<nav class="youtube-nav sticky top-0 z-50">
<div class="container mx-auto px-4 py-3">
<div class="flex items-center justify-between">
<div class="flex items-center gap-6">
<div class="flex items-center gap-2">
<i class="fas fa-bars text-xl cursor-pointer"></i>
<div class="flex items-center gap-2">
<i class="fab fa-youtube text-2xl text-red-500"></i>
<span class="text-xl font-bold">Audio Studio Pro</span>
</div>
</div>
</div>
<div class="flex-1 max-w-2xl mx-8">
<div class="relative">
<input type="text" placeholder="Search recordings..." class="w-full bg-[#121212] border border-[#303030] rounded-full py-2 px-4 text-sm focus:outline-none focus:border-[#3ea6ff] transition-all">
<i class="fas fa-search absolute right-3 top-2.5 text-gray-400"></i>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex items-center gap-2">
<span class="live-indicator flex items-center gap-1">
<i class="fas fa-circle text-xs"></i>
LIVE
</span>
</div>
</div>
</div>
</div>
</div>
</nav>
<div class="flex">
<!-- YouTube-style Sidebar -->
<div class="youtube-sidebar w-64 min-h-screen hidden md:block">
<div class="p-4 space-y-2">
<div class="sidebar-item p-3 rounded-lg bg-[#272727] cursor-pointer">
<div class="flex items-center gap-3">
<i class="fas fa-home text-red-500"></i>
<span>Home</span>
</div>
<div class="sidebar-item p-3 rounded-lg cursor-pointer">
<div class="flex items-center gap-3">
<i class="fas fa-fire text-red-500"></i>
<span>Trending</span>
</div>
<div class="sidebar-item p-3 rounded-lg cursor-pointer">
<div class="flex items-center gap-3">
<i class="fas fa-microphone text-red-500"></i>
<span>Recordings</span>
</div>
<div class="sidebar-item p-3 rounded-lg cursor-pointer">
<div class="flex items-center gap-3">
<i class="fas fa-music text-red-500"></i>
<span>Music</span>
</div>
<div class="sidebar-item p-3 rounded-lg cursor-pointer">
<div class="flex items-center gap-3">
<i class="fas fa-cog text-red-500"></i>
<span>Settings</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="flex-1">
<div class="container mx-auto px-4 py-6">
<div class="max-w-5xl mx-auto">
<!-- Header Section -->
<div class="flex items-center justify-between mb-8">
<div>
<h1 class="text-3xl font-bold gradient-text mb-2">Audio Studio Pro</h1>
<p class="text-gray-400">Professional recording studio with YouTube-inspired interface</p>
</div>
<div class="flex items-center gap-3">
<div class="flex items-center gap-2 text-sm text-gray-400">
<i class="fas fa-users"></i>
<span>1.2K active</span>
</div>
</div>
<!-- Main Recording Card -->
<div class="youtube-card overflow-hidden mb-8">
<div class="p-6">
<!-- Live Status Header -->
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<div class="w-3 h-3 bg-red-500 rounded-full animate-pulse"></div>
<span class="text-red-500 font-semibold">RECORDING STUDIO</div>
<div class="live-indicator flex items-center gap-2">
<i class="fas fa-circle text-xs"></i>
LIVE INPUT
</div>
</div>
<!-- Device Selection Section -->
<div class="mb-6">
<div class="flex items-center justify-between mb-3">
<h3 class="text-lg font-semibold text-white">Audio Input Devices</h3>
<button id="refreshDevices" class="yt-button px-3 py-1 rounded-lg text-sm">
<i class="fas fa-sync-alt mr-1"></i>
Refresh
</button>
</div>
<div class="flex flex-col sm:flex-row gap-4 mb-4">
<div class="flex-1">
<label for="audioSource" class="block text-sm font-medium text-gray-300 mb-1">Audio Input</label>
<select id="audioSource" class="w-full bg-[#272727] border border-[#3f3f3f] rounded-lg py-2 px-3 text-white focus:outline-none focus:border-[#ff0000] transition-all">
<option value="">Default Device</option>
</select>
</div>
<div class="flex-1">
<label for="audioQuality" class="block text-sm font-medium text-gray-300 mb-1">Quality Preset</label>
<select id="audioQuality" class="w-full bg-[#272727] border border-[#3f3f3f] rounded-lg py-2 px-3 text-white focus:outline-none focus:border-[#ff0000] transition-all">
<option value="high">Studio (24-bit/96kHz)</option>
<option value="medium" selected>Professional (16-bit/48kHz)</option>
<option value="low">Podcast (16-bit/44.1kHz)</option>
</select>
</div>
</div>
<!-- Advanced Settings -->
<div class="mb-4">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-300">Advanced Settings</h4>
<button id="toggleAdvanced" class="yt-button px-3 py-1 rounded-lg text-sm">
<i class="fas fa-sliders-h mr-1"></i>
Show Advanced
</button>
</div>
<div id="advancedSettings" class="hidden grid grid-cols-2 gap-4 mb-4">
<div>
<label for="noiseReduction" class="block text-sm text-gray-400 mb-1">Noise Reduction</label>
<select id="noiseReduction" class="w-full bg-[#272727] border border-[#3f3f3f] rounded-lg py-2 px-3 text-white focus:outline-none focus:border-[#ff0000] transition-all">
<option value="high">High</option>
<option value="medium" selected>Medium</option>
<option value="low">Low</option>
<option value="off">Off</option>
</select>
</div>
<div>
<label for="autoGain" class="block text-sm text-gray-400 mb-1">Auto Gain</label>
<select id="autoGain" class="w-full bg-[#272727] border border-[#3f3f3f] rounded-lg py-2 px-3 text-white focus:outline-none focus:border-[#ff0000] transition-all">
<option value="on" selected>On</option>
<option value="off">Off</option>
</select>
</div>
</div>
<div class="visualizer mb-4" id="visualizer">
<!-- Audio bars will be inserted here by JavaScript -->
</div>
<div class="flex items-center justify-between text-sm">
<div id="deviceStatus" class="text-gray-400">
<i class="fas fa-microphone-slash mr-1"></i> No device selected
</div>
<div class="text-gray-400">
<i class="fas fa-database mr-1"></i>
<span id="fileSize">0 MB</span>
</div>
</div>
</div>
<!-- Waveform Display -->
<div class="waveform mb-6" id="waveform"></div>
<!-- Control Buttons -->
<div class="flex flex-col sm:flex-row justify-center items-center gap-4 mb-6">
<button id="recordBtn" class="yt-button-primary font-bold py-3 px-8 rounded-full flex items-center gap-2 transition-all">
<i class="fas fa-record-vinyl"></i> Start Recording
</button>
<button id="stopBtn" disabled class="yt-button font-bold py-3 px-8 rounded-full flex items-center gap-2 transition-all">
<i class="fas fa-stop"></i> Stop
</button>
<button id="playBtn" disabled class="yt-button font-bold py-3 px-8 rounded-full flex items-center gap-2 transition-all">
<i class="fas fa-play"></i> Preview
</button>
</div>
<!-- Progress and Timer -->
<div class="mb-4">
<div class="progress-bar mb-2">
<div id="progressFill" class="progress-fill" style="width: 0%"></div>
<div class="flex justify-between items-center">
<span id="timer" class="text-gray-400">00:00</span>
<span class="text-gray-400">
<i class="fas fa-volume-up mr-1"></i>
<span id="volumeLevel">50%</span>
</div>
</div>
</div>
</div>
<!-- Download Section -->
<div class="bg-[#1f1f1f] p-6 border-t border-[#3f3f3f]">
<h3 class="text-lg font-semibold text-white mb-4">Export & Download</h3>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-6">
<div class="format-option bg-[#272727] p-4 rounded-lg text-center cursor-pointer" data-format="wav">
<i class="fas fa-file-waveform text-3xl text-red-500 mb-2"></i>
<p class="font-medium">WAV</p>
<p class="text-sm text-gray-400">Lossless Quality</p>
</div>
<div class="format-option bg-[#272727] p-4 rounded-lg text-center cursor-pointer" data-format="mp3">
<i class="fas fa-file-audio text-3xl text-red-400 mb-2"></i>
<p class="font-medium">MP3</p>
<p class="text-sm text-gray-400">Universal Format</p>
</div>
<div class="format-option bg-[#272727] p-4 rounded-lg text-center cursor-pointer" data-format="ogg">
<i class="fas fa-file-audio text-3xl text-red-300 mb-2"></i>
<p class="font-medium">OGG</p>
<p class="text-sm text-gray-400">Web Streaming</p>
</div>
<div class="format-option bg-[#272727] p-4 rounded-lg text-center cursor-pointer" data-format="aac">
<i class="fas fa-file-audio text-3xl text-red-200 mb-2"></i>
<p class="font-medium">AAC</p>
<p class="text-sm text-gray-400">Mobile Devices</p>
</div>
</div>
<div class="flex gap-4">
<button id="downloadBtn" disabled class="flex-1 yt-button-primary font-bold py-3 px-4 rounded-lg flex items-center justify-center gap-2 transition-all">
<i class="fas fa-download"></i> Download Selected Format
</button>
<button id="shareBtn" disabled class="yt-button px-4 py-3 rounded-lg flex items-center gap-2">
<i class="fas fa-share-alt"></i>
</button>
</div>
</div>
</div>
<!-- Recording History Section -->
<div class="youtube-card mt-8">
<div class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-white">Recording Library</h3>
<div class="flex items-center gap-2 text-sm text-gray-400">
<i class="fas fa-history"></i>
<span>Recent Recordings</span>
</div>
</div>
<div id="recordingsList" class="space-y-3">
<div class="text-center text-gray-500 py-8">
<i class="fas fa-microphone-slash text-3xl mb-2"></i>
<p>No recordings in your library</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Audio recording variables
let mediaRecorder;
let audioChunks = [];
let audioBlob;
let audioUrl;
let audioElement;
let timerInterval;
let seconds = 0;
let audioContext;
let analyser;
let microphone;
let dataArray;
let animationId;
let currentStream;
// DOM elements
const recordBtn = document.getElementById('recordBtn');
const stopBtn = document.getElementById('stopBtn');
const playBtn = document.getElementById('playBtn');
const downloadBtn = document.getElementById('downloadBtn');
const shareBtn = document.getElementById('shareBtn');
const timer = document.getElementById('timer');
const recordingsList = document.getElementById('recordingsList');
const formatOptions = document.querySelectorAll('.format-option');
const audioSourceSelect = document.getElementById('audioSource');
const audioQualitySelect = document.getElementById('audioQuality');
const visualizer = document.getElementById('visualizer');
const deviceStatus = document.getElementById('deviceStatus');
const refreshDevicesBtn = document.getElementById('refreshDevices');
const toggleAdvancedBtn = document.getElementById('toggleAdvanced');
const advancedSettings = document.getElementById('advancedSettings');
const progressFill = document.getElementById('progressFill');
const fileSize = document.getElementById('fileSize');
const volumeLevel = document.getElementById('volumeLevel');
const mobileRecordBtn = document.getElementById('mobileRecordBtn');
let selectedFormat = 'wav'; // Default format
let isAdvancedVisible = false;
// Initialize features
function initFeatures() {
// Toggle advanced settings
toggleAdvancedBtn.addEventListener('click', function() {
isAdvancedVisible = !isAdvancedVisible;
if (isAdvancedVisible) {
advancedSettings.classList.remove('hidden');
this.innerHTML = '<i class="fas fa-times mr-1"></i> Hide Advanced';
} else {
advancedSettings.classList.add('hidden');
this.innerHTML = '<i class="fas fa-sliders-h mr-1"></i> Show Advanced';
}
});
// Refresh devices
refreshDevicesBtn.addEventListener('click', function() {
this.classList.add('animate-spin');
getAudioDevices().then(success => {
setTimeout(() => {
this.classList.remove('animate-spin');
// Trigger device selection update
if (audioSourceSelect.value) {
audioSourceSelect.dispatchEvent(new Event('change'));
}, 500);
});
// Mobile record button
if (mobileRecordBtn) {
mobileRecordBtn.addEventListener('click', function() {
recordBtn.click();
});
}
// Volume control simulation
let volume = 50;
setInterval(() => {
if (audioElement && !audioElement.paused) {
volume = Math.min(100, Math.max(0, volume + (Math.random() * 4 - 2));
volumeLevel.textContent = `${Math.round(volume)}%`;
}
}, 1000);
}
// Create audio bars for visualizer
const barCount = 40;
for (let i = 0; i < barCount; i++) {
const bar = document.createElement('div');
bar.className = 'bar';
bar.style.left = `${i * (100 / barCount)}%`;
bar.style.height = '0%';
visualizer.appendChild(bar);
}
const bars = document.querySelectorAll('.bar');
// Format selection
formatOptions.forEach(option => {
option.addEventListener('click', function() {
formatOptions.forEach(opt => opt.classList.remove('ring-2', 'ring-red-500'));
this.classList.add('ring-2', 'ring-red-500');
selectedFormat = this.dataset.format;
});
});
// Set first format as selected by default
formatOptions[0].classList.add('ring-2', 'ring-red-500');
// Initialize features
initFeatures();
let selectedFormat = 'wav'; // Default format
// Create audio bars for visualizer
const barCount = 40;
for (let i = 0; i < barCount; i++) {
const bar = document.createElement('div');
bar.className = 'bar';
bar.style.left = `${i * (100 / barCount)}%`;
bar.style.height = '0%';
visualizer.appendChild(bar);
}
const bars = document.querySelectorAll('.bar');
// Format selection
formatOptions.forEach(option => {
option.addEventListener('click', function() {
formatOptions.forEach(opt => opt.classList.remove('ring-2', 'ring-indigo-500'));
this.classList.add('ring-2', 'ring-indigo-500');
selectedFormat = this.dataset.format;
});
});
// Set first format as selected by default
formatOptions[0].classList.add('ring-2', 'ring-indigo-500');
// Get available audio devices
async function getAudioDevices() {
try {
// First get permission by accessing media
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stopStream(stream); // We don't need this stream, just needed for permission
// Now enumerate devices
const devices = await navigator.mediaDevices.enumerateDevices();
const audioInputs = devices.filter(device => device.kind === 'audioinput');
// Clear existing options
audioSourceSelect.innerHTML = '<option value="">Default Device</option>';
// Add each audio input device as an option
audioInputs.forEach(device => {
const option = document.createElement('option');
option.value = device.deviceId;
option.text = device.label || `Microphone ${audioSourceSelect.length}`;
audioSourceSelect.appendChild(option);
});
return true;
} catch (error) {
console.error('Error getting audio devices:', error);
deviceStatus.innerHTML = '<i class="fas fa-exclamation-triangle mr-1"></i> Could not access microphone devices';
return false;
}
}
// Simulate file size calculation
function updateFileSize(duration) {
const baseSize = duration * 0.1; // MB per minute
fileSize.textContent = `${baseSize.toFixed(1)} MB estimated';
}
// Stop a media stream
function stopStream(stream) {
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
}
// Visualize audio input
function visualizeAudio(stream) {
// Create audio context if it doesn't exist
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
// Create analyser node
analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
// Create microphone source
microphone = audioContext.createMediaStreamSource(stream);
microphone.connect(analyser);
// Create data array for analysis
const bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
// Start animation loop
function animate() {
animationId = requestAnimationFrame(animate);
analyser.getByteFrequencyData(dataArray);
// Update bars
for (let i = 0; i < bars.length; i++) {
// Scale the index to cover the full frequency range
const scaledIndex = Math.floor(i * (bufferLength / bars.length));
const value = dataArray[scaledIndex];
const height = value / 2.55; // Convert 0-255 to 0-100
bars[i].style.height = `${height}%`;
bars[i].style.backgroundColor = `hsl(${200 + (height * 1.5)}, 100%, 50%)`;
}
}
// Start animation
animate();
// Update device status
deviceStatus.innerHTML = `<i class="fas fa-microphone mr-1"></i> ${audioSourceSelect.options[audioSourceSelect.selectedIndex].text}`;
}
// Stop visualization
function stopVisualization() {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
// Reset bars
bars.forEach(bar => {
bar.style.height = '0%';
bar.style.backgroundColor = '#3b82f6';
});
// Update device status
deviceStatus.innerHTML = '<i class="fas fa-microphone-slash mr-1"></i> No active input';
}
// Get audio constraints based on selected quality
function getAudioConstraints() {
const deviceId = audioSourceSelect.value;
const quality = audioQualitySelect.value;
let constraints = {
audio: {
deviceId: deviceId ? { exact: deviceId } : undefined,
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
};
// Adjust constraints based on quality
if (quality === 'high') {
constraints.audio.sampleRate = 96000;
constraints.audio.channelCount = 2;
constraints.audio.sampleSize = 24;
constraints.audio.bitrate = 320000;
} else if (quality === 'medium') {
constraints.audio.sampleRate = 48000;
constraints.audio.channelCount = 1;
constraints.audio.sampleSize = 16;
constraints.audio.bitrate = 192000;
} else { // low
constraints.audio.sampleRate = 44100;
constraints.audio.channelCount = 1;
constraints.audio.sampleSize = 16;
constraints.audio.bitrate = 128000;
}
return constraints;
}
// Enhanced visualization with YouTube colors
function visualizeAudio(stream) {
// Create audio context if it doesn't exist
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
// Create analyser node
analyser = audioContext.createAnalyser();
analyser.fftSize = 512;
// Create microphone source
microphone = audioContext.createMediaStreamSource(stream);
microphone.connect(analyser);
// Create data array for analysis
const bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
// Start animation loop
function animate() {
animationId = requestAnimationFrame(animate);
analyser.getByteFrequencyData(dataArray);
// Update bars with YouTube red gradient
for (let i = 0; i < bars.length; i++) {
// Scale the index to cover the full frequency range
const scaledIndex = Math.floor(i * (bufferLength / bars.length));
const value = dataArray[scaledIndex];
const height = value / 2.55; // Convert 0-255 to 0-100
bars[i].style.height = `${height}%`;
// Dynamic color based on amplitude
const hue = 0; // Red
const saturation = 100;
const lightness = 40 + (height * 0.3);
bars[i].style.background = `linear-gradient(to top, hsl(${hue}, ${saturation}%, ${lightness}%), hsl(${hue}, ${saturation}%, ${Math.max(20, lightness - 20)}%)`;
}
}
// Start animation
animate();
// Update device status with YouTube style
deviceStatus.innerHTML = `<i class="fas fa-microphone mr-1"></i> <span class="text-red-400">${audioSourceSelect.options[audioSourceSelect.selectedIndex].text}</span>`;
}
// Initialize device selection
getAudioDevices().then(success => {
if (success) {
// When device selection changes, update the visualization
audioSourceSelect.addEventListener('change', async function() {
// Stop any existing visualization
stopVisualization();
if (currentStream) {
stopStream(currentStream);
}
// If a device is selected, start visualization
if (this.value) {
try {
const constraints = getAudioConstraints();
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
visualizeAudio(currentStream);
updateFileSize(0);
} catch (error) {
console.error('Error accessing selected device:', error);
deviceStatus.innerHTML = '<i class="fas fa-exclamation-triangle mr-1"></i> Could not access selected device';
}
}
});
// Also update when quality changes
audioQualitySelect.addEventListener('change', function() {
if (audioSourceSelect.value) {
// Trigger change event to restart with new quality
audioSourceSelect.dispatchEvent(new Event('change'));
}
});
}
});
// Enhanced recording with progress tracking
recordBtn.addEventListener('click', async function() {
try {
const constraints = getAudioConstraints();
const stream = await navigator.mediaDevices.getUserMedia(constraints);
currentStream = stream;
// Stop visualization if it's running
stopVisualization();
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.ondataavailable = function(event) {
audioChunks.push(event.data);
};
mediaRecorder.onstop = function() {
audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
audioUrl = URL.createObjectURL(audioBlob);
// Create audio element for playback
audioElement = new Audio(audioUrl);
// Enable play, download, and share buttons
playBtn.disabled = false;
downloadBtn.disabled = false;
shareBtn.disabled = false;
// Add to recordings list
addRecordingToList(audioUrl);
// Restart visualization if a device was selected
if (audioSourceSelect.value) {
visualizeAudio(currentStream);
}
};
mediaRecorder.start(10); // Collect data every 10ms
// Start timer and progress
seconds = 0;
updateTimer();
updateProgress();
timerInterval = setInterval(() => {
seconds++;
updateTimer();
updateProgress();
updateFileSize(seconds / 60);
}, 1000);
// Update UI with YouTube styling
recordBtn.disabled = true;
recordBtn.classList.remove('yt-button-primary');
recordBtn.classList.add('bg-gray-600', 'cursor-not-allowed');
stopBtn.disabled = false;
stopBtn.classList.remove('yt-button');
stopBtn.classList.add('yt-button-primary');
// Add recording animation
recordBtn.classList.add('recording-animation');
// Update mobile button if exists
if (mobileRecordBtn) {
mobileRecordBtn.classList.add('recording-animation');
}
} catch (error) {
console.error('Error accessing microphone:', error);
alert('Could not access microphone. Please check permissions.');
}
});
// Enhanced stop recording
stopBtn.addEventListener('click', function() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
// Stop all tracks in the stream
stopStream(currentStream);
// Clear timer and progress
clearInterval(timerInterval);
progressFill.style.width = '0%';
// Update UI
recordBtn.disabled = false;
recordBtn.classList.remove('bg-gray-600', 'cursor-not-allowed');
recordBtn.classList.add('yt-button-primary');
stopBtn.disabled = true;
stopBtn.classList.remove('yt-button-primary');
stopBtn.classList.add('yt-button');
// Remove recording animation
recordBtn.classList.remove('recording-animation');
// Update mobile button if exists
if (mobileRecordBtn) {
mobileRecordBtn.classList.remove('recording-animation');
}
}
});
// Enhanced play with progress tracking
playBtn.addEventListener('click', function() {
if (audioElement) {
if (audioElement.paused) {
audioElement.play();
playBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
// Update progress as audio plays
const updatePlayProgress = setInterval(() => {
if (audioElement.paused || audioElement.ended) {
clearInterval(updatePlayProgress);
playBtn.innerHTML = '<i class="fas fa-play"></i> Preview';
progressFill.style.width = '0%';
} else {
const progress = (audioElement.currentTime / audioElement.duration) * 100;
progressFill.style.width = `${progress}%`;
}, 100);
} else {
audioElement.pause();
playBtn.innerHTML = '<i class="fas fa-play"></i> Preview';
}
}
});
// Enhanced download
downloadBtn.addEventListener('click', function() {
if (audioBlob) {
const a = document.createElement('a');
a.style.display = 'none';
a.href = audioUrl;
a.download = `recording_${new Date().toISOString().slice(0, 19).replace(/[:T-]/g, '_')}.${selectedFormat}`;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
}, 100);
}
});
// Share functionality
shareBtn.addEventListener('click', function() {
if (audioBlob) {
if (navigator.share) {
navigator.share({
title: 'My Recording',
text: 'Check out this recording I made!',
files: [new File([audioBlob], `recording_${new Date().toISOString().slice(0, 19).replace(/[:T-]/g, '_')}.${selectedFormat}`
}).catch(console.error);
} else {
alert('Sharing not supported in this browser. Download the file instead.');
}
}
});
// Update timer display
function updateTimer() {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
timer.textContent = `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
}
// Update progress bar
function updateProgress() {
const progress = (seconds / 300) * 100; // 5 minute max
progressFill.style.width = `${Math.min(100, progress)}%`;
}
// Request permission to use microphone
recordBtn.addEventListener('click', async function() {
try {
const constraints = getAudioConstraints();
const stream = await navigator.mediaDevices.getUserMedia(constraints);
currentStream = stream;
// Stop visualization if it's running
stopVisualization();
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.ondataavailable = function(event) {
audioChunks.push(event.data);
};
mediaRecorder.onstop = function() {
audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
audioUrl = URL.createObjectURL(audioBlob);
// Create audio element for playback
audioElement = new Audio(audioUrl);
// Enable play and download buttons
playBtn.disabled = false;
downloadBtn.disabled = false;
// Add to recordings list
addRecordingToList(audioUrl);
// Restart visualization if a device was selected
if (audioSourceSelect.value) {
visualizeAudio(currentStream);
}
};
mediaRecorder.start(10); // Collect data every 10ms
// Start timer
seconds = 0;
updateTimer();
timerInterval = setInterval(updateTimer, 1000);
// Update UI
recordBtn.disabled = true;
recordBtn.classList.remove('bg-red-500', 'hover:bg-red-600');
recordBtn.classList.add('bg-gray-300', 'text-gray-600');
stopBtn.disabled = false;
stopBtn.classList.remove('bg-gray-300', 'text-gray-600');
stopBtn.classList.add('bg-gray-800', 'hover:bg-gray-900', 'text-white');
// Add recording animation
recordBtn.classList.add('recording-animation');
} catch (error) {
console.error('Error accessing microphone:', error);
alert('Could not access microphone. Please check permissions.');
}
});
// Stop recording
stopBtn.addEventListener('click', function() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
// Stop all tracks in the stream
stopStream(currentStream);
// Clear timer
clearInterval(timerInterval);
// Update UI
recordBtn.disabled = false;
recordBtn.classList.remove('bg-gray-300', 'text-gray-600');
recordBtn.classList.add('bg-red-500', 'hover:bg-red-600');
stopBtn.disabled = true;
stopBtn.classList.remove('bg-gray-800', 'hover:bg-gray-900', 'text-white');
stopBtn.classList.add('bg-gray-300', 'text-gray-600');
// Remove recording animation
recordBtn.classList.remove('recording-animation');
}
});
// Play recording
playBtn.addEventListener('click', function() {
if (audioElement) {
if (audioElement.paused) {
audioElement.play();
playBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
} else {
audioElement.pause();
playBtn.innerHTML = '<i class="fas fa-play"></i> Play';
}
}
});
// Download recording
downloadBtn.addEventListener('click', function() {
if (audioBlob) {
const a = document.createElement('a');
a.style.display = 'none';
a.href = audioUrl;
a.download = `recording_${new Date().toISOString().slice(0, 19).replace(/[:T-]/g, '_')}.${selectedFormat}`;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(audioUrl);
}, 100);
}
});
// Update timer display
function updateTimer() {
seconds++;
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
timer.textContent = `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
}
// Enhanced recording history with YouTube cards
function addRecordingToList(url) {
// Remove placeholder if it exists
if (recordingsList.querySelector('.text-center')) {
recordingsList.innerHTML = '';
}
const recordingItem = document.createElement('div');
recordingItem.className = 'recording-item p-4 flex items-center justify-between';
recordingItem.innerHTML = `
<div class="flex items-center gap-4">
<div class="w-12 h-12 bg-red-500 rounded-lg flex items-center justify-center">
<i class="fas fa-wave-square"></i>
</div>
<div class="flex-1">
<p class="font-medium text-white">Recording ${recordingsList.children.length + 1}</p>
<p class="text-sm text-gray-400">${new Date().toLocaleString()}</p>
<p class="text-xs text-gray-500">${audioSourceSelect.options[audioSourceSelect.selectedIndex].text}</p>
</div>
</div>
<div class="flex gap-2">
<button class="play-history-btn yt-button p-2 rounded-lg">
<i class="fas fa-play text-xs"></i>
</button>
<button class="download-history-btn yt-button p-2 rounded-lg">
<i class="fas fa-download text-xs"></i>
</button>
<button class="share-history-btn yt-button p-2 rounded-lg">
<i class="fas fa-share-alt text-xs"></i>
</button>
</div>
</div>
`;
recordingsList.prepend(recordingItem);
// Add event listeners to the new buttons
const playBtn = recordingItem.querySelector('.play-history-btn');
const downloadBtn = recordingItem.querySelector('.download-history-btn');
const shareBtn = recordingItem.querySelector('.share-history-btn');
const audio = new Audio(url);
playBtn.addEventListener('click', function() {
if (audio.paused) {
// Stop all other audio elements first
document.querySelectorAll('audio').forEach(a => {
if (a !== audio) a.pause();
});
audio.play();
playBtn.innerHTML = '<i class="fas fa-pause text-xs"></i>';
} else {
audio.pause();
playBtn.innerHTML = '<i class="fas fa-play text-xs"></i>';
}
});
downloadBtn.addEventListener('click', function() {
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = `recording_${new Date().toISOString().slice(0, 19).replace(/[:T-]/g, '_')}.${selectedFormat}`;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
}, 100);
});
shareBtn.addEventListener('click', function() {
if (navigator.share) {
navigator.share({
title: `Recording ${recordingsList.children.length}`,
text: 'Check out this recording I made with Audio Studio Pro!',
});
}
});
// Reset play button when audio ends
audio.addEventListener('ended', function() {
playBtn.innerHTML = '<i class="fas fa-play text-xs"></i>';
});
}
});
</script>
<!-- Footer with credits -->
<div class="mt-12 border-t border-[#3f3f3f] pt-6">
<div class="flex flex-col sm:flex-row justify-between items-center text-sm text-gray-400">
<div class="flex items-center gap-4">
<span>Powered by</span>
<a href="https://huggingface.co/terastudio" target="_blank" class="hover:text-white transition-colors">
<i class="fas fa-brain mr-1"></i>
TeraStudio AI
</a>
</div>
<div class="flex items-center gap-4 mt-4 sm:mt-0">
<span>Made with</span>
<a href="https://enzostvs-deepsite.hf.space" target="_blank" class="hover:text-white transition-colors">
<img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);">
DeepSite
</a>
<span></span>
<a href="https://enzostvs-deepsite.hf.space?remix=NeoPy/audio-recorder" target="_blank" class="hover:text-white transition-colors">
🧬 Remix Project
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Floating Action Button for Mobile -->
<div class="fixed bottom-6 right-6 md:hidden">
<button id="mobileRecordBtn" class="yt-button-primary w-14 h-14 rounded-full flex items-center justify-center shadow-lg">
<i class="fas fa-microphone"></i>
</button>
</div>
</body>
</html>