Skip to content

Commit

Permalink
feat(player): add screenshot config
Browse files Browse the repository at this point in the history
  • Loading branch information
aidenlx committed Feb 16, 2024
1 parent 2ddc3bf commit 3a910bc
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 15 deletions.
11 changes: 9 additions & 2 deletions apps/app/src/components/player/buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ import {
PinIcon,
} from "@/components/icon";
import { cn } from "@/lib/utils";
import { useIsEmbed, useScreenshot, useTimestamp } from "../context";
import {
useIsEmbed,
useScreenshot,
useSettings,
useTimestamp,
} from "../context";
import { canProviderScreenshot, takeScreenshot } from "./screenshot";

export const buttonClass =
Expand Down Expand Up @@ -167,12 +172,14 @@ export function useScreenshotHanlder() {
canProviderScreenshot(provider),
);
const onScreenshot = useScreenshot();
const type = useSettings((s) => s.screenshotFormat),
quality = useSettings((s) => s.screenshotQuality);
useEffect(() => {
updateCanScreenshot(canProviderScreenshot(provider));
}, [provider]);
if (!canScreenshot || !onScreenshot || !provider) return null;
return async () => {
onScreenshot(await takeScreenshot(provider));
onScreenshot(await takeScreenshot(provider, type, quality));
};
}

Expand Down
15 changes: 12 additions & 3 deletions apps/app/src/components/player/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ export function canProviderScreenshot(provider: MediaProviderAdapter | null) {
return isVideoProvider(provider) || provider instanceof WebiviewMediaProvider;
}

