Proof of concept tmdb data fetching running on tizen

This commit is contained in:
Aleksi Lassila
2024-03-01 17:49:32 +02:00
parent b14bf78292
commit 1c1fbbf043
32 changed files with 468 additions and 90 deletions

View File

@@ -2,7 +2,7 @@
* @deprecated - Check @/utils/playback-profiles/index
*/
import { isApple, isTizen, isTv, isWebOS } from '$lib/utils/browser-detection';
import { isApple, isTizen, isTv, isWebOS } from '../../../../utils/browser-detection';
/**
* Determines if audio codec is supported

View File

@@ -19,7 +19,7 @@ import {
isWebOS,
isXbox,
safariVersion
} from '$lib/utils/browser-detection';
} from '../../../../utils/browser-detection';
/**
* Gets the max video bitrate

View File

@@ -10,7 +10,7 @@ import {
hasEac3Support,
hasMp3AudioSupport
} from './mp4-audio-formats';
import { isEdge } from '$lib/utils/browser-detection';
import { isEdge } from '../../../../utils/browser-detection';
/**
* Gets an array with the supported fmp4 codecs

View File

@@ -10,7 +10,7 @@ import {
isFirefox,
isTizen,
isWebOS
} from '$lib/utils/browser-detection';
} from '../../../../utils/browser-detection';
/**
* Gets an array of supported fmp4 video codecs

View File

@@ -5,7 +5,7 @@
import { hasH264Support, hasH265Support } from './mp4-video-formats';
import { hasEac3Support, hasAacSupport } from './mp4-audio-formats';
import { getSupportedAudioCodecs } from './audio-formats';
import { isTv } from '$lib/utils/browser-detection';
import { isTv } from '../../../../utils/browser-detection';
/**
* Check if client supports AC3 in HLS stream

View File

@@ -11,7 +11,7 @@ import {
isTizen55,
isTv,
isWebOS
} from '$lib/utils/browser-detection';
} from '../../../../utils/browser-detection';
/**
* Checks if the client can play the AC3 codec

View File

@@ -2,7 +2,7 @@
* @deprecated - Check @/utils/playback-profiles/index
*/
import { isApple, isTizen, isTizen55, isTv, isWebOS5 } from '$lib/utils/browser-detection';
import { isApple, isTizen, isTizen55, isTv, isWebOS5 } from '../../../../utils/browser-detection';
/**
* Checks if the client has support for the H264 codec

View File

@@ -2,7 +2,7 @@
* @deprecated - Check @/utils/playback-profiles/index
*/
import { isEdge, isTizen, isTv, supportsMediaSource } from '$lib/utils/browser-detection';
import { isEdge, isTizen, isTv, supportsMediaSource } from '../../../../utils/browser-detection';
/**
* Checks if the client can play native HLS

View File

@@ -2,7 +2,7 @@
* @deprecated - Check @/utils/playback-profiles/index
*/
import { isWebOS } from '$lib/utils/browser-detection';
import { isWebOS } from '../../../../utils/browser-detection';
/**
* Get an array of supported codecs

View File

@@ -17,7 +17,7 @@ import {
isChromiumBased,
isAndroid,
isTizen
} from '$lib/utils/browser-detection';
} from '../../../utils/browser-detection';
/**
* Returns a valid TranscodingProfile for the current platform.

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import classNames from 'classnames';
import { fade } from 'svelte/transition';
export let index = 0;
export let size: 'dynamic' | 'md' | 'lg' = 'md';
export let orientation: 'portrait' | 'landscape' = 'landscape';
</script>
<div
class={classNames('rounded-xl overflow-hidden shadow-lg placeholder shrink-0', {
'aspect-video': orientation === 'landscape',
'aspect-[2/3]': orientation === 'portrait',
'w-44': size === 'md' && orientation === 'portrait',
'h-44': size === 'md' && orientation === 'landscape',
'w-60': size === 'lg' && orientation === 'portrait',
'h-60': size === 'lg' && orientation === 'landscape',
'w-full': size === 'dynamic'
})}
style={'animation-delay: ' + ((index * 100) % 2000) + 'ms;'}
transition:fade|global
tabindex="0"
/>

View File

@@ -0,0 +1,75 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import IconButton from '../IconButton.svelte';
import { ChevronLeft, ChevronRight } from 'radix-icons-svelte';
import classNames from 'classnames';
import Container from '../../../Container.svelte';
export let gradientFromColor = 'from-stone-950';
export let heading = '';
let carousel: HTMLDivElement | undefined;
let scrollX = 0;
export let scrollClass = '';
</script>
<div class={classNames('flex flex-col gap-4 group/carousel', $$restProps.class)}>
<div class={'flex justify-between items-center gap-4 ' + scrollClass}>
<slot name="title">
<div class="font-semibold text-xl">{heading}</div>
</slot>
<div
class={classNames(
'flex gap-2 sm:opacity-0 transition-opacity sm:group-hover/carousel:opacity-100',
{
hidden: (carousel?.scrollWidth || 0) === (carousel?.clientWidth || 0)
}
)}
>
<IconButton
on:click={() => {
carousel?.scrollTo({ left: scrollX - carousel?.clientWidth * 0.8, behavior: 'smooth' });
}}
>
<ChevronLeft size={20} />
</IconButton>
<IconButton
on:click={() => {
carousel?.scrollTo({ left: scrollX + carousel?.clientWidth * 0.8, behavior: 'smooth' });
}}
>
<ChevronRight size={20} />
</IconButton>
</div>
</div>
<div class="relative">
<Container horizontal>
<div
class={classNames(
'flex overflow-x-scroll items-center overflow-y-visible gap-4 relative scrollbar-hide p-1',
scrollClass
)}
bind:this={carousel}
tabindex="-1"
on:scroll={() => (scrollX = carousel?.scrollLeft || scrollX)}
>
<slot />
</div>
</Container>
{#if scrollX > 50}
<div
transition:fade={{ duration: 200 }}
class={'absolute inset-y-0 left-0 w-0 sm:w-16 md:w-24 bg-gradient-to-r ' +
gradientFromColor}
/>
{/if}
{#if carousel && scrollX < carousel?.scrollWidth - carousel?.clientWidth - 50}
<div
transition:fade={{ duration: 200 }}
class={'absolute inset-y-0 right-0 w-0 sm:w-16 md:w-24 bg-gradient-to-l ' +
gradientFromColor}
/>
{/if}
</div>
</div>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import CardPlaceholder from '../Card/CardPlaceholder.svelte';
import Container from '../../../Container.svelte';
export let size: 'dynamic' | 'md' | 'lg' = 'md';
export let orientation: 'landscape' | 'portrait' = 'landscape';
</script>
{#each Array(10) as _, i (i)}
<Container>
<CardPlaceholder {size} index={i} {orientation} />
</Container>
{/each}

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import classNames from 'classnames';
export let disabled = false;
</script>
<button
class={classNames(
'text-zinc-300 hover:text-zinc-50 p-1 flex items-center justify-center selectable rounded-sm flex-shrink-0',
{
'opacity-30 cursor-not-allowed pointer-events-none': disabled,
'cursor-pointer': !disabled
},
$$restProps.class
)}
on:click
>
<slot />
</button>

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import classNames from 'classnames';
export let src: string;
export let alt: string = '';
let loaded = false;
function handleLoad() {
loaded = true;
}
</script>
<div
class={classNames(
'transition-opacity duration-300',
{
'opacity-0': !loaded,
'opacity-100': loaded
},
$$restProps.class
)}
>
<img
{src}
{alt}
style="object-fit: cover; width: 100%; height: 100%;"
loading="lazy"
on:load={handleLoad}
/>
<slot />
</div>

View File

@@ -0,0 +1,16 @@
<script>
import { TriangleRight } from 'radix-icons-svelte';
import classNames from 'classnames';
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class={classNames(
$$restProps.class,
'backdrop-blur-lg rounded-full bg-[#00000044] text-zinc-300 hover:text-zinc-200 p-2'
)}
on:click
>
<TriangleRight size={30} />
</div>

View File

@@ -0,0 +1,121 @@
<script lang="ts">
import classNames from 'classnames';
import PlayButton from './PlayButton.svelte';
import ProgressBar from './ProgressBar.svelte';
// import { playerState } from '../VideoPlayer/VideoPlayer';
import LazyImg from './LazyImg.svelte';
import { Star } from 'radix-icons-svelte';
import type { TitleType } from '../types';
export let tmdbId: number | undefined = undefined;
export let tvdbId: number | undefined = undefined;
export let openInModal = true;
export let jellyfinId: string = '';
export let type: TitleType = 'movie';
export let backdropUrl: string;
export let title = '';
export let subtitle = '';
export let rating: number | undefined = undefined;
export let progress = 0;
export let shadow = false;
export let size: 'dynamic' | 'md' | 'lg' | 'sm' = 'md';
export let orientation: 'portrait' | 'landscape' = 'landscape';
</script>
<button
on:click={() => {
if (openInModal) {
if (tmdbId) {
//openTitleModal({ type, id: tmdbId, provider: 'tmdb' });
} else if (tvdbId) {
//openTitleModal({ type, id: tvdbId, provider: 'tvdb' });
}
} else {
window.location.href = tmdbId || tvdbId ? `/${type}/${tmdbId || tvdbId}` : '#';
}
}}
class={classNames(
'relative flex rounded-xl selectable group hover:text-inherit flex-shrink-0 overflow-hidden text-left',
{
'aspect-video': orientation === 'landscape',
'aspect-[2/3]': orientation === 'portrait',
'w-32 h-48': size === 'sm' && orientation === 'portrait',
'h-32 w-56': size === 'sm' && orientation === 'landscape',
'w-44 h-64': size === 'md' && orientation === 'portrait',
'h-44 w-80': size === 'md' && orientation === 'landscape',
'w-60 h-96': size === 'lg' && orientation === 'portrait',
'h-60 w-96': size === 'lg' && orientation === 'landscape',
'w-full': size === 'dynamic',
'shadow-lg': shadow
}
)}
>
<LazyImg src={backdropUrl} class="absolute inset-0 group-hover:scale-105 transition-transform" />
<div
class="absolute inset-0 opacity-0 group-hover:opacity-30 transition-opacity bg-black"
style="filter: blur(50px); transform: scale(3);"
>
<LazyImg src={backdropUrl} />
</div>
<!-- <div
style={`background-image: url(${backdropUrl}); background-size: cover; background-position: center; filter: blur(50px); transform: scale(3);`}
class="absolute inset-0 opacity-0 group-hover:opacity-30 transition-opacity bg-black"
/> -->
<div
class={classNames(
'flex-1 flex flex-col justify-between bg-black bg-opacity-40 opacity-0 group-hover:opacity-100 transition-opacity z-[1]',
{
'py-2 px-3': true
}
)}
>
<div class="flex justify-self-start justify-between">
<slot name="top-left">
<div>
<h1 class="text-zinc-100 font-bold line-clamp-2 text-lg">{title}</h1>
<h2 class="text-zinc-300 text-sm font-medium line-clamp-2">{subtitle}</h2>
</div>
</slot>
<slot name="top-right">
<div />
</slot>
</div>
<div class="flex justify-self-end justify-between">
<slot name="bottom-left">
<div>
{#if rating}
<h2 class="flex items-center gap-1.5 text-sm text-zinc-300 font-medium">
<Star />{rating.toFixed(1)}
</h2>
{/if}
</div>
</slot>
<slot name="bottom-right">
<div />
</slot>
</div>
</div>
<!-- <div
class="absolute inset-0 bg-gradient-to-t from-darken group-hover:opacity-0 transition-opacity z-[1]"
/> -->
{#if jellyfinId}
<div class="absolute inset-0 flex items-center justify-center z-[1]">
<PlayButton
on:click={(e) => {
e.preventDefault();
jellyfinId && true; //playerState.streamJellyfinId(jellyfinId);
}}
class="sm:opacity-0 group-hover:opacity-100 transition-opacity"
/>
</div>
{/if}
{#if progress}
<div
class="absolute bottom-2 lg:bottom-3 inset-x-2 lg:inset-x-3 bg-gradient-to-t ease-in-out z-[1]"
>
<ProgressBar {progress} />
</div>
{/if}
</button>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { onMount } from 'svelte';
export let progress = 0;
let mounted = false;
onMount(() => {
mounted = true;
});
</script>
<div class="h-1 bg-zinc-200 bg-opacity-20 rounded-full overflow-hidden">
<div
style={'max-width: ' + (mounted ? progress : 0) + '%'}
class="h-full bg-zinc-200 bg-opacity-80 transition-[max-width] delay-200 duration-500"
/>
</div>

View File

@@ -3,9 +3,9 @@
createJellyfinItemStore,
createRadarrMovieStore,
createSonarrSeriesStore
} from '$lib/stores/data.store';
import type { TitleType } from '$lib/types';
import { formatMinutesToTime } from '$lib/utils';
} from '../../../lib/stores/data.store';
import type { TitleType } from '../../../lib/types';
import { formatMinutesToTime } from '../../../lib/utils';
import classNames from 'classnames';
import { Clock, Star } from 'radix-icons-svelte';
import { openTitleModal } from '../../stores/modal.store';

View File

@@ -1,13 +1,13 @@
import type { TmdbMovie2, TmdbSeries2 } from '$lib/apis/tmdb/tmdbApi';
import type { TmdbMovie2, TmdbSeries2 } from '../../../lib/apis/tmdb/tmdbApi';
import {
TMDB_MOVIE_GENRES,
TMDB_SERIES_GENRES,
getTmdbMovieBackdrop,
getTmdbSeriesBackdrop
} from '$lib/apis/tmdb/tmdbApi';
} from '../../../lib/apis/tmdb/tmdbApi';
import type { ComponentProps } from 'svelte';
import type Card from './Card.svelte';
import { TMDB_BACKDROP_SMALL } from '$lib/constants';
import { TMDB_BACKDROP_SMALL } from '../../../lib/constants';
export const fetchCardTmdbMovieProps = async (movie: TmdbMovie2): Promise<ComponentProps<Card>> => {
const backdropUri = await getTmdbMovieBackdrop(movie.id || 0);

View File

@@ -1,30 +1,12 @@
<script lang="ts">
import CardPlaceholder from '../Card/CardPlaceholder.svelte';
import classNames from 'classnames';
import Container from '../../../Container.svelte';
import type { Readable, Writable } from 'svelte/store';
export let size: 'dynamic' | 'md' | 'lg' = 'md';
export let orientation: 'landscape' | 'portrait' = 'landscape';
export let focusIndex: Readable<number>;
let focusWithin: Writable<boolean>;
</script>
<p
class={classNames({
'bg-blue-500': $focusWithin
})}
>
Index: {$focusIndex}
</p>
{#each Array(10) as _, i (i)}
<Container
bind:focusWithin
class={classNames({
'bg-red-500': $focusIndex === i && $focusWithin
})}
>
<Container>
<CardPlaceholder {size} index={i} {orientation} />
</Container>
{/each}

View File

@@ -1,6 +1,9 @@
<script lang="ts">
import { setJellyfinItemUnwatched, setJellyfinItemWatched } from '$lib/apis/jellyfin/jellyfinApi';
import { jellyfinItemsStore } from '$lib/stores/data.store';
import {
setJellyfinItemUnwatched,
setJellyfinItemWatched
} from '../../../lib/apis/jellyfin/jellyfinApi';
import { jellyfinItemsStore } from '../../../lib/stores/data.store';
import classNames from 'classnames';
import { Check } from 'radix-icons-svelte';
import { fade } from 'svelte/transition';

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type { TitleType } from '$lib/types';
import { TMDB_PROFILE_SMALL } from '$lib/constants';
import { openTitleModal } from '$lib/stores/modal.store';
import type { TitleType } from '../../../lib/types';
import { TMDB_PROFILE_SMALL } from '../../../lib/constants';
import { openTitleModal } from '../../../lib/stores/modal.store';
import classNames from 'classnames';
export let tmdbId: number;

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import { getTmdbPerson } from '$lib/apis/tmdb/tmdbApi';
import Carousel from '$lib/components/Carousel/Carousel.svelte';
import CarouselPlaceholderItems from '$lib/components/Carousel/CarouselPlaceholderItems.svelte';
import Poster from '$lib/components/Poster/Poster.svelte';
import TitlePageLayout from '$lib/components/TitlePageLayout/TitlePageLayout.svelte';
import FacebookIcon from '$lib/components/svgs/FacebookIcon.svelte';
import ImdbIcon from '$lib/components/svgs/ImdbIcon.svelte';
import TiktokIcon from '$lib/components/svgs/TiktokIcon.svelte';
import TmdbIcon from '$lib/components/svgs/TmdbIcon.svelte';
import TwitterIcon from '$lib/components/svgs/TwitterIcon.svelte';
import YoutubeIcon from '$lib/components/svgs/YoutubeIcon.svelte';
import { TMDB_POSTER_SMALL } from '$lib/constants';
import { getTmdbPerson } from '../../lib/apis/tmdb/tmdbApi';
import Carousel from '../../lib/components/Carousel/Carousel.svelte';
import CarouselPlaceholderItems from '../../lib/components/Carousel/CarouselPlaceholderItems.svelte';
import Poster from '../../lib/components/Poster/Poster.svelte';
import TitlePageLayout from '../../lib/components/TitlePageLayout/TitlePageLayout.svelte';
import FacebookIcon from '../../lib/components/svgs/FacebookIcon.svelte';
import ImdbIcon from '../../lib/components/svgs/ImdbIcon.svelte';
import TiktokIcon from '../../lib/components/svgs/TiktokIcon.svelte';
import TmdbIcon from '../../lib/components/svgs/TmdbIcon.svelte';
import TwitterIcon from '../../lib/components/svgs/TwitterIcon.svelte';
import YoutubeIcon from '../../lib/components/svgs/YoutubeIcon.svelte';
import { DotFilled, InstagramLogo } from 'radix-icons-svelte';
import type { ComponentProps } from 'svelte';
import { TMDB_POSTER_SMALL } from '../constants';
const GENDER_OPTIONS = ['Not set', 'Female', 'Male', 'Non-binary'] as const;

View File

@@ -1,12 +1,11 @@
<script lang="ts">
import type { TitleType } from '$lib/types';
import classNames from 'classnames';
import PlayButton from '../PlayButton.svelte';
import ProgressBar from '../ProgressBar.svelte';
import { playerState } from '../VideoPlayer/VideoPlayer';
import LazyImg from '../LazyImg.svelte';
import { Star } from 'radix-icons-svelte';
import { openTitleModal } from '$lib/stores/modal.store';
import type { TitleType } from '../../types';
export let tmdbId: number | undefined = undefined;
export let tvdbId: number | undefined = undefined;
@@ -29,9 +28,9 @@
on:click={() => {
if (openInModal) {
if (tmdbId) {
openTitleModal({ type, id: tmdbId, provider: 'tmdb' });
//openTitleModal({ type, id: tmdbId, provider: 'tmdb' });
} else if (tvdbId) {
openTitleModal({ type, id: tvdbId, provider: 'tvdb' });
//openTitleModal({ type, id: tvdbId, provider: 'tvdb' });
}
} else {
window.location.href = tmdbId || tvdbId ? `/${type}/${tmdbId || tvdbId}` : '#';

View File

@@ -1,35 +1,35 @@
<script lang="ts">
import { getJellyfinEpisodes, type JellyfinItem } from '$lib/apis/jellyfin/jellyfinApi';
import { addSeriesToSonarr, type SonarrSeries } from '$lib/apis/sonarr/sonarrApi';
import { getJellyfinEpisodes, type JellyfinItem } from '../../lib/apis/jellyfin/jellyfinApi';
import { addSeriesToSonarr, type SonarrSeries } from '../../lib/apis/sonarr/sonarrApi';
import {
getTmdbIdFromTvdbId,
getTmdbSeries,
getTmdbSeriesRecommendations,
getTmdbSeriesSeasons,
getTmdbSeriesSimilar
} from '$lib/apis/tmdb/tmdbApi';
import Button from '$lib/components/Button.svelte';
import Card from '$lib/components/Card/Card.svelte';
import { fetchCardTmdbProps } from '$lib/components/Card/card';
import Carousel from '$lib/components/Carousel/Carousel.svelte';
import CarouselPlaceholderItems from '$lib/components/Carousel/CarouselPlaceholderItems.svelte';
import UiCarousel from '$lib/components/Carousel/UICarousel.svelte';
import EpisodeCard from '$lib/components/EpisodeCard/EpisodeCard.svelte';
import PersonCard from '$lib/components/PersonCard/PersonCard.svelte';
import SeriesRequestModal from '$lib/components/RequestModal/SeriesRequestModal.svelte';
import OpenInButton from '$lib/components/TitlePageLayout/OpenInButton.svelte';
import TitlePageLayout from '$lib/components/TitlePageLayout/TitlePageLayout.svelte';
import { playerState } from '$lib/components/VideoPlayer/VideoPlayer';
import { TMDB_BACKDROP_SMALL } from '$lib/constants';
} from '../../lib/apis/tmdb/tmdbApi';
import Button from '../../lib/components/Button.svelte';
import Card from '../../lib/components/Card/Card.svelte';
import { fetchCardTmdbProps } from '../../lib/components/Card/card';
import Carousel from '../../lib/components/Carousel/Carousel.svelte';
import CarouselPlaceholderItems from '../../lib/components/Carousel/CarouselPlaceholderItems.svelte';
import UiCarousel from '../../lib/components/Carousel/UICarousel.svelte';
import EpisodeCard from '../../lib/components/EpisodeCard/EpisodeCard.svelte';
import PersonCard from '../../lib/components/PersonCard/PersonCard.svelte';
import SeriesRequestModal from '../../lib/components/RequestModal/SeriesRequestModal.svelte';
import OpenInButton from '../../lib/components/TitlePageLayout/OpenInButton.svelte';
import TitlePageLayout from '../../lib/components/TitlePageLayout/TitlePageLayout.svelte';
import { playerState } from '../../lib/components/VideoPlayer/VideoPlayer';
import { TMDB_BACKDROP_SMALL } from '../../lib/constants';
import {
createJellyfinItemStore,
createSonarrDownloadStore,
createSonarrSeriesStore
} from '$lib/stores/data.store';
import { modalStack } from '$lib/stores/modal.store';
import { settings } from '$lib/stores/settings.store';
import type { TitleId } from '$lib/types';
import { capitalize, formatMinutesToTime, formatSize } from '$lib/utils';
} from '../../lib/stores/data.store';
import { modalStack } from '../../lib/stores/modal.store';
import { settings } from '../../lib/stores/settings.store';
import type { TitleId } from '../../lib/types';
import { capitalize, formatMinutesToTime, formatSize } from '../../lib/utils';
import classNames from 'classnames';
import { Archive, ChevronLeft, ChevronRight, DotFilled, Plus } from 'radix-icons-svelte';
import type { ComponentProps } from 'svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { TMDB_IMAGES_ORIGINAL, TMDB_POSTER_SMALL } from '$lib/constants';
import type { TitleId, TitleType } from '$lib/types';
import { TMDB_IMAGES_ORIGINAL, TMDB_POSTER_SMALL } from '../../../lib/constants';
import type { TitleId, TitleType } from '../../../lib/types';
import classNames from 'classnames';
import { ChevronLeft, Cross2, DotFilled, ExternalLink } from 'radix-icons-svelte';
import Carousel from '../Carousel/Carousel.svelte';

View File

@@ -6,10 +6,10 @@
reportJellyfinPlaybackProgress,
reportJellyfinPlaybackStarted,
reportJellyfinPlaybackStopped
} from '$lib/apis/jellyfin/jellyfinApi';
import getDeviceProfile from '$lib/apis/jellyfin/playback-profiles';
import { getQualities } from '$lib/apis/jellyfin/qualities';
import { settings } from '$lib/stores/settings.store';
} from '../../apis/jellyfin/jellyfinApi';
import getDeviceProfile from '../../apis/jellyfin/playback-profiles';
import { getQualities } from '../../apis/jellyfin/qualities';
import { settings } from '../../stores/settings.store';
import classNames from 'classnames';
import Hls from 'hls.js';
import {

View File

@@ -1,7 +1,7 @@
import { jellyfinItemsStore } from '$lib/stores/data.store';
import { writable } from 'svelte/store';
import { modalStack } from '../../stores/modal.store';
import VideoPlayer from './VideoPlayer.svelte';
import { jellyfinItemsStore } from '../../stores/data.store';
const initialValue = { visible: false, jellyfinId: '' };
export type PlayerStateValue = typeof initialValue;

View File

@@ -1,5 +1,81 @@
<script lang="ts">
import Container from '../../Container.svelte';
</script>
<Container focusOnMount>SeriesPage</Container>
<script lang="ts">
import Container from '../../Container.svelte';
import { getPosterProps, TmdbApiOpen } from '../apis/tmdb/tmdbApi';
import { formatDateToYearMonthDay } from '../utils';
import { settings } from '../stores/settings.store';
import type { TitleType } from '../types';
import type { ComponentProps } from 'svelte';
import Poster from '../components-new/Poster.svelte';
import type { JellyfinItem } from '../apis/jellyfin/jellyfinApi';
import { jellyfinItemsStore } from '../stores/data.store';
import Carousel from '../components-new/Carousel/Carousel.svelte';
import { _ } from 'svelte-i18n';
import CarouselPlaceholderItems from '../components-new/Carousel/CarouselPlaceholderItems.svelte';
const jellyfinItemsPromise = new Promise<JellyfinItem[]>((resolve) => {
jellyfinItemsStore.subscribe((data) => {
if (data.loading) return;
resolve(data.data || []);
});
});
const fetchCardProps = async (
items: {
name?: string;
title?: string;
id?: number;
vote_average?: number;
number_of_seasons?: number;
first_air_date?: string;
poster_path?: string;
}[],
type: TitleType | undefined = undefined
): Promise<ComponentProps<Poster>[]> => {
const filtered = $settings.discover.excludeLibraryItems
? items.filter(
async (item) =>
!(await jellyfinItemsPromise).find((i) => i.ProviderIds?.Tmdb === String(item.id))
)
: items;
return Promise.all(filtered.map(async (item) => getPosterProps(item, type))).then((props) =>
props.filter((p) => p.backdropUrl)
);
};
const fetchNowStreaming = () =>
TmdbApiOpen.GET('/3/discover/tv', {
params: {
query: {
'air_date.gte': formatDateToYearMonthDay(new Date()),
'first_air_date.lte': formatDateToYearMonthDay(new Date()),
sort_by: 'popularity.desc',
language: $settings.language,
with_original_language: parseIncludedLanguages($settings.discover.includedLanguages)
}
}
})
.then((res) => res.data?.results || [])
.then((i) => fetchCardProps(i, 'series'));
function parseIncludedLanguages(includedLanguages: string) {
return includedLanguages.replace(' ', '').split(',').join('|');
}
</script>
<Container focusOnMount>
<Carousel scrollClass="px-2 sm:px-8 2xl:px-16">
<div slot="title" class="text-lg font-semibold text-zinc-300">
{$_('discover.streamingNow')}
</div>
{#await fetchNowStreaming()}
<CarouselPlaceholderItems />
{:then props}
{#each props as prop (prop.tmdbId)}
<Container>
<Poster {...prop} />
</Container>
{/each}
{/await}
</Carousel>
</Container>

View File

@@ -2,6 +2,7 @@
<widget xmlns:tizen="http://tizen.org/ns/widgets" xmlns="http://www.w3.org/ns/widgets" id="http://yourdomain/SvelteTizen" version="1.0.0" viewmodes="maximized">
<tizen:application id="JZUbM5WimZ.SvelteTizen" package="JZUbM5WimZ" required_version="2.3"/>
<content src="index.html"/>
<tizen:content-security-policy>default-src 'self'</tizen:content-security-policy>
<feature name="http://tizen.org/feature/screen.size.normal.1080.1920"/>
<icon src="icon.png"/>
<tizen:metadata key="http://tizen.org/metadata/app_ui_type/base_screen_resolution" value="extensive"/>

View File

@@ -13,7 +13,10 @@
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
"isolatedModules": true,
"paths": {
"$lib/*": ["src/lib/*"],
}
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]