UNPKG

react-video-thumbnail

Version:

Create a thumbnail by providing a video url

175 lines (157 loc) 5.51 kB
/** * React Video Thumbnail Component * @author mike trieu */ import React from 'react'; import PropTypes from 'prop-types'; import './video-thumbnail.css'; /** * Simple component that renders thumbnail url * @param {string} snapshot */ const ThumbnailImage = ({ snapshot }) => { return ( <div className="react-thumbnail-generator" > <img src={snapshot} alt="my video thumbnail" /> </div> ); } export default class VideoThumbnail extends React.Component { constructor(props) { super(props) this.state = { dataLoaded: false, // boolean metadataLoaded: false, // boolean seeked: false, // boolean snapshot: false, // string thumbnail url || false suspended: false, // boolean // props cors: props.cors, // boolean width: props.width, // number height: props.height, // number renderThumbnail: props.renderThumbnail, // boolean snapshotAtTime: props.snapshotAtTime, // number thumbnailHandler: props.thumbnailHandler, // callback function videoUrl: props.videoUrl, // string } } render() { const { renderThumbnail, snapshot, videoUrl } = this.state; if (!snapshot) { return ( <div className="react-thumbnail-generator" > <canvas className="snapshot-generator" ref="canvas" ></canvas> <video muted className="snapshot-generator" ref="videoEl" src={videoUrl} onLoadedMetadata={() => this.setState({ metadataLoaded: true })} onLoadedData={() => this.setState({ dataLoaded: true })} onSuspend={() => this.setState({ suspended: true })} onSeeked={() => this.setState({ seeked: true })} > </video> </div> ) } else { if (renderThumbnail) { return <ThumbnailImage snapshot={snapshot} />; } else { return null; } } } /** * Update any props that may have changed */ componentWillReceiveProps(nextProps) { let stateChanged = false; const data = {}; for (let prop in nextProps) { if (nextProps[prop] !== this.props[prop]) { data[prop] = nextProps[prop]; if (!stateChanged) { stateChanged = true; } } } if (stateChanged) { this.setState(data); } } componentDidMount() { if (!this.state.cors) this.refs.videoEl.setAttribute('crossOrigin', 'Anonymous'); // console.log('mount state: ', this.state) } /** * (fires every time setState() gets called) */ componentDidUpdate(prevProps, prevState) { if (!this.state.snapshot) { const { metadataLoaded, dataLoaded, suspended, seeked, snapshot, snapshotAtTime } = this.state; // check if all 3 required events fired if (metadataLoaded && dataLoaded && suspended) { if (!this.refs.videoEl.currentTime || this.refs.videoEl.currentTime < this.state.snapshotAtTime) { this.refs.videoEl.currentTime = snapshotAtTime; } if (seeked && !snapshot) { // attempt to generate thumbnail this.getSnapShot(); } } } } /** * Create a canvas and video element to "draw" the * image, then convert it to a data url */ getSnapShot = () => { try { const { width, height } = this.props; const video = this.refs.videoEl; const canvas = this.refs.canvas; canvas.height = video.videoHeight; canvas.width = video.videoWidth; // resize thumbnail or no ? if (!width || !height) { canvas.getContext('2d').drawImage(video, 0, 0); } else { canvas.getContext('2d').drawImage(video, 0, 0, width, height); } const thumbnail = canvas.toDataURL('image/png'); // Remove video & canvas elements (no longer needed) video.src = ""; // setting to empty string stops video from loading video.remove(); canvas.remove(); this.setState({ snapshot: thumbnail }) // pass the thumbnail url back to parent component's thumbnail handler (if any) if (this.state.thumbnailHandler) { this.state.thumbnailHandler(thumbnail); } } catch (e) { console.error(e); } } } /** * Property Types */ VideoThumbnail.propTypes = { cors: PropTypes.bool, width: PropTypes.number, height: PropTypes.number, renderThumbnail: PropTypes.bool, snapshotAtTime: PropTypes.number, thumbnailHandler: PropTypes.func, videoUrl: PropTypes.string.isRequired, } /** * Default Properties */ VideoThumbnail.defaultProps = { cors: false, renderThumbnail: true, snapshotAtTime: 2, }