diff --git a/src/contexts/PlayerContext.tsx b/src/contexts/PlayerContext.tsx index 98c88e1..eee3698 100644 --- a/src/contexts/PlayerContext.tsx +++ b/src/contexts/PlayerContext.tsx @@ -9,6 +9,7 @@ import { unmuteAudio } from "../utils/unmute"; import AppGlobal from "./AppGlobal"; import { PlayerContextReducer } from "./PlayerContextReducer"; import { AppProgressContext } from "./AppProgressContext"; +import { KSSDecoderStartOptions } from "../kss/kss-decoder-worker"; export type PlayListEntry = { title?: string | null; @@ -32,6 +33,8 @@ export interface PlayerContextState { currentEntry: PlayListEntry | null; playState: "playing" | "paused" | "stopped"; playStateChangeCount: number; + defaultLoopCount: number; + defaultDuration: number; channelMask: KSSChannelMask; unmute: () => Promise; } @@ -60,6 +63,8 @@ const createDefaultContextState = () => { playStateChangeCount: 0, playState: "stopped", masterGain: 4.0, + defaultLoopCount: 2, + defaultDuration: 300 * 1000, channelMask: { psg: 0, opl: 0, @@ -88,6 +93,8 @@ const createDefaultContextState = () => { state.masterGain = json.masterGain ?? state.masterGain; state.gainNode.gain.value = state.masterGain; state.repeatMode = json.repeatMode ?? state.repeatMode; + state.defaultLoopCount = json.defaultLoopCount ?? state.defaultLoopCount; + state.defaultDuration = json.defaultDuration ?? state.defaultDuration; } catch (e) { console.error(e); localStorage.clear(); @@ -118,7 +125,16 @@ async function applyPlayStateChange( const { channelMask } = state; const { dataId, song, duration, fadeDuration } = entry; const data = await state.storage.get(dataId); - await state.player.play({ channelMask, data, song, duration, fadeDuration }); + const options: KSSDecoderStartOptions = { + channelMask, + data, + song, + duration, + fadeDuration, + loop: state.defaultLoopCount, + defaultDuration: state.defaultDuration, + }; + await state.player.play(options); }; if (state.playState == "playing") { @@ -163,13 +179,10 @@ export function PlayerContextProvider(props: React.PropsWithChildren) { const [initialized, setInitialized] = useState(false); useEffect(() => { - console.log('attach'); - console.log(window.opener); window.addEventListener("message", onWindowMessage, false); state.player.addEventListener("statechange", onPlayerStateChange); initialize(); return () => { - console.log('detach'); window.removeEventListener("message", onWindowMessage, false); state.player.removeEventListener("statechange", onPlayerStateChange); }; @@ -214,14 +227,27 @@ export function PlayerContextProvider(props: React.PropsWithChildren) { }; const save = () => { - const { channelMask, repeatMode, masterGain } = state; - const data = { version: 1, channelMask, masterGain, repeatMode }; + const { defaultLoopCount, defaultDuration, channelMask, repeatMode, masterGain } = state; + const data = { + version: 1, + defaultLoopCount, + defaultDuration, + channelMask, + masterGain, + repeatMode, + }; localStorage.setItem("m3disp.playerContext", JSON.stringify(data)); }; useEffect(() => { save(); - }, [state.masterGain, state.channelMask, state.repeatMode]); + }, [ + state.masterGain, + state.defaultLoopCount, + state.defaultDuration, + state.channelMask, + state.repeatMode, + ]); const saveEntries = (entries: PlayListEntry[]) => { const data = JSON.stringify(entries); @@ -238,11 +264,12 @@ export function PlayerContextProvider(props: React.PropsWithChildren) { }; const onWindowMessage = async (ev: MessageEvent) => { - console.log(ev); if (ev.data instanceof Uint8Array && ev.data.length <= 65536) { reducer.clearEntries(); - const file = new File([ev.data], 'external.mgs'); - const entries = await loadEntriesFromFileList(state.storage, [new File([ev.data], file.name)]); + const file = new File([ev.data], "external.mgs"); + const entries = await loadEntriesFromFileList(state.storage, [ + new File([ev.data], file.name), + ]); reducer.addEntries(entries, 0); reducer.resume(); reducer.play(0); diff --git a/src/contexts/PlayerContextReducer.tsx b/src/contexts/PlayerContextReducer.tsx index 489151a..2184054 100644 --- a/src/contexts/PlayerContextReducer.tsx +++ b/src/contexts/PlayerContextReducer.tsx @@ -77,6 +77,18 @@ export class PlayerContextReducer { }); } + setDefaultLoopCount(value: number) { + this.setState((state) => { + return { ...state, defaultLoopCount: value }; + }); + } + + setDefaultDuration(value: number) { + this.setState((state) => { + return { ...state, defaultDuration: value }; + }); + } + setChannelMask(channelMask: KSSChannelMask) { this.setState((state) => { if ( diff --git a/src/contexts/SettingsContext.tsx b/src/contexts/SettingsContext.tsx index 1c9603e..9efbbb8 100644 --- a/src/contexts/SettingsContext.tsx +++ b/src/contexts/SettingsContext.tsx @@ -3,7 +3,11 @@ import { KSSChannelMask } from "../kss/kss-device"; import { PlayerContext } from "./PlayerContext"; export type SettingsContextState = { + defaultLoopCount: number; + defaultDuration: number; channelMask: KSSChannelMask; + setDefaultLoopCount: (value: number) => void; + setDefaultDuration: (value: number) => void; setChannelMask: (channelMask: KSSChannelMask) => void; commit: () => void; revert: () => void; @@ -14,7 +18,11 @@ const noop = () => { }; const defaultContextState: SettingsContextState = { + defaultLoopCount: 2, + defaultDuration: 300 * 1000, channelMask: { psg: 0, scc: 0, opll: 0, opl: 0 }, + setDefaultLoopCount: noop, + setDefaultDuration: noop, setChannelMask: noop, commit: noop, revert: noop, @@ -25,19 +33,38 @@ export const SettingsContext = createContext(defaultContextState); export function SettingsContextProvider(props: PropsWithChildren) { const context = useContext(PlayerContext); - const setChannelMask = (channelMask: KSSChannelMask) => { + function setDefaultLoopCount(value: number) { + setState((oldState) => { + return { ...oldState, defaultLoopCount: value }; + }); + } + + function setDefaultDuration(value: number) { + setState((oldState) => { + return { ...oldState, defaultDuration: value }; + }); + } + + function setChannelMask(channelMask: KSSChannelMask) { setState((oldState) => { return { ...oldState, channelMask }; }); - }; + } - const commit = () => { + function commit() { + context.reducer.setDefaultLoopCount(state.defaultLoopCount); + context.reducer.setDefaultDuration(state.defaultDuration); context.reducer.setChannelMask(state.channelMask); - }; + } - const revert = () => { - setState((oldState) => ({ ...oldState, channelMask: { ...context.channelMask } })); - }; + function revert() { + setState((oldState) => ({ + ...oldState, + defaultLoopCount: context.defaultLoopCount, + defaultDuration: context.defaultDuration, + channelMask: { ...context.channelMask }, + })); + } const [state, setState] = useState({ ...defaultContextState, @@ -48,6 +75,8 @@ export function SettingsContextProvider(props: PropsWithChildren) { + + + `${(value / 60 / 1000).toFixed(0)} min.`} + onChange={context.setDefaultDuration} + /> + + + ); +} + function KeyColorTypeSelector(props: { value: KeyHighlightColorType }) { const theme = useTheme(); const app = useContext(AppContext); @@ -265,19 +290,21 @@ function SettingsDialogBody(props: { id: string }) { sx={{ minWidth: "300px", width: { sm: "480px" }, - height: { xs: "480px", sm: "480px" }, - p: 0, + height: { xs: "480px", sm: "520px" }, + p: 1, backgroundColor: "background.paper", }} > + - - + + + diff --git a/src/widgets/NumberSelector.tsx b/src/widgets/NumberSelector.tsx new file mode 100644 index 0000000..0e33acf --- /dev/null +++ b/src/widgets/NumberSelector.tsx @@ -0,0 +1,42 @@ +import { Box, ListSubheader, MenuItem, Select, SelectChangeEvent, Stack } from "@mui/material"; + +export function NumberSelector(props: { + label: string; + values: number[]; + value: number; + valueLabelFn?: (value: number) => string; + onChange: (value: number) => void; +}) { + const { value, values, onChange } = props; + function valueToLabel(value: number) { + return props.valueLabelFn?.(value) ?? value.toString(); + } + const items = []; + for (const i in values) { + items.push( + + {valueToLabel(values[i])} + + ); + } + return ( + + {props.label} + + + + + ); +}