export async function takeScreenshot(provider: MediaProviderAdapter) {
const mimeType = Platform.isSafari ? "image/jpeg" : "image/webp";
export async function takeScreenshot(
provider: MediaProviderAdapter,
type: "image/jpeg" | "image/webp" | "image/png",
quality: number | null,
) {
const mimeType =
Platform.isSafari && type === "image/webp" ? "image/jpeg" : type;
try {
if (isVideoProvider(provider)) {
return await captureScreenshot(provider.video, mimeType);
return await captureScreenshot(
provider.video,
mimeType,
quality ?? undefined,
);
} else if (provider instanceof WebiviewMediaProvider) {
return await provider.media.methods.screenshot(mimeType);
} else {
Expand Down
4 changes: 2 additions & 2 deletions apps/app/src/lib/remote-player/hook/handler-register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ export function registerHandlers(this: MediaPlugin) {
value: await (player as any)[prop](...args),
}));
});
port.handle("screenshot", async (type) => {
port.handle("screenshot", async (type, quality) => {
if (!(player instanceof HTMLVideoElement))
throw new Error("Cannot take screenshot of non-video element");

const value = await captureScreenshot(player, type);
const value = await captureScreenshot(player, type, quality);
return {
value,
transfer: [value.blob.arrayBuffer],
Expand Down
7 changes: 5 additions & 2 deletions apps/app/src/lib/remote-player/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ export type MsgCtrlRemote = MessageController<
pictureInPictureEnabled: () => { value: boolean };
} & {
loadPlugin(code?: string): void;
screenshot(type?: string): {
screenshot(
type?: string,
quality?: number,
): {
value: ScreenshotInfo;
transfer: Transferable[];
};
Expand Down Expand Up @@ -222,7 +225,7 @@ export type MsgCtrlLocal = MessageController<
pictureInPictureEnabled: () => boolean;
} & {
loadPlugin(code?: string): void;
screenshot(type?: string): ScreenshotInfo;
screenshot(type?: string, quality?: number): ScreenshotInfo;
fetch(
url: string,
init?: RequestInit & { gzip?: boolean },
Expand Down
15 changes: 10 additions & 5 deletions apps/app/src/lib/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface ScreenshotInfo {
export async function captureScreenshot(
video: HTMLVideoElement,
type?: string,
quality?: number,
): Promise<ScreenshotInfo> {
const canvas = document.createElement("canvas");

Expand All @@ -28,11 +29,15 @@ export async function captureScreenshot(
ctx.drawImage(video, 0, 0, width, height);
const blob = await new Promise<Blob>((resolve, reject) => {
try {
canvas.toBlob((blob) => {
// the image cannot be created for any reason
if (!blob) reject(new Error("Canvas to blob failed"));
else resolve(blob);
}, type);
canvas.toBlob(
(blob) => {
// the image cannot be created for any reason
if (!blob) reject(new Error("Canvas to blob failed"));
else resolve(blob);
},
type,
quality,
);
} catch (e) {
reject(e);
}
Expand Down
8 changes: 7 additions & 1 deletion apps/app/src/media-note/timestamp/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ export async function saveScreenshot<T extends PlayerComponent>(
new Notice("Screenshot is not supported for this media");
return;
}
const { blob, time } = await takeScreenshot(player.provider);
const { screenshotQuality, screenshotFormat } =
playerComponent.plugin.settings.getState();
const { blob, time } = await takeScreenshot(
player.provider,
screenshotFormat,
screenshotQuality,
);
const genTimestamp = timestampGenerator(time, mediaInfo, playerComponent);

const ext = mime.getExtension(blob.type);
Expand Down
18 changes: 18 additions & 0 deletions apps/app/src/settings/def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type MxSettingValues = {
/** in seconds */
timestampOffset: number;
biliDefaultQuality: BilibiliQuality;
screenshotFormat: "image/png" | "image/jpeg" | "image/webp";
screenshotQuality: number | null;
};
const settingKeys = enumerate<keyof MxSettingValues>()(
"defaultVolume",
Expand All @@ -43,6 +45,8 @@ const settingKeys = enumerate<keyof MxSettingValues>()(
"insertBefore",
"timestampOffset",
"biliDefaultQuality",
"screenshotFormat",
"screenshotQuality",
);

const mxSettingsDefault = {
Expand All @@ -66,6 +70,8 @@ const mxSettingsDefault = {
insertBefore: false,
timestampOffset: 0,
biliDefaultQuality: BilibiliQuality.FHD,
screenshotFormat: "image/webp",
screenshotQuality: null,
} satisfies MxSettingValues;

function getDefaultDeviceName() {
Expand Down Expand Up @@ -108,6 +114,10 @@ export type MxSettings = {
key: "timestamp" | "screenshot" | "screenshotEmbed",
value: string,
) => void;
setScreenshotFormat: (
format: "image/png" | "image/jpeg" | "image/webp",
) => void;
setScreenshotQuality: (quality: number | null) => void;
setTimestampOffset: (offset: number) => void;
setInsertPosition: (pos: "before" | "after") => void;
getUrlMappingData: () => MxSettingValues["urlMappingData"];
Expand Down Expand Up @@ -142,6 +152,14 @@ export function createSettingsStore(plugin: MxPlugin) {
}, 1e3);
return createStore<MxSettings>((set, get) => ({
...omit(mxSettingsDefault, ["urlMappingData"]),
setScreenshotFormat(format) {
set({ screenshotFormat: format });
save(get());
},
setScreenshotQuality(quality) {
set({ screenshotQuality: quality });
save(get());
},
getUrlMappingData() {
return toUrlMappingData(get().urlMapping);
},
Expand Down
54 changes: 54 additions & 0 deletions apps/app/src/settings/tab.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { pathToFileURL } from "url";
import type { PaneType } from "obsidian";
import {
Expand Down Expand Up @@ -421,6 +422,59 @@ export class MxSettingTabs extends PluginSettingTab {
}),
)
.then((s) => s.controlEl.appendText("s"));
new Setting(container)
.setName("Screenshot format")
.setDesc(
createFragment((frag) => {
frag.appendText("The format to use when taking screenshots");
frag.createEl("br");
frag.appendText(
"Note that the webp format falls back to jpeg in iOS or iPadOS",
);
}),
)
.addDropdown((d) =>
d
.addOptions({
"image/png": "PNG",
"image/jpeg": "JPEG",
"image/webp": "WEBP",
})
.setValue(this.state.screenshotFormat)
.onChange((val) =>
this.state.setScreenshotFormat(
val as "image/png" | "image/jpeg" | "image/webp",
),
),
);
new Setting(container)
.setName("Screenshot quality")
.setDesc("Quality of the screenshot")
.addSlider((slide) =>
slide
.setLimits(0, 1, 0.01)
.setValue(
this.state.screenshotQuality ??
(this.state.screenshotFormat === "image/webp" ? 0.8 : 0.92),
)
.onChange(this.state.setScreenshotQuality)
.then((slide) => {
this.sub((s, prev) => {
if (s.screenshotFormat === prev.screenshotFormat) return;
slide.setValue(
s.screenshotQuality ??
(s.screenshotFormat === "image/webp" ? 0.8 : 0.92),
);
});
}),
)
.then((entry) => {
this.sub((s, prev) => {
if (s.screenshotFormat === prev.screenshotFormat) return;
entry.settingEl.style.display =
s.screenshotFormat === "image/png" ? "none" : "";
});
});
}

webpage() {
Expand Down

0 comments on commit 3a910bc

Please sign in to comment.