anycoder-7b035adb / index.html
HI7RAI's picture
Upload folder using huggingface_hub
3f1cc4b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio Recorder & Player</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0a0a0f;
--bg-secondary: #12121a;
--bg-tertiary: #1a1a25;
--fg: #e8e8ed;
--fg-muted: #6b6b7a;
--accent: #00d4aa;
--accent-glow: rgba(0, 212, 170, 0.3);
--accent-secondary: #ff6b6b;
--border: #2a2a3a;
--card: #15151f;
--danger: #ff4757;
--warning: #ffa502;
}
* {
box-sizing: border-box;
}
body {
font-family: 'Space Grotesk', sans-serif;
background: var(--bg);
color: var(--fg);
min-height: 100vh;
margin: 0;
overflow-x: hidden;
}
.mono {
font-family: 'JetBrains Mono', monospace;
}
/* Background atmosphere */
.bg-atmosphere {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
overflow: hidden;
}
.bg-atmosphere::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background:
radial-gradient(ellipse at 20% 20%, rgba(0, 212, 170, 0.08) 0%, transparent 50%),
radial-gradient(ellipse at 80% 80%, rgba(255, 107, 107, 0.05) 0%, transparent 50%),
radial-gradient(ellipse at 50% 50%, rgba(0, 212, 170, 0.03) 0%, transparent 70%);
animation: atmosphereRotate 60s linear infinite;
}
@keyframes atmosphereRotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.noise-overlay {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 1;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
}
/* Grid pattern */
.grid-pattern {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 1;
background-image:
linear-gradient(rgba(0, 212, 170, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 212, 170, 0.03) 1px, transparent 1px);
background-size: 50px 50px;
mask-image: radial-gradient(ellipse at center, black 0%, transparent 70%);
}
.main-container {
position: relative;
z-index: 10;
min-height: 100vh;
padding: 2rem;
}
/* Header */
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 3rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--border);
}
.logo {
display: flex;
align-items: center;
gap: 0.75rem;
}
.logo-icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, var(--accent), var(--accent-secondary));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 20px var(--accent-glow);
}
.logo-text {
font-size: 1.25rem;
font-weight: 600;
letter-spacing: -0.02em;
}
.built-with {
font-size: 0.75rem;
color: var(--fg-muted);
text-decoration: none;
transition: color 0.2s;
}
.built-with:hover {
color: var(--accent);
}
/* Audio Container */
.audio-wrapper {
max-width: 800px;
margin: 0 auto;
}
.block-label {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
color: var(--fg-muted);
font-size: 0.875rem;
font-weight: 500;
}
.block-label svg {
width: 18px;
height: 18px;
color: var(--accent);
}
.audio-container {
background: var(--card);
border: 1px solid var(--border);
border-radius: 20px;
padding: 1.5rem;
position: relative;
overflow: hidden;
transition: border-color 0.3s, box-shadow 0.3s;
}
.audio-container:hover {
border-color: rgba(0, 212, 170, 0.3);
}
.audio-container.dragging {
border-color: var(--accent);
box-shadow: 0 0 40px var(--accent-glow);
}
.audio-container.dragging .drop-zone {
opacity: 1;
visibility: visible;
}
/* Drop Zone */
.drop-zone {
position: absolute;
inset: 0;
background: rgba(0, 212, 170, 0.1);
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
opacity: 0;
visibility: hidden;
transition: all 0.3s;
z-index: 20;
border-radius: 20px;
}
.drop-zone-icon {
width: 60px;
height: 60px;
background: var(--accent);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
animation: dropPulse 1.5s ease-in-out infinite;
}
@keyframes dropPulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.8; }
}
.drop-zone-text {
font-size: 1.125rem;
font-weight: 500;
color: var(--fg);
}
/* Source Selector */
.source-selector {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
background: var(--bg-secondary);
padding: 0.375rem;
border-radius: 12px;
width: fit-content;
}
.source-btn {
padding: 0.625rem 1.25rem;
border-radius: 10px;
border: none;
background: transparent;
color: var(--fg-muted);
font-family: inherit;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.source-btn:hover {
color: var(--fg);
}
.source-btn.active {
background: var(--accent);
color: var(--bg);
box-shadow: 0 4px 15px var(--accent-glow);
}
.source-btn svg {
width: 16px;
height: 16px;
}
/* Upload Area */
.upload-area {
border: 2px dashed var(--border);
border-radius: 16px;
padding: 3rem 2rem;
text-align: center;
transition: all 0.3s;
cursor: pointer;
}
.upload-area:hover {
border-color: var(--accent);
background: rgba(0, 212, 170, 0.03);
}
.upload-icon {
width: 64px;
height: 64px;
margin: 0 auto 1.5rem;
background: var(--bg-tertiary);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: var(--accent);
transition: transform 0.3s;
}
.upload-area:hover .upload-icon {
transform: translateY(-4px);
}
.upload-title {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.upload-subtitle {
color: var(--fg-muted);
font-size: 0.875rem;
}
.upload-formats {
margin-top: 1rem;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
}
.format-tag {
padding: 0.25rem 0.75rem;
background: var(--bg-secondary);
border-radius: 20px;
font-size: 0.75rem;
color: var(--fg-muted);
}
/* Recorder */
.recorder-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
}
.waveform-display {
width: 100%;
height: 120px;
background: var(--bg-secondary);
border-radius: 12px;
margin-bottom: 1.5rem;
overflow: hidden;
position: relative;
}
.waveform-canvas {
width: 100%;
height: 100%;
}
.recording-indicator {
position: absolute;
top: 1rem;
right: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.75rem;
background: rgba(255, 71, 87, 0.2);
border-radius: 20px;
font-size: 0.75rem;
color: var(--danger);
opacity: 0;
transition: opacity 0.3s;
}
.recording-indicator.active {
opacity: 1;
}
.recording-dot {
width: 8px;
height: 8px;
background: var(--danger);
border-radius: 50%;
animation: recordingPulse 1s ease-in-out infinite;
}
@keyframes recordingPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.recorder-controls {
display: flex;
gap: 1rem;
align-items: center;
}
.record-btn {
width: 72px;
height: 72px;
border-radius: 50%;
border: 3px solid var(--accent);
background: transparent;
color: var(--accent);
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.record-btn::before {
content: '';
position: absolute;
inset: -8px;
border-radius: 50%;
border: 2px solid transparent;
transition: border-color 0.3s;
}
.record-btn:hover::before {
border-color: rgba(0, 212, 170, 0.3);
}
.record-btn.recording {
background: var(--danger);
border-color: var(--danger);
animation: recordBtnPulse 1.5s ease-in-out infinite;
}
@keyframes recordBtnPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(255, 71, 87, 0.4); }
50% { box-shadow: 0 0 0 15px rgba(255, 71, 87, 0); }
}
.record-btn svg {
width: 28px;
height: 28px;
}
.control-btn {
width: 48px;
height: 48px;
border-radius: 12px;
border: 1px solid var(--border);
background: var(--bg-secondary);
color: var(--fg-muted);
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.control-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
.control-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.control-btn svg {
width: 20px;
height: 20px;
}
.timer {
font-family: 'JetBrains Mono', monospace;
font-size: 1.5rem;
font-weight: 500;
color: var(--fg);
min-width: 100px;
text-align: center;
}
/* Audio Player */
.player-container {
display: none;
}
.player-container.active {
display: block;
}
.player-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}
.player-info {
display: flex;
align-items: center;
gap: 1rem;
}
.player-thumbnail {
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--accent), var(--accent-secondary));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.player-name {
font-weight: 500;
margin-bottom: 0.25rem;
}
.player-meta {
font-size: 0.75rem;
color: var(--fg-muted);
}
.player-actions {
display: flex;
gap: 0.5rem;
}
.action-btn {
width: 36px;
height: 36px;
border-radius: 8px;
border: 1px solid var(--border);
background: transparent;
color: var(--fg-muted);
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.action-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
.action-btn.danger:hover {
border-color: var(--danger);
color: var(--danger);
}
.action-btn svg {
width: 16px;
height: 16px;
}
.player-waveform {
height: 80px;
background: var(--bg-secondary);
border-radius: 12px;
margin-bottom: 1rem;
position: relative;
overflow: hidden;
cursor: pointer;
}
.waveform-bars {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
height: 100%;
padding: 0 1rem;
}
.waveform-bar {
width: 3px;
background: var(--accent);
border-radius: 2px;
opacity: 0.5;
transition: opacity 0.2s;
}
.waveform-bar.active {
opacity: 1;
}
.progress-overlay {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: linear-gradient(90deg, rgba(0, 212, 170, 0.2), transparent);
pointer-events: none;
transition: width 0.1s;
}
.player-controls {
display: flex;
align-items: center;
gap: 1rem;
}
.play-btn {
width: 56px;
height: 56px;
border-radius: 50%;
border: none;
background: var(--accent);
color: var(--bg);
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 20px var(--accent-glow);
}
.play-btn:hover {
transform: scale(1.05);
}
.play-btn svg {
width: 24px;
height: 24px;
}
.time-display {
font-family: 'JetBrains Mono', monospace;
font-size: 0.875rem;
color: var(--fg-muted);
}
.progress-bar {
flex: 1;
height: 4px;
background: var(--bg-tertiary);
border-radius: 2px;
overflow: hidden;
cursor: pointer;
}
.progress-fill {
height: 100%;
background: var(--accent);
border-radius: 2px;
transition: width 0.1s;
}
.volume-control {
display: flex;
align-items: center;
gap: 0.5rem;
}
.volume-slider {
width: 80px;
height: 4px;
-webkit-appearance: none;
appearance: none;
background: var(--bg-tertiary);
border-radius: 2px;
cursor: pointer;
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
background: var(--accent);
border-radius: 50%;
cursor: pointer;
}
/* Streaming Bar */
.streaming-bar {
height: 3px;
background: var(--bg-tertiary);
border-radius: 0 0 20px 20px;
margin: 0 -1.5rem -1.5rem;
overflow: hidden;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.streaming-progress {
height: 100%;
background: linear-gradient(90deg, var(--accent), var(--accent-secondary));
width: 0%;
animation: streamingAnim 2s ease-in-out infinite;
}
@keyframes streamingAnim {
0% { width: 0%; }
50% { width: 70%; }
100% { width: 0%; }
}
/* Status Messages */
.status-message {
padding: 0.75rem 1rem;
border-radius: 10px;
font-size: 0.875rem;
margin-top: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-message.error {
background: rgba(255, 71, 87, 0.1);
color: var(--danger);
border: 1px solid rgba(255, 71, 87, 0.2);
}
.status-message.success {
background: rgba(0, 212, 170, 0.1);
color: var(--accent);
border: 1px solid rgba(0, 212, 170, 0.2);
}
/* Animations */
.fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.stagger-1 { animation-delay: 0.1s; }
.stagger-2 { animation-delay: 0.2s; }
.stagger-3 { animation-delay: 0.3s; }
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Responsive */
@media (max-width: 640px) {
.main-container {
padding: 1rem;
}
.header {
flex-direction: column;
gap: 1rem;
text-align: center;
}
.audio-container {
padding: 1rem;
}
.upload-area {
padding: 2rem 1rem;
}
.recorder-controls {
flex-wrap: wrap;
justify-content: center;
}
.player-controls {
flex-wrap: wrap;
}
.volume-control {
width: 100%;
justify-content: center;
}
.source-selector {
width: 100%;
}
.source-btn {
flex: 1;
justify-content: center;
}
}
/* Focus states */
button:focus-visible,
input:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
</style>
</head>
<body>
<div class="bg-atmosphere"></div>
<div class="noise-overlay"></div>
<div class="grid-pattern"></div>
<div class="main-container">
<header class="header fade-in">
<div class="logo">
<div class="logo-icon">
<i data-lucide="audio-waveform" style="width: 24px; height: 24px; color: white;"></i>
</div>
<span class="logo-text">Audio Studio</span>
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" class="built-with" target="_blank" rel="noopener">
Built with anycoder
</a>
</header>
<div class="audio-wrapper">
<div class="block-label fade-in stagger-1">
<i data-lucide="music"></i>
<span>Audio Input</span>
</div>
<div class="audio-container fade-in stagger-2" id="audioContainer">
<!-- Drop Zone -->
<div class="drop-zone" id="dropZone">
<div class="drop-zone-icon">
<i data-lucide="upload" style="width: 28px; height: 28px; color: var(--bg);"></i>
</div>
<span class="drop-zone-text">Drop audio file here</span>
</div>
<!-- Source Selector -->
<div class="source-selector">
<button class="source-btn active" data-source="microphone" id="micSource">
<i data-lucide="mic"></i>
Microphone
</button>
<button class="source-btn" data-source="upload" id="uploadSource">
<i data-lucide="upload"></i>
Upload
</button>
</div>
<!-- Microphone Source -->
<div id="microphonePanel">
<div class="recorder-container">
<div class="waveform-display">
<canvas class="waveform-canvas" id="waveformCanvas"></canvas>
<div class="recording-indicator" id="recordingIndicator">
<span class="recording-dot"></span>
<span>REC</span>
</div>
</div>
<div class="recorder-controls">
<button class="control-btn" id="pauseBtn" disabled aria-label="Pause recording">
<i data-lucide="pause"></i>
</button>
<button class="record-btn" id="recordBtn" aria-label="Start recording">
<i data-lucide="mic" id="recordIcon"></i>
</button>
<button class="control-btn" id="stopBtn" disabled aria-label="Stop recording">
<i data-lucide="square"></i>
</button>
</div>
<div class="timer" id="timer">00:00</div>
</div>
</div>
<!-- Upload Source -->
<div id="uploadPanel" style="display: none;">
<div class="upload-area" id="uploadArea">
<div class="upload-icon">
<i data-lucide="file-audio" style="width: 32px; height: 32px;"></i>
</div>
<div class="upload-title">Drop audio file or click to upload</div>
<div class="upload-subtitle">Maximum file size: 50MB</div>
<div class="upload-formats">
<span class="format-tag">MP3</span>
<span class="format-tag">WAV</span>
<span class="format-tag">OGG</span>
<span class="format-tag">FLAC</span>
<span class="format-tag">AAC</span>
<span class="format-tag">WEBM</span>
</div>
<input type="file" id="fileInput" accept="audio/*" style="display: none;">
</div>
</div>
<!-- Audio Player -->
<div class="player-container" id="playerContainer">
<div class="player-header">
<div class="player-info">
<div class="player-thumbnail">
<i data-lucide="music" style="width: 24px; height: 24px; color: white;"></i>
</div>
<div>
<div class="player-name" id="playerName">Recording</div>
<div class="player-meta" id="playerMeta">WAV - 44.1kHz</div>
</div>
</div>
<div class="player-actions">
<button class="action-btn" id="downloadBtn" aria-label="Download">
<i data-lucide="download"></i>
</button>
<button class="action-btn danger" id="clearBtn" aria-label="Clear">
<i data-lucide="trash-2"></i>
</button>
</div>
</div>
<div class="player-waveform" id="playerWaveform">
<div class="progress-overlay" id="progressOverlay"></div>
<div class="waveform-bars" id="waveformBars"></div>
</div>
<div class="player-controls">
<button class="play-btn" id="playBtn" aria-label="Play">
<i data-lucide="play" id="playIcon"></i>
</button>
<span class="time-display" id="currentTime">0:00</span>
<div class="progress-bar" id="progressBar">
<div class="progress-fill" id="progressFill"></div>
</div>
<span class="time-display" id="totalTime">0:00</span>
<div class="volume-control">
<i data-lucide="volume-2" style="width: 18px; height: 18px; color: var(--fg-muted);"></i>
<input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="80">
</div>
</div>
</div>
<!-- Status Message -->
<div class="status-message error" id="statusMessage" style="display: none;"></div>
<!-- Streaming Bar -->
<div class="streaming-bar" id="streamingBar" style="display: none;">
<div class="streaming-progress"></div>
</div>
</div>
</div>
</div>
<audio id="audioElement" style="display: none;"></audio>
<script>
// Initialize Lucide icons
lucide.createIcons();
// State
let audioContext = null;
let analyser = null;
let mediaRecorder = null;
let audioChunks = [];
let isRecording = false;
let isPaused = false;
let startTime = 0;
let elapsedTime = 0;
let timerInterval = null;
let animationId = null;
let currentAudioBlob = null;
let currentAudioUrl = null;
// DOM Elements
const audioContainer = document.getElementById('audioContainer');
const dropZone = document.getElementById('dropZone');
const micSource = document.getElementById('micSource');
const uploadSource = document.getElementById('uploadSource');
const microphonePanel = document.getElementById('microphonePanel');
const uploadPanel = document.getElementById('uploadPanel');
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const waveformCanvas = document.getElementById('waveformCanvas');
const recordBtn = document.getElementById('recordBtn');
const recordIcon = document.getElementById('recordIcon');
const pauseBtn = document.getElementById('pauseBtn');
const stopBtn = document.getElementById('stopBtn');
const timer = document.getElementById('timer');
const recordingIndicator = document.getElementById('recordingIndicator');
const playerContainer = document.getElementById('playerContainer');
const playerName = document.getElementById('playerName');
const playerMeta = document.getElementById('playerMeta');
const playBtn = document.getElementById('playBtn');
const playIcon = document.getElementById('playIcon');
const currentTimeEl = document.getElementById('currentTime');
const totalTimeEl = document.getElementById('totalTime');
const progressBar = document.getElementById('progressBar');
const progressFill = document.getElementById('progressFill');
const progressOverlay = document.getElementById('progressOverlay');
const volumeSlider = document.getElementById('volumeSlider');
const downloadBtn = document.getElementById('downloadBtn');
const clearBtn = document.getElementById('clearBtn');
const statusMessage = document.getElementById('statusMessage');
const streamingBar = document.getElementById('streamingBar');
const audioElement = document.getElementById('audioElement');
const waveformBars = document.getElementById('waveformBars');
// Canvas setup
const ctx = waveformCanvas.getContext('2d');
function resizeCanvas() {
const rect = waveformCanvas.getBoundingClientRect();
waveformCanvas.width = rect.width * window.devicePixelRatio;
waveformCanvas.height = rect.height * window.devicePixelRatio;
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Source switching
micSource.addEventListener('click', () => {
micSource.classList.add('active');
uploadSource.classList.remove('active');
microphonePanel.style.display = 'block';
uploadPanel.style.display = 'none';
});
uploadSource.addEventListener('click', () => {
uploadSource.classList.add('active');
micSource.classList.remove('active');
uploadPanel.style.display = 'block';
microphonePanel.style.display = 'none';
});
// Drag and drop
audioContainer.addEventListener('dragover', (e) => {
e.preventDefault();
audioContainer.classList.add('dragging');
});
audioContainer.addEventListener('dragleave', (e) => {
e.preventDefault();
audioContainer.classList.remove('dragging');
});
audioContainer.addEventListener('drop', (e) => {
e.preventDefault();
audioContainer.classList.remove('dragging');
const files = e.dataTransfer.files;
if (files.length > 0 && files[0].type.startsWith('audio/')) {
handleFile(files[0]);
}
});
// Upload area click
uploadArea.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
// Handle file upload
function handleFile(file) {
if (file.size > 50 * 1024 * 1024) {
showStatus('File size exceeds 50MB limit', 'error');
return;
}
currentAudioBlob = file;
currentAudioUrl = URL.createObjectURL(file);
playerName.textContent = file.name;
playerMeta.textContent = `${file.type.split('/')[1].toUpperCase()} - ${(file.size / 1024 / 1024).toFixed(2)} MB`;
audioElement.src = currentAudioUrl;
audioElement.addEventListener('loadedmetadata', () => {
totalTimeEl.textContent = formatTime(audioElement.duration);
generateWaveform();
});
showPlayer();
showStatus('Audio file loaded successfully', 'success');
}
// Recording functions
async function startRecording() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
const source = audioContext.createMediaStreamSource(stream);
source.connect(analyser);
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.ondataavailable = (e) => {
audioChunks.push(e.data);
};
mediaRecorder.onstop = () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
currentAudioBlob = audioBlob;
currentAudioUrl = URL.createObjectURL(audioBlob);
playerName.textContent = 'Recording';
playerMeta.textContent = 'WAV - Recorded audio';
audioElement.src = currentAudioUrl;
audioElement.addEventListener('loadedmetadata', () => {
totalTimeEl.textContent = formatTime(audioElement.duration);
generateWaveform();
});
showPlayer();
};
mediaRecorder.start();
isRecording = true;
startTime = Date.now();
startTimer();
visualize();
recordBtn.classList.add('recording');
recordIcon.setAttribute('data-lucide', 'square');
lucide.createIcons();
pauseBtn.disabled = false;
stopBtn.disabled = false;
recordingIndicator.classList.add('active');
} catch (err) {
console.error('Error accessing microphone:', err);
showStatus('Could not access microphone. Please check permissions.', 'error');
}
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
mediaRecorder.stream.getTracks().forEach(track => track.stop());
}
isRecording = false;
isPaused = false;
clearInterval(timerInterval);
cancelAnimationFrame(animationId);
recordBtn.classList.remove('recording');
recordIcon.setAttribute('data-lucide', 'mic');
lucide.createIcons();
pauseBtn.disabled = true;
stopBtn.disabled = true;
recordingIndicator.classList.remove('active');
if (audioContext) {
audioContext.close();
}
clearCanvas();
}
function pauseRecording() {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.pause();
isPaused = true;
clearInterval(timerInterval);
pauseBtn.innerHTML = '<i data-lucide="play"></i>';
lucide.createIcons();
} else if (mediaRecorder && mediaRecorder.state === 'paused') {
mediaRecorder.resume();
isPaused = false;
startTimer();
pauseBtn.innerHTML = '<i data-lucide="pause"></i>';
lucide.createIcons();
}
}
// Timer
function startTimer() {
timerInterval = setInterval(() => {
elapsedTime = Date.now() - startTime;
timer.textContent = formatTime(elapsedTime / 1000);
}, 100);
}
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
// Visualization
function visualize() {
if (!analyser) return;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const width = waveformCanvas.width / window.devicePixelRatio;
const height = waveformCanvas.height / window.devicePixelRatio;
function draw() {
if (!isRecording) return;
animationId = requestAnimationFrame(draw);
analyser.getByteFrequencyData(dataArray);
ctx.fillStyle = '#12121a';
ctx.fillRect(0, 0, width, height);
const barWidth = (width / bufferLength) * 2.5;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const barHeight = (dataArray[i] / 255) * height * 0.8;
const gradient = ctx.createLinearGradient(0, height - barHeight, 0, height);
gradient.addColorStop(0, '#00d4aa');
gradient.addColorStop(1, 'rgba(0, 212, 170, 0.2)');
ctx.fillStyle = gradient;
ctx.fillRect(x, height - barHeight, barWidth - 1, barHeight);
x += barWidth;
}
}
draw();
}
function clearCanvas() {
const width = waveformCanvas.width / window.devicePixelRatio;
const height = waveformCanvas.height / window.devicePixelRatio;
ctx.fillStyle = '#12121a';
ctx.fillRect(0, 0, width, height);
}
// Generate static waveform for player
function generateWaveform() {
waveformBars.innerHTML = '';
const barCount = 100;
for (let i = 0; i < barCount; i++) {
const bar = document.createElement('div');
bar.className = 'waveform-bar';
bar.style.height = `${Math.random() * 50 + 20}px`;
waveformBars.appendChild(bar);
}
}
// Player controls
playBtn.addEventListener('click', () => {
if (audioElement.paused) {
audioElement.play();
playIcon.setAttribute('data-lucide', 'pause');
lucide.createIcons();
} else {
audioElement.pause();
playIcon.setAttribute('data-lucide', 'play');
lucide.createIcons();
}
});
audioElement.addEventListener('timeupdate', () => {
const progress = (audioElement.currentTime / audioElement.duration) * 100;
progressFill.style.width = `${progress}%`;
progressOverlay.style.width = `${progress}%`;
currentTimeEl.textContent = formatTime(audioElement.currentTime);
// Update waveform bars
const bars = waveformBars.querySelectorAll('.waveform-bar');
const activeIndex = Math.floor((audioElement.currentTime / audioElement.duration) * bars.length);
bars.forEach((bar, i) => {
bar.classList.toggle('active', i <= activeIndex);
});
});
audioElement.addEventListener('ended', () => {
playIcon.setAttribute('data-lucide', 'play');
lucide.createIcons();
});
progressBar.addEventListener('click', (e) => {
const rect = progressBar.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width;
audioElement.currentTime = pos * audioElement.duration;
});
playerWaveform.addEventListener('click', (e) => {
const rect = playerWaveform.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width;
audioElement.currentTime = pos * audioElement.duration;
});
volumeSlider.addEventListener('input', (e) => {
audioElement.volume = e.target.value / 100;
});
// Download
downloadBtn.addEventListener('click', () => {
if (currentAudioBlob) {
const url = URL.createObjectURL(currentAudioBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'recording.wav';
a.click();
URL.revokeObjectURL(url);
}
});
// Clear
clearBtn.addEventListener('click', () => {
audioElement.pause();
audioElement.src = '';
currentAudioBlob = null;
if (currentAudioUrl) {
URL.revokeObjectURL(currentAudioUrl);
currentAudioUrl = null;
}
hidePlayer();
timer.textContent = '00:00';
elapsedTime = 0;
});
// Event listeners
recordBtn.addEventListener('click', () => {
if (isRecording) {
stopRecording();
} else {
startRecording();
}
});
pauseBtn.addEventListener('click', pauseRecording);
stopBtn.addEventListener('click', stopRecording);
// Helper functions
function showPlayer() {
playerContainer.classList.add('active');
microphonePanel.style.display = 'none';
uploadPanel.style.display = 'none';
micSource.style.display = 'none';
uploadSource.style.display = 'none';
}
function hidePlayer() {
playerContainer.classList.remove('active');
const activeSource = micSource.classList.contains('active') ? 'mic' : 'upload';
if (activeSource === 'mic') {
microphonePanel.style.display = 'block';
} else {
uploadPanel.style.display = 'block';
}
micSource.style.display = 'flex';
uploadSource.style.display = 'flex';
}
function showStatus(message, type) {
statusMessage.textContent = message;
statusMessage.className = `status-message ${type}`;
statusMessage.style.display = 'flex';
setTimeout(() => {
statusMessage.style.display = 'none';
}, 3000);
}
// Initial canvas clear
clearCanvas();
// Set initial volume
audioElement.volume = 0.8;
</script>
</body>
</html>