Skip to content

Commit

Permalink
refactor(app): refactor zustand store
Browse files Browse the repository at this point in the history
  • Loading branch information
aidenlx committed Feb 14, 2024
1 parent af2e1ee commit 7336837
Show file tree
Hide file tree
Showing 37 changed files with 464 additions and 380 deletions.
89 changes: 81 additions & 8 deletions apps/app/src/components/context.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import type { MediaPlayerInstance, TextTrackInit } from "@vidstack/react";
import type { App } from "obsidian";
import type { App, TFile, Vault } from "obsidian";
import { createContext, useContext } from "react";
// eslint-disable-next-line import/no-deprecated -- don't use equalityFn here
import { createStore, useStore } from "zustand";
import { parseHashProps, type HashProps } from "@/lib/hash/hash-prop";
import { TimeoutError } from "@/lib/message";
import noop from "@/lib/no-op";
import { WebiviewMediaProvider } from "@/lib/remote-player/provider";
import type { ScreenshotInfo } from "@/lib/screenshot";
import { getTracks } from "@/lib/subtitle";
import { titleFromUrl } from "@/media-view/base";
import type MediaExtended from "@/mx-main";
import type { MxSettings } from "@/settings/def";
import type { MediaURL } from "@/web/url-match";
import type { SupportedMediaHost } from "@/web/url-match/supported";
import { fromFile, type MediaURL } from "@/web/url-match";
import type { MediaHost } from "@/web/url-match/supported";
import { applyTempFrag, handleTempFrag } from "./state/apply-tf";

