]> git.localhorst.tv Git - alttp.git/blob - resources/js/components/common/PngPlayer.js
d1d5da3bca3bae77b0b6e257395878bc526ef510
[alttp.git] / resources / js / components / common / PngPlayer.js
1 import parseApng from 'apng-js';
2 import axios from 'axios';
3 import PropTypes from 'prop-types';
4 import React from 'react';
5 import { Button } from 'react-bootstrap';
6 import { useTranslation } from 'react-i18next';
7
8 import Icon from './Icon';
9
10 const createPlayer = async (apng, canvas) => {
11         const context = canvas.getContext('2d', { willReadFrequently: true });
12         const player = await apng.getPlayer(context);
13         player.stop();
14         return player;
15 };
16
17 const PngPlayer = ({ src }) => {
18         const canvas = React.useRef();
19         const { t } = useTranslation();
20
21         const [apng, setApng] = React.useState(null);
22         const [error, setError] = React.useState(null);
23         const [loading, setLoading] = React.useState(true);
24         const [player, setPlayer] = React.useState(null);
25
26         React.useEffect(() => {
27                 if (!src) return;
28                 setError(null);
29                 setLoading(true);
30                 const ctrl = new AbortController();
31                 const fetchPng = async () => {
32                         try {
33                                 const response = await axios.get(src, {
34                                         responseType: 'arraybuffer',
35                                         signal: ctrl.signal,
36                                 });
37                                 const png = parseApng(response.data);
38                                 await png.createImages();
39                                 setApng(png);
40                                 setLoading(false);
41                         } catch (e) {
42                                 if (!axios.isCancel(e)) {
43                                         setError(e);
44                                         console.log(e);
45                                 }
46                         }
47                 };
48                 fetchPng();
49                 return () => {
50                         ctrl.abort();
51                 };
52         }, [src]);
53
54         React.useEffect(async () => {
55                 if (loading || !canvas.current) return;
56                 setPlayer(await createPlayer(apng, canvas.current));
57         }, [apng, canvas.current, loading]);
58
59         const play = React.useCallback(() => {
60                 if (player) player.play();
61         }, [player]);
62
63         const pause = React.useCallback(() => {
64                 if (player) player.pause();
65         }, [player]);
66
67         const stop = React.useCallback(() => {
68                 if (player) player.stop();
69         }, [player]);
70
71         const nextFrame = React.useCallback(() => {
72                 if (player) player.renderNextFrame();
73         }, [player]);
74
75         if (error) {
76                 return <div>Error</div>;
77         }
78         if (loading) {
79                 return <div>Loading</div>;
80         }
81
82         return <div className="png-player">
83                 <div className="screen">
84                         <canvas ref={canvas} width={apng.width} height={apng.height} />
85                 </div>
86                 <div className="button-bar controls">
87                         <Button
88                                 onClick={stop}
89                                 title={t('button.stop')}
90                                 variant="outline-secondary"
91                         >
92                                 <Icon.STOP title="" />
93                         </Button>
94                         <Button
95                                 onClick={play}
96                                 title={t('button.play')}
97                                 variant="outline-secondary"
98                         >
99                                 <Icon.PLAY title="" />
100                         </Button>
101                         <Button
102                                 onClick={pause}
103                                 title={t('button.pause')}
104                                 variant="outline-secondary"
105                         >
106                                 <Icon.PAUSE title="" />
107                         </Button>
108                         <Button
109                                 onClick={nextFrame}
110                                 title={t('button.nextFrame')}
111                                 variant="outline-secondary"
112                         >
113                                 <Icon.STEP_FORWARD title="" />
114                         </Button>
115                 </div>
116         </div>;
117 };
118
119 PngPlayer.propTypes = {
120         src: PropTypes.string,
121 };
122
123 export default PngPlayer;