mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-28 03:13:51 +02:00
Made media player work properly on touch devices
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
@@ -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 === */
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user