Made media player work properly on touch devices

This commit is contained in:
Vanilagy
2025-09-01 15:50:11 +02:00
parent ee4bfeed90
commit cdb00a3942
3 changed files with 83 additions and 26 deletions
+17 -15
View File
@@ -50,31 +50,33 @@
</div> </div>
</button> </button>
<button class="p-2 ml-4 mr-1" id="volume-button"> <div class="hidden sm:flex items-center">
<div class="size-6 invert group-hover:scale-110" id="volume-icon-wrapper"> <button class="p-2 mr-1 group" id="volume-button">
<img src="../../docs/assets/volume-off-icon.svg" class="size-full"> <div class="size-6 invert group-hover:scale-110" id="volume-icon-wrapper">
<img src="../../docs/assets/volume-x-icon.svg" class="size-full"> <img src="../../docs/assets/volume-off-icon.svg" class="size-full">
<img src="../../docs/assets/volume-0-icon.svg" class="size-full"> <img src="../../docs/assets/volume-x-icon.svg" class="size-full">
<img src="../../docs/assets/volume-1-icon.svg" class="size-full"> <img src="../../docs/assets/volume-0-icon.svg" class="size-full">
<img src="../../docs/assets/volume-2-icon.svg" class="size-full"> <img src="../../docs/assets/volume-1-icon.svg" class="size-full">
</div> <img src="../../docs/assets/volume-2-icon.svg" class="size-full">
</button> </div>
</button>
<div class="w-16 mr-2 relative rounded-full bg-zinc-800/75 h-1.5 group" id="volume-bar-container"> <div class="w-16 relative rounded-full bg-zinc-800/75 h-1.5 group touch-none" id="volume-bar-container">
<div class="absolute h-full top-0 left-0 rounded-full bg-zinc-200 group-hover:bg-white" id="volume-bar"> <div class="absolute h-full top-0 left-0 rounded-full bg-zinc-200 group-hover:bg-white" id="volume-bar">
<div class="size-3 rounded-full bg-zinc-200 absolute top-1/2 right-0 -translate-y-1/2 translate-x-1/2 group-hover:scale-110 group-hover:bg-white group-hover:shadow"></div> <div class="size-3 rounded-full bg-zinc-200 absolute top-1/2 right-0 -translate-y-1/2 translate-x-1/2 group-hover:scale-110 group-hover:bg-white group-hover:shadow"></div>
</div>
</div> </div>
</div> </div>
<p id="current-time" class="tabular-nums w-24 text-right text-sm"></p> <p id="current-time" class="tabular-nums w-10 sm:w-24 text-right text-sm"></p>
<div class="flex-1 relative rounded-full bg-zinc-800/75 h-2 mx-4 group" id="progress-bar-container"> <div class="flex-1 relative rounded-full bg-zinc-800/75 h-2 mx-4 group touch-none" id="progress-bar-container">
<div class="absolute h-full top-0 left-0 rounded-full bg-zinc-200 group-hover:bg-white" id="progress-bar"> <div class="absolute h-full top-0 left-0 rounded-full bg-zinc-200 group-hover:bg-white" id="progress-bar">
<div class="size-4 rounded-full bg-zinc-200 absolute top-1/2 right-0 -translate-y-1/2 translate-x-1/2 group-hover:scale-110 group-hover:bg-white group-hover:shadow"></div> <div class="size-4 rounded-full bg-zinc-200 absolute top-1/2 right-0 -translate-y-1/2 translate-x-1/2 group-hover:scale-110 group-hover:bg-white group-hover:shadow"></div>
</div> </div>
</div> </div>
<p id="duration" class="tabular-nums w-24 text-sm"></p> <p id="duration" class="tabular-nums w-10 sm:w-24 text-sm"></p>
<button class="p-2 group outline-none" id="fullscreen-button"> <button class="p-2 group outline-none" id="fullscreen-button">
<div class="size-6 invert group-hover:scale-110"> <div class="size-6 invert group-hover:scale-110">
+65 -11
View File
@@ -424,23 +424,28 @@ const updateProgressBarTime = (seconds: number) => {
progressBarContainer.addEventListener('pointerdown', (event) => { progressBarContainer.addEventListener('pointerdown', (event) => {
draggingProgressBar = true; draggingProgressBar = true;
progressBarContainer.setPointerCapture(event.pointerId);
const rect = progressBarContainer.getBoundingClientRect(); const rect = progressBarContainer.getBoundingClientRect();
const completion = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0); const completion = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0);
updateProgressBarTime(completion * totalDuration); updateProgressBarTime(completion * totalDuration);
clearTimeout(hideControlsTimeout);
window.addEventListener('pointerup', (event) => { window.addEventListener('pointerup', (event) => {
draggingProgressBar = false; draggingProgressBar = false;
progressBarContainer.releasePointerCapture(event.pointerId);
const rect = progressBarContainer.getBoundingClientRect(); const rect = progressBarContainer.getBoundingClientRect();
const completion = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0); const completion = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0);
const newTime = completion * totalDuration; const newTime = completion * totalDuration;
void seekToTime(newTime); void seekToTime(newTime);
showControlsTemporarily();
}, { once: true }); }, { once: true });
}); });
window.addEventListener('pointermove', (event) => { progressBarContainer.addEventListener('pointermove', (event) => {
if (draggingProgressBar) { if (draggingProgressBar) {
const rect = progressBarContainer.getBoundingClientRect(); const rect = progressBarContainer.getBoundingClientRect();
const completion = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0); const completion = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0);
@@ -465,18 +470,24 @@ const updateVolume = () => {
volumeBarContainer.addEventListener('pointerdown', (event) => { volumeBarContainer.addEventListener('pointerdown', (event) => {
draggingVolumeBar = true; draggingVolumeBar = true;
volumeBarContainer.setPointerCapture(event.pointerId);
const rect = volumeBarContainer.getBoundingClientRect(); const rect = volumeBarContainer.getBoundingClientRect();
volume = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0); volume = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0);
volumeMuted = false; volumeMuted = false;
updateVolume(); updateVolume();
clearTimeout(hideControlsTimeout);
window.addEventListener('pointerup', (event) => { window.addEventListener('pointerup', (event) => {
draggingVolumeBar = false; draggingVolumeBar = false;
volumeBarContainer.releasePointerCapture(event.pointerId);
const rect = volumeBarContainer.getBoundingClientRect(); const rect = volumeBarContainer.getBoundingClientRect();
volume = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0); volume = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0);
updateVolume(); updateVolume();
showControlsTemporarily();
}, { once: true }); }, { once: true });
}); });
@@ -485,7 +496,7 @@ volumeButton.addEventListener('click', () => {
updateVolume(); updateVolume();
}); });
window.addEventListener('pointermove', (event) => { volumeBarContainer.addEventListener('pointermove', (event) => {
if (draggingVolumeBar) { if (draggingVolumeBar) {
const rect = volumeBarContainer.getBoundingClientRect(); const rect = volumeBarContainer.getBoundingClientRect();
volume = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0); volume = Math.max(Math.min((event.clientX - rect.left) / rect.width, 1), 0);
@@ -502,26 +513,43 @@ const showControlsTemporarily = () => {
} }
controlsElement.style.opacity = '1'; controlsElement.style.opacity = '1';
controlsElement.style.pointerEvents = '';
playerContainer.style.cursor = ''; playerContainer.style.cursor = '';
clearTimeout(hideControlsTimeout); clearTimeout(hideControlsTimeout);
hideControlsTimeout = window.setTimeout(() => { hideControlsTimeout = window.setTimeout(() => {
controlsElement.style.opacity = '0'; if (draggingProgressBar) {
return;
}
hideControls();
playerContainer.style.cursor = 'none'; playerContainer.style.cursor = 'none';
}, 2000); }, 2000);
}; };
const hideControls = () => {
controlsElement.style.opacity = '0';
controlsElement.style.pointerEvents = 'none';
};
hideControls();
let hideControlsTimeout = -1; let hideControlsTimeout = -1;
playerContainer.addEventListener('pointermove', () => { playerContainer.addEventListener('pointermove', (event) => {
showControlsTemporarily(); if (event.pointerType !== 'touch') {
showControlsTemporarily();
}
}); });
playerContainer.addEventListener('pointerleave', () => { playerContainer.addEventListener('pointerleave', (event) => {
if (!videoSink) { if (!videoSink) {
// Shouldn't run if there's only an audio track // Shouldn't run if there's only an audio track
return; return;
} }
controlsElement.style.opacity = '0'; if (draggingProgressBar || draggingVolumeBar || event.pointerType === 'touch') {
return;
}
hideControls();
clearTimeout(hideControlsTimeout); clearTimeout(hideControlsTimeout);
}); });
@@ -563,17 +591,35 @@ fullscreenButton.addEventListener('click', () => {
} }
}); });
// I'm sorry for this
const isTouchDevice = () => {
return (('ontouchstart' in window)
|| (navigator.maxTouchPoints > 0)
|| ('msMaxTouchPoints' in navigator && (navigator.msMaxTouchPoints as number) > 0));
};
playerContainer.addEventListener('click', () => { playerContainer.addEventListener('click', () => {
togglePlay(); if (isTouchDevice()) {
if (controlsElement.style.opacity === '1') {
hideControls();
} else {
showControlsTemporarily();
}
} else {
togglePlay();
}
}); });
controlsElement.addEventListener('click', (event) => { controlsElement.addEventListener('click', (event) => {
// Make sure this does NOT toggle play // Make sure this does NOT toggle play
event.stopPropagation(); event.stopPropagation();
showControlsTemporarily();
}); });
/** === UTILS === */ /** === UTILS === */
const formatSeconds = (seconds: number) => { const formatSeconds = (seconds: number) => {
const showMilliseconds = window.innerWidth >= 640;
seconds = Math.round(seconds * 1000) / 1000; // Round to milliseconds seconds = Math.round(seconds * 1000) / 1000; // Round to milliseconds
const hours = Math.floor(seconds / 3600); const hours = Math.floor(seconds / 3600);
@@ -581,11 +627,19 @@ const formatSeconds = (seconds: number) => {
const remainingSeconds = Math.floor(seconds % 60); const remainingSeconds = Math.floor(seconds % 60);
const millisecs = Math.floor(1000 * seconds % 1000).toString().padStart(3, '0'); const millisecs = Math.floor(1000 * seconds % 1000).toString().padStart(3, '0');
let result: string;
if (hours > 0) { if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}` result = `${hours}:${minutes.toString().padStart(2, '0')}`
+ `:${remainingSeconds.toString().padStart(2, '0')}.${millisecs}`; + `:${remainingSeconds.toString().padStart(2, '0')}`;
} else {
result = `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
} }
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}.${millisecs}`;
if (showMilliseconds) {
result += `.${millisecs}`;
}
return result;
}; };
/** === FILE SELECTION LOGIC === */ /** === FILE SELECTION LOGIC === */
+1
View File
@@ -28,6 +28,7 @@ export default defineConfig({
], ],
server: { server: {
hmr: false, hmr: false,
allowedHosts: true,
}, },
build: { build: {
outDir: 'dist-docs', // Build them directly into the docs build folder outDir: 'dist-docs', // Build them directly into the docs build folder