Skip to content

Commit

Permalink
feat: pause videos when out of view
Browse files Browse the repository at this point in the history
  • Loading branch information
AlejandroAkbal committed Oct 13, 2023
1 parent b53f124 commit 0bcb16e
Showing 1 changed file with 116 additions and 98 deletions.
214 changes: 116 additions & 98 deletions components/pages/posts/post/PostMedia.vue
Original file line number Diff line number Diff line change
@@ -1,140 +1,158 @@
<script lang="ts" setup>
import { ProxyHelper } from 'assets/js/ProxyHelper'
const props = defineProps({
mediaSrc: {
type: String,
required: true
},
mediaSrcHeight: {
type: Number,
required: true
},
mediaSrcWidth: {
type: Number,
required: true
},
mediaPosterSrc: {
type: String,
required: false
},
mediaType: {
type: String,
required: true
},
mediaAlt: {
type: String,
required: true
}
})
const localSrc = ref(props.mediaSrc)
const hasError = ref(false)
const isImage = computed(() => props.mediaType === 'image')
const isVideo = computed(() => props.mediaType === 'video')
const triedToLoadWithProxy = ref(false)
function onMediaError(event: Event) {
if (hasError.value) {
return
}
// TODO: Retry with extra /
// TODO: Retry with other file extensions, e.g. .jpeg to .gif (as seen in realbooru)
// TODO: https://github.com/mikf/gallery-dl/issues/2530
// TODO: https://github.com/Bionus/imgbrd-grabber/issues/2692#issuecomment-1141236485
// TODO: https://realbooru.com/index.php?page=forum&s=view&id=6522&pid=105
if (!triedToLoadWithProxy.value) {
triedToLoadWithProxy.value = true
const proxySrc = ProxyHelper.proxyUrl(localSrc.value)
localSrc.value = proxySrc
return
}
hasError.value = true
<script lang='ts' setup>
import { vIntersectionObserver } from '@vueuse/components'
import { ProxyHelper } from 'assets/js/ProxyHelper'
const props = defineProps({
mediaSrc: {
type: String,
required: true
},
mediaSrcHeight: {
type: Number,
required: true
},
mediaSrcWidth: {
type: Number,
required: true
},
mediaPosterSrc: {
type: String,
required: false
},
mediaType: {
type: String,
required: true
},
mediaAlt: {
type: String,
required: true
}
})
function manuallyReloadMedia() {
// Reset state
triedToLoadWithProxy.value = false
hasError.value = false
const localSrc = ref(props.mediaSrc)
// Reload media
localSrc.value = ''
localSrc.value = props.mediaSrc
const hasError = ref(false)
const isImage = computed(() => props.mediaType === 'image')
const isVideo = computed(() => props.mediaType === 'video')
const triedToLoadWithProxy = ref(false)
function onMediaError(event: Event) {
if (hasError.value) {
return
}
// TODO: Retry with extra /
// TODO: Retry with other file extensions, e.g. .jpeg to .gif (as seen in realbooru)
// TODO: https://github.com/mikf/gallery-dl/issues/2530
// TODO: https://github.com/Bionus/imgbrd-grabber/issues/2692#issuecomment-1141236485
// TODO: https://realbooru.com/index.php?page=forum&s=view&id=6522&pid=105
if (!triedToLoadWithProxy.value) {
triedToLoadWithProxy.value = true
const proxySrc = ProxyHelper.proxyUrl(localSrc.value)
localSrc.value = proxySrc
return
}
hasError.value = true
}
function manuallyReloadMedia() {
// Reset state
triedToLoadWithProxy.value = false
hasError.value = false
// Reload media
localSrc.value = ''
localSrc.value = props.mediaSrc
}
// TODO: Check if this is needed when implementing virtual scrolling
function onIntersectionObserver(entries: IntersectionObserverEntry[]) {
const entry = entries[0]
// Stop video if it's not in view
if (entry.isIntersecting) {
return
}
const video = entry.target as HTMLVideoElement
if (!video || video.paused) {
return
}
// TODO: Stop video when it's out of view
video.pause()
}
</script>

<template>
<div :style="`aspect-ratio: ${mediaSrcWidth}/${mediaSrcHeight};`">
<div :style='`aspect-ratio: ${mediaSrcWidth}/${mediaSrcHeight};`'>
<!-- Error -->
<template v-if="hasError">
<div class="flex h-full flex-col items-center py-4">
<div class="flex flex-1 flex-col items-center justify-center gap-4">
<template v-if='hasError'>
<div class='flex h-full flex-col items-center py-4'>
<div class='flex flex-1 flex-col items-center justify-center gap-4'>
<span
class="rounded-md bg-gradient-to-l from-base-950 via-base-900 to-base-900 px-4 py-1.5 text-center text-base-content-highlight"
class='rounded-md bg-gradient-to-l from-base-950 via-base-900 to-base-900 px-4 py-1.5 text-center text-base-content-highlight'
>
Error loading media
</span>

<button
class="focus-visible:focus-outline-util hover:hover-bg-util hover:hover-text-util mx-auto inline-flex items-center justify-center rounded-md px-2 py-1 text-sm ring-1 ring-base-0/20"
type="button"
@click="manuallyReloadMedia"
class='focus-visible:focus-outline-util hover:hover-bg-util hover:hover-text-util mx-auto inline-flex items-center justify-center rounded-md px-2 py-1 text-sm ring-1 ring-base-0/20'
type='button'
@click='manuallyReloadMedia'
>
Try again?
</button>
</div>

<NuxtLink
class="hover:hover-text-util focus-visible:focus-outline-util justify-self-end text-sm text-base-content underline"
href="https://www.rule34.app/frequently-asked-questions#74cfdf0316b04111b0c65b7f8502dfda"
target="_blank"
class='hover:hover-text-util focus-visible:focus-outline-util justify-self-end text-sm text-base-content underline'
href='https://www.rule34.app/frequently-asked-questions#74cfdf0316b04111b0c65b7f8502dfda'
target='_blank'
>
Media not loading? Learn more
</NuxtLink>
</div>
</template>

<!-- Image -->
<template v-else-if="isImage">
<template v-else-if='isImage'>
<img
:alt="mediaAlt"
:height="mediaSrcHeight"
:src="localSrc"
:width="mediaSrcWidth"
class="h-auto w-full opacity-0 transition-opacity duration-700 ease-in-out"
decoding="async"
loading="lazy"
:alt='mediaAlt'
:height='mediaSrcHeight'
:src='localSrc'
:width='mediaSrcWidth'
class='h-auto w-full opacity-0 transition-opacity duration-700 ease-in-out'
decoding='async'
loading='lazy'
onload='this.classList.remove("opacity-0")'
referrerpolicy="no-referrer"
@error="onMediaError"
referrerpolicy='no-referrer'
@error='onMediaError'
/>
</template>

<!-- Video -->
<template v-else-if="isVideo">
<template v-else-if='isVideo'>
<!-- TODO: Add load animation -->
<video
:height="mediaSrcHeight"
:poster="mediaPosterSrc"
:src="localSrc"
:width="mediaSrcWidth"
class="h-auto w-full"
v-intersection-observer='onIntersectionObserver'
:height='mediaSrcHeight'
:poster='mediaPosterSrc'
:src='localSrc'
:width='mediaSrcWidth'
class='h-auto w-full'
controls
loop
playsinline
preload="none"
@error="onMediaError"
preload='none'
@error='onMediaError'
/>
</template>
</div>
Expand Down

0 comments on commit 0bcb16e

Please sign in to comment.