// ==UserScript==
// @name Enhanced Audio Speed Controller with Time Info, Speed Highlight, Font Size Control, and Toggle
// @namespace http://tampermonkey.net/
// @version 3.3
// @description Adds time information (duration, currentTime, etc.), adjusts for playback speed, highlights the active speed button, toggle for hiding/showing the control panel, and font size control.
// @author You
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let startTime = Date.now(); // Track the real-time start
let isPanelVisible = true; // Track panel visibility
let currentFontSize = 7; // Initial font size in pt
const odIcon = '🕰️';
const adIcon = '⏰';
const ctIcon = '⌚';
const pcIcon = '➗';
const trIcon = '⏳';
const wcIcon = '🕛';
// Helper function to convert fractional minutes into hh:mm:ss format
function convertToTimeFormat(minutes) {
const totalSeconds = Math.floor(minutes * 60); // Convert minutes to seconds
const hours = Math.floor(totalSeconds / 3600); // Calculate full hours
const remainingSeconds = totalSeconds % 3600; // Remaining seconds after hours
const mins = Math.floor(remainingSeconds / 60); // Full minutes
const secs = remainingSeconds % 60; // Remaining seconds
const formattedTime =
(hours > 9 ? hours : '0' + hours) + ':' +
(mins > 9 ? mins : '0' + mins) + ':' +
(secs > 9 ? secs : '0' + secs);
return formattedTime;
}
// Function to calculate and update time stats
function updateTimeStats() {
const audioElement = document.querySelector('audio');
if (audioElement && audioElement.duration && !isNaN(audioElement.duration)) {
const duration = audioElement.duration;
const adjustedDuration = audioElement.duration / audioElement.playbackRate / 60;
const currentTime = audioElement.currentTime;
const playbackRate = audioElement.playbackRate;
const currentTimeDisp = currentTime / 60 / playbackRate;
const percentComplete = (currentTime / duration) * 100;
const timeRemaining = (adjustedDuration - currentTimeDisp);
const elapsedWallClockTime = (Date.now() - startTime) / 1000 / 60;
document.getElementById('original-duration').innerHTML = `${odIcon}<br>${convertToTimeFormat(duration / 60)}`;
document.getElementById('adjusted-duration').innerHTML = `${adIcon}<br>${convertToTimeFormat(adjustedDuration)}`;
document.getElementById('current-time').innerHTML = `${ctIcon}<br>${convertToTimeFormat(currentTimeDisp)}`;
document.getElementById('percent-complete').innerHTML = `<span class="rotate">${pcIcon}</span><br>${percentComplete.toFixed(2)}%`;
document.getElementById('time-remaining').innerHTML = `${trIcon}<br>${convertToTimeFormat(timeRemaining)}`;
document.getElementById('elapsed-wall-clock').innerHTML = `${wcIcon}<br>${convertToTimeFormat(elapsedWallClockTime)}`;
}
}
// Function to create the control panel with buttons and time information
function createControlPanel() {
if (document.getElementById('audio-speed-control')) return;
const controlDiv = document.createElement('div');
controlDiv.id = 'audio-speed-control';
controlDiv.style.position = 'fixed';
controlDiv.style.top = '20%';
controlDiv.style.right = '0';
controlDiv.style.background = 'rgba(0, 0, 0, 0.05)';
controlDiv.style.padding = '5px';
controlDiv.style.borderRadius = '5px';
controlDiv.style.zIndex = '999999';
controlDiv.style.display = 'flex';
controlDiv.style.flexDirection = 'column';
controlDiv.style.fontSize = `${currentFontSize}pt`;
controlDiv.style.transition = 'transform 0.5s ease';
const timeStats = document.createElement('div');
timeStats.style.marginBottom = '4px';
timeStats.style.fontSize = '6pt';
timeStats.style.color = 'black';
timeStats.style.fontWeight = "bold";
timeStats.style.textAlign = 'center';
const timeStatsData = [
{ id: 'original-duration', label: odIcon },
{ id: 'adjusted-duration', label: adIcon },
{ id: 'current-time', label: ctIcon },
{ id: 'percent-complete', label: pcIcon },
{ id: 'time-remaining', label: trIcon },
{ id: 'elapsed-wall-clock', label: wcIcon }
];
timeStatsData.forEach(stat => {
const statDiv = document.createElement('div');
statDiv.id = stat.id;
statDiv.innerHTML = stat.label + "<br>--:--:--";
timeStats.appendChild(statDiv);
});
controlDiv.appendChild(timeStats);
// Create PLAY button
const playButton = document.createElement('button');
playButton.innerText = '▶'; // Label with play symbol
playButton.style.padding = '3px 5px';
playButton.style.marginBottom = '2px';
playButton.style.backgroundColor = '#bada55';
playButton.style.border = 'none';
playButton.style.borderRadius = '3px';
playButton.style.cursor = 'pointer';
playButton.style.fontSize = '8px';
playButton.style.fontWeight = 'bold';
playButton.style.color = '#222';
playButton.style.width = '36px';
playButton.addEventListener('click', function () {
const audioElement = document.querySelector('audio');
if (audioElement) {
let attempts = 25;
audioElement.play(); // Initial attempt
const playInterval = setInterval(() => {
if (attempts > 0) {
audioElement.play(); // Force play again
attempts--;
} else {
clearInterval(playInterval);
}
}, 125);
}
});
controlDiv.appendChild(playButton);
// Create PAUSE button
const pauseButton = document.createElement('button');
pauseButton.innerText = '||'; // Label with pause symbol
pauseButton.style.padding = '3px 5px';
pauseButton.style.marginBottom = '2px';
pauseButton.style.backgroundColor = '#bada55';
pauseButton.style.border = 'none';
pauseButton.style.borderRadius = '3px';
pauseButton.style.cursor = 'pointer';
pauseButton.style.fontSize = '8px';
pauseButton.style.fontWeight = 'bold';
pauseButton.style.color = '#222';
pauseButton.style.width = '36px';
pauseButton.addEventListener('click', function () {
const audioElement = document.querySelector('audio');
if (audioElement) {
let attempts = 25;
audioElement.pause(); // Initial attempt
const pauseInterval = setInterval(() => {
if (attempts > 0) {
audioElement.pause(); // Force pause again
attempts--;
} else {
clearInterval(pauseInterval);
}
}, 125);
}
});
controlDiv.appendChild(pauseButton);
/*
// Ceate PAUoE button
cst pauseButton = document.createElement('button');
pauseButton.innerText = '||'; // Label with pause symbol
pauseButton.style.padding = '3px 5px';
pauseButton.style.marginBottom = '2px';
pauseButton.style.backgroundColor = '#bada55';
pauseButton.style.border = 'none';
pauseButton.style.borderRadius = '3px';
pauseButton.style.cursor = 'pointer';
pauseButton.style.fontSize = '8px';
= 'bold';
= '#222';
');
if (audioElement) {
const targetState = audioElement.paused; // Determine the desired state
function tryTogglePlayPause() {
if (targetState) {
audioElement.play().catch(error => {
console.error("Error playing audio:", error);
setTimeout(tryTogglePlayPause, 100);
});
pauseButton.innerText = '||';
} else {
audioElement.pause();
pauseButton.innerText = '▶';
}
}
tryTogglePlayPause(); // Initial attempt
if (targetState) {
// If the target state is playing, schedule retries
setTimeout(tryTogglePlayPause, 100);
}
}
});
// pauseButton.addEventListener('click', function () {
// const audioElement = document.querySelector('audio');
// if (audioElement) {
// if (audioElement.paused) {
// audioElement.play();
// pauseButton.innerText = '||';
// } else {
// audioElement.pause();
// pauseButton.innerText = '▶';
// }
// }
// });
// controlDiv.appendChild(pauseButton);
*/
const speeds = [1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5, 3.75, 4.0, 4.5, 5.0];
let activeButton = null;
speeds.forEach(speed => {
const button = document.createElement('button');
button.innerText = speed.toFixed(2);
button.style.padding = '3px 5px';
button.style.marginBottom = '2px';
button.style.backgroundColor = '#bada55';
button.style.border = 'none';
button.style.borderRadius = '3px';
button.style.cursor = 'pointer';
button.style.fontSize = '8px';
button.style.fontWeight = 'bold';
button.style.color = '#222';
button.style.width = '36px';
function setActiveButton() {
if (activeButton) {
activeButton.style.backgroundColor = '#bada55';
activeButton.style.color = '#222';
activeButton.style.fontWeight = 'normal';
}
button.style.backgroundColor = '#006400';
button.style.color = '#fff';
button.style.fontWeight = 'bold';
activeButton = button;
}
button.addEventListener('click', function () {
const audioElement = document.querySelector('audio');
if (audioElement) {
audioElement.playbackRate = speed;
setActiveButton();
}
});
controlDiv.appendChild(button);
});
const toggleButton = document.createElement('button');
toggleButton.innerText = '◀';
toggleButton.style.position = 'absolute';
toggleButton.style.top = '5px';
toggleButton.style.left = '-15px';
toggleButton.style.backgroundColor = '#bada55';
toggleButton.style.border = 'none';
toggleButton.style.borderRadius = '20%';
toggleButton.style.cursor = 'pointer';
toggleButton.style.padding = '2px';
toggleButton.style.zIndex = '1000';
toggleButton.addEventListener('click', () => {
if (isPanelVisible) {
controlDiv.style.transform = 'translateX(100%)';
toggleButton.innerText = '▶';
} else {
controlDiv.style.transform = 'translateX(0)';
toggleButton.innerText = '◀';
}
isPanelVisible = !isPanelVisible;
});
controlDiv.appendChild(toggleButton);
// Font size control
const fontSizeControl = document.createElement('div');
fontSizeControl.style.marginTop = '4px';
const fontIncreaseButton = document.createElement('button');
fontIncreaseButton.innerText = '⬆';
fontIncreaseButton.style.margin = '2px';
fontIncreaseButton.style.padding = '3px';
fontIncreaseButton.style.cursor = 'pointer';
fontIncreaseButton.addEventListener('click', () => {
currentFontSize += 2;
console.log("Size: " + currentFontSize);
controlDiv.style.fontSize = `${currentFontSize}pt`;
});
const fontDecreaseButton = document.createElement('button');
fontDecreaseButton.innerText = '⬇';
fontDecreaseButton.style.margin = '2px';
fontDecreaseButton.style.padding = '3px';
fontDecreaseButton.style.cursor = 'pointer';
fontDecreaseButton.addEventListener('click', () => {
currentFontSize = Math.max(4, currentFontSize - 2); // Prevent font size from going too small
controlDiv.style.fontSize = `${currentFontSize}pt`;
});
fontSizeControl.appendChild(fontIncreaseButton);
fontSizeControl.appendChild(fontDecreaseButton);
controlDiv.appendChild(fontSizeControl);
document.body.appendChild(controlDiv);
}
const style = document.createElement('style');
style.innerHTML = `
.rotate {
display: inline-block;
transform: rotate(45deg); /* Rotates emoji */
}
`;
document.head.appendChild(style);
// Update the time stats periodically
setInterval(updateTimeStats, 1000); // Update every second
// Wait for the document to fully load and ensure audio element exists
const observer = new MutationObserver((mutations, observer) => {
const audioElement = document.querySelector('audio');
if (audioElement) {
console.log("Chapter duration:");
console.log(audioElement.duration / 60);
audioElement.addEventListener('loadedmetadata', () => {
audioElement.playbackRate = 2.0; // Set default playback speed to 2.0
createControlPanel();
updateTimeStats();
});
observer.disconnect(); // Stop observing once the audio is found
}
});
// Start observing the document for changes
observer.observe(document, {
childList: true,
subtree: true
});
})();