mirror of
https://github.com/TeamPiped/Piped.git
synced 2024-08-14 23:57:27 +00:00
Add a seekbar thumbnail preview
This commit is contained in:
parent
bfd859ebe7
commit
367bb0f0a0
1 changed files with 80 additions and 0 deletions
|
@ -6,6 +6,7 @@
|
||||||
:class="{ 'player-container': !isEmbed }"
|
:class="{ 'player-container': !isEmbed }"
|
||||||
>
|
>
|
||||||
<video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
|
<video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
|
||||||
|
<canvas height="130" width="230" id="preview" />
|
||||||
<button
|
<button
|
||||||
v-if="inSegment"
|
v-if="inSegment"
|
||||||
class="skip-segment-button"
|
class="skip-segment-button"
|
||||||
|
@ -512,6 +513,8 @@ export default {
|
||||||
|
|
||||||
const player = this.$ui.getControls().getPlayer();
|
const player = this.$ui.getControls().getPlayer();
|
||||||
|
|
||||||
|
this.setupSeekbarPreview();
|
||||||
|
|
||||||
this.$player = player;
|
this.$player = player;
|
||||||
|
|
||||||
const disableVideo = this.getPreferenceBoolean("listen", false) && !this.video.livestream;
|
const disableVideo = this.getPreferenceBoolean("listen", false) && !this.video.livestream;
|
||||||
|
@ -710,6 +713,75 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setupSeekbarPreview() {
|
||||||
|
if (!this.video.previewFrames) return;
|
||||||
|
let seekBar = document.querySelector(".shaka-seek-bar-container");
|
||||||
|
// load the thumbnail preview when the user moves over the seekbar
|
||||||
|
seekBar.addEventListener("mousemove", e => {
|
||||||
|
const position = (this.video.duration * e.clientX) / seekBar.clientWidth;
|
||||||
|
this.showSeekbarPreview(position * 1000);
|
||||||
|
});
|
||||||
|
// hide the preview when the user stops hovering the seekbar
|
||||||
|
seekBar.addEventListener("mouseout", () => {
|
||||||
|
let canvas = document.querySelector("#preview");
|
||||||
|
canvas.style.display = "none";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async showSeekbarPreview(position) {
|
||||||
|
let frame = this.getFrame(position);
|
||||||
|
let originalImage = await this.loadImage(frame.url);
|
||||||
|
let seekBar = document.querySelector(".shaka-seek-bar-container");
|
||||||
|
let canvas = document.querySelector("#preview");
|
||||||
|
let ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
// get the new sizes for the image to be drawn into the canvas
|
||||||
|
const originalWidth = originalImage.naturalWidth;
|
||||||
|
const originalHeight = originalImage.naturalHeight;
|
||||||
|
const offsetX = originalWidth * (frame.positionX / frame.framesPerPageX);
|
||||||
|
const offsetY = originalHeight * (frame.positionY / frame.framesPerPageY);
|
||||||
|
const newWidth = originalWidth / frame.framesPerPageX;
|
||||||
|
const newHeight = originalHeight / frame.framesPerPageY;
|
||||||
|
|
||||||
|
// draw the thumbnail preview into the canvas by cropping only the relevant part
|
||||||
|
ctx.drawImage(originalImage, offsetX, offsetY, newWidth, newHeight, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// calculate the thumbnail preview offset and display it
|
||||||
|
const centerOffset = position / this.video.duration / 10;
|
||||||
|
const left = centerOffset - (canvas.width / seekBar.clientWidth / 1.3) * 100;
|
||||||
|
canvas.style.left = `max(2%, min(${left}%, 90%))`;
|
||||||
|
canvas.style.display = "block";
|
||||||
|
},
|
||||||
|
// ineffective algorithm to find the thumbnail corresponding to the currently hovered position in the video
|
||||||
|
getFrame(position) {
|
||||||
|
let startPosition = 0;
|
||||||
|
let framePage = this.video.previewFrames.at(-1);
|
||||||
|
for (let i = 0; i < framePage.urls.length; i++) {
|
||||||
|
for (let positionY = 0; positionY < framePage.framesPerPageY; positionY++) {
|
||||||
|
for (let positionX = 0; positionX < framePage.framesPerPageX; positionX++) {
|
||||||
|
const endPosition = startPosition + framePage.durationPerFrame;
|
||||||
|
if (position >= startPosition && position <= endPosition) {
|
||||||
|
return {
|
||||||
|
url: framePage.urls[i],
|
||||||
|
positionX: positionX,
|
||||||
|
positionY: positionY,
|
||||||
|
framesPerPageX: framePage.framesPerPageX,
|
||||||
|
framesPerPageY: framePage.framesPerPageY,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
startPosition = endPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
// creates a new image from an URL
|
||||||
|
loadImage(url) {
|
||||||
|
return new Promise(r => {
|
||||||
|
let i = new Image();
|
||||||
|
i.onload = () => r(i);
|
||||||
|
i.src = url;
|
||||||
|
});
|
||||||
|
},
|
||||||
destroy(hotkeys) {
|
destroy(hotkeys) {
|
||||||
if (this.$ui && !document.pictureInPictureElement) {
|
if (this.$ui && !document.pictureInPictureElement) {
|
||||||
this.$ui.destroy();
|
this.$ui.destroy();
|
||||||
|
@ -789,4 +861,12 @@ export default {
|
||||||
font-size: 1.6em !important;
|
font-size: 1.6em !important;
|
||||||
line-height: inherit !important;
|
line-height: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#preview {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2000;
|
||||||
|
bottom: 0;
|
||||||
|
margin-bottom: 4.5%;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue