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';
8 import Icon from './Icon';
10 const createPlayer = async (apng, canvas) => {
11 const context = canvas.getContext('2d', { willReadFrequently: true });
12 const player = await apng.getPlayer(context);
17 const PngPlayer = ({ src }) => {
18 const canvas = React.useRef();
19 const { t } = useTranslation();
21 const [apng, setApng] = React.useState(null);
22 const [error, setError] = React.useState(null);
23 const [frameInfo, setFrameInfo] = React.useState('');
24 const [loading, setLoading] = React.useState(true);
25 const [player, setPlayer] = React.useState(null);
27 React.useEffect(() => {
31 const ctrl = new AbortController();
32 const fetchPng = async () => {
34 const response = await axios.get(src, {
35 responseType: 'arraybuffer',
38 const png = parseApng(response.data);
39 await png.createImages();
43 if (!axios.isCancel(e)) {
55 React.useEffect(async () => {
56 if (loading || !canvas.current) return;
57 setFrameInfo(`1/${apng.frames.length}`);
58 const p = await createPlayer(apng, canvas.current);
60 const updateFrame = (number) => {
61 setFrameInfo(`${number + 1}/${apng.frames.length}`);
63 p.on('frame', updateFrame);
65 p.off('frame', updateFrame);
67 }, [apng, canvas.current, loading]);
69 const stop = React.useCallback(() => {
70 if (player) player.stop();
73 const toggle = React.useCallback(() => {
82 const nextFrame = React.useCallback(() => {
83 if (player) player.renderNextFrame();
87 return <div>Error</div>;
90 return <div>Loading</div>;
93 return <div className="png-player">
94 <div className="screen">
95 <canvas ref={canvas} width={apng.width} height={apng.height} />
97 <span className="ms-auto">{frameInfo}</span>
98 <div className="button-bar controls">
101 title={t('button.stop')}
102 variant="outline-secondary"
104 <Icon.STOP title="" />
108 title={t('button.playPause')}
109 variant="outline-secondary"
111 <Icon.PLAY title="" />
113 <Icon.PAUSE title="" />
117 title={t('button.nextFrame')}
118 variant="outline-secondary"
120 <Icon.STEP_FORWARD title="" />
126 PngPlayer.propTypes = {
127 src: PropTypes.string,
130 export default PngPlayer;