export interface TransformConfig {
rotate: "90" | "180" | "270";
Expand All @@ -18,6 +23,15 @@ export interface TransformConfig {
zoom: number;
}

export interface SourceFacet {
hash: string;
/**
* true: auto detect title from url
*/
title: string | true;
enableWebview: boolean;
}

export interface MediaViewState {
player: MediaPlayerInstance | null;
playerRef: React.RefCallback<MediaPlayerInstance>;
Expand All @@ -28,25 +42,81 @@ export interface MediaViewState {
type?: string;
}
| undefined;
hash: string;
title: string;
hash: HashProps;
setHash: (hash: string) => void;
getPlayer(): Promise<MediaPlayerInstance>;
loadFile(file: TFile, vault: Vault, subpath?: string): Promise<void>;
setSource(url: MediaURL, other?: Partial<SourceFacet>): void;
transform: Partial<TransformConfig> | null;
setTransform: (transform: Partial<TransformConfig> | null) => void;
controls?: boolean;
disableWebFullscreen?: boolean;
toggleControls: (showCustom: boolean) => void;
toggleWebFullscreen: (enableWebFs: boolean) => void;
textTracks: TextTrackInit[];
webHost?: Exclude<SupportedMediaHost, SupportedMediaHost.Generic>;
updateWebHost: (webHost: SupportedMediaHost) => void;
webHost?: Exclude<MediaHost, MediaHost.Generic>;
updateWebHost: (webHost: MediaHost) => void;
}

export function createMediaViewStore() {
return createStore<MediaViewState>((set, get) => ({
const store = createStore<MediaViewState>((set, get, store) => ({
player: null,
playerRef: (inst) => set({ player: inst }),
source: undefined,
hash: "",
hash: {
autoplay: undefined,
controls: undefined,
loop: undefined,
muted: undefined,
tempFragment: null,
volume: undefined,
},
async getPlayer(timeout = 10e3) {
const { player } = get();
if (player) return player;
return new Promise((resolve, reject) => {
const unsubscribe = store.subscribe(({ player }) => {
if (!player) return;
unsubscribe();
resolve(player);
window.clearTimeout(timeoutId);
});
const timeoutId = window.setTimeout(() => {
unsubscribe();
reject(new TimeoutError(timeout));
}, timeout);
});
},
setSource(url, { hash, enableWebview, title: givenTitle } = {}) {
const title =
givenTitle === true ? titleFromUrl(url.source.href) : givenTitle;
set(({ source, title: ogTitle }) => ({
source: {
...source,
url,
enableWebview:
enableWebview !== undefined ? enableWebview : source?.enableWebview,
},
hash: parseHashProps(hash || url.hash),
title: title ?? ogTitle,
}));
applyTempFrag(get());
},
setHash(hash) {
set({ hash: parseHashProps(hash) });
applyTempFrag(get());
},
async loadFile(file, vault, subpath) {
const textTracks = await getTracks(file, vault);
set(({ source, hash }) => ({
source: { ...source, url: fromFile(file, vault) },
textTracks,
title: file.name,
hash: subpath ? parseHashProps(subpath) : hash,
}));
await applyTempFrag(get());
},
title: "",
transform: null,
setTransform: (transform: Partial<TransformConfig> | null) => {
Expand Down Expand Up @@ -90,6 +160,9 @@ export function createMediaViewStore() {
updateWebHost: (webHost) =>
set({ webHost: webHost === "generic" ? undefined : webHost }),
}));

handleTempFrag(store);
return store;
}

export type MediaViewStoreApi = ReturnType<typeof createMediaViewStore>;
Expand Down
8 changes: 5 additions & 3 deletions apps/app/src/components/hook/use-hash.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { MediaVolumeChange } from "@vidstack/react";
import { useCallback, useState } from "react";
import { convertHashToProps } from "@/lib/hash/hash-prop";
import { useMediaViewStore, useSettings } from "../context";

export function useControls() {
Expand All @@ -18,8 +17,11 @@ export function useControls() {
}

export function useHashProps() {
const hash = useMediaViewStore((s) => s.hash);
const { volume: init, ...props } = convertHashToProps(hash);
const {
volume: init,
tempFragment,
...props
} = useMediaViewStore((s) => s.hash);
const defaultVolume = useSettings((s) => s.defaultVolume / 100);

const [volume, setVolume] = useState(init ?? defaultVolume);
Expand Down
36 changes: 5 additions & 31 deletions apps/app/src/components/hook/use-temporal-frag.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { useMediaPlayer, type MediaPlayerInstance } from "@vidstack/react";
import { useEffect } from "react";
import {
isTempFragEqual,
isTimestamp,
parseTempFrag,
type TempFragment,
} from "@/lib/hash/temporal-frag";
import { isTimestamp, type TempFragment } from "@/lib/hash/temporal-frag";
import type { MediaViewStoreApi } from "../context";
import { useMediaViewStoreInst } from "../context";

Expand All @@ -19,7 +14,6 @@ export function useTempFragHandler() {
}

function handleTempFrag(player: MediaPlayerInstance, store: MediaViewStoreApi) {
let frag: TempFragment | null = null;
const prev = {
currentTime: player.state.currentTime,
paused: player.state.paused,
Expand All @@ -28,6 +22,10 @@ function handleTempFrag(player: MediaPlayerInstance, store: MediaViewStoreApi) {
let seekingLock = false;
const unloads = [
player.subscribe(({ currentTime, paused, loop }) => {
const frag = limitRange(
store.getState().hash.tempFragment,
player.state.duration,
);
if (!frag || isTimestamp(frag)) return;
const { start, end } = frag;
// onPlay
Expand Down Expand Up @@ -62,30 +60,6 @@ function handleTempFrag(player: MediaPlayerInstance, store: MediaViewStoreApi) {
}
Object.assign(prev, { currentTime, paused, loop });
}),
store.subscribe((curr, prev) => {
if (curr.hash === prev.hash) return;
try {
const newFrag = limitRange(
parseTempFrag(curr.hash),
player.state.duration,
);
if (isTempFragEqual(newFrag, frag)) return;
frag = newFrag;
if (!frag) return;
} catch {
frag = null;
}
}),
player.subscribe(({ duration }) => {
const { hash } = store.getState();
try {
const newFrag = limitRange(parseTempFrag(hash), duration);
if (isTempFragEqual(newFrag, frag)) return;
frag = newFrag;
} catch {
frag = null;
}
}),
];
return () => unloads.forEach((u) => u());
}
Expand Down
5 changes: 3 additions & 2 deletions apps/app/src/components/player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ export function Player() {

const src = useMediaViewStore(({ source }) => {
if (!source) return;
const url = source.url.source.href;
if (source.enableWebview) {
// webview will create a new MediaURL instance
return encodeWebpageUrl(source.url.href);
return encodeWebpageUrl(url);
}
return source.url.source.href;
return url;
});
const textTracks = useMediaViewStore(({ textTracks }) => textTracks);

Expand Down
2 changes: 0 additions & 2 deletions apps/app/src/components/player/menus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export function MoreOptions() {
const {
toggleControls,
controls,
hash,
setTransform,
transform,
disableWebFullscreen,
Expand All @@ -83,7 +82,6 @@ export function MoreOptions() {
source,
toggleControls,
controls,
hash,
setTransform,
transform,
plugin,
Expand Down
96 changes: 96 additions & 0 deletions apps/app/src/components/state/apply-tf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { MediaPlayerInstance } from "@vidstack/react";
import { isTimestamp } from "@/lib/hash/temporal-frag";
import type { MediaViewState, MediaViewStoreApi } from "../context";

const tfNotInitial = new WeakSet<MediaPlayerInstance>();

export function handleTempFrag(store: MediaViewStoreApi) {
store.subscribe((currState, prevState) => {
if (currState.player === prevState.player) return;
applyTempFrag(currState);
});

store.subscribe((currState, prevState) => {
const player = currState.player;
if (!player) return;

const currSrc = currState.source;
const prevSrc = prevState.source;

if (currSrc === prevSrc) return;

const currUrl = currSrc?.url;
const prevUrl = prevSrc?.url;

if (currUrl === prevUrl) return;
if (
(!currUrl && prevUrl !== undefined) ||
(currUrl !== undefined && !currUrl.compare(prevUrl))
) {
// when url is reset
tfNotInitial.delete(player);
}
});
}

export async function applyTempFrag({
player,
hash: { tempFragment: tf },
}: MediaViewState) {
if (!player || !tf) return;
const initial = !tfNotInitial.has(player);
tfNotInitial.add(player);
// eslint-disable-next-line @typescript-eslint/naming-convention
let _newTime: number | null = null;
// allow 0.25s offset from end, in case delay in seeking
const allowedOffset = 0.25;
if (
isTimestamp(tf) ||
player.currentTime < tf.start ||
Math.abs(player.currentTime - tf.end) < allowedOffset
) {
_newTime = tf.start;
} else if (player.currentTime - allowedOffset > tf.end) {
_newTime = tf.end;
}
if (_newTime !== null) {
const newTime = _newTime;
player.currentTime = newTime;
// trying to fix youtube iframe autoplay on initial seek
if (
!player.state.canPlay &&
["video/youtube"].includes(player.state.source.type) &&
!player.state.autoPlay
) {
await waitFor(player, "seeked");
await player.pause();
}
}
if (isTimestamp(tf) && player.state.canPlay && !initial) {
await player.play(new Event("hashchange"));
}
}

function waitFor(
player: MediaPlayerInstance,
event:
| "time-update"
| "play"
| "can-play"
| "canplay"
| "timeupdate"
| "seeking"
| "seeked",
) {
return new Promise<void>((resolve) => {
const timeout = window.setTimeout(() => {
resolve();
unload();
}, 5e3);
const unload = player.listen(event, () => {
resolve();
window.clearTimeout(timeout);
unload();
});
});
}
15 changes: 6 additions & 9 deletions apps/app/src/icons.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { addIcon } from "obsidian";
import { SupportedMediaHost } from "./web/url-match/supported";
import { MediaHost } from "./web/url-match/supported";

const icons: Record<
Exclude<SupportedMediaHost, SupportedMediaHost.Generic>,
string | null
> = {
[SupportedMediaHost.Bilibili]: `<path fill-rule="evenodd" clip-rule="evenodd" d="M 20.736 14.88 C 18.513 12.735 18.513 9.173 20.736 7.028 C 22.849 4.99 26.197 4.99 28.311 7.028 L 40.096 18.397 C 40.43 18.72 40.715 19.075 40.949 19.453 L 58.772 19.453 C 59.006 19.075 59.291 18.72 59.625 18.397 L 71.41 7.028 C 73.523 4.99 76.871 4.99 78.984 7.028 C 81.208 9.173 81.208 12.735 78.984 14.88 L 74.244 19.453 L 77.778 19.453 C 90.051 19.453 100 29.402 100 41.675 L 100 72.262 C 100 84.534 90.051 94.484 77.778 94.484 L 22.222 94.484 C 9.949 94.484 0 84.534 0 72.262 L 0 41.675 C 0 29.402 9.949 19.453 22.222 19.453 L 25.477 19.453 L 20.736 14.88 Z M 22.222 30.172 C 16.086 30.172 11.111 35.146 11.111 41.283 L 11.111 72.654 C 11.111 78.79 16.086 83.765 22.222 83.765 L 77.778 83.765 C 83.914 83.765 88.889 78.79 88.889 72.654 L 88.889 41.283 C 88.889 35.146 83.914 30.172 77.778 30.172 L 22.222 30.172 Z M 27.778 51.805 C 27.778 48.737 30.265 46.25 33.333 46.25 C 36.402 46.25 38.889 48.737 38.889 51.805 L 38.889 56.772 C 38.889 59.84 36.402 62.328 33.333 62.328 C 30.265 62.328 27.778 59.84 27.778 56.772 L 27.778 51.805 Z M 66.667 46.25 C 63.598 46.25 61.111 48.737 61.111 51.805 L 61.111 56.772 C 61.111 59.84 63.598 62.328 66.667 62.328 C 69.735 62.328 72.222 59.84 72.222 56.772 L 72.222 51.805 C 72.222 48.737 69.735 46.25 66.667 46.25 Z" fill="currentColor"/>`,
[SupportedMediaHost.Vimeo]: `<path d="M 99.952 26.773 C 99.508 36.508 92.705 49.844 79.555 66.768 C 65.962 84.445 54.458 93.287 45.044 93.287 C 39.218 93.287 34.286 87.905 30.254 77.134 C 27.566 67.263 24.872 57.4 22.185 47.53 C 19.194 36.765 15.986 31.377 12.552 31.377 C 11.806 31.377 9.182 32.952 4.701 36.09 L 0 30.027 C 4.932 25.692 9.799 21.352 14.59 17.005 C 21.175 11.321 26.113 8.324 29.412 8.022 C 37.193 7.276 41.983 12.6 43.783 23.988 C 45.725 36.283 47.069 43.929 47.822 46.919 C 50.066 57.117 52.535 62.21 55.229 62.21 C 57.325 62.21 60.47 58.898 64.663 52.287 C 68.849 45.671 71.093 40.636 71.395 37.183 C 71.993 31.473 69.749 28.612 64.663 28.612 C 62.264 28.612 59.795 29.158 57.261 30.252 C 62.174 14.15 71.562 6.324 85.426 6.768 C 95.701 7.07 100.544 13.739 99.952 26.773" fill="currentColor"/>`,
[SupportedMediaHost.Coursera]: `<path d="M 2.8 50.013 C 2.8 22.278 25.472 0.001 53.82 0.001 C 71.135 -0.12 87.347 8.489 96.943 22.903 L 75.688 35.232 C 70.696 28.099 62.527 23.863 53.82 23.893 C 39.244 23.893 27.298 36.043 27.298 50.013 C 27.298 63.983 39.244 76.133 53.82 76.133 C 62.954 76.192 71.463 71.505 76.294 63.754 L 97.339 76.306 C 87.854 91.149 71.422 100.093 53.808 99.999 C 25.472 100.024 2.8 77.321 2.8 50.013 Z" fill="currentColor" />`,
[SupportedMediaHost.YouTube]: null,
const icons: Record<Exclude<MediaHost, MediaHost.Generic>, string | null> = {
[MediaHost.Bilibili]: `<path fill-rule="evenodd" clip-rule="evenodd" d="M 20.736 14.88 C 18.513 12.735 18.513 9.173 20.736 7.028 C 22.849 4.99 26.197 4.99 28.311 7.028 L 40.096 18.397 C 40.43 18.72 40.715 19.075 40.949 19.453 L 58.772 19.453 C 59.006 19.075 59.291 18.72 59.625 18.397 L 71.41 7.028 C 73.523 4.99 76.871 4.99 78.984 7.028 C 81.208 9.173 81.208 12.735 78.984 14.88 L 74.244 19.453 L 77.778 19.453 C 90.051 19.453 100 29.402 100 41.675 L 100 72.262 C 100 84.534 90.051 94.484 77.778 94.484 L 22.222 94.484 C 9.949 94.484 0 84.534 0 72.262 L 0 41.675 C 0 29.402 9.949 19.453 22.222 19.453 L 25.477 19.453 L 20.736 14.88 Z M 22.222 30.172 C 16.086 30.172 11.111 35.146 11.111 41.283 L 11.111 72.654 C 11.111 78.79 16.086 83.765 22.222 83.765 L 77.778 83.765 C 83.914 83.765 88.889 78.79 88.889 72.654 L 88.889 41.283 C 88.889 35.146 83.914 30.172 77.778 30.172 L 22.222 30.172 Z M 27.778 51.805 C 27.778 48.737 30.265 46.25 33.333 46.25 C 36.402 46.25 38.889 48.737 38.889 51.805 L 38.889 56.772 C 38.889 59.84 36.402 62.328 33.333 62.328 C 30.265 62.328 27.778 59.84 27.778 56.772 L 27.778 51.805 Z M 66.667 46.25 C 63.598 46.25 61.111 48.737 61.111 51.805 L 61.111 56.772 C 61.111 59.84 63.598 62.328 66.667 62.328 C 69.735 62.328 72.222 59.84 72.222 56.772 L 72.222 51.805 C 72.222 48.737 69.735 46.25 66.667 46.25 Z" fill="currentColor"/>`,
[MediaHost.Vimeo]: `<path d="M 99.952 26.773 C 99.508 36.508 92.705 49.844 79.555 66.768 C 65.962 84.445 54.458 93.287 45.044 93.287 C 39.218 93.287 34.286 87.905 30.254 77.134 C 27.566 67.263 24.872 57.4 22.185 47.53 C 19.194 36.765 15.986 31.377 12.552 31.377 C 11.806 31.377 9.182 32.952 4.701 36.09 L 0 30.027 C 4.932 25.692 9.799 21.352 14.59 17.005 C 21.175 11.321 26.113 8.324 29.412 8.022 C 37.193 7.276 41.983 12.6 43.783 23.988 C 45.725 36.283 47.069 43.929 47.822 46.919 C 50.066 57.117 52.535 62.21 55.229 62.21 C 57.325 62.21 60.47 58.898 64.663 52.287 C 68.849 45.671 71.093 40.636 71.395 37.183 C 71.993 31.473 69.749 28.612 64.663 28.612 C 62.264 28.612 59.795 29.158 57.261 30.252 C 62.174 14.15 71.562 6.324 85.426 6.768 C 95.701 7.07 100.544 13.739 99.952 26.773" fill="currentColor"/>`,
[MediaHost.Coursera]: `<path d="M 2.8 50.013 C 2.8 22.278 25.472 0.001 53.82 0.001 C 71.135 -0.12 87.347 8.489 96.943 22.903 L 75.688 35.232 C 70.696 28.099 62.527 23.863 53.82 23.893 C 39.244 23.893 27.298 36.043 27.298 50.013 C 27.298 63.983 39.244 76.133 53.82 76.133 C 62.954 76.192 71.463 71.505 76.294 63.754 L 97.339 76.306 C 87.854 91.149 71.422 100.093 53.808 99.999 C 25.472 100.024 2.8 77.321 2.8 50.013 Z" fill="currentColor" />`,
[MediaHost.YouTube]: null,
};

Object.entries(icons).forEach(([name, svg]) => {
Expand Down
17 changes: 7 additions & 10 deletions apps/app/src/lib/hash/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,13 @@ const fillZero = (time: number, fractionDigits = 2) => {
return frac ? main + "." + frac : main;
};

export function addTempFrag(url: URL, tempFrag: TempFragment | null): URL {
url = new URL(url);
const hashWithoutTempFrag = url.hash.replace(/t=[^&]+/, "");
if (!tempFrag) {
url.hash = hashWithoutTempFrag;
} else {
const tempFragString = toTempFragString(tempFrag);
url.hash = `${hashWithoutTempFrag}&${tempFragString}`;
}
return url;
export function removeTempFrag(hash: string) {
return hash.replace(/t=[^&]+/, "");
}

export function addTempFrag(hash: string, tempFrag: TempFragment) {
const tempFragString = toTempFragString(tempFrag);
return `${hash}&${tempFragString}`;
}

export function toTempFrag(start: number, end: number): TempFragment {
Expand Down
Loading

0 comments on commit 7336837

Please sign in to comment.