diff --git a/src/lib/components/HeroCarousel/HeroBackground.svelte b/src/lib/components/HeroCarousel/HeroBackground.svelte index 758113f..07433c0 100644 --- a/src/lib/components/HeroCarousel/HeroBackground.svelte +++ b/src/lib/components/HeroCarousel/HeroBackground.svelte @@ -3,11 +3,14 @@ import classNames from 'classnames'; import { onDestroy } from 'svelte'; import { isFirefox } from '../../utils/browser-detection'; - import YouTubeBackground from '../YouTubeBackground.svelte'; + import YouTubeVideo from '../YoutubeVideo.svelte'; + import { fade } from 'svelte/transition'; + import { localSettings } from '$lib/stores/localstorage.store'; - export let urls: Promise; + export let items: Promise<{ backdropUrl: string; videoUrl?: string }[]>; export let index: number; export let hasFocus = true; + export let heroHasFocus = false; export let hideInterface = false; let visibleIndex = -2; let visibleIndexTimeout: ReturnType; @@ -37,47 +40,52 @@
- {#await urls then urlArray} - {#if !isFirefox()} - {#each urlArray as { trailerUrl, backdropUrl }, i} - {#if i === index} - {#if trailerUrl} - - {:else} -
- {/if} - {/if} - {/each} - {:else} -
- {#each urlArray as { backdropUrl }, i} -
-
+ style={`background-image: url('${backdropUrl}'); transition: opacity 500ms, transform 500ms;`} + > + {#if videoUrl && i === visibleIndex && $localSettings.enableTrailers && $localSettings.autoplayTrailers} + + {/if}
{/each} + {/await} + {:else} +
+ {#await items then items} + {#each items as { backdropUrl, videoUrl }, i} +
+
+ +
+ {/each} + {/await}
{/if} -{/await}
import Container from '../Container.svelte'; - import HeroShowcaseBackground from './HeroBackground.svelte'; + import HeroBackground from './HeroBackground.svelte'; import IconButton from '../FloatingIconButton.svelte'; import { ChevronRight } from 'radix-icons-svelte'; import PageDots from '../HeroShowcase/PageDots.svelte'; @@ -10,13 +10,13 @@ const dispatch = createEventDispatcher(); - export let urls: Promise<{ trailerUrl: string; backdropUrl: string }[]>; + export let items: Promise<{ backdropUrl: string; videoUrl?: string }[]>; export let index = 0; export let hideInterface = false; let length = 0; - $: urls.then((urls) => (length = urls.length)); + $: items.then((urls) => (length = urls.length)); function onNext() { if (index === length - 1) { @@ -42,9 +42,9 @@ return true; } - let hasFocusWithin: Readable; + let heroHasFocusWithin: Readable; let focusIndex: Writable; - $: backgroundHasFocus = $hasFocusWithin && $focusIndex === 0; + $: backgroundHasFocus = $heroHasFocusWithin && $focusIndex === 0; - +
items.map((i) => ({ - backdropUrl: `${TMDB_IMAGES_ORIGINAL}${i.backdropUri}`, - trailerUrl: i.trailerUrl || '' - })))} + items={items.then((items) => + items.map((i) => ({ + backdropUrl: `${TMDB_IMAGES_ORIGINAL}${i.backdropUri}`, + videoUrl: i.videoUrl + })) + )} bind:index={showcaseIndex} on:enter on:navigate={({ detail }) => { diff --git a/src/lib/components/HeroShowcase/TmdbMoviesHeroShowcase.svelte b/src/lib/components/HeroShowcase/TmdbMoviesHeroShowcase.svelte index 2573b69..ab99de1 100644 --- a/src/lib/components/HeroShowcase/TmdbMoviesHeroShowcase.svelte +++ b/src/lib/components/HeroShowcase/TmdbMoviesHeroShowcase.svelte @@ -7,14 +7,13 @@ export let movies: Promise; - $: items = movies.then(async (movies) => { - return Promise.all( + $: items = movies + .then(async (movies) => movies.map(async (movie) => { - const trailerUrl = movie.id - ? await tmdbApi.getMovieVideos(movie.id).then((videos) => - videos.find((video) => video.type === 'Trailer' && video.site === 'YouTube')?.key || '' - ) - : ''; + const movieFull = await tmdbApi.getTmdbMovie(movie.id ?? 0); + const videoUrl = movieFull?.videos?.results?.find( + (video) => video.type === 'Trailer' && video.site === 'YouTube' + )?.key; return { id: movie.id ?? 0, @@ -23,7 +22,7 @@ backdropUri: movie.backdrop_path ?? '', title: movie.title ?? '', overview: movie.overview ?? '', - trailerUrl: trailerUrl ?? '', + videoUrl, infoProperties: [ ...(movie.release_date ? [{ label: new Date(movie.release_date).getFullYear().toString() }] @@ -43,8 +42,8 @@ ] }; }) - ); - }); + ) + .then((i) => Promise.all(i)); navigate(`/movie/${detail?.id}`)} {items} /> diff --git a/src/lib/components/HeroShowcase/TmdbSeriesHeroShowcase.svelte b/src/lib/components/HeroShowcase/TmdbSeriesHeroShowcase.svelte index 8a24cde..e6d8beb 100644 --- a/src/lib/components/HeroShowcase/TmdbSeriesHeroShowcase.svelte +++ b/src/lib/components/HeroShowcase/TmdbSeriesHeroShowcase.svelte @@ -10,20 +10,19 @@ $: items = series.then(async (series) => { return Promise.all( series.map(async (series) => { - const trailerUrl = series.id - ? await tmdbApi.getSeriesVideos(series.id).then((videos) => - videos.find((video) => video.type === 'Trailer' && video.site === 'YouTube')?.key || '' - ) - : ''; + const seriesFull = await tmdbApi.getTmdbSeries(series.id ?? 0); + const videoUrl = seriesFull?.videos?.results?.find( + (video) => video.type === 'Trailer' && video.site === 'YouTube' + )?.key; return { id: series.id ?? 0, - type: 'series' as const, + type: 'tv' as const, posterUri: series.poster_path ?? '', backdropUri: series.backdrop_path ?? '', title: series.name ?? '', overview: series.overview ?? '', - trailerUrl: trailerUrl ?? '', + videoUrl, infoProperties: [ ...(series.status !== 'Ended' ? [{ label: `Since ${new Date(series.first_air_date ?? 0).getFullYear()}` }] diff --git a/src/lib/components/YouTubeBackground.svelte b/src/lib/components/YouTubeBackground.svelte deleted file mode 100644 index 2e7cb1d..0000000 --- a/src/lib/components/YouTubeBackground.svelte +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - -
- -{#if showBackgroundImage || showBackgroundImageError} -
-{/if} - - diff --git a/src/lib/components/YoutubeVideo.svelte b/src/lib/components/YoutubeVideo.svelte new file mode 100644 index 0000000..63765f1 --- /dev/null +++ b/src/lib/components/YoutubeVideo.svelte @@ -0,0 +1,222 @@ + + +
+
+
+ + diff --git a/src/lib/pages/ManagePage/ManagePage.svelte b/src/lib/pages/ManagePage/ManagePage.svelte index 28a2312..4741cb6 100644 --- a/src/lib/pages/ManagePage/ManagePage.svelte +++ b/src/lib/pages/ManagePage/ManagePage.svelte @@ -332,6 +332,22 @@ localSettings.update((p) => ({ ...p, checkForUpdates: detail }))} />
+
+ + + localSettings.update((p) => ({ ...p, enableTrailers: detail }))} + /> +
+
+ + + localSettings.update((p) => ({ ...p, autoplayTrailers: detail }))} + /> +
diff --git a/src/lib/pages/TitlePages/MoviePage/MoviePage.svelte b/src/lib/pages/TitlePages/MoviePage/MoviePage.svelte index e00b2ba..2762877 100644 --- a/src/lib/pages/TitlePages/MoviePage/MoviePage.svelte +++ b/src/lib/pages/TitlePages/MoviePage/MoviePage.svelte @@ -37,6 +37,23 @@ $: recommendations = tmdbApi.getMovieRecommendations(tmdbId); + $: images = $tmdbMovie.then((movie) => { + const trailer = movie?.videos?.results?.find( + (video) => video.type === 'Trailer' && video.site === 'YouTube' + )?.key; + + return ( + movie?.images.backdrops + ?.sort((a, b) => (b.vote_count || 0) - (a.vote_count || 0)) + ?.map((bd, i) => ({ + backdropUrl: TMDB_IMAGES_ORIGINAL + bd.file_path || '', + + videoUrl: trailer && i === 0 ? trailer : undefined + })) + .slice(0, 5) || [] + ); + }); + let titleProperties: { href?: string; label: string }[] = []; $tmdbMovie.then((movie) => { if (movie?.runtime) { @@ -71,15 +88,7 @@ class="h-[calc(100vh-4rem)] flex flex-col py-16 px-32" on:enter={scrollIntoView({ top: 999 })} > - - movie?.images.backdrops - ?.sort((a, b) => (b.vote_count || 0) - (a.vote_count || 0)) - ?.map((bd) => ({backdropUrl: TMDB_IMAGES_ORIGINAL + bd.file_path || ''})) - .slice(0, 5) || [] - )} - > +
{#await $tmdbMovie then movie} diff --git a/src/lib/pages/TitlePages/SeriesPage/SeriesPage.svelte b/src/lib/pages/TitlePages/SeriesPage/SeriesPage.svelte index 0559244..8c5e913 100644 --- a/src/lib/pages/TitlePages/SeriesPage/SeriesPage.svelte +++ b/src/lib/pages/TitlePages/SeriesPage/SeriesPage.svelte @@ -40,6 +40,22 @@ const episodeCards = useRegistrar(); let scrollTop: number; + $: images = $tmdbSeries.then((series) => { + const trailer = series?.videos?.results?.find( + (video) => video.type === 'Trailer' && video.site === 'YouTube' + )?.key; + + return ( + series?.images.backdrops + ?.sort((a, b) => (b.vote_count || 0) - (a.vote_count || 0)) + ?.map((bd, i) => ({ + backdropUrl: TMDB_IMAGES_ORIGINAL + bd.file_path || '', + videoUrl: trailer && i === 0 ? trailer : undefined + })) + .slice(0, 5) || [] + ); + }); + let titleProperties: { href?: string; label: string }[] = []; $tmdbSeries.then((series) => { if (series && series.status !== 'Ended') { @@ -86,15 +102,7 @@ } }} > - - series?.images.backdrops - ?.sort((a, b) => (b.vote_count || 0) - (a.vote_count || 0)) - ?.map((bd) => ({backdropUrl: TMDB_IMAGES_ORIGINAL + bd.file_path || ''})) - .slice(0, 5) || [] - )} - > +
{#await $tmdbSeries then series} diff --git a/src/lib/stores/localstorage.store.ts b/src/lib/stores/localstorage.store.ts index 7243f09..345047f 100644 --- a/src/lib/stores/localstorage.store.ts +++ b/src/lib/stores/localstorage.store.ts @@ -44,11 +44,15 @@ export const localSettings = createLocalStorageStore<{ useCssTransitions: boolean; checkForUpdates: boolean; skippedVersion: string; + enableTrailers: boolean; + autoplayTrailers: boolean; }>('settings', { animateScrolling: true, useCssTransitions: true, checkForUpdates: true, - skippedVersion: '' + skippedVersion: '', + enableTrailers: true, + autoplayTrailers: true }); export type LibraryViewSettings = { diff --git a/src/lib/types.ts b/src/lib/types.ts index 67ea202..7a62c08 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -9,4 +9,59 @@ export type MediaType = 'Movie' | 'Series'; declare global { const REIVERR_VERSION: string; + + type YTPlayerOptions = { + videoId: string; + playerVars: Record; + // playerVars: { + // autoplay: 0 | 1; + // controls: 0 | 1; + // disablekb: 0 | 1; + // enablejsapi: 0 | 1; + // iv_load_policy: 1 | 3; + // loop: 0 | 1; + // modestbranding: 0 | 1; + // playsinline: 0 | 1; + // rel: 0 | 1; + // showinfo: 0 | 1; + // start: number; + // fs: 0 | 1; + // cc_load_policy: 0 | 1; + // mute: 0 | 1; + // }; + events: { + onReady: (event: any) => void; + onStateChange: (event: any) => void; + onError: (event: any) => void; + }; + }; + + type YTPlayer = { + new (id: string, options: YTPlayerOptions): YTPlayer; + destroy(): void; + getDuration(): number; + getCurrentTime(): number; + pauseVideo(): void; + playVideo(): void; + stopVideo(): void; + seekTo(seconds: number, allowSeekAhead?: boolean): void; + }; + + // Youtube API + interface YT { + Player: YTPlayer; + PlayerState: { + ENDED: number; + PLAYING: number; + PAUSED: number; + BUFFERING: number; + CUED: number; + }; + } + + const YT: YT; + + interface Window { + YT: YT; + } } diff --git a/tizen/config.xml b/tizen/config.xml index b5d0000..e588d95 100644 --- a/tizen/config.xml +++ b/tizen/config.xml @@ -9,4 +9,6 @@ Reiverr + * +