Blocked Content
{
- const speed = parseFloat(e.target.value) || 1.0;
- playbackSpeedInput.value = speed;
- player.setPlaybackSpeed(speed);
- });
-
- // Input allows full 0.01-100 range
- const handleInputChange = () => {
- const speed = parseFloat(playbackSpeedInput.value) || 1.0;
- const validSpeed = Math.max(0.01, Math.min(100, speed));
+ // Helper function to update both controls
+ const updatePlaybackSpeedControls = (speed) => {
+ const validSpeed = Math.max(0.01, Math.min(100, parseFloat(speed) || 1.0));
playbackSpeedInput.value = validSpeed;
// Only update slider if value is within slider range
if (validSpeed >= 0.25 && validSpeed <= 4.0) {
playbackSpeedSlider.value = validSpeed;
}
- player.setPlaybackSpeed(validSpeed);
+ return validSpeed;
};
- playbackSpeedInput.addEventListener('change', handleInputChange);
- playbackSpeedInput.addEventListener('blur', handleInputChange);
+ // Initialize with current value
+ const currentSpeed = audioEffectsSettings.getSpeed();
+ updatePlaybackSpeedControls(currentSpeed);
+
+ playbackSpeedSlider.addEventListener('input', (e) => {
+ const speed = parseFloat(e.target.value);
+ playbackSpeedInput.value = speed;
+ audioEffectsSettings.setSpeed(speed);
+ player.setPlaybackSpeed(speed);
+ });
+
+ playbackSpeedInput.addEventListener('input', (e) => {
+ const speed = parseFloat(e.target.value);
+ if (!isNaN(speed) && speed >= 0.01 && speed <= 100) {
+ if (speed >= 0.25 && speed <= 4.0) {
+ playbackSpeedSlider.value = speed;
+ }
+ audioEffectsSettings.setSpeed(speed);
+ player.setPlaybackSpeed(speed);
+ }
+ });
+
+ playbackSpeedInput.addEventListener('change', (e) => {
+ const speed = parseFloat(e.target.value);
+ const validSpeed = updatePlaybackSpeedControls(speed);
+ audioEffectsSettings.setSpeed(validSpeed);
+ player.setPlaybackSpeed(validSpeed);
+ });
+
+ if (playbackSpeedReset) {
+ playbackSpeedReset.addEventListener('click', () => {
+ const defaultSpeed = audioEffectsSettings.resetSpeed();
+ updatePlaybackSpeedControls(defaultSpeed);
+ player.setPlaybackSpeed(defaultSpeed);
+ });
+ }
}
// ========================================
diff --git a/js/storage.js b/js/storage.js
index c238f58..dfdaa98 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -1352,6 +1352,11 @@ export const audioEffectsSettings = {
localStorage.setItem(this.SPEED_KEY, validSpeed.toString());
},
+ resetSpeed() {
+ this.setSpeed(1.0);
+ return 1.0;
+ },
+
// Preserve pitch when changing speed (default true)
isPreservePitchEnabled() {
try {
diff --git a/styles.css b/styles.css
index 49ae84b..e779837 100644
--- a/styles.css
+++ b/styles.css
@@ -2617,6 +2617,65 @@ input[type='search']::-webkit-search-cancel-button {
width: 100px;
}
+.playback-speed-control {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-md);
+}
+
+.playback-speed-slider {
+ appearance: none;
+ width: 150px;
+ height: 6px;
+ background: var(--border);
+ border-radius: var(--radius-full);
+ cursor: pointer;
+ outline: none;
+}
+
+.playback-speed-slider::-webkit-slider-thumb {
+ appearance: none;
+ width: 16px;
+ height: 16px;
+ background: var(--primary);
+ border-radius: 50%;
+ cursor: pointer;
+ transition: transform 0.1s ease;
+}
+
+.playback-speed-slider::-webkit-slider-thumb:hover {
+ transform: scale(1.2);
+}
+
+.playback-speed-number-input {
+ width: 80px;
+ padding: 0.25rem 0.5rem;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--input);
+ color: var(--foreground);
+ text-align: center;
+ font-size: 0.9rem;
+}
+
+.playback-speed-unit {
+ font-size: 0.9rem;
+ color: var(--muted-foreground);
+ min-width: 1rem;
+}
+
+.playback-speed-number-input:focus {
+ outline: none;
+ border-color: var(--primary);
+}
+
+/* Hide arrows/spinners for number input */
+.playback-speed-number-input::-webkit-outer-spin-button,
+.playback-speed-number-input::-webkit-inner-spin-button {
+ appearance: none;
+ margin: 0;
+}
+
.template-input {
width: 100%;
max-width: 400px;