add countdown until next video

This commit is contained in:
vr10t 2023-03-02 21:56:06 +00:00
parent 2134e82efe
commit e46cbadc51
4 changed files with 119 additions and 6 deletions

View File

@ -45,6 +45,16 @@
@change="onChange($event)" @change="onChange($event)"
/> />
</label> </label>
<label class="pref" for="chkAutoPlayNextCountdown">
<strong v-t="'actions.autoplay_next_countdown'" />
<input
id="chkAutoPlayNextCountdown"
v-model="autoPlayNextCountdown"
class="input w-24"
type="number"
@change="onChange($event)"
/>
</label>
<label class="pref" for="chkAudioOnly"> <label class="pref" for="chkAudioOnly">
<strong v-t="'actions.audio_only'" /> <strong v-t="'actions.audio_only'" />
<input id="chkAudioOnly" v-model="listen" class="checkbox" type="checkbox" @change="onChange($event)" /> <input id="chkAudioOnly" v-model="listen" class="checkbox" type="checkbox" @change="onChange($event)" />
@ -346,6 +356,7 @@ export default {
minSegmentLength: 0, minSegmentLength: 0,
selectedTheme: "dark", selectedTheme: "dark",
autoPlayVideo: true, autoPlayVideo: true,
autoPlayNextCountdown: 5,
listen: false, listen: false,
resolutions: [144, 240, 360, 480, 720, 1080, 1440, 2160, 4320], resolutions: [144, 240, 360, 480, 720, 1080, 1440, 2160, 4320],
defaultQuality: 0, defaultQuality: 0,
@ -461,6 +472,7 @@ export default {
this.minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0); this.minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
this.selectedTheme = this.getPreferenceString("theme", "dark"); this.selectedTheme = this.getPreferenceString("theme", "dark");
this.autoPlayVideo = this.getPreferenceBoolean("playerAutoPlay", true); this.autoPlayVideo = this.getPreferenceBoolean("playerAutoPlay", true);
this.autoPlayNextCountdown = this.getPreferenceNumber("autoPlayNextCountdown", 5);
this.listen = this.getPreferenceBoolean("listen", false); this.listen = this.getPreferenceBoolean("listen", false);
this.defaultQuality = Number(localStorage.getItem("quality")); this.defaultQuality = Number(localStorage.getItem("quality"));
this.bufferingGoal = Math.max(Number(localStorage.getItem("bufferGoal")), 10); this.bufferingGoal = Math.max(Number(localStorage.getItem("bufferGoal")), 10);
@ -515,6 +527,7 @@ export default {
localStorage.setItem("minSegmentLength", this.minSegmentLength); localStorage.setItem("minSegmentLength", this.minSegmentLength);
localStorage.setItem("theme", this.selectedTheme); localStorage.setItem("theme", this.selectedTheme);
localStorage.setItem("playerAutoPlay", this.autoPlayVideo); localStorage.setItem("playerAutoPlay", this.autoPlayVideo);
localStorage.setItem("autoPlayNextCountdown", this.autoPlayNextCountdown);
localStorage.setItem("listen", this.listen); localStorage.setItem("listen", this.listen);
localStorage.setItem("quality", this.defaultQuality); localStorage.setItem("quality", this.defaultQuality);
localStorage.setItem("bufferGoal", this.bufferingGoal); localStorage.setItem("bufferGoal", this.bufferingGoal);

View File

@ -0,0 +1,25 @@
<template>
<div class="toast">
<slot />
<button @click="dismiss" v-t="'actions.dismiss'" />
</div>
</template>
<script>
export default {
methods: {
dismiss() {
this.$emit("dismissed");
},
},
};
</script>
<style>
.toast {
@apply bg-dark-900/80 text-white flex flex-col justify-center fixed top-12 right-12 p-4 min-w-max shadow rounded duration-200 z-9999;
}
.toast button {
@apply underline;
}
</style>

View File

@ -4,8 +4,6 @@
ref="videoPlayer" ref="videoPlayer"
:video="video" :video="video"
:sponsors="sponsors" :sponsors="sponsors"
:playlist="playlist"
:index="index"
:selected-auto-play="false" :selected-auto-play="false"
:selected-auto-loop="selectedAutoLoop" :selected-auto-loop="selectedAutoLoop"
:is-embed="isEmbed" :is-embed="isEmbed"
@ -14,6 +12,11 @@
<div v-if="video && !isEmbed" class="w-full"> <div v-if="video && !isEmbed" class="w-full">
<ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" /> <ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" />
<Transition>
<ToastComponent v-if="shouldShowToast" @dismissed="dismiss">
<i18n-t keypath="info.next_video_countdown">{{ counter }}</i18n-t>
</ToastComponent>
</Transition>
<div v-show="!video.error"> <div v-show="!video.error">
<div :class="isMobile ? 'flex-col' : 'flex'"> <div :class="isMobile ? 'flex-col' : 'flex'">
@ -21,11 +24,10 @@
ref="videoPlayer" ref="videoPlayer"
:video="video" :video="video"
:sponsors="sponsors" :sponsors="sponsors"
:playlist="playlist"
:index="index"
:selected-auto-play="selectedAutoPlay" :selected-auto-play="selectedAutoPlay"
:selected-auto-loop="selectedAutoLoop" :selected-auto-loop="selectedAutoLoop"
@timeupdate="onTimeUpdate" @timeupdate="onTimeUpdate"
@ended="onVideoEnded"
/> />
<ChaptersBar <ChaptersBar
:mobileLayout="isMobile" :mobileLayout="isMobile"
@ -230,6 +232,7 @@ import PlaylistAddModal from "./PlaylistAddModal.vue";
import ShareModal from "./ShareModal.vue"; import ShareModal from "./ShareModal.vue";
import PlaylistVideos from "./PlaylistVideos.vue"; import PlaylistVideos from "./PlaylistVideos.vue";
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue"; import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
import ToastComponent from "./ToastComponent.vue";
export default { export default {
name: "App", name: "App",
@ -243,6 +246,7 @@ export default {
ShareModal, ShareModal,
PlaylistVideos, PlaylistVideos,
WatchOnYouTubeButton, WatchOnYouTubeButton,
ToastComponent,
}, },
data() { data() {
const smallViewQuery = window.matchMedia("(max-width: 640px)"); const smallViewQuery = window.matchMedia("(max-width: 640px)");
@ -270,6 +274,9 @@ export default {
showShareModal: false, showShareModal: false,
isMobile: true, isMobile: true,
currentTime: 0, currentTime: 0,
shouldShowToast: false,
timeoutCounter: null,
counter: 0,
}; };
}, },
computed: { computed: {
@ -291,6 +298,9 @@ export default {
year: "numeric", year: "numeric",
}); });
}, },
defaultCounter(_this) {
return _this.getPreferenceNumber("autoPlayNextCountdown", 5);
},
}, },
mounted() { mounted() {
// check screen size // check screen size
@ -560,6 +570,68 @@ export default {
onTimeUpdate(time) { onTimeUpdate(time) {
this.currentTime = time; this.currentTime = time;
}, },
onVideoEnded() {
if (
!this.selectedAutoLoop &&
this.selectedAutoPlay &&
(this.playlist?.relatedStreams?.length > 0 || this.video.relatedStreams.length > 0)
) {
this.showToast();
}
},
showToast() {
this.counter = this.defaultCounter;
if (this.counter < 1) {
this.navigateNext();
return;
}
if (this.timeoutCounter) clearInterval(this.timeoutCounter);
this.timeoutCounter = setInterval(() => {
this.counter--;
if (this.counter === 0) {
this.dismiss();
this.navigateNext();
}
}, 1000);
this.shouldShowToast = true;
},
dismiss() {
clearInterval(this.timeoutCounter);
this.shouldShowToast = false;
},
navigateNext() {
const params = this.$route.query;
let url = this.playlist?.relatedStreams?.[this.index]?.url ?? this.video.relatedStreams[0].url;
const searchParams = new URLSearchParams();
for (var param in params)
switch (param) {
case "v":
case "t":
break;
case "index":
if (this.index < this.playlist.relatedStreams.length) searchParams.set("index", this.index + 1);
break;
case "list":
if (this.index < this.playlist.relatedStreams.length) searchParams.set("list", params.list);
break;
default:
searchParams.set(param, params[param]);
break;
}
// save the fullscreen state
searchParams.set("fullscreen", this.$refs.videoPlayer.$ui.getControls().isFullScreenEnabled());
const paramStr = searchParams.toString();
if (paramStr.length > 0) url += "&" + paramStr;
this.$router.push(url);
},
}, },
}; };
</script> </script>
<style>
.v-enter-from,
.v-leave-to {
opacity: 0;
transform: translateX(100%) scale(0.5);
}
</style>

