mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-17 21:53:12 +02:00
feat: trailer icons/links, improved trailer fade animations
This commit is contained in:
@@ -51,8 +51,12 @@
|
||||
})}
|
||||
style={`background-image: url('${backdropUrl}'); transition: opacity 500ms, transform 500ms;`}
|
||||
>
|
||||
{#if videoUrl && i === visibleIndex && $localSettings.enableTrailers && $localSettings.autoplayTrailers}
|
||||
<YouTubeVideo videoId={videoUrl} play={heroHasFocus} />
|
||||
{#if videoUrl && i === visibleIndex && $localSettings.enableTrailers}
|
||||
<YouTubeVideo
|
||||
videoId={videoUrl}
|
||||
autoplay={$localSettings.autoplayTrailers}
|
||||
visible={$localSettings.autoplayTrailers ? heroHasFocus : hasFocus}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import Container from '../Container.svelte';
|
||||
import HeroBackground from './HeroBackground.svelte';
|
||||
import IconButton from '../FloatingIconButton.svelte';
|
||||
import { ChevronRight } from 'radix-icons-svelte';
|
||||
import { ChevronRight, ChevronUp } from 'radix-icons-svelte';
|
||||
import PageDots from '../HeroShowcase/PageDots.svelte';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
@@ -71,13 +71,25 @@
|
||||
bind:hasFocusWithin={heroHasFocusWithin}
|
||||
bind:focusIndex
|
||||
>
|
||||
<HeroBackground {items} {index} hasFocus={backgroundHasFocus} heroHasFocus={$heroHasFocusWithin} {hideInterface} />
|
||||
<HeroBackground
|
||||
{items}
|
||||
{index}
|
||||
hasFocus={backgroundHasFocus}
|
||||
heroHasFocus={$heroHasFocusWithin}
|
||||
{hideInterface}
|
||||
/>
|
||||
<div
|
||||
class={classNames('flex flex-1 z-10 transition-opacity', {
|
||||
'opacity-0': hideInterface
|
||||
})}
|
||||
>
|
||||
<slot />
|
||||
<!-- <div
|
||||
class="absolute inset-x-1/2 -translate-x-1/2 top-16 min-w-fit flex flex-col items-center justify-center"
|
||||
>
|
||||
<ChevronUp size={38} />
|
||||
<div class="whitespace-nowrap">View Trailer</div>
|
||||
</div> -->
|
||||
<div class="flex flex-col justify-end ml-4">
|
||||
<div class="flex flex-1 justify-end items-center">
|
||||
<IconButton on:click={onNext}>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import { TMDB_IMAGES_ORIGINAL, TMDB_POSTER_SMALL } from '../../constants';
|
||||
import { registrars } from '../../selectable.js';
|
||||
import HeroCarousel from '../HeroCarousel/HeroCarousel.svelte';
|
||||
import { localSettings } from '$lib/stores/localstorage.store';
|
||||
import type { TitleInfoProperty } from '$lib/pages/TitlePages/HeroTitleInfo';
|
||||
|
||||
type ShowcaseItem = {
|
||||
id: number;
|
||||
@@ -14,7 +16,7 @@
|
||||
videoUrl?: string;
|
||||
title: string;
|
||||
overview: string;
|
||||
infoProperties: { label: string; href?: string }[];
|
||||
infoProperties: TitleInfoProperty[];
|
||||
url?: string;
|
||||
};
|
||||
|
||||
@@ -38,7 +40,7 @@
|
||||
items={items.then((items) =>
|
||||
items.map((i) => ({
|
||||
backdropUrl: `${TMDB_IMAGES_ORIGINAL}${i.backdropUri}`,
|
||||
videoUrl: i.videoUrl
|
||||
videoUrl: $localSettings.autoplayTrailers ? i.videoUrl : undefined
|
||||
}))
|
||||
)}
|
||||
bind:index={showcaseIndex}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { navigate } from '../StackRouter/StackRouter';
|
||||
import HeroShowcase from './HeroShowcase.svelte';
|
||||
import { tmdbApi } from '$lib/apis/tmdb/tmdb-api';
|
||||
import { Video } from 'radix-icons-svelte';
|
||||
|
||||
export let movies: Promise<TmdbMovie2[]>;
|
||||
|
||||
@@ -38,7 +39,12 @@
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(movie.genres ? [{ label: movie.genres.map((genre) => genre.name).join(', ') }] : [])
|
||||
...(movie.genres
|
||||
? [{ label: movie.genres.map((genre) => genre.name).join(', ') }]
|
||||
: []),
|
||||
...(videoUrl
|
||||
? [{ icon: Video, href: `https://www.youtube.com/watch?v=${videoUrl}` }]
|
||||
: [])
|
||||
]
|
||||
};
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { navigate } from '../StackRouter/StackRouter';
|
||||
import HeroShowcase from './HeroShowcase.svelte';
|
||||
import { tmdbApi } from '$lib/apis/tmdb/tmdb-api';
|
||||
import { Video } from 'radix-icons-svelte';
|
||||
|
||||
export let series: Promise<TmdbSeries2[]>;
|
||||
|
||||
@@ -39,7 +40,12 @@
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(series.genres ? [{ label: series.genres.map((genre) => genre.name).join(', ') }] : [])
|
||||
...(series.genres
|
||||
? [{ label: series.genres.map((genre) => genre.name).join(', ') }]
|
||||
: []),
|
||||
...(videoUrl
|
||||
? [{ icon: Video, href: `https://www.youtube.com/watch?v=${videoUrl}` }]
|
||||
: [])
|
||||
]
|
||||
};
|
||||
})
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
const STOP_WHEN_REMAINING = 12;
|
||||
|
||||
export let videoId: string | null = null;
|
||||
export let play = false;
|
||||
// Load video only if visible, pause with delay when not visible
|
||||
export let visible = true;
|
||||
// Play/pause video
|
||||
export let play = true;
|
||||
// Autoplay after load
|
||||
export let autoplay = true;
|
||||
export let autoplayDelay = 2000;
|
||||
export let loadTime = PLATFORM_TV ? 2500 : 1000;
|
||||
@@ -20,14 +24,20 @@
|
||||
let checkStopInterval: ReturnType<typeof setInterval>;
|
||||
let autoplayTimeout: ReturnType<typeof setTimeout>;
|
||||
let loadTimeout: ReturnType<typeof setTimeout>;
|
||||
let pauseTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
$: if (isInitialized && player?.playVideo && play) {
|
||||
$: if (isInitialized && player?.playVideo && visible && play) {
|
||||
clearTimeout(pauseTimeout);
|
||||
player.playVideo();
|
||||
} else if (isInitialized && player?.pauseVideo && !play) {
|
||||
player.pauseVideo();
|
||||
} else if (isInitialized && player?.pauseVideo && !visible) {
|
||||
pauseTimeout = setTimeout(() => {
|
||||
player.pauseVideo();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
$: if (didMount && !isInitialized && play) loadYouTubeAPI();
|
||||
$: if (didMount && !isInitialized && visible && play) loadYouTubeAPI();
|
||||
function loadYouTubeAPI() {
|
||||
isInitialized = true;
|
||||
console.log('Loading YouTube API for ' + videoId);
|
||||
@@ -49,6 +59,7 @@
|
||||
clearInterval(checkStopInterval);
|
||||
clearTimeout(autoplayTimeout);
|
||||
clearTimeout(loadTimeout);
|
||||
clearTimeout(pauseTimeout);
|
||||
isPlayerReady = false;
|
||||
|
||||
if (!player) return;
|
||||
@@ -169,11 +180,11 @@
|
||||
});
|
||||
$: {
|
||||
const el = document.getElementById(playerId);
|
||||
if (el) el.style.opacity = isPlayerReady && play ? '1' : '0';
|
||||
if (el) el.style.opacity = isPlayerReady && visible ? '1' : '0';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div out:fade={{ delay: isPlayerReady && play ? 2000 : 0 }}>
|
||||
<div out:fade={{ delay: isPlayerReady && visible ? 1000 : 0 }}>
|
||||
<div id={playerId} class="video-background" style="opacity: 0;" />
|
||||
</div>
|
||||
|
||||
@@ -185,7 +196,7 @@
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
transform: translate(-50%, -50%) scale(1.6);
|
||||
transition: transform 0.5s ease-in-out, opacity 1s ease-in-out;
|
||||
transition: transform 0.5s ease-in-out, opacity 0.5s ease-in-out;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<script lang="ts">
|
||||
import classNames from 'classnames';
|
||||
import { DotFilled } from 'radix-icons-svelte';
|
||||
import type { TitleInfoProperty } from './HeroTitleInfo';
|
||||
|
||||
export let title: string;
|
||||
export let properties: {
|
||||
href?: string;
|
||||
label?: string;
|
||||
}[] = [];
|
||||
export let properties: TitleInfoProperty[] = [];
|
||||
export let overview: string;
|
||||
export let onClickTitle: (() => void) | undefined = undefined;
|
||||
</script>
|
||||
@@ -26,18 +24,28 @@
|
||||
<div
|
||||
class="flex items-center gap-1 uppercase text-zinc-300 font-semibold tracking-wider mt-2 text-lg"
|
||||
>
|
||||
{#each properties.filter((p) => !!p.label) as property, i}
|
||||
{#each properties.filter((p) => !!p.label || !!p.icon) as property, i}
|
||||
{#if i !== 0}
|
||||
<DotFilled />
|
||||
{/if}
|
||||
{#if property.href}
|
||||
<p class="flex-shrink-0">
|
||||
<a href={property.href} target="_blank">{property.label}</a>
|
||||
<a href={property.href} target="_blank">
|
||||
{#if property.label}
|
||||
{property.label}
|
||||
{:else if property.icon}
|
||||
<svelte:component this={property.icon} size={22} />
|
||||
{/if}
|
||||
</a>
|
||||
</p>
|
||||
{:else}
|
||||
{:else if property.label}
|
||||
<p class="flex-shrink-0">
|
||||
{property.label}
|
||||
</p>
|
||||
{:else if property.icon}
|
||||
<span>
|
||||
<svelte:component this={property.icon} size={22} />
|
||||
</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
7
src/lib/pages/TitlePages/HeroTitleInfo.ts
Normal file
7
src/lib/pages/TitlePages/HeroTitleInfo.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ComponentType } from 'svelte';
|
||||
|
||||
export type TitleInfoProperty = {
|
||||
href?: string;
|
||||
label?: string;
|
||||
icon?: ComponentType;
|
||||
};
|
||||
@@ -12,9 +12,11 @@
|
||||
import { tmdbMovieDataStore } from '$lib/stores/data.store';
|
||||
import { useMovieUserData } from '$lib/stores/media-user-data.store';
|
||||
import { formatMinutesToTime, formatThousands } from '$lib/utils';
|
||||
import { Bookmark, Check, ExternalLink, Minus, Play } from 'radix-icons-svelte';
|
||||
import { Bookmark, Check, ExternalLink, Minus, Play, Video } from 'radix-icons-svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import HeroTitleInfo from '../HeroTitleInfo.svelte';
|
||||
import { localSettings } from '$lib/stores/localstorage.store';
|
||||
import type { TitleInfoProperty } from '../HeroTitleInfo';
|
||||
|
||||
export let id: string;
|
||||
const tmdbId = Number(id);
|
||||
@@ -54,8 +56,12 @@
|
||||
);
|
||||
});
|
||||
|
||||
let titleProperties: { href?: string; label: string }[] = [];
|
||||
let titleProperties: TitleInfoProperty[] = [];
|
||||
$tmdbMovie.then((movie) => {
|
||||
const trailer = movie?.videos?.results?.find(
|
||||
(video) => video.type === 'Trailer' && video.site === 'YouTube'
|
||||
)?.key;
|
||||
|
||||
if (movie?.runtime) {
|
||||
titleProperties.push({
|
||||
label: formatMinutesToTime(movie.runtime)
|
||||
@@ -74,6 +80,13 @@
|
||||
label: movie.genres.map((g) => g.name).join(', ')
|
||||
});
|
||||
}
|
||||
|
||||
if ($localSettings.enableTrailers && trailer) {
|
||||
titleProperties.push({
|
||||
icon: Video,
|
||||
href: `https://www.youtube.com/watch?v=${trailer}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
import { scrollIntoView, useRegistrar } from '$lib/selectable';
|
||||
import { useSeriesUserData } from '$lib/stores/media-user-data.store';
|
||||
import { formatThousands } from '$lib/utils';
|
||||
import { Bookmark, Check, ExternalLink, Minus, Play } from 'radix-icons-svelte';
|
||||
import { Bookmark, Check, ExternalLink, Minus, Play, Video } from 'radix-icons-svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import TitleProperties from '../HeroTitleInfo.svelte';
|
||||
import EpisodeGrid from './EpisodeGrid.svelte';
|
||||
import { localSettings } from '$lib/stores/localstorage.store';
|
||||
import type { TitleInfoProperty } from '../HeroTitleInfo';
|
||||
|
||||
export let id: string;
|
||||
const tmdbId = Number(id);
|
||||
@@ -56,8 +58,12 @@
|
||||
);
|
||||
});
|
||||
|
||||
let titleProperties: { href?: string; label: string }[] = [];
|
||||
let titleProperties: TitleInfoProperty[] = [];
|
||||
$tmdbSeries.then((series) => {
|
||||
const trailer = series?.videos?.results?.find(
|
||||
(video) => video.type === 'Trailer' && video.site === 'YouTube'
|
||||
)?.key;
|
||||
|
||||
if (series && series.status !== 'Ended') {
|
||||
titleProperties.push({
|
||||
label: `Since ${new Date(series.first_air_date || Date.now())?.getFullYear()}`
|
||||
@@ -82,6 +88,13 @@
|
||||
label: series.genres.map((g) => g.name).join(', ')
|
||||
});
|
||||
}
|
||||
|
||||
if ($localSettings.enableTrailers && trailer) {
|
||||
titleProperties.push({
|
||||
icon: Video,
|
||||
href: `https://www.youtube.com/watch?v=${trailer}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
|
||||
Reference in New Issue
Block a user