Skip to content

Commit f2aa51a

Browse files
committed
add player controller feature
1 parent 9948eaa commit f2aa51a

File tree

12 files changed

+204
-48
lines changed

12 files changed

+204
-48
lines changed

src/main/schema/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,8 @@ export default {
3838
type: 'boolean',
3939
default: false,
4040
},
41+
showPlayerController: {
42+
type: 'boolean',
43+
default: true,
44+
},
4145
} as const;

src/renderer/components/views/FlashPlayer.tsx

+153-41
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,79 @@
11
/** @jsxImportSource @emotion/react */
2-
import { useEffect, useMemo, useRef } from 'react';
2+
import { useEffect, useMemo, useRef, useState } from 'react';
33
import { css } from '@emotion/react';
44
import { useDispatch, useSelector } from 'react-redux';
55
import { RootState } from '@/renderer/store';
66
import { setConfig } from '@/renderer/store/slices/appScreenSlice';
7+
import Grid from '@mui/material/Grid';
8+
import IconButton from '@mui/material/IconButton';
9+
import Tooltip from '@mui/material/Tooltip';
10+
import Replay from '@mui/icons-material/Replay';
11+
import Slider from '@mui/material/Slider';
12+
import Stack from '@mui/material/Stack';
13+
import Pause from '@mui/icons-material/Pause';
14+
import PlayArrow from '@mui/icons-material/PlayArrow';
15+
import VolumeOff from '@mui/icons-material/VolumeOff';
16+
import VolumeUp from '@mui/icons-material/VolumeUp';
17+
import { useTranslation } from 'react-i18next';
718