View File

@ -49,6 +49,7 @@
"dark": "Dark", "dark": "Dark",
"light": "Light", "light": "Light",
"autoplay_video": "Autoplay Video", "autoplay_video": "Autoplay Video",
"autoplay_next_countdown": "Default Countdown until next video (in seconds)",
"audio_only": "Audio Only", "audio_only": "Audio Only",
"default_quality": "Default Quality", "default_quality": "Default Quality",
"buffering_goal": "Buffering Goal (in seconds)", "buffering_goal": "Buffering Goal (in seconds)",
@ -127,7 +128,8 @@
"no_valid_playlists": "The file doesn't contain valid playlists!", "no_valid_playlists": "The file doesn't contain valid playlists!",
"with_playlist": "Share with playlist", "with_playlist": "Share with playlist",
"bookmark_playlist": "Bookmark", "bookmark_playlist": "Bookmark",
"playlist_bookmarked": "Bookmarked" "playlist_bookmarked": "Bookmarked",
"dismiss": "Dismiss"
}, },
"comment": { "comment": {
"pinned_by": "Pinned by {author}", "pinned_by": "Pinned by {author}",
@ -180,6 +182,7 @@
"copied": "Copied!", "copied": "Copied!",
"cannot_copy": "Can't copy!", "cannot_copy": "Can't copy!",
"local_storage": "This action requires localStorage, are cookies enabled?", "local_storage": "This action requires localStorage, are cookies enabled?",
"register_no_email_note": "Using an e-mail as username is not recommended. Proceed anyways?" "register_no_email_note": "Using an e-mail as username is not recommended. Proceed anyways?",
"next_video_countdown": "Playing next video in {0}s"
} }
} }