Fix layout (#454)
* player layout autosize * fix video layout * improve style
This commit is contained in:
@@ -296,7 +296,7 @@
|
||||
"releaseToStop": "Release to stop",
|
||||
"deleteRecording": "delete recording",
|
||||
"deleteRecordingConfirmation": "Are you sure to delete this recording?",
|
||||
"myRecordings": "my recordings",
|
||||
"myRecordings": "recordings",
|
||||
"noRecordingForThisSegmentYet": "No recordings for this segment yet. Press <kbd>R</kbd> to start recording.",
|
||||
"lastYear": "last year",
|
||||
"less": "less",
|
||||
|
||||
@@ -440,16 +440,20 @@ ${log}
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
icon: "./assets/icon.png",
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
minWidth: 1440,
|
||||
minHeight: 900,
|
||||
width: 1440,
|
||||
height: 900,
|
||||
minWidth: 1024,
|
||||
minHeight: 768,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
spellcheck: false,
|
||||
},
|
||||
});
|
||||
|
||||
mainWindow.on("resize", () => {
|
||||
mainWindow.webContents.send("window-on-resize", mainWindow.getBounds());
|
||||
});
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler(() => {
|
||||
return { action: "allow" };
|
||||
});
|
||||
|
||||
@@ -34,6 +34,17 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
|
||||
},
|
||||
version,
|
||||
},
|
||||
window: {
|
||||
onResize: (
|
||||
callback: (
|
||||
event: IpcRendererEvent,
|
||||
bounds: { x: number; y: number; width: number; height: number }
|
||||
) => void
|
||||
) => ipcRenderer.on("window-on-resize", callback),
|
||||
removeListeners: () => {
|
||||
ipcRenderer.removeAllListeners("window-on-resize");
|
||||
},
|
||||
},
|
||||
system: {
|
||||
preferences: {
|
||||
mediaAccess: (mediaType: "microphone" | "camera") => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useContext, useRef } from "react";
|
||||
import { useEffect, useContext, useState } from "react";
|
||||
import { MediaPlayerProviderContext } from "@renderer/context";
|
||||
import {
|
||||
MediaLoadingModal,
|
||||
@@ -7,43 +7,45 @@ import {
|
||||
MediaTabs,
|
||||
MediaCurrentRecording,
|
||||
MediaPlayer,
|
||||
LoaderSpin,
|
||||
} from "@renderer/components";
|
||||
import { useAudio } from "@renderer/hooks";
|
||||
|
||||
export const AudioPlayer = (props: { id?: string; md5?: string }) => {
|
||||
const { id, md5 } = props;
|
||||
const { setMedia } = useContext(MediaPlayerProviderContext);
|
||||
const { setMedia, layout } = useContext(MediaPlayerProviderContext);
|
||||
const { audio } = useAudio({ id, md5 });
|
||||
|
||||
useEffect(() => {
|
||||
if (!audio) return;
|
||||
|
||||
setMedia(audio);
|
||||
}, [audio]);
|
||||
|
||||
if (!layout) return <LoaderSpin />;
|
||||
|
||||
return (
|
||||
<div data-testid="audio-player">
|
||||
<div className="h-[calc(100vh-37.5rem)] mb-4">
|
||||
<div className="grid grid-cols-3 gap-6 px-6 h-full">
|
||||
<div className="col-span-1 rounded-lg border shadow-lg h-[calc(100vh-37.5rem)]">
|
||||
<div className={`${layout.upperWrapper} mb-4`}>
|
||||
<div className="grid grid-cols-5 xl:grid-cols-3 gap-6 px-6 h-full">
|
||||
<div className={`col-span-2 xl:col-span-1 rounded-lg border shadow-lg ${layout.upperWrapper}`}>
|
||||
<MediaTabs />
|
||||
</div>
|
||||
<div className="col-span-2 h-[calc(100vh-37.5rem)]">
|
||||
<div className={`col-span-3 xl:col-span-2 ${layout.upperWrapper}`}>
|
||||
<MediaCaption />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[33rem] flex flex-col">
|
||||
<div className="h-[13rem] py-2 px-6 mb-4">
|
||||
<MediaCurrentRecording />
|
||||
<div className={`${layout.lowerWrapper} flex flex-col`}>
|
||||
<div className={`${layout.playerWrapper} py-2 px-6`}>
|
||||
<MediaCurrentRecording height={layout.playerHeight} />
|
||||
</div>
|
||||
|
||||
<div className="w-full h-[13rem] px-6 py-2 mb-4">
|
||||
<div className={`${layout.playerWrapper} py-2 px-6`}>
|
||||
<MediaPlayer />
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-background z-10 shadow-xl">
|
||||
<div className={`${layout.panelWrapper} w-full bg-background z-10 shadow-xl`}>
|
||||
<MediaPlayerControls />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,8 +46,9 @@ import { formatDuration } from "@renderer/lib/utils";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
|
||||
export const MediaCurrentRecording = (props: { height?: number }) => {
|
||||
const { height = 192 } = props;
|
||||
const { height } = props;
|
||||
const {
|
||||
layout,
|
||||
isRecording,
|
||||
setIsRecording,
|
||||
currentRecording,
|
||||
@@ -281,9 +282,9 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
|
||||
});
|
||||
|
||||
return () => {
|
||||
ws.destroy();
|
||||
ws?.destroy();
|
||||
};
|
||||
}, [ref, currentRecording, isRecording]);
|
||||
}, [ref, currentRecording, isRecording, height]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsComparing(false);
|
||||
@@ -315,7 +316,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
|
||||
}
|
||||
|
||||
const subscriptions = [
|
||||
regions.on("region-created", () => {}),
|
||||
regions.on("region-created", () => { }),
|
||||
|
||||
regions.on("region-clicked", (region, e) => {
|
||||
e.stopPropagation();
|
||||
@@ -383,16 +384,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
|
||||
|
||||
useEffect(() => {
|
||||
calContainerWidth();
|
||||
window.addEventListener("resize", () => {
|
||||
calContainerWidth();
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", () => {
|
||||
calContainerWidth();
|
||||
});
|
||||
};
|
||||
}, [currentRecording, isRecording]);
|
||||
}, [currentRecording, isRecording, layout?.width]);
|
||||
|
||||
useHotkeys(
|
||||
["Ctrl+R", "Meta+R"],
|
||||
@@ -410,7 +402,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
|
||||
[player]
|
||||
);
|
||||
|
||||
if (isRecording) return <MediaRecorder />;
|
||||
if (isRecording) return <MediaRecorder height={height} />;
|
||||
if (!currentRecording?.src)
|
||||
return (
|
||||
<div className="h-full w-full flex items-center space-x-4">
|
||||
@@ -480,27 +472,31 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
|
||||
setIsRecording={setIsRecording}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant={isComparing ? "secondary" : "outline"}
|
||||
size="icon"
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("compare")}
|
||||
className="rounded-full w-8 h-8 p-0"
|
||||
onClick={toggleCompare}
|
||||
>
|
||||
<GitCompareIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
{
|
||||
height >= 192 && <>
|
||||
<Button
|
||||
variant={isComparing ? "secondary" : "outline"}
|
||||
size="icon"
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("compare")}
|
||||
className="rounded-full w-8 h-8 p-0"
|
||||
onClick={toggleCompare}
|
||||
>
|
||||
<GitCompareIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={isSelectingRegion ? "secondary" : "outline"}
|
||||
size="icon"
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("selectRegion")}
|
||||
className="rounded-full w-8 h-8 p-0"
|
||||
onClick={() => setIsSelectingRegion(!isSelectingRegion)}
|
||||
>
|
||||
<TextCursorInputIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={isSelectingRegion ? "secondary" : "outline"}
|
||||
size="icon"
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("selectRegion")}
|
||||
className="rounded-full w-8 h-8 p-0"
|
||||
onClick={() => setIsSelectingRegion(!isSelectingRegion)}
|
||||
>
|
||||
<TextCursorInputIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -522,17 +518,16 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
|
||||
>
|
||||
<GaugeCircleIcon
|
||||
className={`w-4 h-4 mr-4
|
||||
${
|
||||
currentRecording.pronunciationAssessment
|
||||
? currentRecording.pronunciationAssessment
|
||||
.pronunciationScore >= 80
|
||||
? "text-green-500"
|
||||
: currentRecording.pronunciationAssessment
|
||||
.pronunciationScore >= 60
|
||||
? "text-yellow-600"
|
||||
: "text-red-500"
|
||||
: ""
|
||||
}
|
||||
${currentRecording.pronunciationAssessment
|
||||
? currentRecording.pronunciationAssessment
|
||||
.pronunciationScore >= 80
|
||||
? "text-green-500"
|
||||
: currentRecording.pronunciationAssessment
|
||||
.pronunciationScore >= 60
|
||||
? "text-yellow-600"
|
||||
: "text-red-500"
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<span>{t("pronunciationAssessment")}</span>
|
||||
|
||||
@@ -456,7 +456,7 @@ export const MediaPlayerControls = () => {
|
||||
}, [grouping]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-20 flex items-center justify-center px-6">
|
||||
<div className="w-full h-full flex items-center justify-center px-6">
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -480,11 +480,10 @@ export const MediaPlayerControls = () => {
|
||||
{PLAYBACK_RATE_OPTIONS.map((rate, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`cursor-pointer h-10 w-10 leading-10 rounded-full flex items-center justify-center ${
|
||||
rate === playbackRate
|
||||
className={`cursor-pointer h-10 w-10 leading-10 rounded-full flex items-center justify-center ${rate === playbackRate
|
||||
? "bg-primary text-white text-md"
|
||||
: "text-black/70 text-xs"
|
||||
}`}
|
||||
}`}
|
||||
onClick={() => {
|
||||
setPlaybackRate(rate);
|
||||
}}
|
||||
|
||||
@@ -41,6 +41,7 @@ const MAX_ZOOM_RATIO = 4.0;
|
||||
export const MediaPlayer = () => {
|
||||
const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext);
|
||||
const {
|
||||
layout,
|
||||
media,
|
||||
currentTime,
|
||||
setRef,
|
||||
@@ -132,16 +133,7 @@ export const MediaPlayer = () => {
|
||||
|
||||
useEffect(() => {
|
||||
calContainerWidth();
|
||||
window.addEventListener("resize", () => {
|
||||
calContainerWidth();
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", () => {
|
||||
calContainerWidth();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
}, [layout.width]);
|
||||
|
||||
return (
|
||||
<div className="flex space-x-4 media-player-wrapper">
|
||||
@@ -192,38 +184,44 @@ export const MediaPlayer = () => {
|
||||
<ZoomInIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={`${zoomRatio < 1.0 ? "secondary" : "outline"}`}
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("zoomOut")}
|
||||
className="relative aspect-square rounded-full p-0 h-8"
|
||||
onClick={() => {
|
||||
if (zoomRatio > MIN_ZOOM_RATIO) {
|
||||
const nextZoomRatio = ZOOM_RATIO_OPTIONS.reverse().find(
|
||||
(rate) => rate < zoomRatio
|
||||
);
|
||||
setZoomRatio(nextZoomRatio || MIN_ZOOM_RATIO);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ZoomOutIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
{
|
||||
layout.name === "lg" && (
|
||||
<>
|
||||
<Button
|
||||
variant={`${zoomRatio < 1.0 ? "secondary" : "outline"}`}
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("zoomOut")}
|
||||
className="relative aspect-square rounded-full p-0 h-8"
|
||||
onClick={() => {
|
||||
if (zoomRatio > MIN_ZOOM_RATIO) {
|
||||
const nextZoomRatio = ZOOM_RATIO_OPTIONS.reverse().find(
|
||||
(rate) => rate < zoomRatio
|
||||
);
|
||||
setZoomRatio(nextZoomRatio || MIN_ZOOM_RATIO);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ZoomOutIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={`${displayInlineCaption ? "secondary" : "outline"}`}
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("inlineCaption")}
|
||||
className="relative aspect-square rounded-full p-0 h-8"
|
||||
onClick={() => {
|
||||
setDisplayInlineCaption(!displayInlineCaption);
|
||||
if (pitchChart) {
|
||||
pitchChart.options.scales.x.display = !displayInlineCaption;
|
||||
pitchChart.update();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SpellCheckIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={`${displayInlineCaption ? "secondary" : "outline"}`}
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("inlineCaption")}
|
||||
className="relative aspect-square rounded-full p-0 h-8"
|
||||
onClick={() => {
|
||||
setDisplayInlineCaption(!displayInlineCaption);
|
||||
if (pitchChart) {
|
||||
pitchChart.options.scales.x.display = !displayInlineCaption;
|
||||
pitchChart.update();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SpellCheckIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -239,6 +237,40 @@ export const MediaPlayer = () => {
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
{
|
||||
layout.name === "sm" && (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (zoomRatio > MIN_ZOOM_RATIO) {
|
||||
const nextZoomRatio = ZOOM_RATIO_OPTIONS.reverse().find(
|
||||
(rate) => rate < zoomRatio
|
||||
);
|
||||
setZoomRatio(nextZoomRatio || MIN_ZOOM_RATIO);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ZoomOutIcon className="w-4 h-4 mr-4" />
|
||||
<span>{t("zoomOut")}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
setDisplayInlineCaption(!displayInlineCaption);
|
||||
if (pitchChart) {
|
||||
pitchChart.options.scales.x.display = !displayInlineCaption;
|
||||
pitchChart.update();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SpellCheckIcon className="w-4 h-4 mr-4" />
|
||||
<span>{t("inlineCaption")}</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
|
||||
@@ -20,8 +20,9 @@ export const MediaProvider = () => {
|
||||
if (!media?.src) return null;
|
||||
|
||||
return (
|
||||
<div className="px-4" data-testid="media-player">
|
||||
<div className="px-2 py-4" data-testid="media-player">
|
||||
<VidstackMediaPlayer
|
||||
className="my-auto"
|
||||
controls
|
||||
src={media.src}
|
||||
onCanPlayThrough={(detail, nativeEvent) => {
|
||||
|
||||
@@ -24,15 +24,13 @@ export const MediaTabs = () => {
|
||||
return (
|
||||
<ScrollArea className="h-full">
|
||||
<div
|
||||
className={`p-1 bg-muted rounded-t-lg mb-2 text-sm sticky top-0 z-10 grid gap-4 ${
|
||||
media?.mediaType === "Video" ? "grid-cols-4" : "grid-cols-3"
|
||||
}`}
|
||||
className={`p-1 bg-muted rounded-t-lg mb-2 text-sm sticky top-0 z-10 grid gap-4 ${media?.mediaType === "Video" ? "grid-cols-4" : "grid-cols-3"
|
||||
}`}
|
||||
>
|
||||
{media.mediaType === "Video" && (
|
||||
<div
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize ${
|
||||
tab === "provider" ? "bg-background" : ""
|
||||
}`}
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "provider" ? "bg-background" : ""
|
||||
}`}
|
||||
onClick={() => setTab("provider")}
|
||||
>
|
||||
{t("player")}
|
||||
@@ -40,25 +38,22 @@ export const MediaTabs = () => {
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize ${
|
||||
tab === "transcription" ? "bg-background" : ""
|
||||
}`}
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "transcription" ? "bg-background" : ""
|
||||
}`}
|
||||
onClick={() => setTab("transcription")}
|
||||
>
|
||||
{t("transcription")}
|
||||
</div>
|
||||
<div
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize ${
|
||||
tab === "recordings" ? "bg-background" : ""
|
||||
}`}
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "recordings" ? "bg-background" : ""
|
||||
}`}
|
||||
onClick={() => setTab("recordings")}
|
||||
>
|
||||
{t("myRecordings")}
|
||||
</div>
|
||||
<div
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize ${
|
||||
tab === "info" ? "bg-background" : ""
|
||||
}`}
|
||||
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "info" ? "bg-background" : ""
|
||||
}`}
|
||||
onClick={() => setTab("info")}
|
||||
>
|
||||
{t("mediaInfo")}
|
||||
|
||||
@@ -7,12 +7,13 @@ import {
|
||||
MediaTabs,
|
||||
MediaCurrentRecording,
|
||||
MediaPlayer,
|
||||
LoaderSpin,
|
||||
} from "@renderer/components";
|
||||
import { useVideo } from "@renderer/hooks";
|
||||
|
||||
export const VideoPlayer = (props: { id?: string; md5?: string }) => {
|
||||
const { id, md5 } = props;
|
||||
const { setMedia } = useContext(MediaPlayerProviderContext);
|
||||
const { setMedia, layout } = useContext(MediaPlayerProviderContext);
|
||||
const { video } = useVideo({ id, md5 });
|
||||
|
||||
useEffect(() => {
|
||||
@@ -21,29 +22,31 @@ export const VideoPlayer = (props: { id?: string; md5?: string }) => {
|
||||
setMedia(video);
|
||||
}, [video]);
|
||||
|
||||
if (!layout) return <LoaderSpin />;
|
||||
|
||||
return (
|
||||
<div data-testid="video-player">
|
||||
<div className="h-[calc(100vh-37.5rem)] mb-4">
|
||||
<div className="grid grid-cols-3 gap-4 px-6 h-full">
|
||||
<div className="col-span-1 rounded-lg border shadow-lg h-[calc(100vh-37.5rem)]">
|
||||
<div className={`${layout.upperWrapper} mb-4`}>
|
||||
<div className="grid grid-cols-5 xl:grid-cols-3 gap-6 px-6 h-full">
|
||||
<div className={`col-span-2 xl:col-span-1 rounded-lg border shadow-lg ${layout.upperWrapper}`}>
|
||||
<MediaTabs />
|
||||
</div>
|
||||
<div className="col-span-2 h-[calc(100vh-37.5rem)]">
|
||||
<div className={`col-span-3 xl:col-span-2 ${layout.upperWrapper}`}>
|
||||
<MediaCaption />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[33rem] flex flex-col">
|
||||
<div className="h-[13rem] py-2 px-6 mb-4">
|
||||
<MediaCurrentRecording />
|
||||
<div className={`${layout.lowerWrapper} flex flex-col`}>
|
||||
<div className={`${layout.playerWrapper} py-2 px-6`}>
|
||||
<MediaCurrentRecording height={layout.playerHeight} />
|
||||
</div>
|
||||
|
||||
<div className="w-full h-[13rem] px-6 py-2 mb-4">
|
||||
<div className={`${layout.playerWrapper} py-2 px-6`}>
|
||||
<MediaPlayer />
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-background z-10 shadow-xl">
|
||||
<div className={`${layout.panelWrapper} w-full bg-background z-10 shadow-xl`}>
|
||||
<MediaPlayerControls />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,8 +11,10 @@ import { TimelineEntry } from "echogarden/dist/utilities/Timeline.d.js";
|
||||
import { IPA_MAPPING } from "@/constants";
|
||||
import { toast } from "@renderer/components/ui";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { debounce } from "lodash";
|
||||
|
||||
type MediaPlayerContextType = {
|
||||
layout: { name: string, width: number, height: number, upperWrapper: string, lowerWrapper: string, playerWrapper: string, panelWrapper: string, playerHeight: number };
|
||||
media: AudioType | VideoType;
|
||||
setMedia: (media: AudioType | VideoType) => void;
|
||||
setMediaProvider: (mediaProvider: HTMLAudioElement | null) => void;
|
||||
@@ -68,15 +70,35 @@ type MediaPlayerContextType = {
|
||||
export const MediaPlayerProviderContext =
|
||||
createContext<MediaPlayerContextType>(null);
|
||||
|
||||
const LAYOUT = {
|
||||
sm: {
|
||||
name: 'sm',
|
||||
upperWrapper: "h-[calc(100vh-27.5rem)]",
|
||||
lowerWrapper: "h-[23rem]",
|
||||
playerWrapper: "h-[9rem] mb-2",
|
||||
panelWrapper: "h-16",
|
||||
playerHeight: 128,
|
||||
},
|
||||
lg: {
|
||||
name: 'lg',
|
||||
upperWrapper: "h-[calc(100vh-37.5rem)]",
|
||||
lowerWrapper: "h-[33rem]",
|
||||
panelWrapper: "h-20",
|
||||
playerWrapper: "h-[13rem] mb-4",
|
||||
playerHeight: 192,
|
||||
},
|
||||
}
|
||||
|
||||
export const MediaPlayerProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const height = 192;
|
||||
const minPxPerSec = 150;
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
|
||||
const [layout, setLayout] = useState<{ name: string, width: number, height: number, upperWrapper: string, lowerWrapper: string, playerWrapper: string, panelWrapper: string, playerHeight: number }>();
|
||||
|
||||
const [media, setMedia] = useState<AudioType | VideoType>(null);
|
||||
const [mediaProvider, setMediaProvider] = useState<HTMLAudioElement | null>(
|
||||
null
|
||||
@@ -126,7 +148,7 @@ export const MediaPlayerProvider = ({
|
||||
|
||||
const ws = WaveSurfer.create({
|
||||
container: ref.current,
|
||||
height,
|
||||
height: layout.playerHeight,
|
||||
waveColor: "#eaeaea",
|
||||
progressColor: "#c0d6df",
|
||||
cursorColor: "#ff0054",
|
||||
@@ -196,7 +218,7 @@ export const MediaPlayerProvider = ({
|
||||
const canvasId = options?.canvasId || `pitch-contour-${region.id}-canvas`;
|
||||
canvas.id = canvasId;
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
canvas.style.height = `${layout.playerHeight}px`;
|
||||
pitchContourWidthContainer.appendChild(canvas);
|
||||
|
||||
pitchContourWidthContainer.style.position = "absolute";
|
||||
@@ -204,7 +226,7 @@ export const MediaPlayerProvider = ({
|
||||
pitchContourWidthContainer.style.left = "0";
|
||||
|
||||
pitchContourWidthContainer.style.width = `${width}px`;
|
||||
pitchContourWidthContainer.style.height = `${height}px`;
|
||||
pitchContourWidthContainer.style.height = `${layout.playerHeight}px`;
|
||||
pitchContourWidthContainer.style.marginLeft = `${offsetLeft}px`;
|
||||
pitchContourWidthContainer.classList.add(
|
||||
"pitch-contour",
|
||||
@@ -317,6 +339,16 @@ export const MediaPlayerProvider = ({
|
||||
);
|
||||
};
|
||||
|
||||
const calculateHeight = () => {
|
||||
if (window.innerHeight <= 1080) {
|
||||
setLayout({ ...LAYOUT.sm, width: window.innerWidth, height: window.innerHeight });
|
||||
} else {
|
||||
setLayout({ ...LAYOUT.lg, width: window.innerWidth, height: window.innerHeight });
|
||||
}
|
||||
}
|
||||
|
||||
const deboundeCalculateHeight = debounce(calculateHeight, 100);
|
||||
|
||||
/*
|
||||
* When wavesurfer is decoded,
|
||||
* set up event listeners for wavesurfer
|
||||
@@ -432,15 +464,37 @@ export const MediaPlayerProvider = ({
|
||||
* and mediaProvider is available
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!layout?.playerHeight) return;
|
||||
if (!media) return;
|
||||
if (!ref) return;
|
||||
if (!mediaProvider) return;
|
||||
|
||||
initializeWavesurfer();
|
||||
setDecoded(false);
|
||||
setDecodeError(null);
|
||||
}, [media, ref, mediaProvider]);
|
||||
|
||||
return () => {
|
||||
if (wavesurfer) wavesurfer.destroy();
|
||||
}
|
||||
}, [media, ref, mediaProvider, layout?.playerHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
calculateHeight();
|
||||
|
||||
EnjoyApp.window.onResize((event, bounds) => {
|
||||
deboundeCalculateHeight();
|
||||
})
|
||||
|
||||
return () => {
|
||||
EnjoyApp.window.removeListeners();
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MediaPlayerProviderContext.Provider
|
||||
value={{
|
||||
layout,
|
||||
media,
|
||||
setMedia,
|
||||
setMediaProvider,
|
||||
|
||||
4
enjoy/src/types/enjoy-app.d.ts
vendored
4
enjoy/src/types/enjoy-app.d.ts
vendored
@@ -11,6 +11,10 @@ type EnjoyAppType = {
|
||||
createIssue: (title: string, body: string) => Promise<void>;
|
||||
version: string;
|
||||
};
|
||||
window: {
|
||||
onResize: (callback: (event, bounds: any) => void) => void;
|
||||
removeListeners: () => void;
|
||||
};
|
||||
system: {
|
||||
preferences: {
|
||||
mediaAccess: (mediaType: "microphone") => Promise<boolean>;
|
||||
|
||||
Reference in New Issue
Block a user