Update index.html
Browse files- index.html +108 -9
index.html
CHANGED
@@ -49,15 +49,39 @@
|
|
49 |
border-radius: 5px;
|
50 |
cursor: pointer;
|
51 |
transition: background-color 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
}
|
53 |
|
54 |
-
.
|
55 |
-
|
|
|
|
|
56 |
}
|
57 |
|
58 |
audio {
|
59 |
display: none;
|
60 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
</style>
|
62 |
</head>
|
63 |
<body>
|
@@ -78,6 +102,10 @@
|
|
78 |
|
79 |
const container = document.getElementById('audioContainer');
|
80 |
let currentAudio = null;
|
|
|
|
|
|
|
|
|
81 |
|
82 |
voices.forEach(voice => {
|
83 |
const audioElement = document.createElement('audio');
|
@@ -90,35 +118,106 @@
|
|
90 |
const nameSpan = document.createElement('span');
|
91 |
nameSpan.textContent = voice.name;
|
92 |
|
|
|
|
|
|
|
93 |
const playButton = document.createElement('button');
|
94 |
playButton.className = 'play-button';
|
95 |
-
playButton.
|
96 |
-
|
|
|
|
|
|
|
|
|
97 |
|
98 |
itemDiv.appendChild(nameSpan);
|
|
|
99 |
itemDiv.appendChild(playButton);
|
100 |
container.appendChild(itemDiv);
|
101 |
container.appendChild(audioElement);
|
102 |
});
|
103 |
|
104 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
if (currentAudio && currentAudio !== audioElement) {
|
106 |
currentAudio.pause();
|
107 |
-
currentAudio.previousElementSibling.
|
|
|
108 |
}
|
109 |
|
110 |
if (audioElement.paused) {
|
|
|
|
|
|
|
111 |
audioElement.play();
|
112 |
-
button.
|
|
|
|
|
113 |
currentAudio = audioElement;
|
114 |
} else {
|
115 |
audioElement.pause();
|
116 |
-
button.
|
|
|
|
|
117 |
currentAudio = null;
|
118 |
}
|
119 |
|
120 |
audioElement.addEventListener('ended', () => {
|
121 |
-
button.
|
|
|
|
|
122 |
currentAudio = null;
|
123 |
});
|
124 |
}
|
|
|
49 |
border-radius: 5px;
|
50 |
cursor: pointer;
|
51 |
transition: background-color 0.3s;
|
52 |
+
display: flex;
|
53 |
+
align-items: center;
|
54 |
+
gap: 8px;
|
55 |
+
}
|
56 |
+
|
57 |
+
.status-indicator {
|
58 |
+
width: 12px;
|
59 |
+
height: 12px;
|
60 |
+
border-radius: 50%;
|
61 |
+
background-color: #fff;
|
62 |
+
transition: background-color 0.3s;
|
63 |
+
}
|
64 |
+
|
65 |
+
.playing .status-indicator {
|
66 |
+
background-color: #2ecc71;
|
67 |
+
animation: pulse 1s infinite;
|
68 |
}
|
69 |
|
70 |
+
.waveform-container {
|
71 |
+
width: 100px;
|
72 |
+
height: 40px;
|
73 |
+
margin-left: 15px;
|
74 |
}
|
75 |
|
76 |
audio {
|
77 |
display: none;
|
78 |
}
|
79 |
+
|
80 |
+
@keyframes pulse {
|
81 |
+
0% { transform: scale(0.95); }
|
82 |
+
50% { transform: scale(1.1); }
|
83 |
+
100% { transform: scale(0.95); }
|
84 |
+
}
|
85 |
</style>
|
86 |
</head>
|
87 |
<body>
|
|
|
102 |
|
103 |
const container = document.getElementById('audioContainer');
|
104 |
let currentAudio = null;
|
105 |
+
let audioContext = null;
|
106 |
+
let analyser = null;
|
107 |
+
let dataArray = null;
|
108 |
+
let animationFrameId = null;
|
109 |
|
110 |
voices.forEach(voice => {
|
111 |
const audioElement = document.createElement('audio');
|
|
|
118 |
const nameSpan = document.createElement('span');
|
119 |
nameSpan.textContent = voice.name;
|
120 |
|
121 |
+
const waveformCanvas = document.createElement('canvas');
|
122 |
+
waveformCanvas.className = 'waveform-container';
|
123 |
+
|
124 |
const playButton = document.createElement('button');
|
125 |
playButton.className = 'play-button';
|
126 |
+
playButton.innerHTML = `
|
127 |
+
<div class="status-indicator"></div>
|
128 |
+
<span>Play</span>
|
129 |
+
`;
|
130 |
+
|
131 |
+
playButton.onclick = () => toggleAudio(audioElement, playButton, waveformCanvas);
|
132 |
|
133 |
itemDiv.appendChild(nameSpan);
|
134 |
+
itemDiv.appendChild(waveformCanvas);
|
135 |
itemDiv.appendChild(playButton);
|
136 |
container.appendChild(itemDiv);
|
137 |
container.appendChild(audioElement);
|
138 |
});
|
139 |
|
140 |
+
function setupAudioContext(audioElement) {
|
141 |
+
if (!audioContext) {
|
142 |
+
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
143 |
+
analyser = audioContext.createAnalyser();
|
144 |
+
analyser.fftSize = 256;
|
145 |
+
dataArray = new Uint8Array(analyser.frequencyBinCount);
|
146 |
+
}
|
147 |
+
|
148 |
+
const source = audioContext.createMediaElementSource(audioElement);
|
149 |
+
source.connect(analyser);
|
150 |
+
analyser.connect(audioContext.destination);
|
151 |
+
}
|
152 |
+
|
153 |
+
function drawWaveform(canvas) {
|
154 |
+
if (!analyser) return;
|
155 |
+
|
156 |
+
const ctx = canvas.getContext('2d');
|
157 |
+
const width = canvas.width;
|
158 |
+
const height = canvas.height;
|
159 |
+
|
160 |
+
function draw() {
|
161 |
+
animationFrameId = requestAnimationFrame(draw);
|
162 |
+
analyser.getByteTimeDomainData(dataArray);
|
163 |
+
|
164 |
+
ctx.fillStyle = '#f8f9fa';
|
165 |
+
ctx.fillRect(0, 0, width, height);
|
166 |
+
ctx.lineWidth = 2;
|
167 |
+
ctx.strokeStyle = '#3498db';
|
168 |
+
ctx.beginPath();
|
169 |
+
|
170 |
+
const sliceWidth = width / dataArray.length;
|
171 |
+
let x = 0;
|
172 |
+
|
173 |
+
for (let i = 0; i < dataArray.length; i++) {
|
174 |
+
const v = dataArray[i] / 128.0;
|
175 |
+
const y = v * height / 2;
|
176 |
+
|
177 |
+
if (i === 0) {
|
178 |
+
ctx.moveTo(x, y);
|
179 |
+
} else {
|
180 |
+
ctx.lineTo(x, y);
|
181 |
+
}
|
182 |
+
|
183 |
+
x += sliceWidth;
|
184 |
+
}
|
185 |
+
|
186 |
+
ctx.lineTo(width, height/2);
|
187 |
+
ctx.stroke();
|
188 |
+
}
|
189 |
+
|
190 |
+
draw();
|
191 |
+
}
|
192 |
+
|
193 |
+
function toggleAudio(audioElement, button, canvas) {
|
194 |
if (currentAudio && currentAudio !== audioElement) {
|
195 |
currentAudio.pause();
|
196 |
+
currentAudio.previousElementSibling.querySelector('.play-button').classList.remove('playing');
|
197 |
+
cancelAnimationFrame(animationFrameId);
|
198 |
}
|
199 |
|
200 |
if (audioElement.paused) {
|
201 |
+
if (!audioContext) {
|
202 |
+
setupAudioContext(audioElement);
|
203 |
+
}
|
204 |
audioElement.play();
|
205 |
+
button.classList.add('playing');
|
206 |
+
button.querySelector('span').textContent = 'Pause';
|
207 |
+
drawWaveform(canvas);
|
208 |
currentAudio = audioElement;
|
209 |
} else {
|
210 |
audioElement.pause();
|
211 |
+
button.classList.remove('playing');
|
212 |
+
button.querySelector('span').textContent = 'Play';
|
213 |
+
cancelAnimationFrame(animationFrameId);
|
214 |
currentAudio = null;
|
215 |
}
|
216 |
|
217 |
audioElement.addEventListener('ended', () => {
|
218 |
+
button.classList.remove('playing');
|
219 |
+
button.querySelector('span').textContent = 'Play';
|
220 |
+
cancelAnimationFrame(animationFrameId);
|
221 |
currentAudio = null;
|
222 |
});
|
223 |
}
|