From 2c724eae5cf14f039f3eaa05b802461734fe2c57 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 23 Oct 2022 18:16:14 +0900 Subject: [PATCH] Add mini player features --- chromecast/src/api.ts | 136 +++++++++++------- chromecast/src/index.ts | 45 ++++-- docker-compose.dev.yml | 1 + docker-compose.yml | 2 +- front/src/player/cast/cast-provider.tsx | 4 +- .../src/player/cast/internal-mini-player.tsx | 111 ++++++++++++++ front/src/player/cast/mini-player.tsx | 86 ++--------- front/src/player/cast/state.tsx | 73 +++++++--- front/src/player/components/hover.tsx | 4 +- front/src/player/components/left-buttons.tsx | 22 +-- front/src/player/components/progress-bar.tsx | 18 ++- 11 files changed, 325 insertions(+), 177 deletions(-) create mode 100644 front/src/player/cast/internal-mini-player.tsx diff --git a/chromecast/src/api.ts b/chromecast/src/api.ts index b7b4412d..04afde59 100644 --- a/chromecast/src/api.ts +++ b/chromecast/src/api.ts @@ -19,57 +19,68 @@ */ export type Item = { - /** - * The slug of this episode. - */ - slug: string; - /** - * The title of the show containing this episode. - */ - showTitle?: string; - /** - * The slug of the show containing this episode - */ - showSlug?: string; - /** - * The season in witch this episode is in. - */ - seasonNumber?: number; - /** - * The number of this episode is it's season. - */ - episodeNumber?: number; - /** - * The absolute number of this episode. It's an episode number that is not reset to 1 after a new season. - */ - absoluteNumber?: number; - /** - * The title of this episode. - */ - name: string; - /** - * true if this is a movie, false otherwise. - */ - isMovie: boolean; - /** - * An url to the poster of this resource. If this resource does not have an image, the link will be null. If the kyoo's instance is not capable of handling this kind of image for the specific resource, this field won't be present. - */ - poster?: string | null; - /** - * An url to the thumbnail of this resource. If this resource does not have an image, the link will be null. If the kyoo's instance is not capable of handling this kind of image for the specific resource, this field won't be present. - */ - thumbnail?: string | null; - /** - * An url to the logo of this resource. If this resource does not have an image, the link will be null. If the kyoo's instance is not capable of handling this kind of image for the specific resource, this field won't be present. - */ - logo?: string | null; + /** + * The slug of this episode. + */ + slug: string; + /** + * The title of the show containing this episode. + */ + showTitle?: string; + /** + * The slug of the show containing this episode + */ + showSlug?: string; + /** + * The season in witch this episode is in. + */ + seasonNumber?: number; + /** + * The number of this episode is it's season. + */ + episodeNumber?: number; + /** + * The absolute number of this episode. It's an episode number that is not reset to 1 after a + * new season. + */ + absoluteNumber?: number; + /** + * The title of this episode. + */ + name: string; + /** + * The air date of this episode. + */ + releaseDate: Date; + /** + * True if this is a movie, false otherwise. + */ + isMovie: boolean; + /** + * An url to the poster of this resource. If this resource does not have an image, the link will + * be null. If the kyoo's instance is not capable of handling this kind of image for the + * specific resource, this field won't be present. + */ + poster?: string | null; + /** + * An url to the thumbnail of this resource. If this resource does not have an image, the link + * will be null. If the kyoo's instance is not capable of handling this kind of image for the + * specific resource, this field won't be present. + */ + thumbnail?: string | null; + /** + * An url to the logo of this resource. If this resource does not have an image, the link will + * be null. If the kyoo's instance is not capable of handling this kind of image for the + * specific resource, this field won't be present. + */ + logo?: string | null; /** * The links to the videos of this watch item. */ link: { - direct: string, - transmux: string, - } + direct: string; + transmux: string; + }; }; export const getItem = async (slug: string, apiUrl: string) => { @@ -79,13 +90,38 @@ export const getItem = async (slug: string, apiUrl: string) => { console.error(await resp.text()); return null; } - const ret = await resp.json() as Item; + const ret = (await resp.json()) as Item; if (!ret) return null; + ret.name = (ret as any).title; + ret.releaseDate = new Date(ret.releaseDate); ret.link.direct = `${apiUrl}/${ret.link.direct}`; ret.link.transmux = `${apiUrl}/${ret.link.transmux}`; + ret.thumbnail = `${apiUrl}/${ret.thumbnail}`; + ret.poster = `${apiUrl}/${ret.poster}`; + ret.logo = `${apiUrl}/${ret.logo}`; return ret; - } catch(e) { + } catch (e) { console.error("Fetch error", e); - return null + return null; } +}; + +export const itemToTvMetadata = (item: Item) => { + const metadata = new cast.framework.messages.TvShowMediaMetadata(); + metadata.title = item.name; + metadata.season = item.seasonNumber; + metadata.episode = item.episodeNumber; + metadata.seriesTitle = item.showTitle; + metadata.originalAirdate = item.releaseDate.toISOString().substring(0, 10); + metadata.images = item.poster ? [new cast.framework.messages.Image(item.poster)] : []; + return metadata; } + +export const itemToMovie = (item: Item) => { + const metadata = new cast.framework.messages.MovieMediaMetadata(); + metadata.title = item.name; + metadata.releaseDate = item.releaseDate.toISOString().substring(0, 10); + metadata.images = item.poster ? [new cast.framework.messages.Image(item.poster)] : []; + return metadata; +} + diff --git a/chromecast/src/index.ts b/chromecast/src/index.ts index acebc9be..5a3ff202 100644 --- a/chromecast/src/index.ts +++ b/chromecast/src/index.ts @@ -18,23 +18,44 @@ * along with Kyoo. If not, see . */ -import { getItem } from "./api"; +import { getItem, itemToMovie, itemToTvMetadata } from "./api"; +const Command = cast.framework.messages.Command; const context = cast.framework.CastReceiverContext.getInstance(); const playerManager = context.getPlayerManager(); -playerManager.setMessageInterceptor(cast.framework.messages.MessageType.LOAD, async (loadRequestData) => { - if (loadRequestData.media.contentUrl && loadRequestData.media.metadata) return loadRequestData; +playerManager.setSupportedMediaCommands( + Command.PAUSE | + Command.SEEK | + Command.QUEUE_NEXT | + Command.QUEUE_PREV | + Command.EDIT_TRACKS | + Command.STREAM_MUTE | + Command.STREAM_VOLUME | + Command.STREAM_TRANSFER, +); - const item = await getItem(loadRequestData.media.contentId, loadRequestData.media.customData.serverUrl); - if (!item) { - return new cast.framework.messages.ErrorData( - cast.framework.messages.ErrorType.LOAD_FAILED, + + +playerManager.setMessageInterceptor( + cast.framework.messages.MessageType.LOAD, + async (loadRequestData) => { + if (loadRequestData.media.contentUrl && loadRequestData.media.metadata) return loadRequestData; + + const item = await getItem( + loadRequestData.media.contentId, + loadRequestData.media.customData.serverUrl, ); - } - loadRequestData.media.contentUrl = item.link.direct; - loadRequestData.media.metadata = item; - return loadRequestData; -}); + if (!item) { + return new cast.framework.messages.ErrorData(cast.framework.messages.ErrorType.LOAD_FAILED); + } + loadRequestData.media.contentUrl = item.link.direct; + loadRequestData.media.metadata = item.isMovie + ? itemToMovie(item) + : itemToTvMetadata(item); + loadRequestData.media.customData = item; + return loadRequestData; + }, +); context.start(); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 52626a7c..c68f79fa 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -37,6 +37,7 @@ services: environment: - KYOO_URL=http://back:5000 - NEXT_PUBLIC_BACK_URL=${PUBLIC_BACK_URL} + - NEXT_PUBLIC_CAST_APPLICATION_ID=${CAST_APPLICATION_ID} ingress: image: nginx restart: on-failure diff --git a/docker-compose.yml b/docker-compose.yml index 2c8a5dad..b556413c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,7 @@ services: restart: on-failure environment: - KYOO_URL=http://back:5000 + - NEXT_PUBLIC_CAST_APPLICATION_ID=${CAST_APPLICATION_ID} ingress: image: nginx restart: on-failure @@ -30,7 +31,6 @@ services: - PORT=8901 - FRONT_URL=http://front:8901 - BACK_URL=http://back:5000 - - CAST_APPLICATION_ID=${CAST_APPLICATION_ID} volumes: - ./nginx.conf.template:/etc/nginx/templates/kyoo.conf.template:ro depends_on: diff --git a/front/src/player/cast/cast-provider.tsx b/front/src/player/cast/cast-provider.tsx index 5c775707..0f9f80bc 100644 --- a/front/src/player/cast/cast-provider.tsx +++ b/front/src/player/cast/cast-provider.tsx @@ -34,9 +34,10 @@ export const CastProvider = () => { window.__onGCastApiAvailable = (isAvailable) => { if (!isAvailable) return; cast.framework.CastContext.getInstance().setOptions({ - receiverApplicationId: process.env.CAST_APPLICATION_ID, + receiverApplicationId: process.env.NEXT_PUBLIC_CAST_APPLICATION_ID, autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, }); + setLoaded(true); }; }, []); @@ -45,7 +46,6 @@ export const CastProvider = () => {