diff --git a/src/api/config.ts b/src/api/config.ts index c6ab72a..5c30af8 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -2,6 +2,8 @@ import { EmbySite } from "@model/EmbySite"; import { ENV, EmbyConfig } from "../helper/env"; import { Map } from "../model/Map"; import { PlaybackInfo } from "@model/PlaybackInfo"; +import { PictureQuality } from "@store/configSlice"; +import { Image } from "@model/Image"; export type { EmbyConfig } from "../helper/env" @@ -33,6 +35,15 @@ export interface ImageProps { quality: number } +export function getItemImage(site: EmbySite, id: string|number, quality = PictureQuality.High) { + const image: Image = { + primary: imageUrl(site, id, null, "Primary"), + backdrop: imageUrl(site, id, null, "Backdrop"), + logo: imageUrl(site, id, null, "Logo"), + } + return image +} + export function imageUrl(site: EmbySite, id: string|number, options: string|Partial|null, type: "Primary"|string = "Primary") { const endpoint = site.server! if (typeof options === "string") { diff --git a/src/api/emby.ts b/src/api/emby.ts index 21df295..c4eb63a 100644 --- a/src/api/emby.ts +++ b/src/api/emby.ts @@ -3,7 +3,7 @@ import { getActor, getCollection, getEpisodes, getItem, getLatestMedia, getMedia import { getPublicInfo } from "./info"; import { login } from "./login"; import { EmbySite } from "@model/EmbySite"; -import { imageUrl, playUrl } from "./config"; +import { getItemImage, imageUrl, playUrl } from "./config"; export class Emby { private _site?: EmbySite; @@ -36,6 +36,7 @@ export class Emby { this.getPublicInfo = getPublicInfo.bind(this, this.site) this.getItem = getItem.bind(this, this.site) this.getActor = getActor.bind(this, this.site) + this.getItemImage = getItemImage.bind(this, this.site) this.imageUrl = imageUrl.bind(this, this.site) this.videoUrl = playUrl.bind(this, this.site) this.markFavorite = markFavorite.bind(this, this.site) @@ -58,6 +59,7 @@ export class Emby { public searchRecommend = this._site ? searchRecommend.bind(this, this.site) : null public getItem = this._site ? getItem.bind(this, this.site) : null public getActor = this._site ? getActor.bind(this, this.site) : null + public getItemImage = this._site ? getItemImage.bind(this, this.site) : null public imageUrl = this._site ? imageUrl.bind(this, this.site) : null public videoUrl = this._site ? playUrl.bind(this, this.site) : null public markFavorite = this._site ? markFavorite.bind(this, this.site) : null diff --git a/src/api/view.ts b/src/api/view.ts index 6c77e99..a2368d3 100644 --- a/src/api/view.ts +++ b/src/api/view.ts @@ -1,5 +1,5 @@ import { View } from "@model/View"; -import { makeEmbyUrl } from "./config"; +import { getItemImage, makeEmbyUrl } from "./config"; import { Media } from "@model/Media"; import { MediaDetail } from "@model/MediaDetail"; import { Season } from "@model/Season"; @@ -10,6 +10,7 @@ import { Version } from "@helper/device"; import { UserData } from "@model/UserData"; import { PlaybackData, kPlaybackData } from "@model/PlaybackData"; import { logger } from "@helper/log"; +import { PictureQuality } from "@store/configSlice"; export const EMBY_CLIENT_HEADERS = { "X-Emby-Client": Version.displayName, @@ -32,6 +33,9 @@ export async function getView(site: EmbySite) { } }); const data = await response.json() as View + for (const item of data.Items) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data } @@ -53,6 +57,9 @@ export async function getLatestMedia(site: EmbySite, parentId: number) { } }) const data = await response.json() as Media[] + for (const item of data) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data } @@ -66,6 +73,10 @@ export async function getMedia(site: EmbySite, id: number) { const url = makeEmbyUrl(params, `emby/Users/${uid}/Items/${id}`, site.server) const response = await fetch(url) const data = await response.json() as MediaDetail + data.image = getItemImage(site, id, PictureQuality.High) + for (const item of data.People) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data } @@ -90,6 +101,9 @@ export async function getResume(site: EmbySite, type: "Video"|"Audio" = "Video") } }) const data = await response.json() as EmbyResponse + for (const item of data.Items) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data.Items } @@ -106,6 +120,9 @@ export async function getRecommendations(site: EmbySite) { } }) const data = await response.json() as EmbyResponse + for (const item of data.Items) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data.Items } @@ -124,6 +141,9 @@ export async function getSeasons(site: EmbySite, id: number) { } }) const data = await response.json() as EmbyResponse + for (const item of data.Items) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data.Items } @@ -143,6 +163,9 @@ export async function getEpisodes(site: EmbySite, vid: number, sid: number) { } }) const data = await response.json() as EmbyResponse + for (const item of data.Items) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data.Items } @@ -179,6 +202,9 @@ export async function getItem(site: EmbySite, options: ItemOptions) { } }) const data = await response.json() as EmbyResponse + for (const item of data.Items) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data } @@ -193,6 +219,7 @@ export async function getCollection(site: EmbySite, cid: number, type: "Series"| Limit = kEmbyItemPageSize, SortBy = "DateCreated,SortName" }: CollectionOptions) { + logger.info("getCollection", cid, type, StartIndex, Limit) const uid = site.user.User.Id const params = { UserId: site.user.User.Id, @@ -216,6 +243,9 @@ export async function getCollection(site: EmbySite, cid: number, type: "Series"| } }) const data = await response.json() as EmbyResponse + for (const item of data.Items) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data } @@ -238,6 +268,9 @@ export async function searchRecommend(site: EmbySite) { } }) const data = await response.json() as EmbyResponse + for (const item of data.Items) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data } @@ -263,6 +296,9 @@ export async function lookupItem(site: EmbySite, title: string) { } }) const data = await response.json() as EmbyResponse + for (const item of data.Items) { + item.image = getItemImage(site, item.Id, PictureQuality.High) + } return data } diff --git a/src/model/Episode.ts b/src/model/Episode.ts index 5a41ae0..6ff4371 100644 --- a/src/model/Episode.ts +++ b/src/model/Episode.ts @@ -1,3 +1,4 @@ +import { Image } from "./Image"; import { UserData } from "./UserData"; export interface Episode { @@ -14,4 +15,6 @@ export interface Episode { Primary: string; }; UserData: UserData; + + image: Image } diff --git a/src/model/Image.ts b/src/model/Image.ts new file mode 100644 index 0000000..8ef49c3 --- /dev/null +++ b/src/model/Image.ts @@ -0,0 +1,5 @@ +export interface Image { + primary?: string, + backdrop?: string, + logo?: string, +} \ No newline at end of file diff --git a/src/model/Media.ts b/src/model/Media.ts index 404193b..d7f00c2 100644 --- a/src/model/Media.ts +++ b/src/model/Media.ts @@ -1,3 +1,4 @@ +import { Image } from "./Image" import { UserData } from "./UserData" export interface Media { @@ -32,4 +33,7 @@ export interface Media { SupportsSync: boolean Type: "Series"|"Movie"|"Episode" UserData: UserData + + + image: Image } \ No newline at end of file diff --git a/src/model/MediaDetail.ts b/src/model/MediaDetail.ts index 53d0cd9..22f0c03 100644 --- a/src/model/MediaDetail.ts +++ b/src/model/MediaDetail.ts @@ -1,5 +1,6 @@ import { MediaSource } from "@model/PlaybackInfo" import { UserData } from "./UserData" +import { Image } from "./Image" export interface MediaDetail { Name: string OriginalTitle: string @@ -21,6 +22,8 @@ export interface MediaDetail { SeriesId: string SeriesName: string MediaSources?: MediaSource[] + + image: Image } export interface People { @@ -29,4 +32,6 @@ export interface People { PrimaryImageTag: string Role: string Type: string + + image: Image } \ No newline at end of file diff --git a/src/model/Season.ts b/src/model/Season.ts index de3aeef..35f3eb1 100644 --- a/src/model/Season.ts +++ b/src/model/Season.ts @@ -1,3 +1,4 @@ +import { Image } from "./Image" import { UserData } from "./UserData" export interface Season { @@ -22,4 +23,6 @@ export interface Season { SupportsSync: boolean Type: string UserData: UserData + + image: Image } \ No newline at end of file diff --git a/src/model/View.ts b/src/model/View.ts index a5eb015..f4316df 100644 --- a/src/model/View.ts +++ b/src/model/View.ts @@ -1,3 +1,5 @@ +import { Image } from "./Image" + export interface ViewDetail { BackdropImageTags: string[] CanDelete: boolean @@ -31,6 +33,8 @@ export interface ViewDetail { SortName: string Taglines: string[] Type: string + + image: Image } export interface View { diff --git a/src/page/album/index.tsx b/src/page/album/index.tsx index c228bb6..59fb0c7 100644 --- a/src/page/album/index.tsx +++ b/src/page/album/index.tsx @@ -21,7 +21,7 @@ const style = StyleSheet.create({ }); -export function Page({ navigation, route}: PropsWithNavigation<'album'>) { +export function Page({ route}: PropsWithNavigation<'album'>) { const data = useAppSelector(state => state.emby?.source?.albumMedia?.[route.params.albumId]); const [loading, setLoading] = useState(true); const theme = useAppSelector(selectThemeBasicStyle); @@ -32,7 +32,7 @@ export function Page({ navigation, route}: PropsWithNavigation<'album'>) { useEffect(() => { setLoading(!cached); - logger.info("fetch album media", route.params.albumId) + logger.info("fetch album medias", route.params.albumId) const id = route.params.albumId; const query = { id, @@ -57,13 +57,14 @@ export function Page({ navigation, route}: PropsWithNavigation<'album'>) { (kFullScreenStyle.width - 20) / Math.floor((kFullScreenStyle.width - 20) / 120); + logger.info(`data[0] = ${data?.[0]}`) return ( {data && data.length > 0 ? ( layoutType ?? LayoutType.Card} + typeForIndex={() => layoutType ?? LayoutType.Card} layoutForType={(i, dim) => { if (i === LayoutType.Card) { dim.width = rowItemWidth; diff --git a/src/page/movie/index.tsx b/src/page/movie/index.tsx index d58db91..5631f33 100644 --- a/src/page/movie/index.tsx +++ b/src/page/movie/index.tsx @@ -7,7 +7,7 @@ import { SeasonCardList } from "@view/SeasonCard"; import { Tag } from "@view/Tag"; import { ExternalPlayer } from "@view/player/ExternalPlayer"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { DimensionValue, ImageStyle, ScrollView, StyleProp, StyleSheet, Text, TouchableOpacity, View } from "react-native"; +import { DimensionValue, ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { Image } from '@view/Image'; import { Toast } from "@helper/toast"; import { Spin, SpinBox } from "@view/Spin"; @@ -103,9 +103,7 @@ export function Page({route, navigation}: PropsWithNavigation<"movie">) { const [infoLoading, setInfoLoading] = useState(false) const pageStyle = useAppSelector(selectThemedPageStyle) const subtitleFontName = useAppSelector(s => s.player.fontFamily) - const poster = type==="Episode" ? - emby?.imageUrl?.(movie.Id, null) : - emby?.imageUrl?.(movie.Id, movie.BackdropImageTags?.[0], "Backdrop/0") + const poster = type==="Episode" ? movie.image.primary : movie.image.backdrop const fetchPlayUrl = useCallback(async () => { let url = getPlayUrl(detail) @@ -197,7 +195,7 @@ export function Page({route, navigation}: PropsWithNavigation<"movie">) { })) } } - const logoUrl = emby?.imageUrl?.(movie.Id, movie.BackdropImageTags?.[0], "Logo") + const logoUrl = movie.image.logo const isPlayable = movie.Type === "Movie" || movie.Type === "Episode" const iconSize = preferedSize(24, 56, windowWidth/9) const layout = useMemo(() => ({ diff --git a/src/page/player/index.tsx b/src/page/player/index.tsx index 4c85ad0..3d60165 100644 --- a/src/page/player/index.tsx +++ b/src/page/player/index.tsx @@ -83,7 +83,7 @@ export function Page({navigation, route}: PlayerPageProps) { const playEpisode = (episode: Episode) => { setLoading(true) setEpisode(episode) - setPoster(emby?.imageUrl?.(episode.Id, episode.ImageTags.Primary)) + setPoster(episode.image.primary) emby?.getPlaybackInfo?.(Number(episode.Id)) .then(res => { setUrl(emby?.videoUrl?.(res)) @@ -178,7 +178,6 @@ export function Page({navigation, route}: PlayerPageProps) { showsVerticalScrollIndicator={false}> {showExternalPlayer && !isTablet && url ? : null} {episodes?.map((e, idx) => ({ page: { @@ -92,7 +92,7 @@ export function Page({route, navigation}: SeasonPageProps) { } {episodes?.map(episode => - diff --git a/src/store/embySlice.ts b/src/store/embySlice.ts index 39606fa..3bea563 100644 --- a/src/store/embySlice.ts +++ b/src/store/embySlice.ts @@ -334,6 +334,7 @@ export const slice = createSlice({ .addCase(fetchAlbumMediaAsync.fulfilled, (state, action) => { const album = action.payload if (!album) return + logger.info(`album.items[0]`, album.items[0]) if (state.source.albumMedia) { state.source.albumMedia[album.id] = album.items } else { diff --git a/src/view/ActorCard.tsx b/src/view/ActorCard.tsx index e829769..8af3076 100644 --- a/src/view/ActorCard.tsx +++ b/src/view/ActorCard.tsx @@ -35,8 +35,7 @@ export interface ActorCardProps { } export function ActorCard({actor, theme, onPress}: ActorCardProps) { - const emby = useAppSelector(state => state.emby?.emby) - const avatorUrl = emby?.imageUrl?.(actor.Id, actor.PrimaryImageTag, "Primary") + const avatorUrl = actor.image.primary return ( onPress?.(actor)}> diff --git a/src/view/AlbumList.tsx b/src/view/AlbumList.tsx index 8b7f8c5..d53695f 100644 --- a/src/view/AlbumList.tsx +++ b/src/view/AlbumList.tsx @@ -63,7 +63,7 @@ export function AlbumCardList({albums, theme}: {albums: ViewDetail[], theme?: Th onPress(album)}> {album.Name} diff --git a/src/view/EpisodeCard.tsx b/src/view/EpisodeCard.tsx index 027a6e2..d738a7e 100644 --- a/src/view/EpisodeCard.tsx +++ b/src/view/EpisodeCard.tsx @@ -1,13 +1,10 @@ -import { Emby } from "@api/emby"; import { Episode } from "@model/Episode"; import { StyleSheet, Text, TouchableOpacity, View, ViewStyle } from "react-native"; import { Image } from '@view/Image'; -import { useAppSelector } from "@hook/store"; import { Tag } from "./Tag"; import { Like } from "./like/Like"; import { PlayCount } from "./counter/PlayCount"; import { ThemeBasicStyle } from "@global"; -import { getImageUrl } from "@store/embySlice"; const DEFULT_OVERVIEW = `数据源中缺少相关描述 Data source lacks relevant description` @@ -65,8 +62,8 @@ export interface EpisodeCardProps { } export function EpisodeCard({style: extraStyle, theme, episode, onPress}: EpisodeCardProps) { - const thumbUrl = useAppSelector(getImageUrl(episode.Id, episode.ImageTags.Primary)) - const posterUrl = useAppSelector(getImageUrl(episode.SeasonId, episode.ImageTags.Primary)) + const thumbUrl = episode.image.backdrop + const posterUrl = episode.image.primary return ( onPress?.(episode)}> diff --git a/src/view/MediaCard.tsx b/src/view/MediaCard.tsx index 546f68d..3e11b6f 100644 --- a/src/view/MediaCard.tsx +++ b/src/view/MediaCard.tsx @@ -8,7 +8,6 @@ import { Image } from '@view/Image'; import {Media} from '@model/Media'; import {useNavigation} from '@react-navigation/native'; import {Navigation, ThemeBasicStyle} from '@global'; -import {Api} from '@api/emby'; import {useAppSelector} from '@hook/store'; import { Device } from '@helper/device'; @@ -70,7 +69,7 @@ export function MediaCard({media, theme}: {media: Media, theme?: ThemeBasicStyle onPress(media)}> onPress(media)}> diff --git a/src/view/SeasonCard.tsx b/src/view/SeasonCard.tsx index ea04a16..5e69d21 100644 --- a/src/view/SeasonCard.tsx +++ b/src/view/SeasonCard.tsx @@ -46,19 +46,12 @@ export interface SeasonCardProps { } export function SeasonCard({ season, theme, onPress }: SeasonCardProps) { - const emby = useAppSelector(state => state.emby?.emby); return ( onPress?.(season)}> {season.UserData.UnplayedItemCount}