mirror of
https://github.com/VueTubeApp/VueTube
synced 2024-11-01 01:12:39 +00:00
player split
This commit is contained in:
parent
c41c66fa5b
commit
136ed86afa
12 changed files with 373 additions and 423 deletions
5
NUXT/components/Player/captions.vue
Normal file
5
NUXT/components/Player/captions.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<v-btn fab text style="position: absolute; top: 0; right: 3.5rem">
|
||||
<v-icon>mdi-closed-caption-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
|
@ -1,116 +0,0 @@
|
|||
<template>
|
||||
<div class="controls" @click="toggleControls()">
|
||||
<div ref="controlsWrap" class="controlsWrap">
|
||||
<div class="centerVideoControls">
|
||||
<v-btn
|
||||
text
|
||||
style="height: 5em; width: 5em"
|
||||
color="white"
|
||||
@click="togglePlaying()"
|
||||
>
|
||||
<v-icon
|
||||
ref="pausePlayIndicator"
|
||||
size="5rem"
|
||||
v-text="playing ? 'mdi-pause' : 'mdi-play'"
|
||||
/>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div class="bottomVideoControls white--text">
|
||||
<div class="pl-4">
|
||||
{{ watched }}
|
||||
<span style="color: #999">
|
||||
/ {{ $vuetube.humanTime(video.duration) }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <v-slider
|
||||
id="slider"
|
||||
v-model="video.currentTime"
|
||||
:max="video.duration"
|
||||
style="margin-bottom: -2rem !important"
|
||||
/> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
playing: true,
|
||||
controls: true,
|
||||
|
||||
watched: 0,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
console.log("videovideovideo");
|
||||
console.log(this.video);
|
||||
this.video.ontimeupdate = () => {
|
||||
console.log(this.video.currentTime);
|
||||
this.watched = this.$vuetube.humanTime(this.video.currentTime);
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
togglePlaying() {
|
||||
if (this.video.paused) {
|
||||
this.video.play();
|
||||
this.playing = true;
|
||||
} else {
|
||||
this.video.pause();
|
||||
this.playing = false;
|
||||
}
|
||||
this.toggleControls(); // Prevent Screen From Closing
|
||||
},
|
||||
|
||||
toggleControls() {
|
||||
const setControls = this.controls ? "none" : "block";
|
||||
this.$refs.controlsWrap.style.display = setControls;
|
||||
this.controls = !this.controls;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#slider * {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.centerVideoControls {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.bottomVideoControls {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.pausePlay {
|
||||
min-height: 5em;
|
||||
min-width: 5em;
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.controlsWrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
17
NUXT/components/Player/fullscreen.vue
Normal file
17
NUXT/components/Player/fullscreen.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
style="position: absolute; bottom: 0.25rem; right: 0"
|
||||
@click.stop="$emit('fullscreen')"
|
||||
>
|
||||
<v-icon>{{ fullscreen ? "mdi-fullscreen-exit" : "mdi-fullscreen" }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["fullscreen"],
|
||||
emits: ["fullscreen"],
|
||||
};
|
||||
</script>
|
|
@ -39,7 +39,7 @@
|
|||
height: 100%;
|
||||
opacity: 0;
|
||||
"
|
||||
@doubleclick.stop="$refs.player.currentTime -= $refs.player.duration / 10"
|
||||
@dbclick.stop="$refs.player.currentTime -= $refs.player.duration / 10"
|
||||
>
|
||||
<v-icon>mdi-rewind</v-icon>
|
||||
</v-btn>
|
||||
|
@ -55,7 +55,7 @@
|
|||
height: 100%;
|
||||
opacity: 0;
|
||||
"
|
||||
@doubleclick.stop="$refs.player.currentTime += $refs.player.duration / 10"
|
||||
@dbclick.stop="$refs.player.currentTime += $refs.player.duration / 10"
|
||||
>
|
||||
<v-icon>mdi-fast-forward</v-icon>
|
||||
</v-btn>
|
||||
|
@ -67,318 +67,68 @@
|
|||
<v-btn fab text style="position: absolute; top: 0; left: 0">
|
||||
<v-icon>mdi-chevron-down</v-icon>
|
||||
</v-btn>
|
||||
<v-btn fab text style="position: absolute; top: 0; right: 7rem">
|
||||
<v-icon>mdi-sync</v-icon>
|
||||
</v-btn>
|
||||
<v-btn fab text style="position: absolute; top: 0; right: 3.5rem">
|
||||
<v-icon>mdi-closed-caption-outline</v-icon>
|
||||
</v-btn>
|
||||
<loop />
|
||||
<captions />
|
||||
<v-btn fab text style="position: absolute; top: 0; right: 0">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
<playpause
|
||||
v-if="$refs.player"
|
||||
fab
|
||||
text
|
||||
style="
|
||||
height: 5rem;
|
||||
width: 5rem;
|
||||
position: absolute;
|
||||
top: calc(50% - 2.5rem);
|
||||
left: calc(50% - 2.5rem);
|
||||
"
|
||||
color="white"
|
||||
@click.stop="
|
||||
$refs.player.paused
|
||||
? ($refs.player.play(), (controls = false))
|
||||
: $refs.player.pause()
|
||||
"
|
||||
>
|
||||
<v-icon
|
||||
ref="pausePlayIndicator"
|
||||
size="5rem"
|
||||
v-text="$refs.player.paused ? 'mdi-play' : 'mdi-pause'"
|
||||
:video="$refs.player"
|
||||
@close="controls = false"
|
||||
/>
|
||||
</v-btn>
|
||||
|
||||
<div
|
||||
v-if="$refs.player"
|
||||
style="position: absolute; bottom: 1.25rem; left: 1.25rem"
|
||||
>
|
||||
{{ watched }}
|
||||
<span style="color: #999"> / {{ total }} </span>
|
||||
</div>
|
||||
<!-- <v-btn fab text style="position: absolute; bottom: 0.25rem; right: 7rem">
|
||||
1X
|
||||
</v-btn>
|
||||
<v-btn fab text style="position: absolute; bottom: 0.25rem; right: 3rem">
|
||||
HD
|
||||
</v-btn> -->
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
style="position: absolute; bottom: 0.25rem; right: 0"
|
||||
@click.stop="(controls = $refs.player.paused), handleFullscreenChange()"
|
||||
>
|
||||
<v-icon>{{
|
||||
isFullscreen ? "mdi-fullscreen-exit" : "mdi-fullscreen"
|
||||
}}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div>
|
||||
<video
|
||||
ref="playerfake"
|
||||
muted
|
||||
autoplay
|
||||
style="display: none"
|
||||
:src="vidWrs"
|
||||
<watchtime v-if="$refs.player" :video="$refs.player" />
|
||||
<quality />
|
||||
<speed />
|
||||
<fullscreen
|
||||
:fullscreen="isFullscreen"
|
||||
@fullscreen="(controls = $refs.player.paused), handleFullscreenChange()"
|
||||
/>
|
||||
<v-progress-linear
|
||||
query
|
||||
active
|
||||
style="width: 100%"
|
||||
background-opacity="0.5"
|
||||
background-color="primary"
|
||||
:buffer-value="buffered"
|
||||
:value="percent"
|
||||
color="primary"
|
||||
height="2"
|
||||
/>
|
||||
<!-- Scrubber -->
|
||||
<v-slider
|
||||
v-if="$refs.player"
|
||||
hide-details
|
||||
height="2"
|
||||
dense
|
||||
style="
|
||||
position: absolute;
|
||||
z-index: 69420;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
"
|
||||
:thumb-size="0"
|
||||
:max="duration"
|
||||
:value="progress"
|
||||
@start="scrubbing = true"
|
||||
@end="scrubbing = false"
|
||||
@change="scrub($event)"
|
||||
@input="seek($event)"
|
||||
>
|
||||
<!-- :thumb-size="$refs.player.clientHeight / 3" -->
|
||||
<template #thumb-label="{ value }">
|
||||
<div style="transform: translateY(-50%)">
|
||||
<canvas
|
||||
ref="preview"
|
||||
class="white"
|
||||
:width="$refs.player.clientWidth / 3"
|
||||
:height="$refs.player.clientHeight / 3"
|
||||
style="border: 2px solid white"
|
||||
:style="{
|
||||
borderRadius: $store.state.tweaks.roundWatch
|
||||
? `${$store.state.tweaks.roundTweak / 3}rem`
|
||||
: '0',
|
||||
}"
|
||||
></canvas>
|
||||
<div class="text-center pb-4" style="font-size: 0.8rem">
|
||||
<b>{{ $vuetube.humanTime(value) }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-slider>
|
||||
</div>
|
||||
<seekbar v-if="$refs.player" :sources="sources" :video="$refs.player" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import loop from "~/components/Player/loop.vue";
|
||||
import speed from "~/components/Player/speed.vue";
|
||||
import seekbar from "~/components/Player/seekbar.vue";
|
||||
import controls from "~/components/Player/controls.vue";
|
||||
|
||||
import quality from "~/components/Player/quality.vue";
|
||||
import captions from "~/components/Player/captions.vue";
|
||||
import playpause from "~/components/Player/playpause.vue";
|
||||
import watchtime from "~/components/Player/watchtime.vue";
|
||||
import fullscreen from "~/components/Player/fullscreen.vue";
|
||||
export default {
|
||||
components: {
|
||||
fullscreen,
|
||||
watchtime,
|
||||
playpause,
|
||||
captions,
|
||||
quality,
|
||||
seekbar,
|
||||
controls,
|
||||
speed,
|
||||
loop,
|
||||
},
|
||||
props: {
|
||||
sources: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
props: ["sources"],
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false,
|
||||
scrubbing: false,
|
||||
controls: false,
|
||||
percent: 0,
|
||||
progress: 0,
|
||||
buffered: 0,
|
||||
duration: 0,
|
||||
watched: 0,
|
||||
total: 0,
|
||||
vidSrc: "",
|
||||
vidWrs: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
console.log("sources", this.sources);
|
||||
this.vidSrc = this.sources[this.sources.length - 1].url;
|
||||
this.vidWrs = this.sources[1].url;
|
||||
let vid = this.$refs.player;
|
||||
vid.addEventListener("loadeddata", (e) => {
|
||||
console.log("%c loadeddata", "color: #00ff00");
|
||||
console.log(e);
|
||||
//Video should now be loaded but we can add a second check
|
||||
if (vid.readyState >= 3) {
|
||||
vid.ontimeupdate = () => {
|
||||
console.log("%c timeupdate", "color: #aaaaff");
|
||||
this.duration = vid.duration;
|
||||
if (!this.scrubbing) this.progress = vid.currentTime;
|
||||
this.percent = (vid.currentTime / vid.duration) * 100;
|
||||
this.watched = this.$vuetube.humanTime(vid.currentTime);
|
||||
this.total = this.$vuetube.humanTime(vid.duration);
|
||||
};
|
||||
vid.onprogress = () => {
|
||||
console.log("%c progress", "color: #ff00ff");
|
||||
this.buffered = (vid.buffered.end(0) / vid.duration) * 100;
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
loadVideoFrames() {
|
||||
// Exit loop if desired number of frames have been extracted
|
||||
if (this.frames.length >= frameCount) {
|
||||
this.visibleFrame = 0;
|
||||
|
||||
// Append all canvases to container div
|
||||
this.frames.forEach((frame) => {
|
||||
this.frameContainerElement.appendChild(frame);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If extraction hasn’t started, set desired time for first frame
|
||||
if (this.frames.length === 0) {
|
||||
this.requestedTime = 0;
|
||||
} else {
|
||||
this.requestedTime = this.requestedTime + this.frameTimestep;
|
||||
}
|
||||
|
||||
// Send seek request to video player for the next frame.
|
||||
this.videoElement.currentTime = this.requestedTime;
|
||||
},
|
||||
extractFrame(videoWidth, videoHeight) {
|
||||
// Create DOM canvas object
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.className = "video-scrubber-frame";
|
||||
canvas.height = videoHeight;
|
||||
canvas.width = videoWidth;
|
||||
|
||||
// Copy current frame to canvas
|
||||
var context = canvas.getContext("2d");
|
||||
context.drawImage(this.videoElement, 0, 0, videoWidth, videoHeight);
|
||||
this.frames.push(canvas);
|
||||
|
||||
// Load the next frame
|
||||
loadVideoFrames();
|
||||
},
|
||||
prefetch_file(url, fetched_callback, progress_callback, error_callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, true);
|
||||
xhr.responseType = "blob";
|
||||
|
||||
xhr.addEventListener(
|
||||
"load",
|
||||
function () {
|
||||
if (xhr.status === 200) {
|
||||
var URL = window.URL || window.webkitURL;
|
||||
var blob_url = URL.createObjectURL(xhr.response);
|
||||
fetched_callback(blob_url);
|
||||
} else {
|
||||
error_callback();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
var prev_pc = 0;
|
||||
xhr.addEventListener("progress", function (event) {
|
||||
if (event.lengthComputable) {
|
||||
var pc = Math.round((event.loaded / event.total) * 100);
|
||||
if (pc != prev_pc) {
|
||||
prev_pc = pc;
|
||||
progress_callback(pc);
|
||||
}
|
||||
}
|
||||
});
|
||||
xhr.send();
|
||||
},
|
||||
async extractFramesFromVideo(videoUrl, fps = 25) {
|
||||
// fully download it first (no buffering):
|
||||
console.log(videoUrl);
|
||||
console.log(fps);
|
||||
let videoBlob = await fetch(videoUrl, {
|
||||
headers: { range: "bytes=0-567139" },
|
||||
}).then((r) => r.blob());
|
||||
console.log(videoBlob);
|
||||
let videoObjectUrl = URL.createObjectURL(videoBlob);
|
||||
let video = document.createElement("video");
|
||||
|
||||
let seekResolve;
|
||||
video.addEventListener("seeked", async function () {
|
||||
if (seekResolve) seekResolve();
|
||||
});
|
||||
|
||||
video.src = videoObjectUrl;
|
||||
|
||||
// workaround chromium metadata bug (https://stackoverflow.com/q/38062864/993683)
|
||||
while (
|
||||
(video.duration === Infinity || isNaN(video.duration)) &&
|
||||
video.readyState < 2
|
||||
) {
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
video.currentTime = 10000000 * Math.random();
|
||||
}
|
||||
let duration = video.duration;
|
||||
|
||||
let canvas = document.createElement("canvas");
|
||||
let context = canvas.getContext("2d");
|
||||
let [w, h] = [video.videoWidth, video.videoHeight];
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
|
||||
let interval = 1;
|
||||
let currentTime = 0;
|
||||
|
||||
while (currentTime < duration) {
|
||||
video.currentTime = currentTime;
|
||||
await new Promise((r) => (seekResolve = r));
|
||||
|
||||
context.drawImage(video, 0, 0, w, h);
|
||||
let base64ImageData = canvas.toDataURL();
|
||||
console.log(base64ImageData);
|
||||
this.frames.push(base64ImageData);
|
||||
|
||||
currentTime += interval;
|
||||
}
|
||||
console.log("%c frames", "color: #00ff00");
|
||||
console.log(this.frames);
|
||||
},
|
||||
seek(e) {
|
||||
console.log(`scrubbing ${e}`);
|
||||
let vid = this.$refs.playerfake;
|
||||
let canvas = this.$refs.preview;
|
||||
this.$refs.playerfake.currentTime = e;
|
||||
canvas
|
||||
.getContext("2d")
|
||||
.drawImage(
|
||||
vid,
|
||||
0,
|
||||
0,
|
||||
this.$refs.player.clientWidth / 3,
|
||||
this.$refs.player.clientHeight / 3
|
||||
);
|
||||
},
|
||||
scrub(e) {
|
||||
this.$refs.player.currentTime = e;
|
||||
},
|
||||
handleFullscreenChange() {
|
||||
console.log(this.$refs.player);
|
||||
console.log(document.fullscreenElement);
|
||||
|
|
5
NUXT/components/Player/loop.vue
Normal file
5
NUXT/components/Player/loop.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<v-btn fab text style="position: absolute; top: 0; right: 7rem">
|
||||
<v-icon>mdi-sync</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
32
NUXT/components/Player/playpause.vue
Normal file
32
NUXT/components/Player/playpause.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
style="
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
position: absolute;
|
||||
top: calc(50% - 2.5rem);
|
||||
left: calc(50% - 2.5rem);
|
||||
"
|
||||
color="white"
|
||||
@click.stop="
|
||||
(paused = !video.paused),
|
||||
video.paused ? (video.play(), $emit('close')) : video.pause()
|
||||
"
|
||||
>
|
||||
<v-icon size="5rem">
|
||||
{{ paused ? "mdi-play" : "mdi-pause" }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
emits: ["close"],
|
||||
data: () => ({
|
||||
paused: false,
|
||||
}),
|
||||
};
|
||||
</script>
|
5
NUXT/components/Player/quality.vue
Normal file
5
NUXT/components/Player/quality.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<v-btn fab text style="position: absolute; bottom: 0.25rem; right: 3.5rem">
|
||||
HD
|
||||
</v-btn>
|
||||
</template>
|
|
@ -1,34 +1,243 @@
|
|||
<template>
|
||||
<v-progress-linear
|
||||
active
|
||||
background-color="primary"
|
||||
background-opacity="0.5"
|
||||
:buffer-value="buffered"
|
||||
color="primary"
|
||||
height="3"
|
||||
query
|
||||
:value="percentage"
|
||||
<div>
|
||||
<video
|
||||
ref="playerfake"
|
||||
muted
|
||||
autoplay
|
||||
style="display: none"
|
||||
:src="vidWrs"
|
||||
/>
|
||||
<v-progress-linear
|
||||
query
|
||||
active
|
||||
style="width: 100%"
|
||||
background-opacity="0.5"
|
||||
background-color="primary"
|
||||
:buffer-value="buffered"
|
||||
:value="percent"
|
||||
color="primary"
|
||||
height="2"
|
||||
/>
|
||||
<!-- Scrubber -->
|
||||
<v-slider
|
||||
v-if="$refs.player"
|
||||
hide-details
|
||||
height="2"
|
||||
dense
|
||||
style="
|
||||
position: absolute;
|
||||
z-index: 69420;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
"
|
||||
:thumb-size="0"
|
||||
:max="duration"
|
||||
:value="progress"
|
||||
@start="scrubbing = true"
|
||||
@end="scrubbing = false"
|
||||
@change="scrub($event)"
|
||||
@input="scrubbing ? seek($event) : null"
|
||||
>
|
||||
<template #thumb-label="{ value }">
|
||||
<div style="transform: translateY(-50%)">
|
||||
<canvas
|
||||
ref="preview"
|
||||
class="white"
|
||||
:width="$refs.player.clientWidth / 3"
|
||||
:height="$refs.player.clientHeight / 3"
|
||||
style="border: 2px solid white"
|
||||
:style="{
|
||||
borderRadius: $store.state.tweaks.roundWatch
|
||||
? `${$store.state.tweaks.roundTweak / 3}rem`
|
||||
: '0',
|
||||
}"
|
||||
></canvas>
|
||||
<div class="text-center pb-4" style="font-size: 0.8rem">
|
||||
<b>{{ $vuetube.humanTime(value) }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
props: ["sources", "video"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
percentage: 0,
|
||||
scrubbing: false,
|
||||
percent: 0,
|
||||
progress: 0,
|
||||
buffered: 0,
|
||||
duration: 0,
|
||||
vidSrc: "",
|
||||
vidWrs: "",
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.video.ontimeupdate = () => {
|
||||
this.percentage = (this.video.currentTime / this.video.duration) * 100;
|
||||
console.log("sources", this.sources);
|
||||
this.vidSrc = this.sources[this.sources.length - 1].url;
|
||||
this.vidWrs = this.sources[1].url;
|
||||
let vid = this.video;
|
||||
vid.addEventListener("loadeddata", (e) => {
|
||||
// console.log("%c loadeddata", "color: #00ff00");
|
||||
console.log(e);
|
||||
//Video should now be loaded but we can add a second check
|
||||
if (vid.readyState >= 3) {
|
||||
vid.ontimeupdate = () => {
|
||||
// console.log("%c timeupdate", "color: #aaaaff");
|
||||
this.duration = vid.duration;
|
||||
if (!this.scrubbing) this.progress = vid.currentTime;
|
||||
this.percent = (vid.currentTime / vid.duration) * 100;
|
||||
};
|
||||
this.video.addEventListener("progress", () => {
|
||||
this.buffered = (this.video.buffered.end(0) / this.video.duration) * 100;
|
||||
vid.onprogress = () => {
|
||||
// console.log("%c progress", "color: #ff00ff");
|
||||
this.buffered = (vid.buffered.end(0) / vid.duration) * 100;
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
loadVideoFrames() {
|
||||
// Exit loop if desired number of frames have been extracted
|
||||
if (this.frames.length >= frameCount) {
|
||||
this.visibleFrame = 0;
|
||||
|
||||
// Append all canvases to container div
|
||||
this.frames.forEach((frame) => {
|
||||
this.frameContainerElement.appendChild(frame);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If extraction hasn’t started, set desired time for first frame
|
||||
if (this.frames.length === 0) {
|
||||
this.requestedTime = 0;
|
||||
} else {
|
||||
this.requestedTime = this.requestedTime + this.frameTimestep;
|
||||
}
|
||||
|
||||
// Send seek request to video player for the next frame.
|
||||
this.videoElement.currentTime = this.requestedTime;
|
||||
},
|
||||
extractFrame(videoWidth, videoHeight) {
|
||||
// Create DOM canvas object
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.className = "video-scrubber-frame";
|
||||
canvas.height = videoHeight;
|
||||
canvas.width = videoWidth;
|
||||
|
||||
// Copy current frame to canvas
|
||||
var context = canvas.getContext("2d");
|
||||
context.drawImage(this.videoElement, 0, 0, videoWidth, videoHeight);
|
||||
this.frames.push(canvas);
|
||||
|
||||
// Load the next frame
|
||||
loadVideoFrames();
|
||||
},
|
||||
prefetch_file(url, fetched_callback, progress_callback, error_callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, true);
|
||||
xhr.responseType = "blob";
|
||||
|
||||
xhr.addEventListener(
|
||||
"load",
|
||||
function () {
|
||||
if (xhr.status === 200) {
|
||||
var URL = window.URL || window.webkitURL;
|
||||
var blob_url = URL.createObjectURL(xhr.response);
|
||||
fetched_callback(blob_url);
|
||||
} else {
|
||||
error_callback();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
var prev_pc = 0;
|
||||
xhr.addEventListener("progress", function (event) {
|
||||
if (event.lengthComputable) {
|
||||
var pc = Math.round((event.loaded / event.total) * 100);
|
||||
if (pc != prev_pc) {
|
||||
prev_pc = pc;
|
||||
progress_callback(pc);
|
||||
}
|
||||
}
|
||||
});
|
||||
xhr.send();
|
||||
},
|
||||
async extractFramesFromVideo(videoUrl, fps = 25) {
|
||||
// fully download it first (no buffering):
|
||||
console.log(videoUrl);
|
||||
console.log(fps);
|
||||
let videoBlob = await fetch(videoUrl, {
|
||||
headers: { range: "bytes=0-567139" },
|
||||
}).then((r) => r.blob());
|
||||
console.log(videoBlob);
|
||||
let videoObjectUrl = URL.createObjectURL(videoBlob);
|
||||
let video = document.createElement("video");
|
||||
|
||||
let seekResolve;
|
||||
video.addEventListener("seeked", async function () {
|
||||
if (seekResolve) seekResolve();
|
||||
});
|
||||
|
||||
video.src = videoObjectUrl;
|
||||
|
||||
// workaround chromium metadata bug (https://stackoverflow.com/q/38062864/993683)
|
||||
while (
|
||||
(video.duration === Infinity || isNaN(video.duration)) &&
|
||||
video.readyState < 2
|
||||
) {
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
video.currentTime = 10000000 * Math.random();
|
||||
}
|
||||
let duration = video.duration;
|
||||
|
||||
let canvas = document.createElement("canvas");
|
||||
let context = canvas.getContext("2d");
|
||||
let [w, h] = [video.videoWidth, video.videoHeight];
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
|
||||
let interval = 1;
|
||||
let currentTime = 0;
|
||||
|
||||
while (currentTime < duration) {
|
||||
video.currentTime = currentTime;
|
||||
await new Promise((r) => (seekResolve = r));
|
||||
|
||||
context.drawImage(video, 0, 0, w, h);
|
||||
let base64ImageData = canvas.toDataURL();
|
||||
console.log(base64ImageData);
|
||||
this.frames.push(base64ImageData);
|
||||
|
||||
currentTime += interval;
|
||||
}
|
||||
console.log("%c frames", "color: #00ff00");
|
||||
console.log(this.frames);
|
||||
},
|
||||
seek(e) {
|
||||
// console.log(`scrubbing ${e}`);
|
||||
let vid = this.$refs.playerfake;
|
||||
let canvas = this.$refs.preview;
|
||||
this.$refs.playerfake.currentTime = e;
|
||||
canvas
|
||||
.getContext("2d")
|
||||
.drawImage(
|
||||
vid,
|
||||
0,
|
||||
0,
|
||||
this.video.clientWidth / 3,
|
||||
this.video.clientHeight / 3
|
||||
);
|
||||
},
|
||||
scrub(e) {
|
||||
this.video.currentTime = e;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
5
NUXT/components/Player/speed.vue
Normal file
5
NUXT/components/Player/speed.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<v-btn fab text style="position: absolute; bottom: 0.25rem; right: 7rem">
|
||||
1X
|
||||
</v-btn>
|
||||
</template>
|
31
NUXT/components/Player/watchtime.vue
Normal file
31
NUXT/components/Player/watchtime.vue
Normal file
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div
|
||||
style="
|
||||
left: 1.25rem;
|
||||
bottom: 1.25rem;
|
||||
font-size: 0.75rem;
|
||||
position: absolute;
|
||||
"
|
||||
>
|
||||
{{ watched }}
|
||||
<span style="color: #999"> / {{ duration }} </span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["video"],
|
||||
data() {
|
||||
return {
|
||||
watched: 0,
|
||||
duration: 0,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.video.addEventListener("timeupdate", () => {
|
||||
this.duration = this.$vuetube.humanTime(this.video.duration);
|
||||
this.watched = this.$vuetube.humanTime(this.video.currentTime);
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -51,6 +51,7 @@
|
|||
:key="index"
|
||||
text
|
||||
fab
|
||||
no-caps
|
||||
class="vertical-button mx-1"
|
||||
elevation="0"
|
||||
style="width: 4.2rem !important; height: 4.2rem !important"
|
||||
|
|
|
@ -143,12 +143,18 @@ const module = {
|
|||
|
||||
let returntext = new String();
|
||||
for (const i in levels) {
|
||||
const num = levels[i].toString().length == 1 ? "0" + levels[i] : levels[i]; // If Number Is Single Digit, Add 0 In Front
|
||||
|
||||
const num =
|
||||
levels[i].toString().length == 1 ? "0" + levels[i] : levels[i]; // If Number Is Single Digit, Add 0 In Front
|
||||
returntext += ":" + num;
|
||||
}
|
||||
while (returntext.startsWith(":00")) { returntext = returntext.substring(3); } // Remove Prepending 0s (eg. 00:00:00:01:00)
|
||||
if (returntext.startsWith(":0")) { returntext = returntext.substring(2); } else { returntext = returntext.substring(1); } // Prevent Time Starting With 0 (eg. 01:00)
|
||||
while (returntext.startsWith(":00")) {
|
||||
returntext = returntext.substring(3);
|
||||
} // Remove Prepending 0s (eg. 00:00:00:01:00)
|
||||
if (returntext.startsWith(":0")) {
|
||||
returntext = returntext.substring(2);
|
||||
} else {
|
||||
returntext = returntext.substring(1);
|
||||
} // Prevent Time Starting With 0 (eg. 01:00)
|
||||
// console.log("Human Time:", returntext);
|
||||
return returntext;
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue