diff --git a/backend/migrations/1739757875787-clear-metadata-cache.ts b/backend/migrations/1739757875787-clear-metadata-cache.ts new file mode 100644 index 0000000..c237d3e --- /dev/null +++ b/backend/migrations/1739757875787-clear-metadata-cache.ts @@ -0,0 +1,10 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ClearMetadataCache1739757875787 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // Drop all rows in SERIES table + await queryRunner.query('DELETE FROM "series"'); + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/backend/package.json b/backend/package.json index cc05b2c..e98422e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,7 +26,7 @@ "typeorm": "ts-node ./node_modules/typeorm/cli", "typeorm:run-migrations": "npm run typeorm migration:run -- -d ./dist/data-source.js", "typeorm:generate-migration": "ts-node ./node_modules/typeorm/cli -d ./dist/data-source.js migration:generate", - "typeorm:create-migration": "ts-node ./node_modules/typeorm/cli migration:create ./migrations/$npm_config_name", + "typeorm:create-migration": "ts-node ./node_modules/typeorm/cli migration:create", "typeorm:revert-migration": "ts-node ./node_modules/typeorm/cli -d ./dist/data-source.js migration:revert" }, "dependencies": { diff --git a/backend/src/main.ts b/backend/src/main.ts index 48152b8..383869d 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -24,7 +24,14 @@ async function createAdminUser(userService: UsersService) { } async function bootstrap() { - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule, { + logger: [ + 'error', + 'warn', + 'log', + ...(ENV === 'development' ? (['debug'] as const) : []), + ], + }); app.setGlobalPrefix('api'); app.enableCors(); app.use(json({ limit: '50mb' })); diff --git a/backend/src/metadata/metadata.module.ts b/backend/src/metadata/metadata.module.ts index eb045a9..bd64432 100644 --- a/backend/src/metadata/metadata.module.ts +++ b/backend/src/metadata/metadata.module.ts @@ -1,12 +1,25 @@ +import { CacheModule } from '@nestjs/cache-manager'; import { Module } from '@nestjs/common'; -import { DatabaseModule } from 'src/database/database.module'; +import { TMDB_CACHE_TTL } from 'src/consts'; +import { UsersModule } from 'src/users/users.module'; import { metadataProviders } from './metadata.providers'; import { MetadataService } from './metadata.service'; -import { TmdbModule } from './tmdb/tmdb.module'; +import { TmdbController } from './tmdb/tmdb.controller'; +import { tmdbProviders } from './tmdb/tmdb.providers'; +import { TmdbService } from './tmdb/tmdb.service'; @Module({ - imports: [TmdbModule], - providers: [...metadataProviders, MetadataService], + imports: [ + UsersModule, + CacheModule.register({ ttl: TMDB_CACHE_TTL, max: 10_000 }), + ], + providers: [ + ...metadataProviders, + MetadataService, + ...tmdbProviders, + TmdbService, + ], + controllers: [TmdbController], exports: [MetadataService], }) export class MetadataModule {} diff --git a/backend/src/metadata/metadata.service.ts b/backend/src/metadata/metadata.service.ts index 3801904..8356e0f 100644 --- a/backend/src/metadata/metadata.service.ts +++ b/backend/src/metadata/metadata.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, Logger } from '@nestjs/common'; import { Repository } from 'typeorm'; import { Movie, Series } from './metadata.entity'; import { MOVIE_REPOSITORY, SERIES_REPOSITORY } from './metadata.providers'; @@ -9,6 +9,8 @@ import { TmdbService } from './tmdb/tmdb.service'; @Injectable() export class MetadataService { + private logger = new Logger(MetadataService.name); + constructor( @Inject(TMDB_API) private tmdbApi: TmdbApi, @@ -39,11 +41,7 @@ export class MetadataService { !movie.updatedAt || new Date().getTime() - movie.updatedAt.getTime() > TMDB_CACHE_TTL ) { - const tmdbMovie = await this.tmdbApi.v3 - .movieDetails(Number(tmdbId), { - append_to_response: 'videos,credits,external_ids,images', - }) - .then((r) => r.data as TmdbMovieFull); + const tmdbMovie = await this.tmdbService.getFullMovie(Number(tmdbId)); movie.tmdbMovie = tmdbMovie; } @@ -65,7 +63,7 @@ export class MetadataService { } if (series.isStale()) { - console.log('getting metadata for series', tmdbId); + this.logger.debug(`Caching series ${tmdbId}`); const tmdbSeries = await this.tmdbService.getFullSeries(Number(tmdbId)); if (tmdbSeries) series.tmdbSeries = tmdbSeries; } diff --git a/backend/src/metadata/tmdb/tmdb.controller.ts b/backend/src/metadata/tmdb/tmdb.controller.ts index 0c3082c..b1ed1f8 100644 --- a/backend/src/metadata/tmdb/tmdb.controller.ts +++ b/backend/src/metadata/tmdb/tmdb.controller.ts @@ -1,55 +1,28 @@ -import { - Cache, - CACHE_MANAGER -} from '@nestjs/cache-manager'; +import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager'; import { All, Controller, Inject, + Logger, Param, Req, Res, - UseGuards + UseGuards, } from '@nestjs/common'; import { Request, Response } from 'express'; import { GetAuthUser, UserAccessControl } from 'src/auth/auth.guard'; import { TMDB_API_KEY, TMDB_CACHE_TTL } from 'src/consts'; import { User } from 'src/users/user.entity'; +import { MetadataService } from '../metadata.service'; @UseGuards(UserAccessControl) @Controller('tmdb') export class TmdbController { - constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {} - // constructor(private metadataService: MetadataService) {} - - // @Get('v3/proxy/3/movie/:tmdbId') - // async getMovieDetails( - // @Param('tmdbId') tmdbId: string, - // @Next() next: NextFunction, - // @GetAuthUser() user: User, - // // @Res({ passthrough: true }) res: Response, // Passthrough required - // ) { - // if (!parseInt(tmdbId)) { - // console.log('Invalid TMDB ID', tmdbId); - // next(); - // return; - // } - - // console.log('getting cached movie', tmdbId); - - // const movie = await this.metadataService - // .getMovieByTmdbId(tmdbId) - // .catch((e) => { - // console.error('Error getting movie by TMDB ID', tmdbId, e); - // return null; - // }); - - // if (!movie?.tmdbMovie) { - // console.error('No movie found for TMDB ID', tmdbId); - // } - // console.log('returning cached movie', tmdbId); - // return movie?.tmdbMovie; - // } + private logger = new Logger(TmdbController.name); + constructor( + @Inject(CACHE_MANAGER) private cacheManager: Cache, + private metadataService: MetadataService, + ) {} // @UseInterceptors(CacheInterceptor) // @CacheTTL(METADATA_CACHE_TTL) @@ -62,7 +35,7 @@ export class TmdbController { ) { const uri = params[0] + '?' + req.url.split('?')[1]; const cached = await this.cacheManager.get(uri).catch((e) => { - console.error('Error getting cache', e); + this.logger.error('Error getting cache', e); return null; }); @@ -72,44 +45,34 @@ export class TmdbController { return cached; } - // console.log('TMDB PROXY', req.url); + // 3/tv/87739?append_to_response=videos%2Caggregate_credits%2Cexternal_ids%2Cimages&include_image_language=en%2Cen%2Cnull + const first = uri.split('?')?.[0]; + if (req.method === 'GET' && first.match(/3\/tv\/\d+$/)) { + const tmdbId = first.split('/').pop(); + this.logger.debug(`Getting series from cache: ${tmdbId}`); + const metadata = await this.metadataService.getSeriesByTmdbId(tmdbId); + res.json(metadata.tmdbSeries); + return metadata; + } else if (req.method === 'GET' && first.match(/3\/movie\/\d+$/)) { + const tmdbId = first.split('/').pop(); + this.logger.debug(`Getting movie from cache: ${tmdbId}`); + const metadata = await this.metadataService.getMovieByTmdbId(tmdbId); + res.json(metadata.tmdbMovie); + return metadata; + } - // if (params[0].match(/^3\/movie\/\d+\/?$/)) { - // // console.log('req.params', req.params); - - // const movie = await this.metadataService.getMovieByTmdbId( - // req.params[0].split('/')[2], - // ); - - // // console.log('movie', movie); - // if (movie?.tmdbMovie) { - // // console.log('returning cached movie'); - // res.json(movie.tmdbMovie); - // return; - // } - // } + this.logger.debug(`TMDB proxy cache miss: ${req.method} ${uri}`); const proxyRes = await fetch(`https://api.themoviedb.org/${uri}`, { method: req.method || 'GET', headers: { Authorization: `Bearer ${TMDB_API_KEY}`, - // ...headers, - // Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${settings.apiKey}"`, }, - }) - // .then((r) => { - // // r.text().then((text) => - // // console.log('TMDB Proxy response', uri, r.status, text), - // // ); - // return r; - // }) - .catch((e) => { - console.error('TMDB Proxy error', e); - // res.status(500).send('Proxy error'); - throw e; - }); + }).catch((e) => { + this.logger.error('TMDB Proxy error', e); + throw e; + }); - // Readable.from(proxyRes.body).pipe(res); const json = await proxyRes.json(); res.status(proxyRes.status); res.json(json); diff --git a/backend/src/metadata/tmdb/tmdb.dto.ts b/backend/src/metadata/tmdb/tmdb.dto.ts index 2861907..1d1592d 100644 --- a/backend/src/metadata/tmdb/tmdb.dto.ts +++ b/backend/src/metadata/tmdb/tmdb.dto.ts @@ -12,11 +12,22 @@ export type MovieExternalIds = Awaited< export type MovieImages = Awaited< ReturnType >['data']; - export type TmdbMovie = Awaited< ReturnType >['data']; +export type SeriesVideos = Awaited< + ReturnType +>['data']; +export type SeriesCredits = Awaited< + ReturnType +>['data']; +export type SeriesExternalIds = Awaited< + ReturnType +>['data']; +export type SeriesImages = Awaited< + ReturnType +>['data']; export type TmdbSeries = Awaited< ReturnType >['data']; @@ -28,4 +39,9 @@ export type TmdbMovieFull = TmdbMovie & { images: MovieImages; }; -export type TmdbSeriesFull = TmdbSeries; +export type TmdbSeriesFull = TmdbSeries & { + videos: SeriesVideos; + aggregate_credits: SeriesCredits; + external_ids: SeriesExternalIds; + images: SeriesImages; +}; diff --git a/backend/src/metadata/tmdb/tmdb.module.ts b/backend/src/metadata/tmdb/tmdb.module.ts deleted file mode 100644 index 284c406..0000000 --- a/backend/src/metadata/tmdb/tmdb.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CacheModule } from '@nestjs/cache-manager'; -import { Module } from '@nestjs/common'; -import { TMDB_CACHE_TTL } from 'src/consts'; -import { UsersModule } from 'src/users/users.module'; -import { TmdbController } from './tmdb.controller'; -import { tmdbProviders } from './tmdb.providers'; -import { TmdbService } from './tmdb.service'; - -@Module({ - imports: [ - UsersModule, - CacheModule.register({ ttl: TMDB_CACHE_TTL, max: 10_000 }), - ], - providers: [...tmdbProviders, TmdbService], - exports: [...tmdbProviders, TmdbService], - controllers: [TmdbController], -}) -export class TmdbModule {} diff --git a/backend/src/metadata/tmdb/tmdb.service.ts b/backend/src/metadata/tmdb/tmdb.service.ts index 734b7f1..51b3658 100644 --- a/backend/src/metadata/tmdb/tmdb.service.ts +++ b/backend/src/metadata/tmdb/tmdb.service.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { TmdbSeriesFull } from './tmdb.dto'; +import { TmdbMovieFull, TmdbSeriesFull } from './tmdb.dto'; import { TMDB_API, TmdbApi } from './tmdb.providers'; @Injectable() @@ -11,8 +11,10 @@ export class TmdbService { async getFullSeries(tmdbId: number): Promise { const tmdbSeries = await this.tmdbApi.v3 - .tvSeriesDetails(Number(tmdbId)) - .then((r) => r.data); + .tvSeriesDetails(Number(tmdbId), { + append_to_response: 'videos,aggregate_credits,external_ids,images', + }) + .then((r) => r.data as TmdbSeriesFull); // .catch((e) => { // console.error('could not get metadata for series', tmdbId, e); // return e; @@ -20,4 +22,12 @@ export class TmdbService { return tmdbSeries; } + + async getFullMovie(tmdbId: number) { + return this.tmdbApi.v3 + .movieDetails(Number(tmdbId), { + append_to_response: 'videos,credits,external_ids,images', + }) + .then((r) => r.data as TmdbMovieFull); + } } diff --git a/src/lib/apis/tmdb/tmdb-api.ts b/src/lib/apis/tmdb/tmdb-api.ts index f721761..8f26041 100644 --- a/src/lib/apis/tmdb/tmdb-api.ts +++ b/src/lib/apis/tmdb/tmdb-api.ts @@ -305,6 +305,35 @@ export class TmdbApi implements Api { getPersonBackdrops = async (person_id: number) => this.getPersonTaggedImages(person_id).then((r) => r.filter((i) => (i.aspect_ratio || 0) > 1.5)); + getMovieVideos = async (tmdbId: number) => { + return this.getClient() + .GET('/3/movie/{movie_id}/videos', { + params: { + path: { + movie_id: tmdbId + }, + query: { + language: get(settings)?.language || 'en', + } + } + }) + .then((res) => res.data?.results || []); + }; + getSeriesVideos = async (tmdbId: number) => { + return this.getClient() + ?.GET('/3/tv/{series_id}/videos', { + params: { + path: { + series_id: tmdbId + }, + query: { + language: get(settings)?.language || 'en', + } + } + }) + .then((res) => res.data?.results || []); + }; + // OTHER // USER diff --git a/src/lib/components/HeroCarousel/HeroBackground.svelte b/src/lib/components/HeroCarousel/HeroBackground.svelte index 1a39ec4..07433c0 100644 --- a/src/lib/components/HeroCarousel/HeroBackground.svelte +++ b/src/lib/components/HeroCarousel/HeroBackground.svelte @@ -3,10 +3,14 @@ import classNames from 'classnames'; import { onDestroy } from 'svelte'; import { isFirefox } from '../../utils/browser-detection'; + 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; @@ -36,17 +40,21 @@
- {#if !isFirefox()} - {#await urls then urls} - {#each urls as url, i} + {#if true} + {#await items then items} + {#each items as { videoUrl, backdropUrl }, i}
+ style={`background-image: url('${backdropUrl}'); transition: opacity 500ms, transform 500ms;`} + > + {#if videoUrl && i === visibleIndex && $localSettings.enableTrailers && $localSettings.autoplayTrailers} + + {/if} +
{/each} {/await} {:else} @@ -56,8 +64,8 @@ })} style="perspective: 1px; -webkit-perspective: 1px;" > - {#await urls then urls} - {#each urls as url, i} + {#await items then items} + {#each items as { backdropUrl, videoUrl }, i}
+
{/each} {/await} diff --git a/src/lib/components/HeroCarousel/HeroCarousel.svelte b/src/lib/components/HeroCarousel/HeroCarousel.svelte index 86540c1..6df43d2 100644 --- a/src/lib/components/HeroCarousel/HeroCarousel.svelte +++ b/src/lib/components/HeroCarousel/HeroCarousel.svelte @@ -1,6 +1,6 @@ - +
items.map((i) => `${TMDB_IMAGES_ORIGINAL}${i.backdropUri}`))} + 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 190d2f0..ab99de1 100644 --- a/src/lib/components/HeroShowcase/TmdbMoviesHeroShowcase.svelte +++ b/src/lib/components/HeroShowcase/TmdbMoviesHeroShowcase.svelte @@ -3,35 +3,47 @@ import { formatMinutesToTime, formatThousands } from '$lib/utils'; import { navigate } from '../StackRouter/StackRouter'; import HeroShowcase from './HeroShowcase.svelte'; + import { tmdbApi } from '$lib/apis/tmdb/tmdb-api'; export let movies: Promise; - $: items = movies.then((movies) => - movies.map((movie) => ({ - id: movie.id ?? 0, - type: 'movie' as const, - posterUri: movie.poster_path ?? '', - backdropUri: movie.backdrop_path ?? '', - title: `${movie.title}`, - overview: movie.overview ?? '', - infoProperties: [ - ...(movie.release_date - ? [{ label: new Date(movie.release_date).getFullYear().toString() }] - : []), - ...(movie.runtime ? [{ label: formatMinutesToTime(movie.runtime) }] : []), - ...(movie.vote_average - ? [ - { - label: `${movie.vote_average.toFixed(1)} TMDB (${formatThousands( - movie.vote_count ?? 0 - )})`, - href: `https://www.themoviedb.org/movie/${movie.id}` - } - ] - : []), - ...(movie.genres ? [{ label: movie.genres.map((genre) => genre.name).join(', ') }] : []) - ] - })) - ); + + $: items = movies + .then(async (movies) => + movies.map(async (movie) => { + 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, + type: 'movie' as const, + posterUri: movie.poster_path ?? '', + backdropUri: movie.backdrop_path ?? '', + title: movie.title ?? '', + overview: movie.overview ?? '', + videoUrl, + infoProperties: [ + ...(movie.release_date + ? [{ label: new Date(movie.release_date).getFullYear().toString() }] + : []), + ...(movie.runtime ? [{ label: formatMinutesToTime(movie.runtime) }] : []), + ...(movie.vote_average + ? [ + { + label: `${movie.vote_average.toFixed(1)} TMDB (${formatThousands( + movie.vote_count ?? 0 + )})`, + href: `https://www.themoviedb.org/movie/${movie.id}` + } + ] + : []), + ...(movie.genres ? [{ label: movie.genres.map((genre) => genre.name).join(', ') }] : []) + ] + }; + }) + ) + .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 792692f..e6d8beb 100644 --- a/src/lib/components/HeroShowcase/TmdbSeriesHeroShowcase.svelte +++ b/src/lib/components/HeroShowcase/TmdbSeriesHeroShowcase.svelte @@ -1,38 +1,50 @@ navigate(`/series/${detail?.id}`)} {items} /> 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 d881297..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) => 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 02011f2..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((i) => TMDB_IMAGES_ORIGINAL + i.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 + * +