Merge pull request #525 from movie-web/coop-fixes

Tag team fixes!
This commit is contained in:
mrjvs 2023-12-13 21:45:10 +01:00 committed by GitHub
commit 6a9eb11884
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 42 additions and 19 deletions

View File

@ -17,7 +17,7 @@
"login": { "login": {
"title": "Login to your account", "title": "Login to your account",
"description": "Please enter your passphrase to login to your account", "description": "Please enter your passphrase to login to your account",
"validationError": "Invalid or incomplete passphrase", "validationError": "Incorrect or incomplete passphrase",
"deviceLengthError": "Please enter a device name", "deviceLengthError": "Please enter a device name",
"submit": "Login", "submit": "Login",
"passphraseLabel": "12-Word passphrase", "passphraseLabel": "12-Word passphrase",

View File

@ -16,7 +16,7 @@
"passphrasePlaceholder": "Passphrase", "passphrasePlaceholder": "Passphrase",
"submit": "Hoist Anchor", "submit": "Hoist Anchor",
"title": "Hoist the Jolly Roger", "title": "Hoist the Jolly Roger",
"validationError": "Arr, invalid or incomplete passphrase" "validationError": "Arr, incorrect or incomplete passphrase"
}, },
"register": { "register": {
"information": { "information": {

View File

@ -95,7 +95,7 @@ function MediaCardContent({
{percentage !== undefined ? ( {percentage !== undefined ? (
<> <>
<div <div
className={`absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-mediaCard-shadow to-transparent transition-colors ${ className={`absolute inset-x-0 -bottom-px pb-1 h-12 bg-gradient-to-t from-mediaCard-shadow to-transparent transition-colors ${
canLink ? "group-hover:from-mediaCard-hoverShadow" : "" canLink ? "group-hover:from-mediaCard-hoverShadow" : ""
}`} }`}
/> />

View File

@ -16,7 +16,7 @@ export function OverlayMobilePosition(props: MobilePositionProps) {
return ( return (
<div <div
className={classNames([ className={classNames([
"pointer-events-auto px-4 pb-6 z-10 bottom-0 origin-top-left inset-x-0 absolute overflow-hidden max-h-[calc(100vh-1.5rem)] grid grid-rows-[minmax(0,1fr),auto]", "pointer-events-auto px-4 pb-6 z-10 ml-[env(safe-area-inset-left)] mr-[env(safe-area-inset-right)] bottom-0 origin-top-left inset-x-0 absolute overflow-hidden max-h-[calc(100vh-1.5rem)] grid grid-rows-[minmax(0,1fr),auto]",
props.className, props.className,
])} ])}
> >

View File

@ -62,9 +62,11 @@ export function NextEpisodeButton(props: {
if (isHidden || status !== "playing" || duration === 0) show = false; if (isHidden || status !== "playing" || duration === 0) show = false;
const animation = showingState === "hover" ? "slide-up" : "fade"; const animation = showingState === "hover" ? "slide-up" : "fade";
let bottom = "bottom-24"; let bottom = "bottom-[calc(6rem+env(safe-area-inset-bottom))]";
if (showingState === "always") if (showingState === "always")
bottom = props.controlsShowing ? "bottom-24" : "bottom-12"; bottom = props.controlsShowing
? bottom
: "bottom-[calc(3rem+env(safe-area-inset-bottom))]";
const nextEp = meta?.episodes?.find( const nextEp = meta?.episodes?.find(
(v) => v.number === (meta?.episode?.number ?? 0) + 1 (v) => v.number === (meta?.episode?.number ?? 0) + 1
@ -86,7 +88,7 @@ export function NextEpisodeButton(props: {
<Transition <Transition
animation={animation} animation={animation}
show={show} show={show}
className="absolute right-12 bottom-0" className="absolute right-[calc(3rem+env(safe-area-inset-right))] bottom-0"
> >
<div <div
className={classNames([ className={classNames([

View File

@ -5,6 +5,7 @@ import { playerStatus } from "@/stores/player/slices/source";
import { ThumbnailImage } from "@/stores/player/slices/thumbnails"; import { ThumbnailImage } from "@/stores/player/slices/thumbnails";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
import { LoadableSource, selectQuality } from "@/stores/player/utils/qualities"; import { LoadableSource, selectQuality } from "@/stores/player/utils/qualities";
import { isSafari } from "@/utils/detectFeatures";
function makeQueue(layers: number): number[] { function makeQueue(layers: number): number[] {
const output = [0, 1]; const output = [0, 1];
@ -39,7 +40,9 @@ class ThumnbnailWorker {
} }
start(source: LoadableSource) { start(source: LoadableSource) {
if (isSafari) return false;
const el = document.createElement("video"); const el = document.createElement("video");
el.setAttribute("muted", "true");
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
this.hls = new Hls(); this.hls = new Hls();
if (source.type === "mp4") { if (source.type === "mp4") {
@ -76,9 +79,14 @@ class ThumnbnailWorker {
private async takeSnapshot(at: number) { private async takeSnapshot(at: number) {
if (!this.videoEl || !this.canvasEl) return; if (!this.videoEl || !this.canvasEl) return;
await this.videoEl.play(); // so that `seeked` actually triggers
this.videoEl.currentTime = at; this.videoEl.currentTime = at;
await new Promise((resolve) => { await new Promise((resolve) => {
this.videoEl?.addEventListener("seeked", resolve); const onSeeked = () => {
this.videoEl?.removeEventListener("seeked", onSeeked);
resolve(null);
};
this.videoEl?.addEventListener("seeked", onSeeked);
}); });
if (!this.videoEl || !this.canvasEl) return; if (!this.videoEl || !this.canvasEl) return;
const ctx = this.canvasEl.getContext("2d"); const ctx = this.canvasEl.getContext("2d");
@ -91,6 +99,7 @@ class ThumnbnailWorker {
this.canvasEl.height this.canvasEl.height
); );
const imgUrl = this.canvasEl.toDataURL(); const imgUrl = this.canvasEl.toDataURL();
if (this.interrupted) return; if (this.interrupted) return;
if (imgUrl === "data:," || !imgUrl) return; // failed image rendering if (imgUrl === "data:," || !imgUrl) return; // failed image rendering
@ -142,6 +151,7 @@ export function ThumbnailScraper() {
workerRef.current = ins; workerRef.current = ins;
ins.start(inputStream.stream); ins.start(inputStream.stream);
}, [source, addImage, resetImages, status]); }, [source, addImage, resetImages, status]);
const startRef = useRef(start); const startRef = useRef(start);
useEffect(() => { useEffect(() => {
startRef.current = start; startRef.current = start;

View File

@ -161,8 +161,8 @@ function ParticlesCanvas() {
export function Lightbar(props: { className?: string }) { export function Lightbar(props: { className?: string }) {
return ( return (
<div className="absolute inset-0 w-full h-screen overflow-hidden pointer-events-none -mt-64"> <div className="absolute inset-0 w-full h-[calc(100vh+16rem)] overflow-hidden pointer-events-none -mt-64">
<div className="max-w-screen w-full h-screen relative pt-64"> <div className="max-w-screen w-full h-[calc(100vh+16rem)] relative pt-64">
<div className={props.className}> <div className={props.className}>
<div className="lightbar"> <div className="lightbar">
<ParticlesCanvas /> <ParticlesCanvas />

View File

@ -19,7 +19,7 @@ const testMeta: PlayerMeta = {
const testStreams: Record<StreamType, string> = { const testStreams: Record<StreamType, string> = {
hls: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8", hls: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8",
mp4: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4", mp4: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4",
}; };
const streamTypes: Record<StreamType, string> = { const streamTypes: Record<StreamType, string> = {

View File

@ -26,7 +26,7 @@ export function BlurEllipsis(props: { positionClass?: string }) {
export function SubPageLayout(props: { children: React.ReactNode }) { export function SubPageLayout(props: { children: React.ReactNode }) {
return ( return (
<div <div
className="from-[#0D0D1A] to-background-main" className="bg-background-main"
style={{ style={{
backgroundImage: backgroundImage:
"linear-gradient(to bottom, var(--tw-gradient-from), var(--tw-gradient-to) 800px)", "linear-gradient(to bottom, var(--tw-gradient-from), var(--tw-gradient-to) 800px)",

View File

@ -1,6 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { useAsyncFn } from "react-use"; import { useAsyncFn } from "react-use";
import type { AsyncReturnType } from "type-fest";
import { verifyValidMnemonic } from "@/backend/accounts/crypto"; import { verifyValidMnemonic } from "@/backend/accounts/crypto";
import { Button } from "@/components/buttons/Button"; import { Button } from "@/components/buttons/Button";
@ -37,12 +38,19 @@ export function LoginFormPart(props: LoginFormPartProps) {
if (validatedDevice.length === 0) if (validatedDevice.length === 0)
throw new Error(t("auth.login.deviceLengthError") ?? undefined); throw new Error(t("auth.login.deviceLengthError") ?? undefined);
const account = await login({ let account: AsyncReturnType<typeof login>;
try {
account = await login({
mnemonic: inputMnemonic, mnemonic: inputMnemonic,
userData: { userData: {
device: validatedDevice, device: validatedDevice,
}, },
}); });
} catch (err) {
if ((err as any).status === 401)
throw new Error(t("auth.login.validationError") ?? undefined);
throw err;
}
await importData(account, progressItems, bookmarkItems); await importData(account, progressItems, bookmarkItems);

View File

@ -1,6 +1,7 @@
import { ProviderControls, ScrapeMedia } from "@movie-web/providers"; import { ProviderControls, ScrapeMedia } from "@movie-web/providers";
import classNames from "classnames"; import classNames from "classnames";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { useMountedState } from "react-use";
import type { AsyncReturnType } from "type-fest"; import type { AsyncReturnType } from "type-fest";
import { import {
@ -30,6 +31,7 @@ export interface ScrapingProps {
export function ScrapingPart(props: ScrapingProps) { export function ScrapingPart(props: ScrapingProps) {
const { report } = useReportProviders(); const { report } = useReportProviders();
const { startScraping, sourceOrder, sources, currentSource } = useScrape(); const { startScraping, sourceOrder, sources, currentSource } = useScrape();
const isMounted = useMountedState();
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
const listRef = useRef<HTMLDivElement | null>(null); const listRef = useRef<HTMLDivElement | null>(null);
@ -57,6 +59,7 @@ export function ScrapingPart(props: ScrapingProps) {
started.current = true; started.current = true;
(async () => { (async () => {
const output = await startScraping(props.media); const output = await startScraping(props.media);
if (!isMounted()) return;
props.onResult?.( props.onResult?.(
resultRef.current.sources, resultRef.current.sources,
resultRef.current.sourceOrder resultRef.current.sourceOrder
@ -70,7 +73,7 @@ export function ScrapingPart(props: ScrapingProps) {
); );
props.onGetStream?.(output); props.onGetStream?.(output);
})(); })();
}, [startScraping, props, report]); }, [startScraping, props, report, isMounted]);
const currentProvider = sourceOrder.find( const currentProvider = sourceOrder.find(
(s) => sources[s.id].status === "pending" (s) => sources[s.id].status === "pending"