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:
parent
f472f04735
commit
e7a6484094
9 changed files with 63 additions and 7 deletions
|
@ -1,5 +1,5 @@
|
|||
import { FetchError } from "ofetch";
|
||||
import { makeUrl, mwFetch } from "../helpers/fetch";
|
||||
import { makeUrl, proxiedFetch } from "../helpers/fetch";
|
||||
import {
|
||||
formatJWMeta,
|
||||
JWMediaResult,
|
||||
|
@ -45,7 +45,7 @@ export async function getMetaFromId(
|
|||
type: queryType,
|
||||
id,
|
||||
});
|
||||
data = await mwFetch<JWDetailedMeta>(url, { baseURL: JW_API_BASE });
|
||||
data = await proxiedFetch<JWDetailedMeta>(url, { baseURL: JW_API_BASE });
|
||||
} catch (err) {
|
||||
if (err instanceof FetchError) {
|
||||
// 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", {
|
||||
id: seasonToScrape,
|
||||
});
|
||||
seasonData = await mwFetch<any>(url, { baseURL: JW_API_BASE });
|
||||
seasonData = await proxiedFetch<any>(url, { baseURL: JW_API_BASE });
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { SimpleCache } from "@/utils/cache";
|
||||
import { mwFetch } from "../helpers/fetch";
|
||||
import { proxiedFetch } from "../helpers/fetch";
|
||||
import {
|
||||
formatJWMeta,
|
||||
JWContentTypes,
|
||||
|
@ -42,7 +42,7 @@ export async function searchForMedia(query: MWQuery): Promise<MWMediaMeta[]> {
|
|||
page_size: 40,
|
||||
};
|
||||
|
||||
const data = await mwFetch<JWPage<JWMediaResult>>(
|
||||
const data = await proxiedFetch<JWPage<JWMediaResult>>(
|
||||
"/content/titles/en_US/popular",
|
||||
{
|
||||
baseURL: JW_API_BASE,
|
||||
|
|
|
@ -25,6 +25,7 @@ export enum Icons {
|
|||
VOLUME_X = "volume_x",
|
||||
X = "x",
|
||||
EDIT = "edit",
|
||||
AIRPLAY = "airplay",
|
||||
}
|
||||
|
||||
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>`,
|
||||
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>`,
|
||||
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) => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { MWMediaMeta } from "@/backend/metadata/types";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
import { AirplayControl } from "./controls/AirplayControl";
|
||||
import { BackdropControl } from "./controls/BackdropControl";
|
||||
import { FullscreenControl } from "./controls/FullscreenControl";
|
||||
import { LoadingControl } from "./controls/LoadingControl";
|
||||
|
@ -91,6 +92,7 @@ export function DecoratedVideoPlayer(
|
|||
<div className="flex items-center">
|
||||
<LeftSideControls />
|
||||
<div className="flex-1" />
|
||||
<AirplayControl />
|
||||
<FullscreenControl />
|
||||
</div>
|
||||
</div>
|
||||
|
|
26
src/components/video/controls/AirplayControl.tsx
Normal file
26
src/components/video/controls/AirplayControl.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -30,6 +30,7 @@ export interface PlayerControls {
|
|||
setLeftControlsHover(hovering: boolean): void;
|
||||
initPlayer(sourceUrl: string, sourceType: MWStreamType): void;
|
||||
setShowData(data: ShowData): void;
|
||||
startAirplay(): void;
|
||||
}
|
||||
|
||||
export const initialControls: PlayerControls = {
|
||||
|
@ -43,6 +44,7 @@ export const initialControls: PlayerControls = {
|
|||
setLeftControlsHover: () => null,
|
||||
initPlayer: () => null,
|
||||
setShowData: () => null,
|
||||
startAirplay: () => null,
|
||||
};
|
||||
|
||||
export function populateControls(
|
||||
|
@ -118,6 +120,11 @@ export function populateControls(
|
|||
setShowData(data) {
|
||||
update((s) => ({ ...s, seasonData: data }));
|
||||
},
|
||||
startAirplay() {
|
||||
const videoPlayer = player as any;
|
||||
if (videoPlayer.webkitShowPlaybackTargetPicker)
|
||||
videoPlayer.webkitShowPlaybackTargetPicker();
|
||||
},
|
||||
initPlayer(sourceUrl: string, sourceType: MWStreamType) {
|
||||
this.setVolume(getStoredVolume());
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ export type PlayerState = {
|
|||
name: string;
|
||||
description: string;
|
||||
};
|
||||
canAirplay: boolean;
|
||||
};
|
||||
|
||||
export type PlayerContext = PlayerState & PlayerControls;
|
||||
|
@ -57,6 +58,7 @@ export const initialPlayerState: PlayerContext = {
|
|||
seasonData: {
|
||||
isSeries: false,
|
||||
},
|
||||
canAirplay: false,
|
||||
...initialControls,
|
||||
};
|
||||
|
||||
|
@ -159,6 +161,14 @@ function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
|
|||
: null,
|
||||
}));
|
||||
};
|
||||
const canAirplay = (e: any) => {
|
||||
if (e.availability === "available") {
|
||||
update((s) => ({
|
||||
...s,
|
||||
canAirplay: true,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
player.addEventListener("pause", pause);
|
||||
player.addEventListener("playing", playing);
|
||||
|
@ -172,6 +182,10 @@ function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
|
|||
player.addEventListener("waiting", waiting);
|
||||
player.addEventListener("canplay", canplay);
|
||||
player.addEventListener("error", error);
|
||||
player.addEventListener(
|
||||
"webkitplaybacktargetavailabilitychanged",
|
||||
canAirplay
|
||||
);
|
||||
|
||||
return () => {
|
||||
player.removeEventListener("pause", pause);
|
||||
|
@ -186,6 +200,10 @@ function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
|
|||
player.removeEventListener("waiting", waiting);
|
||||
player.removeEventListener("canplay", canplay);
|
||||
player.removeEventListener("error", error);
|
||||
player.removeEventListener(
|
||||
"webkitplaybacktargetavailabilitychanged",
|
||||
canAirplay
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ if (key) {
|
|||
// - source selection
|
||||
// - 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 cannot be dragged
|
||||
|
||||
// TODO stuff to test:
|
||||
// - browser: firefox, chrome, edge, safari desktop
|
||||
|
@ -41,7 +42,8 @@ if (key) {
|
|||
// - AFTER all that: rank providers/embedscrapers
|
||||
|
||||
// TODO general todos:
|
||||
// - localize everything
|
||||
// - localize everything (fix loading screen text (series vs movies))
|
||||
// - make mobile friendly
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
|
|
|
@ -155,7 +155,6 @@ export function MediaView() {
|
|||
const [stream, setStream] = useState<MWStream | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("I am being ran");
|
||||
exec(params.media, params.season).then((v) => {
|
||||
setMeta(v ?? null);
|
||||
if (v) {
|
||||
|
|
Loading…
Reference in a new issue