From 2134e82efe7fc7db522db630299ccc9c72945d50 Mon Sep 17 00:00:00 2001
From: vr10t <ai6@tuta.io>
Date: Thu, 2 Mar 2023 21:55:23 +0000
Subject: [PATCH 1/4] add countdown until next video

---
 src/components/VideoPlayer.vue | 41 +---------------------------------
 1 file changed, 1 insertion(+), 40 deletions(-)

diff --git a/src/components/VideoPlayer.vue b/src/components/VideoPlayer.vue
index 148fb0db..21b5bec9 100644
--- a/src/components/VideoPlayer.vue
+++ b/src/components/VideoPlayer.vue
@@ -38,14 +38,6 @@ export default {
                 return {};
             },
         },
-        playlist: {
-            type: Object,
-            default: null,
-        },
-        index: {
-            type: Number,
-            default: -1,
-        },
         sponsors: {
             type: Object,
             default: () => {
@@ -395,13 +387,7 @@ export default {
                 });
 
                 videoEl.addEventListener("ended", () => {
-                    if (
-                        !this.selectedAutoLoop &&
-                        this.selectedAutoPlay &&
-                        (this.playlist?.relatedStreams?.length > 0 || this.video.relatedStreams.length > 0)
-                    ) {
-                        this.navigateNext();
-                    }
+                    this.$emit("ended");
                 });
             }
 
@@ -609,31 +595,6 @@ export default {
                 this.$refs.videoEl.currentTime = time;
             }
         },
