mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
commit
6a9eb11884
11 changed files with 42 additions and 19 deletions
|
@ -17,7 +17,7 @@
|
|||
"login": {
|
||||
"title": "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",
|
||||
"submit": "Login",
|
||||
"passphraseLabel": "12-Word passphrase",
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"passphrasePlaceholder": "Passphrase",
|
||||
"submit": "Hoist Anchor",
|
||||
"title": "Hoist the Jolly Roger",
|
||||
"validationError": "Arr, invalid or incomplete passphrase"
|
||||
"validationError": "Arr, incorrect or incomplete passphrase"
|
||||
},
|
||||
"register": {
|
||||
"information": {
|
||||
|
|
|
@ -95,7 +95,7 @@ function MediaCardContent({
|
|||
{percentage !== undefined ? (
|
||||
<>
|
||||
<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" : ""
|
||||
}`}
|
||||
/>
|
||||
|
|
|
@ -16,7 +16,7 @@ export function OverlayMobilePosition(props: MobilePositionProps) {
|
|||
return (
|
||||
<div
|
||||
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,
|
||||
])}
|
||||
>
|
||||
|
|
|
@ -62,9 +62,11 @@ export function NextEpisodeButton(props: {
|
|||
if (isHidden || status !== "playing" || duration === 0) show = false;
|
||||
|
||||
const animation = showingState === "hover" ? "slide-up" : "fade";
|
||||
let bottom = "bottom-24";
|
||||
let bottom = "bottom-[calc(6rem+env(safe-area-inset-bottom))]";
|
||||
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(
|
||||
(v) => v.number === (meta?.episode?.number ?? 0) + 1
|
||||
|
@ -86,7 +88,7 @@ export function NextEpisodeButton(props: {
|
|||
<Transition
|
||||
animation={animation}
|
||||
show={show}
|
||||
className="absolute right-12 bottom-0"
|
||||
className="absolute right-[calc(3rem+env(safe-area-inset-right))] bottom-0"
|
||||
>
|
||||
<div
|
||||
className={classNames([
|
||||
|
|
|
@ -5,6 +5,7 @@ import { playerStatus } from "@/stores/player/slices/source";
|
|||
import { ThumbnailImage } from "@/stores/player/slices/thumbnails";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
import { LoadableSource, selectQuality } from "@/stores/player/utils/qualities";
|
||||
import { isSafari } from "@/utils/detectFeatures";
|
||||
|
||||
function makeQueue(layers: number): number[] {
|
||||
const output = [0, 1];
|
||||
|
@ -39,7 +40,9 @@ class ThumnbnailWorker {
|
|||
}
|
||||
|
||||
start(source: LoadableSource) {
|
||||
if (isSafari) return false;
|
||||
const el = document.createElement("video");
|
||||
el.setAttribute("muted", "true");
|
||||
const canvas = document.createElement("canvas");
|
||||
this.hls = new Hls();
|
||||
if (source.type === "mp4") {
|
||||
|
@ -76,9 +79,14 @@ class ThumnbnailWorker {
|
|||
|
||||
private async takeSnapshot(at: number) {
|
||||
if (!this.videoEl || !this.canvasEl) return;
|
||||
await this.videoEl.play(); // so that `seeked` actually triggers
|
||||
this.videoEl.currentTime = at;
|
||||
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;
|
||||
const ctx = this.canvasEl.getContext("2d");
|
||||
|
@ -91,6 +99,7 @@ class ThumnbnailWorker {
|
|||
this.canvasEl.height
|
||||
);
|
||||
const imgUrl = this.canvasEl.toDataURL();
|
||||
|
||||
if (this.interrupted) return;
|
||||
if (imgUrl === "data:," || !imgUrl) return; // failed image rendering
|
||||
|
||||
|
@ -142,6 +151,7 @@ export function ThumbnailScraper() {
|
|||
workerRef.current = ins;
|
||||
ins.start(inputStream.stream);
|
||||
}, [source, addImage, resetImages, status]);
|
||||
|
||||
const startRef = useRef(start);
|
||||
useEffect(() => {
|
||||
startRef.current = start;
|
||||
|
|
|
@ -161,8 +161,8 @@ function ParticlesCanvas() {
|
|||
|
||||
export function Lightbar(props: { className?: string }) {
|
||||
return (
|
||||
<div className="absolute inset-0 w-full h-screen overflow-hidden pointer-events-none -mt-64">
|
||||
<div className="max-w-screen w-full h-screen relative pt-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-[calc(100vh+16rem)] relative pt-64">
|
||||
<div className={props.className}>
|
||||
<div className="lightbar">
|
||||
<ParticlesCanvas />
|
||||
|
|
|
@ -19,7 +19,7 @@ const testMeta: PlayerMeta = {
|
|||
|
||||
const testStreams: Record<StreamType, string> = {
|
||||
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> = {
|
||||
|
|
|
@ -26,7 +26,7 @@ export function BlurEllipsis(props: { positionClass?: string }) {
|
|||
export function SubPageLayout(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
className="from-[#0D0D1A] to-background-main"
|
||||
className="bg-background-main"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(to bottom, var(--tw-gradient-from), var(--tw-gradient-to) 800px)",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useAsyncFn } from "react-use";
|
||||
import type { AsyncReturnType } from "type-fest";
|
||||
|
||||
import { verifyValidMnemonic } from "@/backend/accounts/crypto";
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
|
@ -37,12 +38,19 @@ export function LoginFormPart(props: LoginFormPartProps) {
|
|||
if (validatedDevice.length === 0)
|
||||
throw new Error(t("auth.login.deviceLengthError") ?? undefined);
|
||||
|
||||
const account = await login({
|
||||
mnemonic: inputMnemonic,
|
||||
userData: {
|
||||
device: validatedDevice,
|
||||
},
|
||||
});
|
||||
let account: AsyncReturnType<typeof login>;
|
||||
try {
|
||||
account = await login({
|
||||
mnemonic: inputMnemonic,
|
||||
userData: {
|
||||
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);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ProviderControls, ScrapeMedia } from "@movie-web/providers";
|
||||
import classNames from "classnames";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useMountedState } from "react-use";
|
||||
import type { AsyncReturnType } from "type-fest";
|
||||
|
||||
import {
|
||||
|
@ -30,6 +31,7 @@ export interface ScrapingProps {
|
|||
export function ScrapingPart(props: ScrapingProps) {
|
||||
const { report } = useReportProviders();
|
||||
const { startScraping, sourceOrder, sources, currentSource } = useScrape();
|
||||
const isMounted = useMountedState();
|
||||
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const listRef = useRef<HTMLDivElement | null>(null);
|
||||
|
@ -57,6 +59,7 @@ export function ScrapingPart(props: ScrapingProps) {
|
|||
started.current = true;
|
||||
(async () => {
|
||||
const output = await startScraping(props.media);
|
||||
if (!isMounted()) return;
|
||||
props.onResult?.(
|
||||
resultRef.current.sources,
|
||||
resultRef.current.sourceOrder
|
||||
|
@ -70,7 +73,7 @@ export function ScrapingPart(props: ScrapingProps) {
|
|||
);
|
||||
props.onGetStream?.(output);
|
||||
})();
|
||||
}, [startScraping, props, report]);
|
||||
}, [startScraping, props, report, isMounted]);
|
||||
|
||||
const currentProvider = sourceOrder.find(
|
||||
(s) => sources[s.id].status === "pending"
|
||||
|
|
Loading…
Reference in a new issue