Ported the “Seekbar Preview” feature

This commit is contained in:
root
2023-04-22 20:55:13 +05:30
parent 3fe5a68667
commit 9b265ec15d
4 changed files with 103 additions and 1 deletions
+1
View File
@@ -32,6 +32,7 @@
"marked": "^3.0.8",
"mux.js": "^5.14.1",
"psychic-tiny-keys": "https://bafybeidtcftvyig2w34aodqfiqiige5zgwjntqtt6ofrqrydf3hydwgv4u.ipfs.cf-ipfs.com/?filename=psychic-tiny-keys-v0.0.1.tar.gz",
"quick-lru": "^6.1.1",
"register-service-worker": "^1.7.2",
"sass-loader": "^10",
"shaka-player": "https://f003.backblazeb2.com/file/Bronze8113/shaka-v4.3.4.tar",
+8
View File
@@ -17,3 +17,11 @@
font-style: normal;
}
}
.pm-shaka-preview {
position: absolute;
z-index: 2000;
bottom: 0;
margin-bottom: 4.5%;
border-radius: 0.3rem;
}
+89 -1
View File
@@ -4,6 +4,7 @@
style="width: 100%; height: calc(100vh - 48px); background: #000"
ref="container"
>
<canvas height="130" width="230" ref="preview" class="pm-shaka-preview" />
<video
data-shaka-player
style="min-width: 100%;"
@@ -15,6 +16,7 @@
</template>
<script>
import QuickLRU from 'quick-lru'
import shaka from 'shaka-player/dist/shaka-player.ui.js'
import 'shaka-player/dist/controls.css'
import * as LocaleMatcher from '@formatjs/intl-localematcher'
@@ -34,6 +36,8 @@ if (!window.muxjs) {
})
}
const ImageLRU = new QuickLRU({ maxSize: 8 })
export default {
props: {
video: Object,
@@ -80,8 +84,91 @@ export default {
this.$refs.videoEl.currentTime = timeInSeconds
},
// Most of the below Storyboard code is from Bnyro's PR 2559 in Piped, some minor algorithmic improvements have been added
// The Storyboard setup Start
setupSeekbarPreview () {
if (!this.video.previewFrames) return
const 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', () => {
this.$refs.preview.style.display = 'none'
})
},
async showSeekbarPreview (position) {
const frame = this.getFrame(position)
const originalImage = await this.getOrLoadImage(frame.url)
const seekBar = document.querySelector('.shaka-seek-bar-container')
const canvas = this.$refs.preview
const 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
const 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
async loadImage (url) {
const i = new Image()
i.src = url
await i.decode()
return i
},
// The Storyboard setup End
async getOrLoadImage (url) {
let img = ImageLRU.get(url)
if (img == null) {
img = await this.loadImage(url)
ImageLRU.set(url, img)
}
return img
},
async loadVideo () {
console.log('PIPED | LOADING VIDEO')
console.log('PM | LOADING VIDEO')
const component = this
const videoEl = this.$refs.videoEl
@@ -243,6 +330,7 @@ export default {
const player = this.$ui.getControls().getPlayer()
this.setupSeekbarPreview()
this.$player = player
this.$player.configure({
+5
View File
@@ -5872,6 +5872,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
quick-lru@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-6.1.1.tgz#f8e5bf9010376c126c80c1a62827a526c0e60adf"
integrity sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"