diff options
| author | Bobby <[email protected]> | 2024-08-29 20:29:03 -0400 |
|---|---|---|
| committer | Bobby <[email protected]> | 2024-08-29 20:29:03 -0400 |
| commit | c58fc0f3ad71160e9d2d335fd378ed8b95f7e873 (patch) | |
| tree | ce0b0357f1ffd1a209eeb9965cfa342cf77ea59d | |
| parent | b8ac3f2a369285189c2da5b3a56984418c10b62a (diff) | |
| download | yugen-c58fc0f3ad71160e9d2d335fd378ed8b95f7e873.tar.xz yugen-c58fc0f3ad71160e9d2d335fd378ed8b95f7e873.zip | |
User Prefs in video playback
| -rw-r--r-- | static/css/main.css | 71 | ||||
| -rw-r--r-- | templates/watch/watch.html | 149 | ||||
| -rw-r--r-- | watch/views.py | 9 |
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 |
