1
0
Fork 0
mirror of https://github.com/sussy-code/smov.git synced 2024-12-20 14:37:43 +01:00

fix multi origin and add airplay support

This commit is contained in:
Jelle van Snik 2023-01-22 20:51:58 +01:00
parent f472f04735
commit e7a6484094
9 changed files with 63 additions and 7 deletions

View file

@ -1,5 +1,5 @@
import { FetchError } from "ofetch"; import { FetchError } from "ofetch";
import { makeUrl, mwFetch } from "../helpers/fetch"; import { makeUrl, proxiedFetch } from "../helpers/fetch";
import { import {
formatJWMeta, formatJWMeta,
JWMediaResult, JWMediaResult,
@ -45,7 +45,7 @@ export async function getMetaFromId(
type: queryType, type: queryType,
id, id,
}); });
data = await mwFetch<JWDetailedMeta>(url, { baseURL: JW_API_BASE }); data = await proxiedFetch<JWDetailedMeta>(url, { baseURL: JW_API_BASE });
} catch (err) { } catch (err) {
if (err instanceof FetchError) { if (err instanceof FetchError) {
// 400 and 404 are treated as not found // 400 and 404 are treated as not found
@ -69,7 +69,7 @@ export async function getMetaFromId(
const url = makeUrl("/content/titles/show_season/{id}/locale/en_US", { const url = makeUrl("/content/titles/show_season/{id}/locale/en_US", {
id: seasonToScrape, id: seasonToScrape,
}); });
seasonData = await mwFetch<any>(url, { baseURL: JW_API_BASE }); seasonData = await proxiedFetch<any>(url, { baseURL: JW_API_BASE });
} }
return { return {

View file

@ -1,5 +1,5 @@
import { SimpleCache } from "@/utils/cache"; import { SimpleCache } from "@/utils/cache";
import { mwFetch } from "../helpers/fetch"; import { proxiedFetch } from "../helpers/fetch";
import { import {
formatJWMeta, formatJWMeta,
JWContentTypes, JWContentTypes,
@ -42,7 +42,7 @@ export async function searchForMedia(query: MWQuery): Promise<MWMediaMeta[]> {
page_size: 40, page_size: 40,
}; };
const data = await mwFetch<JWPage<JWMediaResult>>( const data = await proxiedFetch<JWPage<JWMediaResult>>(
"/content/titles/en_US/popular", "/content/titles/en_US/popular",
{ {
baseURL: JW_API_BASE, baseURL: JW_API_BASE,

View file

@ -25,6 +25,7 @@ export enum Icons {
VOLUME_X = "volume_x", VOLUME_X = "volume_x",
X = "x", X = "x",
EDIT = "edit", EDIT = "edit",
AIRPLAY = "airplay",
} }
export interface IconProps { export interface IconProps {
@ -57,6 +58,7 @@ const iconList: Record<Icons, string> = {
x: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"/></svg>`, x: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"/></svg>`,
edit: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z"/></svg>`, edit: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z"/></svg>`,
bookmark_outline: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M336 0h-288C21.49 0 0 21.49 0 48v431.9c0 24.7 26.79 40.08 48.12 27.64L192 423.6l143.9 83.93C357.2 519.1 384 504.6 384 479.9V48C384 21.49 362.5 0 336 0zM336 452L192 368l-144 84V54C48 50.63 50.63 48 53.1 48h276C333.4 48 336 50.63 336 54V452z"/></svg>`, bookmark_outline: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M336 0h-288C21.49 0 0 21.49 0 48v431.9c0 24.7 26.79 40.08 48.12 27.64L192 423.6l143.9 83.93C357.2 519.1 384 504.6 384 479.9V48C384 21.49 362.5 0 336 0zM336 452L192 368l-144 84V54C48 50.63 50.63 48 53.1 48h276C333.4 48 336 50.63 336 54V452z"/></svg>`,
airplay: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-airplay"><path d="M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1"></path><polygon fill="currentColor" points="12 15 17 21 7 21 12 15"></polygon></svg>`,
}; };
export const Icon = memo((props: IconProps) => { export const Icon = memo((props: IconProps) => {

View file

@ -1,6 +1,7 @@
import { MWMediaMeta } from "@/backend/metadata/types"; import { MWMediaMeta } from "@/backend/metadata/types";
import { useCallback, useRef, useState } from "react"; import { useCallback, useRef, useState } from "react";
import { CSSTransition } from "react-transition-group"; import { CSSTransition } from "react-transition-group";
import { AirplayControl } from "./controls/AirplayControl";
import { BackdropControl } from "./controls/BackdropControl"; import { BackdropControl } from "./controls/BackdropControl";
import { FullscreenControl } from "./controls/FullscreenControl"; import { FullscreenControl } from "./controls/FullscreenControl";
import { LoadingControl } from "./controls/LoadingControl"; import { LoadingControl } from "./controls/LoadingControl";
@ -91,6 +92,7 @@ export function DecoratedVideoPlayer(
<div className="flex items-center"> <div className="flex items-center">
<LeftSideControls /> <LeftSideControls />
<div className="flex-1" /> <div className="flex-1" />
<AirplayControl />
<FullscreenControl /> <FullscreenControl />
</div> </div>
</div> </div>

View file

@ -0,0 +1,26 @@
import { Icons } from "@/components/Icon";
import { useCallback } from "react";
import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
import { useVideoPlayerState } from "../VideoContext";
interface Props {
className?: string;
}
export function AirplayControl(props: Props) {
const { videoState } = useVideoPlayerState();
const handleClick = useCallback(() => {
videoState.startAirplay();
}, [videoState]);
if (!videoState.canAirplay) return null;
return (
<VideoPlayerIconButton
className={props.className}
onClick={handleClick}
icon={Icons.AIRPLAY}
/>
);
}

View file

@ -30,6 +30,7 @@ export interface PlayerControls {
setLeftControlsHover(hovering: boolean): void; setLeftControlsHover(hovering: boolean): void;
initPlayer(sourceUrl: string, sourceType: MWStreamType): void; initPlayer(sourceUrl: string, sourceType: MWStreamType): void;
setShowData(data: ShowData): void; setShowData(data: ShowData): void;
startAirplay(): void;
} }
export const initialControls: PlayerControls = { export const initialControls: PlayerControls = {
@ -43,6 +44,7 @@ export const initialControls: PlayerControls = {
setLeftControlsHover: () => null, setLeftControlsHover: () => null,
initPlayer: () => null, initPlayer: () => null,
setShowData: () => null, setShowData: () => null,
startAirplay: () => null,
}; };
export function populateControls( export function populateControls(
@ -118,6 +120,11 @@ export function populateControls(
setShowData(data) { setShowData(data) {
update((s) => ({ ...s, seasonData: data })); update((s) => ({ ...s, seasonData: data }));
}, },
startAirplay() {
const videoPlayer = player as any;
if (videoPlayer.webkitShowPlaybackTargetPicker)
videoPlayer.webkitShowPlaybackTargetPicker();
},
initPlayer(sourceUrl: string, sourceType: MWStreamType) { initPlayer(sourceUrl: string, sourceType: MWStreamType) {
this.setVolume(getStoredVolume()); this.setVolume(getStoredVolume());

View file

@ -34,6 +34,7 @@ export type PlayerState = {
name: string; name: string;
description: string; description: string;
}; };
canAirplay: boolean;
}; };
export type PlayerContext = PlayerState & PlayerControls; export type PlayerContext = PlayerState & PlayerControls;
@ -57,6 +58,7 @@ export const initialPlayerState: PlayerContext = {
seasonData: { seasonData: {
isSeries: false, isSeries: false,
}, },
canAirplay: false,
...initialControls, ...initialControls,
}; };
@ -159,6 +161,14 @@ function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
: null, : null,
})); }));
}; };
const canAirplay = (e: any) => {
if (e.availability === "available") {
update((s) => ({
...s,
canAirplay: true,
}));
}
};
player.addEventListener("pause", pause); player.addEventListener("pause", pause);
player.addEventListener("playing", playing); player.addEventListener("playing", playing);
@ -172,6 +182,10 @@ function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
player.addEventListener("waiting", waiting); player.addEventListener("waiting", waiting);
player.addEventListener("canplay", canplay); player.addEventListener("canplay", canplay);
player.addEventListener("error", error); player.addEventListener("error", error);
player.addEventListener(
"webkitplaybacktargetavailabilitychanged",
canAirplay
);
return () => { return () => {
player.removeEventListener("pause", pause); player.removeEventListener("pause", pause);
@ -186,6 +200,10 @@ function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
player.removeEventListener("waiting", waiting); player.removeEventListener("waiting", waiting);
player.removeEventListener("canplay", canplay); player.removeEventListener("canplay", canplay);
player.removeEventListener("error", error); player.removeEventListener("error", error);
player.removeEventListener(
"webkitplaybacktargetavailabilitychanged",
canAirplay
);
}; };
} }

View file

@ -25,6 +25,7 @@ if (key) {
// - source selection // - source selection
// - safari fullscreen will make video overlap player controls // - safari fullscreen will make video overlap player controls
// - safari progress bar is fucked (video doesnt change time but video.currentTime does change) // - safari progress bar is fucked (video doesnt change time but video.currentTime does change)
// - safari progress bar cannot be dragged
// TODO stuff to test: // TODO stuff to test:
// - browser: firefox, chrome, edge, safari desktop // - browser: firefox, chrome, edge, safari desktop
@ -41,7 +42,8 @@ if (key) {
// - AFTER all that: rank providers/embedscrapers // - AFTER all that: rank providers/embedscrapers
// TODO general todos: // TODO general todos:
// - localize everything // - localize everything (fix loading screen text (series vs movies))
// - make mobile friendly
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>

View file

@ -155,7 +155,6 @@ export function MediaView() {
const [stream, setStream] = useState<MWStream | null>(null); const [stream, setStream] = useState<MWStream | null>(null);
useEffect(() => { useEffect(() => {
console.log("I am being ran");
exec(params.media, params.season).then((v) => { exec(params.media, params.season).then((v) => {
setMeta(v ?? null); setMeta(v ?? null);
if (v) { if (v) {