aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-08-29 20:29:03 -0400
committerBobby <[email protected]>2024-08-29 20:29:03 -0400
commitc58fc0f3ad71160e9d2d335fd378ed8b95f7e873 (patch)
treece0b0357f1ffd1a209eeb9965cfa342cf77ea59d
parentb8ac3f2a369285189c2da5b3a56984418c10b62a (diff)
downloadyugen-c58fc0f3ad71160e9d2d335fd378ed8b95f7e873.tar.xz
yugen-c58fc0f3ad71160e9d2d335fd378ed8b95f7e873.zip
User Prefs in video playback
-rw-r--r--static/css/main.css71
-rw-r--r--templates/watch/watch.html149
-rw-r--r--watch/views.py9
3 files changed, 192 insertions, 37 deletions
diff --git a/static/css/main.css b/static/css/main.css
index 09a4a9b..fc93ed6 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -661,6 +661,18 @@ video {
top: 100%;
}
+.bottom-24 {
+ bottom: 6rem;
+}
+
+.right-4 {
+ right: 1rem;
+}
+
+.top-4 {
+ top: 1rem;
+}
+
.z-10 {
z-index: 10;
}
@@ -729,6 +741,10 @@ video {
margin-top: 2rem;
}
+.mr-4 {
+ margin-right: 1rem;
+}
+
.block {
display: block;
}
@@ -890,6 +906,10 @@ video {
max-width: max-content;
}
+.origin-left {
+ transform-origin: left;
+}
+
.-translate-x-1\/2 {
--tw-translate-x: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@@ -900,6 +920,16 @@ video {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
+.scale-x-0 {
+ --tw-scale-x: 0;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.scale-x-100 {
+ --tw-scale-x: 1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@@ -1002,6 +1032,10 @@ video {
overflow: auto;
}
+.overflow-hidden {
+ overflow: hidden;
+}
+
.overflow-y-auto {
overflow-y: auto;
}
@@ -1349,6 +1383,10 @@ video {
--tw-bg-opacity: 0.4;
}
+.bg-opacity-30 {
+ --tw-bg-opacity: 0.3;
+}
+
.bg-cover {
background-size: cover;
}
@@ -1786,6 +1824,11 @@ video {
color: rgb(161 98 7 / var(--tw-text-opacity));
}
+.text-black {
+ --tw-text-opacity: 1;
+ color: rgb(0 0 0 / var(--tw-text-opacity));
+}
+
.opacity-25 {
opacity: 0.25;
}
@@ -1823,6 +1866,26 @@ video {
transition-duration: 150ms;
}
+.transition {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-all {
+ transition-property: all;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-transform {
+ transition-property: transform;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
.duration-100 {
transition-duration: 100ms;
}
@@ -1831,10 +1894,18 @@ video {
transition-duration: 500ms;
}
+.duration-\[5000ms\] {
+ transition-duration: 5000ms;
+}
+
.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
+.ease-linear {
+ transition-timing-function: linear;
+}
+
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
diff --git a/templates/watch/watch.html b/templates/watch/watch.html
index 2f80cbb..796ce3c 100644
--- a/templates/watch/watch.html
+++ b/templates/watch/watch.html
@@ -6,42 +6,54 @@
{% endblock css %}
{% block content %}
<div class="flex flex-col lg:flex-row mt-4 gap-2">
- <!-- Video Player Section (75%) -->
- <div class="w-full lg:w-3/4">
- <!-- Set a fixed height based on the 16:9 aspect ratio -->
- <!-- This grey div is a placeholder for the video player -->
- {% comment %} <div class="w-full h-0 lg:h-auto aspect-video bg-gray-500 flex items-center justify-center rounded"> {% endcomment %}
- <div id="video-player" class="aspect-video">
- </div>
+ <div class="w-full lg:w-3/4 relative">
+ <div id="video-player" class="aspect-video"></div>
+ <div id="skip-buttons" class="absolute bottom-24 right-4 flex flex-col space-y-2">
+ <div id="skipIntro" class="relative bg-white bg-opacity-20 text-white text-center py-2 px-4 rounded-lg cursor-pointer overflow-hidden hidden">
+ <span class="relative z-10">Skip Intro</span>
+ <div id="intro-overlay" class="absolute inset-0 bg-{{ user.preferences.accent_colour }}-600 transition-transform duration-[5000ms] ease-linear origin-left scale-x-0"></div>
+ </div>
+ <div id="skipOutro" class="relative bg-white bg-opacity-20 text-white text-center py-2 px-4 rounded-lg cursor-pointer overflow-hidden hidden">
+ <span class="relative z-10">Skip Outro</span>
+ <div id="outro-overlay" class="absolute inset-0 bg-{{ user.preferences.accent_colour }}-600 transition-transform duration-[5000ms] ease-linear origin-left scale-x-0"></div>
+ </div>
+ </div>
</div>
-
- <!-- Episode List Section (25%) -->
+
<div class="w-full lg:w-1/4 px-2 overflow-y-auto">
<h2 class="text-white text-xl font-bold mb-4">Episodes</h2>
<ul class="space-y-2">
- <!-- Example Episode Items -->
<li class="bg-gray-700 text-white p-2 rounded">Episode 1: The Beginning</li>
<li class="bg-gray-700 text-white p-2 rounded">Episode 2: The Journey</li>
<li class="bg-gray-700 text-white p-2 rounded">Episode 3: The Encounter</li>
- <!-- Add more episodes here -->
</ul>
</div>
</div>
-
+{% comment %} {{ anime_data.totalEpisodes > current_episode }} {% endcomment %}
+{{ anime_episodes.totalEpisodes }}
+{{ current_episode }}
{% endblock content %}
{% block scripts %}
<script type="module">
- import { VidstackPlayer, VidstackPlayerLayout } from 'https://cdn.vidstack.io/player';
+ const introStart = {{ episode_data.intro.start }};
+ const introEnd = {{ episode_data.intro.end }};
+ const outroStart = {{ episode_data.outro.start }};
+ const outroEnd = {{ episode_data.outro.end }};
+ const skipIntroButton = document.getElementById('skipIntro');
+ const skipOutroButton = document.getElementById('skipOutro');
+ const introOverlay = document.getElementById('intro-overlay');
+ const outroOverlay = document.getElementById('outro-overlay');
+
+ import { VidstackPlayer, VidstackPlayerLayout, TextTrack } from 'https://cdn.vidstack.io/player';
const layout = new VidstackPlayerLayout({
- {% for track in episode_data.subtitles %}
- {% if track.kind == "thumbnails" %}
- thumbnails: '{{ track.file }}',
- {% endif %}
+ {% for track in episode_data.tracks %}
+ {% if track.kind == 'thumbnails' %}
+ thumbnails: "https://vtt.blasphemy8473.workers.dev/{{ track.file }}",
+ {% endif %}
{% endfor %}
});
- // thumbnails: 'https://files.vidstack.io/sprite-fight/thumbnails.vtt',
const player = await VidstackPlayer.create({
target: '#video-player',
autoStartLoad: true,
@@ -72,26 +84,89 @@
],
});
- player.addEventListener('auto-play', (event) => {
- const requestEvent = event.request;
+ const chapters = [
+ { startTime: introStart, endTime: introEnd, title: 'Intro' },
+ { startTime: outroStart, endTime: outroEnd, title: 'Outro' }
+ ];
+
+ const chaptersTrack = new TextTrack({
+ kind: "chapters",
+ default: true,
});
-
- // autoplay has failed.
- player.addEventListener('auto-play-fail', (event) => {
- const requestEvent = event.request;
- console.log(event.detail.muted); // was media muted?
- console.log(event.detail.error); // media error
+
+ for (const chapter of chapters) {
+ if (chapter.startTime === chapter.endTime) {
+ continue;
+ }
+ chaptersTrack.addCue(new VTTCue(chapter.startTime, chapter.endTime, chapter.title));
+ }
+
+ player.textTracks.add(chaptersTrack);
+ const autoSkipIntro = {% if user.preferences.auto_skip_intro %}true{% else %}false{% endif %};
+
+ player.addEventListener('time-update', (event) => {
+ const currentTime = event.detail.currentTime;
+
+ if (currentTime >= introStart && currentTime <= introEnd && introStart !== introEnd) {
+ if (autoSkipIntro) {
+ player.currentTime = introEnd;
+ } else {
+ skipIntroButton.classList.remove('hidden');
+ skipOutroButton.classList.add('hidden');
+ setTimeout(() => introOverlay.classList.add('scale-x-100'), 100);
+ }
+ } else if (currentTime >= outroStart && currentTime <= outroEnd && outroStart !== outroEnd) {
+ if (autoSkipIntro) {
+ player.currentTime = outroEnd;
+ } else {
+ skipOutroButton.classList.remove('hidden');
+ skipIntroButton.classList.add('hidden');
+ setTimeout(() => outroOverlay.classList.add('scale-x-100'), 100);
+ }
+ } else {
+ skipIntroButton.classList.add('hidden');
+ skipOutroButton.classList.add('hidden');
+ introOverlay.classList.remove('scale-x-100');
+ outroOverlay.classList.remove('scale-x-100');
+ }
+
+ {% if user.preferences.auto_next_episode and anime_episodes.totalEpisodes > current_episode %}
+ if (currentTime >= player.duration && player.duration !== 0) {
+ const currentUrl = window.location.href;
+ const animeId = {{ anime_id }};
+ const currentEpisode = {{ current_episode }};
+ const nextEpisode = currentEpisode + 1;
+ const nextUrl = currentUrl.replace(`/watch/${animeId}/${currentEpisode}`, `/watch/${animeId}/${nextEpisode}`);
+ window.location.href = nextUrl;
+ }
+ {% endif %}
});
- /*const url = '{{ stream_url }}';
-
- const player = await VidstackPlayer.create({
- target: '#video-player',
- title: '{{ anime.title }}',
- src: url,
- layout: new VidstackPlayerLayout({
- thumbnails: 'https://files.vidstack.io/sprite-fight/thumbnails.vtt',
- }),
- });*/
- </script>
+ skipIntroButton.addEventListener('click', () => {
+ player.currentTime = introEnd;
+ introOverlay.classList.remove('scale-x-100');
+ });
+
+ skipOutroButton.addEventListener('click', () => {
+ player.currentTime = outroEnd;
+ outroOverlay.classList.remove('scale-x-100');
+ });
+
+ {% if user.preferences.auto_play_video %}
+ function attemptPlayback() {
+ player.play().catch((error) => {
+ console.error('Autoplay failed:', error);
+ });
+ }
+ function onUserInteraction() {
+ document.removeEventListener('pointerdown', onUserInteraction);
+ attemptPlayback();
+ }
+ document.addEventListener('pointerdown', onUserInteraction, { once: true });
+ player.addEventListener('can-play', () => {
+ attemptPlayback();
+ });
+ {% endif %}
+
+</script>
{% endblock scripts %}
diff --git a/watch/views.py b/watch/views.py
index d48e163..70cc340 100644
--- a/watch/views.py
+++ b/watch/views.py
@@ -25,6 +25,9 @@ def watch(request, anime_id, episode=None):
else:
anime_selected = anime_selected[0]
+ if mode == "dub" and not anime_selected["episodes"]["dub"]:
+ mode = "sub"
+
base_url = f"{os.getenv("ZORO_URL")}/anime/episodes/{anime_selected["id"]}"
response = requests.get(base_url)
anime_episodes = response.json()
@@ -38,6 +41,11 @@ def watch(request, anime_id, episode=None):
response = requests.get(base_url)
episode_data = response.json()
+ if "message" in episode_data and episode_data["message"] == "Couldn't find server. Try another server":
+ base_url = f"{os.getenv("ZORO_URL")}/anime/episode-srcs?id={episode_d["episodeId"]}?server=hd-2&category={mode}"
+ response = requests.get(base_url)
+ episode_data = response.json()
+
return render(request, "watch/watch.html", {
"anime_data": anime_data,
"anime_selected": anime_selected,
@@ -45,5 +53,6 @@ def watch(request, anime_id, episode=None):
"episode_data": episode_data,
"current_episode": episode,
"stream_url": episode_data["sources"][0]["url"],
+ "anime_id": anime_id,
"mode": mode,
}) \ No newline at end of file