819
const FlashPlayer = ({ url = '', autoplay = true, filePath = '', header = true }) => {
20+
const [t] = useTranslation(['menu']);
921
const player: any = useRef();
22+
const [rufflePlayer, setRufflePlayer]: any = useState(null);
1023
const dispatch = useDispatch();
1124
const stateAppScreen = useSelector((state: RootState) => state.appScreen);
1225
const os = useMemo(() => stateAppScreen.mainGlobalValues.ENV_OS, []);
1326

14-
useEffect((): any => {
15-
const container = player.current;
16-
container.innerHTML = '';
17-
18-
let realPath;
27+
const realPath = useMemo(() => {
28+
let path;
1929
if (os === 'Linux' || os === 'macOS') {
20-
realPath = filePath.indexOf('file:') === -1 ? `file:///${filePath}` : filePath;
30+
path = filePath.indexOf('file:') === -1 ? `file:///${filePath}` : filePath;
31+
} else {
32+
path = filePath.replace('file:///', '');
33+
}
34+
return path;
35+
}, [filePath, os]);
36+
37+
const handlePauseOrPlay = () => {
38+
if (!rufflePlayer) {
39+
return;
40+
}
41+
42+
if (rufflePlayer.isPlaying) {
43+
rufflePlayer.pause();
2144
} else {
22-
realPath = filePath.replace('file:///', '');
45+
rufflePlayer.play();
2346
}
47+
};
48+
49+
const handleMute = async () => {
50+
if (stateAppScreen.flashVolume === 0) {
51+
rufflePlayer.volume = 1;
52+
await dispatch(setConfig({ flashVolume: 100 }));
53+
} else {
54+
rufflePlayer.volume = 0;
55+
await dispatch(setConfig({ flashVolume: 0 }));
56+
}
57+
};
58+
59+
const handleVolumeSliderChange = async (event, newValue) => {
60+
rufflePlayer.volume = newValue / 100;
61+
await dispatch(setConfig({ flashVolume: newValue }));
62+
};
63+
64+
const loadFlash = () => {
65+
rufflePlayer.load(realPath);
66+
rufflePlayer.addEventListener('oncontextmenu', (e) => e.preventDefault());
67+
};
68+
69+
useEffect(() => {
70+
const ruffle = window.RufflePlayer.newest();
71+
setRufflePlayer(ruffle.createPlayer());
72+
}, [url, filePath]);
73+
74+
useEffect((): any => {
75+
const container = player.current;
76+
container.innerHTML = '';
2477

2578
window.RufflePlayer = window.RufflePlayer || {};
2679
window.RufflePlayer.config = {
@@ -39,11 +92,14 @@ const FlashPlayer = ({ url = '', autoplay = true, filePath = '', header = true }
3992
realPath.lastIndexOf(os === 'Windows' ? '\\' : '/'),
4093
)}`,
4194
};
42-
const ruffle = window.RufflePlayer.newest();
43-
const rPlayer = ruffle.createPlayer();
44-
rPlayer.id = 'player';
45-
rPlayer.addEventListener('loadedmetadata', async () => {
46-
const metaData = rPlayer?.metadata;
95+
96+
if (!rufflePlayer) {
97+
return;
98+
}
99+
100+
rufflePlayer.id = 'player';
101+
rufflePlayer.addEventListener('loadedmetadata', async () => {
102+
const metaData = rufflePlayer?.metadata;
47103
await dispatch(
48104
setConfig({
49105
flashFileSwfVer: metaData?.swfVersion,
@@ -62,39 +118,95 @@ const FlashPlayer = ({ url = '', autoplay = true, filePath = '', header = true }
62118
});
63119
}
64120
});
65-
container.appendChild(rPlayer);
66-
rPlayer.load(realPath);
67-
rPlayer.addEventListener('oncontextmenu', (e) => e.preventDefault());
68-
}, [url, filePath, stateAppScreen.appConfigEmulatePlayerVersion]);
121+
122+
container.appendChild(rufflePlayer);
123+
loadFlash();
124+
}, [url, filePath, stateAppScreen.appConfigEmulatePlayerVersion, rufflePlayer]);
69125

70126
return (
71127
<div
72-
id="main"
73128
css={css`
74-
align-items: stretch;
75-
width: 100%;
76-
height: ${header ? 'calc(100vh - 42px)' : '100vh'};
77-
top: 0;
78-
left: 0;
79-
right: 0;
80-
bottom: 0;
81-
background: #000000;
82-
color: white;
83-
#player {
84-
width: 100%;
85-
height: 100%;
86-
display: block;
87-
}
88-
#container {
89-
margin: 10px;
90-
width: 100%;
91-
height: 100%;
92-
align-self: center;
93-
}
129+
height: ${header ? 'calc(100vh - 40px)' : '100vh'};
94130
`}
95-
ref={player}
96-
onContextMenu={(e) => e.preventDefault()}
97-
/>
131+
>
132+
<div
133+
id="main"
134+
css={css`
135+
align-items: stretch;
136+
width: 100%;
137+
height: calc(100% - ${stateAppScreen.appConfigShowPlayerController ? 42 : 0}px);
138+
top: 0;
139+
left: 0;
140+
right: 0;
141+
bottom: 0;
142+
background: #000000;
143+
color: white;
144+
#player {
145+
width: 100%;
146+
height: 100%;
147+
display: block;
148+
}
149+
#container {
150+
margin: 10px;
151+
width: 100%;
152+
height: 100%;
153+
align-self: center;
154+
}
155+
`}
156+
ref={player}
157+
onContextMenu={(e) => e.preventDefault()}
158+
/>
159+
{stateAppScreen.appConfigShowPlayerController && (
160+
<Grid
161+
container
162+
alignItems="center"
163+
css={css`
164+
height: 42px;
165+
`}
166+
>
167+
<Grid item xs={12}>
168+
<Grid container alignItems="center">
169+
<Grid item>
170+
<IconButton
171+
color="primary"
172+
size="small"
173+
aria-label="player-pause-and-play"
174+
onClick={handlePauseOrPlay}
175+
>
176+
{rufflePlayer?.isPlaying ? <Pause /> : <PlayArrow />}
177+
</IconButton>
178+
</Grid>
179+
<Grid item>
180+
<Tooltip title={t('replay')}>
181+
<IconButton
182+
size="small"
183+
aria-label="player-replay"
184+
component="span"
185+
onClick={loadFlash}
186+
>
187+
<Replay />
188+
</IconButton>
189+
</Tooltip>
190+
</Grid>
191+
<Grid item xs={6} md={4}>
192+
<Stack spacing={2} direction="row" alignItems="center">
193+
<IconButton size="small" aria-label="player-mute" onClick={handleMute}>
194+
{stateAppScreen.flashVolume === 0 ? <VolumeOff /> : <VolumeUp />}
195+
</IconButton>
196+
<Slider
197+
value={stateAppScreen.flashVolume}
198+
defaultValue={100}
199+
onChange={handleVolumeSliderChange}
200+
valueLabelDisplay="off"
201+
aria-labelledby="player-volume"
202+
/>
203+
</Stack>
204+
</Grid>
205+
</Grid>
206+
</Grid>
207+
</Grid>
208+
)}
209+
</div>
98210
);
99211
};
100212

src/renderer/public/locales/de/menu.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@
2929
"save": "Speichern",
3030
"reset-localstorage": "Alle Daten zurücksetzen",
3131
"cancel": "Stornieren",
32-
"ok": "OK"
32+
"ok": "OK",
33+
"show-player-controller": "Player-Controller anzeigen",
34+
"replay": "Neu starten"
3335
}

src/renderer/public/locales/en/menu.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@
2929
"save": "Save",
3030
"reset-localstorage": "Reset All Data",
3131
"cancel": "Cancel",
32-
"ok": "OK"
32+
"ok": "OK",
33+
"show-player-controller": "Show player controller",
34+
"replay": "Restart"
3335
}

src/renderer/public/locales/es/menu.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@
2929
"save": "Ahorrar",
3030
"reset-localstorage": "Restablecer todos los datos",
3131
"cancel": "Cancelar",
32-
"ok": "DE ACUERDO"
32+
"ok": "DE ACUERDO",
33+
"show-player-controller": "Mostrar controlador de jugador",
34+
"replay": "Reanudar"
3335
}

src/renderer/public/locales/fr/menu.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@
2929
"save": "Sauvegarder",
3030
"reset-localstorage": "Réinitialiser toutes les données",
3131
"cancel": "Annuler",
32-
"ok": "OK"
32+
"ok": "OK",
33+
"show-player-controller": "Afficher le contrôleur du lecteur",
34+
"replay": "Redémarrage"
3335
}

src/renderer/public/locales/ja/menu.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@
2929
"save": "保存",
3030
"reset-localstorage": "すべてのデータをリセット",
3131
"cancel": "キャンセル",
32-
"ok": "OK"
32+
"ok": "OK",
33+
"show-player-controller": "プレーヤーコントローラーの表示",
34+
"replay": "再起動"
3335
}

src/renderer/public/locales/ko/menu.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@
2929
"save": "저장",
3030
"reset-localstorage": "데이터 초기화",
3131
"cancel": "취소",
32-
"ok": "확인"
32+
"ok": "확인",
33+
"show-player-controller": "플레이어 컨트롤러 표시",
34+
"replay": "다시 시작"
3335
}

src/renderer/public/locales/pt/menu.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@
2929
"save": "Salvar",
3030
"reset-localstorage": "Redefinir todos os dados",
3131
"cancel": "Cancelar",
32-
"ok": "OK"
32+
"ok": "OK",
33+
"show-player-controller": "Mostrar controlador do jogador",
34+
"replay": "Reiniciar"
3335
}

src/renderer/screens/Main.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const Main = () => {
6565
appConfigRestoreWindowBounds: configs.restoreWindowBounds,
6666
appConfigAdjustOriginalSize: configs.adjustOriginalSize,
6767
appConfigShowPlayerVersionSelect: configs.showPlayerVersionSelect,
68+
appConfigShowPlayerController: configs.showPlayerController,
6869
}),
6970
);
7071

src/renderer/screens/Settings.tsx

+21
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const Settings = () => {
3131
const [hideHeaderChecked, setHideHeaderChecked] = useState(stateAppScreen.appConfigHideHeader);
3232
const [letterboxChecked, setLetterboxChecked] = useState(stateAppScreen.appConfigLetterbox);
3333
const [hideContextChecked, setHideContextChecked] = useState(stateAppScreen.appConfigHideContext);
34+
const [showPlayerControllerChecked, setShowPlayerControllerChecked] = useState(
35+
stateAppScreen.appConfigShowPlayerController,
36+
);
3437
const [showPlayerVersionSelectChecked, setShowPlayerVersionSelectChecked] = useState(
3538
stateAppScreen.appConfigShowPlayerVersionSelect,
3639
);
@@ -93,6 +96,11 @@ const Settings = () => {
9396
window.mainApi.send('setAppConfig', { showPlayerVersionSelect: value });
9497
await dispatch(setConfig({ appConfigShowPlayerVersionSelect: value }));
9598
break;
99+
case 'showPlayerControllerChecked':
100+
setShowPlayerControllerChecked(value);
101+
window.mainApi.send('setAppConfig', { showPlayerController: value });
102+
await dispatch(setConfig({ appConfigShowPlayerController: value }));
103+
break;
96104
default:
97105
break;
98106
}
@@ -189,6 +197,19 @@ const Settings = () => {
189197
<Grid item xs={12} css={marginTopMd}>
190198
<PanelHeader title={t('settings-title-1')} desc={t('settings-desc-1')} />
191199
<Grid container>
200+
<Grid item xs={12}>
201+
<FormControlLabel
202+
control={
203+
<Checkbox
204+
color="primary"
205+
checked={showPlayerControllerChecked}
206+
onChange={handleCheckboxChange}
207+
name="showPlayerControllerChecked"
208+
/>
209+
}
210+
label={t('menu:show-player-controller')}
211+
/>
212+
</Grid>
192213
<Grid item xs={12}>
193214
<FormControlLabel
194215
control={

src/renderer/store/slices/appScreenSlice.ts

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface AppScreenState {
2525
flashFileHeight: number;
2626
flashFileBackgroundColor: string;
2727
flashFileFrameRate: number;
28+
flashVolume: number;
2829
isDarkTheme: boolean;
2930
appConfigTheme: string;
3031
appConfigHideHeader: boolean;
@@ -36,6 +37,7 @@ export interface AppScreenState {
3637
appConfigAdjustOriginalSize: boolean;
3738
appConfigEmulatePlayerVersion: number;
3839
appConfigShowPlayerVersionSelect: boolean;
40+
appConfigShowPlayerController: boolean;
3941
recentFiles: string[];
4042
mainGlobalValues: GlobalValues;
4143
dialogMetadataOpen: boolean;
@@ -52,6 +54,7 @@ const initialState: AppScreenState = {
5254
flashFileHeight: 0,
5355
flashFileBackgroundColor: '',
5456
flashFileFrameRate: 0,
57+
flashVolume: 100,
5558
isDarkTheme: false,
5659
appConfigTheme: 'light',
5760
appConfigHideHeader: false,
@@ -63,6 +66,7 @@ const initialState: AppScreenState = {
6366
appConfigAdjustOriginalSize: false,
6467
appConfigEmulatePlayerVersion: 0,
6568
appConfigShowPlayerVersionSelect: false,
69+
appConfigShowPlayerController: true,
6670
recentFiles: [],
6771
mainGlobalValues: {
6872
APP_NAME: '',

0 commit comments

Comments
 (0)