-        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.$ui.getControls().isFullScreenEnabled());
-            const paramStr = searchParams.toString();
-            if (paramStr.length > 0) url += "&" + paramStr;
-            this.$router.push(url);
-        },
         updateMarkers() {
             const markers = this.$refs.container.querySelector(".shaka-ad-markers");
             const array = ["to right"];

From e46cbadc51cd5e620ef745ad475f895542f056db Mon Sep 17 00:00:00 2001
From: vr10t <ai6@tuta.io>
Date: Thu, 2 Mar 2023 21:56:06 +0000
Subject: [PATCH 2/4] add countdown until next video

---
 src/components/PreferencesPage.vue | 13 +++++
 src/components/ToastComponent.vue  | 25 ++++++++++
 src/components/WatchVideo.vue      | 80 ++++++++++++++++++++++++++++--
 src/locales/en.json                |  7 ++-
 4 files changed, 119 insertions(+), 6 deletions(-)
 create mode 100644 src/components/ToastComponent.vue

diff --git a/src/components/PreferencesPage.vue b/src/components/PreferencesPage.vue
index 65a381f7..3db2d434 100644
--- a/src/components/PreferencesPage.vue
+++ b/src/components/PreferencesPage.vue
@@ -45,6 +45,16 @@
             @change="onChange($event)"
         />
     </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">
         <strong v-t="'actions.audio_only'" />
         <input id="chkAudioOnly" v-model="listen" class="checkbox" type="checkbox" @change="onChange($event)" />
@@ -346,6 +356,7 @@ export default {
             minSegmentLength: 0,
             selectedTheme: "dark",
             autoPlayVideo: true,
+            autoPlayNextCountdown: 5,
             listen: false,
             resolutions: [144, 240, 360, 480, 720, 1080, 1440, 2160, 4320],
             defaultQuality: 0,
@@ -461,6 +472,7 @@ export default {
             this.minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
             this.selectedTheme = this.getPreferenceString("theme", "dark");
             this.autoPlayVideo = this.getPreferenceBoolean("playerAutoPlay", true);
+            this.autoPlayNextCountdown = this.getPreferenceNumber("autoPlayNextCountdown", 5);
             this.listen = this.getPreferenceBoolean("listen", false);
             this.defaultQuality = Number(localStorage.getItem("quality"));
             this.bufferingGoal = Math.max(Number(localStorage.getItem("bufferGoal")), 10);
@@ -515,6 +527,7 @@ export default {
                 localStorage.setItem("minSegmentLength", this.minSegmentLength);
                 localStorage.setItem("theme", this.selectedTheme);
                 localStorage.setItem("playerAutoPlay", this.autoPlayVideo);
+                localStorage.setItem("autoPlayNextCountdown", this.autoPlayNextCountdown);
                 localStorage.setItem("listen", this.listen);
                 localStorage.setItem("quality", this.defaultQuality);
                 localStorage.setItem("bufferGoal", this.bufferingGoal);
diff --git a/src/components/ToastComponent.vue b/src/components/ToastComponent.vue
new file mode 100644
index 00000000..dd436ce8
--- /dev/null
+++ b/src/components/ToastComponent.vue
@@ -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>
diff --git a/src/components/WatchVideo.vue b/src/components/WatchVideo.vue
index db1a3e30..402355c8 100644
--- a/src/components/WatchVideo.vue
+++ b/src/components/WatchVideo.vue
@@ -4,8 +4,6 @@
             ref="videoPlayer"
             :video="video"
             :sponsors="sponsors"
-            :playlist="playlist"
-            :index="index"
             :selected-auto-play="false"
             :selected-auto-loop="selectedAutoLoop"
             :is-embed="isEmbed"
@@ -14,6 +12,11 @@
 
     <div v-if="video && !isEmbed" class="w-full">
         <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 :class="isMobile ? 'flex-col' : 'flex'">
@@ -21,11 +24,10 @@
                     ref="videoPlayer"
                     :video="video"
                     :sponsors="sponsors"
-                    :playlist="playlist"
-                    :index="index"
                     :selected-auto-play="selectedAutoPlay"
                     :selected-auto-loop="selectedAutoLoop"
                     @timeupdate="onTimeUpdate"
+                    @ended="onVideoEnded"
                 />
                 <ChaptersBar
                     :mobileLayout="isMobile"
@@ -230,6 +232,7 @@ import PlaylistAddModal from "./PlaylistAddModal.vue";
 import ShareModal from "./ShareModal.vue";
 import PlaylistVideos from "./PlaylistVideos.vue";
 import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
+import ToastComponent from "./ToastComponent.vue";
 
 export default {
     name: "App",
@@ -243,6 +246,7 @@ export default {
         ShareModal,
         PlaylistVideos,
         WatchOnYouTubeButton,
+        ToastComponent,
     },
     data() {
         const smallViewQuery = window.matchMedia("(max-width: 640px)");
@@ -270,6 +274,9 @@ export default {
             showShareModal: false,
             isMobile: true,
             currentTime: 0,
+            shouldShowToast: false,
+            timeoutCounter: null,
+            counter: 0,
         };
     },
     computed: {
@@ -291,6 +298,9 @@ export default {
                 year: "numeric",
             });
         },
+        defaultCounter(_this) {
+            return _this.getPreferenceNumber("autoPlayNextCountdown", 5);
+        },
     },
     mounted() {
         // check screen size
@@ -560,6 +570,68 @@ export default {
         onTimeUpdate(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>
+
+<style>
+.v-enter-from,
+.v-leave-to {
+    opacity: 0;
+    transform: translateX(100%) scale(0.5);
+}
+</style>
diff --git a/src/locales/en.json b/src/locales/en.json
index 6ddfab97..0b0d5ac5 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -49,6 +49,7 @@
         "dark": "Dark",
         "light": "Light",
         "autoplay_video": "Autoplay Video",
+        "autoplay_next_countdown": "Default Countdown until next video (in seconds)",
         "audio_only": "Audio Only",
         "default_quality": "Default Quality",
         "buffering_goal": "Buffering Goal (in seconds)",
@@ -127,7 +128,8 @@
         "no_valid_playlists": "The file doesn't contain valid playlists!",
         "with_playlist": "Share with playlist",
         "bookmark_playlist": "Bookmark",
-        "playlist_bookmarked": "Bookmarked"
+        "playlist_bookmarked": "Bookmarked",
+        "dismiss": "Dismiss"
     },
     "comment": {
         "pinned_by": "Pinned by {author}",
@@ -180,6 +182,7 @@
         "copied": "Copied!",
         "cannot_copy": "Can't copy!",
         "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"
     }
 }

From 39a64c89390560b722da94159bbb0fa6fd5af4b3 Mon Sep 17 00:00:00 2001
From: vr10t <ai6@tuta.io>
Date: Sat, 11 Mar 2023 17:55:25 +0000
Subject: [PATCH 3/4] allow light mode

---
 src/components/ToastComponent.vue | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/components/ToastComponent.vue b/src/components/ToastComponent.vue
index dd436ce8..e80bed60 100644
--- a/src/components/ToastComponent.vue
+++ b/src/components/ToastComponent.vue
@@ -17,7 +17,10 @@ export default {
 
 <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;
+    @apply bg-white/80 text-black flex flex-col justify-center fixed top-12 right-12 p-4 min-w-max shadow rounded duration-200 z-9999;
+}
+.dark .toast {
+    @apply bg-dark-900/80 text-white;
 }
 .toast button {
     @apply underline;

From f1c1cb94f86f96113cf48d4c7a9a0263d2007bd3 Mon Sep 17 00:00:00 2001
From: Kavin <20838718+FireMasterK@users.noreply.github.com>
Date: Sat, 29 Apr 2023 19:45:13 +0100
Subject: [PATCH 4/4] Dismiss autoplay on page unload.

---
 src/components/WatchVideo.vue | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/components/WatchVideo.vue b/src/components/WatchVideo.vue
index 248a6640..3a8e029a 100644
--- a/src/components/WatchVideo.vue
+++ b/src/components/WatchVideo.vue
@@ -372,10 +372,12 @@ export default {
     deactivated() {
         this.active = false;
         window.removeEventListener("scroll", this.handleScroll);
+        this.dismiss();
     },
     unmounted() {
         window.removeEventListener("scroll", this.handleScroll);
         window.removeEventListener("click", this.handleClick);
+        this.dismiss();
     },
     methods: {
         fetchVideo() {