Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions assets/js/BackupHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface IBackupState {
export async function createBackupState(): Promise<IBackupState> {
const { userBooruList } = useBooruList()
const { tagCollections } = useTagCollections()
const { postFullSizeImages, postsPerPage, autoplayAnimatedMedia } = useUserSettings()
const { postFullSizeImages, postsPerPage, autoplayAnimatedMedia, autoplayVideos } = useUserSettings()

// TODO: Only save data that is not defaulted

Expand All @@ -30,7 +30,8 @@ export async function createBackupState(): Promise<IBackupState> {
settings: {
postFullSizeImages: postFullSizeImages.value,
postsPerPage: postsPerPage.value,
autoplayAnimatedMedia: autoplayAnimatedMedia.value
autoplayAnimatedMedia: autoplayAnimatedMedia.value,
autoplayVideos: autoplayVideos.value
}
}

Expand All @@ -51,7 +52,7 @@ async function restoreV3Backup(backupState: IBackupState) {
}

if (backupState.settings) {
const { postFullSizeImages, postsPerPage, autoplayAnimatedMedia } = useUserSettings()
const { postFullSizeImages, postsPerPage, autoplayAnimatedMedia, autoplayVideos } = useUserSettings()

if (backupState.settings.postFullSizeImages != null) {
postFullSizeImages.value = backupState.settings.postFullSizeImages
Expand All @@ -64,6 +65,10 @@ async function restoreV3Backup(backupState: IBackupState) {
if (backupState.settings.autoplayAnimatedMedia != null) {
autoplayAnimatedMedia.value = backupState.settings.autoplayAnimatedMedia
}

if (backupState.settings.autoplayVideos != null) {
autoplayVideos.value = backupState.settings.autoplayVideos
}
}
}

Expand Down
240 changes: 50 additions & 190 deletions components/pages/posts/post/PostMedia.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
<script lang="ts" setup>
import type { IPost } from '~/assets/js/post.dto'
import { vIntersectionObserver } from '@vueuse/components'
import fluidPlayer from 'fluid-player'
import { proxyUrl } from 'assets/js/proxy'

// Lazy-load Fluid Player CSS only when this component is used
if (import.meta.client) {
import('fluid-player/src/css/fluidplayer.css')
}

const { isPremium } = useUserData()
const { autoplayAnimatedMedia } = useUserSettings()
import type { IPost } from '~/assets/js/post.dto'
import { vIntersectionObserver } from '@vueuse/components'
import { proxyUrl } from 'assets/js/proxy'

const { isPremium } = useUserData()
const { autoplayAnimatedMedia, autoplayVideos } = useUserSettings()
let { timesVideoHasRendered } = useEthics()
const { wasCurrentPageSSR } = useSSRDetection()
const { hasInteracted } = useInteractionDetector()

const instance = getCurrentInstance()
const componentId = instance!.uid

export interface PostMediaProps {
postIndex: number
Expand Down Expand Up @@ -44,20 +42,19 @@ const { isPremium } = useUserData()
const triedToLoadWithProxy = shallowRef(false)
const triedToLoadPosterWithProxy = shallowRef(false)

let videoPlayer: FluidPlayerInstance | undefined

const isAnimatedMediaLoading = ref(false)
const isAnimatedMediaPlaying = ref(false)
const mediaHasLoaded = ref(false)

const { activeVideoId, updateCandidate } = useFeedAutoplay()

onMounted(() => {
if (!mediaElement.value) {
return
}

switch (true) {
case isVideo.value:
createVideoPlayer()
break

case isAnimatedMedia.value:
Expand All @@ -69,6 +66,8 @@ const { isPremium } = useUserData()
})

onBeforeUnmount(() => {
updateCandidate(componentId, null, false)

let finalMediaElement = mediaElement.value

if (finalMediaElement == null) {
Expand All @@ -89,181 +88,15 @@ const { isPremium } = useUserData()
// Cancel any pending media requests - https://stackoverflow.com/a/28060352
finalMediaElement.removeAttribute('src')
}

//
else if (isVideo.value) {
destroyVideoPlayer()
}
})

function createVideoPlayer() {
if (!mediaElement.value) {
throw new Error('Media element not found')
}

if (!isVideo.value) {
throw new Error('Media is not a video')
}

const fluidPlayerOptions: Partial<FluidPlayerOptions> = {
layoutControls: {
primaryColor: 'rgba(0, 0, 0, 0.7)',

fillToContainer: true,

preload: 'none',

loop: true,

playbackRateEnabled: true,

allowTheatre: false,

autoRotateFullScreen: true,

// Fix: Opening in fullscreen when searching something with "F"
keyboardControl: false,

controlBar: {
autoHide: true,

playbackRates: ['x2', 'x1.5', 'x1', 'x0.75', 'x0.5', 'x0.25']
},

contextMenu: {
controls: true,

links: [
{
label: 'Remove ads',
href: '/premium?utm_source=internal&utm_medium=player-context-menu'
},
{
label: 'Download',
href: localSrc.value
}
]
},

miniPlayer: {
enabled: false
}
},

onBeforeXMLHttpRequest(request) {
request.withCredentials = false
},

vastOptions: {
adText: 'Only one ad per hour. Never see ads again with Premium!',

vastAdvanced: {
/**
* Handle empty VAST
*/
vastVideoEndedCallback() {
if (!mediaElement.value?.src.endsWith('/null')) {
return
}

mediaElement.value.src = localSrc.value
videoPlayer?.play()
}
},

adList: []
}
}

if (!isPremium.value) {
timesVideoHasRendered.value++

// Only show pause roll ads every 2 videos
if (timesVideoHasRendered.value % 2 === 0) {
fluidPlayerOptions.vastOptions.adList.push(
// In-Video Banner
{
roll: 'onPauseRoll',
vastTag:
/**
* ExoClick
* Pros:
* Cons: Low revenue (7)
*/
'https://s.magsrv.com/splash.php?idzone=5386214'
}
)
}
//

// Only show preroll ads after 3 videos, and every 3 videos
if (timesVideoHasRendered.value > 3 && timesVideoHasRendered.value % 3 === 0) {
fluidPlayerOptions.vastOptions.adList.push(
// In-Stream Video
{
roll: 'preRoll',
vastTag:
/**
* ExoClick
* Pros:
* Cons: Low revenue (9)
*/
'https://s.magsrv.com/splash.php?idzone=5386496'

/**
* HilltopAds
* Pros:
* Cons: Low revenue (4)
*/
// 'https://ellipticaltrack.com/dCm.FXz/doGMNPv/Z-GhUX/OermX9/u-ZqUEltk/PYTgYBy/ODTZQI5oNHDDEHtdNbjLIS5eNvDhk/0uMGgu?limit=1'

/**
* Clickadu
* Pros:
* Cons:
*/
// 'https://anewfeedliberty.com/ceef/gdt3g0/tbt/2034767/tlk.xml'

/**
* AdSession
* Pros:
* Cons:
*/
// 'https://s.eunow4u.com/v1/vast.php?idzone=2310'
}
)
}
}

videoPlayer = fluidPlayer(mediaElement.value as HTMLVideoElement, fluidPlayerOptions)

// TODO: Handle poster error
}

function destroyVideoPlayer() {
if (!videoPlayer) {
throw new Error('Player not found')
}

videoPlayer.destroy()
}

function reloadVideoPlayer(shouldPlay: boolean = false) {
nextTick(() => {
destroyVideoPlayer()

if (shouldPlay) {
nextTick(() => {
createVideoPlayer()

if (!shouldPlay) {
return
}

nextTick(() => {
videoPlayer?.play()
})
const video = mediaElement.value as HTMLVideoElement | null
video?.play()
})
})
}
}

function startPlayingAnimatedMedia() {
Expand Down Expand Up @@ -362,11 +195,37 @@ const { isPremium } = useUserData()
}

const entry = entries[0]
updateCandidate(componentId, mediaElement.value, entry.isIntersecting)
}

if (!entry.isIntersecting) {
videoPlayer?.pause()
// Watch for active video changes to play/pause accordingly
watch(activeVideoId, (newId) => {
if (!isVideo.value || !mediaElement.value || !autoplayVideos.value) {
return
}
}

const video = mediaElement.value as HTMLVideoElement

if (newId === componentId) {
console.debug('Autoplaying video', props.postIndex)

// If user hasn't interacted with the page yet, force mute to ensure autoplay works
// and to avoid blasting sound unexpectedly
const playPromise = video.play()

if (playPromise !== undefined) {
playPromise.catch((error) => {
// Auto-play was prevented
// AbortError is expected when scrolling fast
if (error.name !== 'AbortError') {
console.error('Error playing video:', error)
}
})
}
} else {
video.pause()
}
})

function onMediaLoad(event: Event) {
mediaHasLoaded.value = true
Expand Down Expand Up @@ -577,17 +436,18 @@ const { isPremium } = useUserData()
<!-- Fix(rounded borders): add the same rounded borders that the parent has -->
<video
ref="mediaElement"
v-intersection-observer="[onVideoIntersectionObserver, { rootMargin: '100px' }]"
v-intersection-observer="[onVideoIntersectionObserver, { threshold: 0.2 }]"
:height="mediaSrcHeight"
:poster="localPosterSrc"
:preload="autoplayVideos ? 'metadata' : 'none'"
:muted="autoplayVideos ? !hasInteracted : undefined"
:src="localSrc"
:style="`aspect-ratio: ${mediaSrcWidth}/${mediaSrcHeight};`"
:width="mediaSrcWidth"
class="h-auto w-full rounded-t-md"
controls
loop
playsinline
preload="none"
@error="onMediaError"
/>
</div>
Expand Down
Loading