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 { 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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
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;
|
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());
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue