react-vimeo
Version:
React component to load video from Vimeo
303 lines (261 loc) • 6.73 kB
JSX
import PropTypes from 'prop-types';
import React, { cloneElement } from 'react';
import keyMirror from 'keymirror';
import jsonp from 'jsonp';
import debugFactory from 'debug';
import PlayButton from './Play-Button';
import Spinner from './Spinner';
const debug = debugFactory('vimeo:player');
const noop = () => {};
const playerEvents = keyMirror({
cueChange: null,
ended: null,
loaded: null,
pause: null,
play: null,
progress: null,
seeked: null,
textTrackChange: null,
timeUpdate: null,
volumeChange: null
});
function capitalize(str = '') {
return str.charAt(0).toUpperCase() + str.substring(1);
}
function getFuncForEvent(event, props) {
return props['on' + capitalize(event)] || (() => {});
}
function post(method, value, player, playerOrigin) {
try {
player.contentWindow.postMessage({ method, value }, playerOrigin);
} catch (err) {
return err;
}
return null;
}
export default class extends React.Component {
static displayName = 'Vimeo';
static propTypes = {
autoplay: PropTypes.bool,
className: PropTypes.string,
loading: PropTypes.element,
playButton: PropTypes.node,
playerOptions: PropTypes.object,
videoId: PropTypes.string.isRequired,
// event callbacks
onCueChange: PropTypes.func,
onEnded: PropTypes.func,
onError: PropTypes.func,
onLoaded: PropTypes.func,
onPause: PropTypes.func,
onPlay: PropTypes.func,
onProgress: PropTypes.func,
onReady: PropTypes.func,
onSeeked: PropTypes.func,
onTextTrackChanged: PropTypes.func,
onTimeUpdate: PropTypes.func,
onVolumeChange: PropTypes.func
};
static defaultProps = (function() {
const defaults = Object.keys(playerEvents)
.concat(['ready'])
.reduce((defaults, event) => {
defaults['on' + capitalize(event)] = noop;
return defaults;
}, {});
defaults.className = 'vimeo';
defaults.playerOptions = { autoplay: 1 };
defaults.autoplay = false;
return defaults;
})();
state = {
imageLoaded: false,
playerOrigin: '*',
showingVideo: this.props.autoplay,
thumb: null
};
componentWillReceiveProps(nextProps) {
if (nextProps.videoId !== this.props.videoId) {
this.setState({
thumb: null,
imageLoaded: false,
showingVideo: false
});
}
}
componentDidMount() {
this.fetchVimeoData();
}
componentDidUpdate() {
this.fetchVimeoData();
}
componentWillUnmount() {
const removeEventListener = typeof window !== 'undefined' ?
::window.removeEventListener :
noop;
removeEventListener('message', this.onMessage);
}
addMessageListener = () => {
const addEventListener = typeof window !== 'undefined' ?
::window.addEventListener :
noop;
addEventListener('message', this.onMessage);
};
onError = (err) => {
if (this.props.onError) {
this.props.onError(err);
}
throw err;
};
onMessage = ({ origin, data }) => {
const { onReady } = this.props;
const { playerOrigin } = this.state;
if (playerOrigin === '*') {
this.setState({
playerOrigin: origin
});
}
// Handle messages from the vimeo player only
if (!(/^https?:\/\/player.vimeo.com/).test(origin)) {
return false;
}
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (err) {
debug('error parsing message', err);
data = { event: '' };
}
}
if (data.event === 'ready') {
debug('player ready');
this.onReady(
this._player,
playerOrigin === '*' ? origin : playerOrigin
);
return onReady(data);
}
if (!data.event) {
// we get messages when the first event callbacks are added to the frame
return;
}
debug('firing event: ', data.event);
getFuncForEvent(data.event, this.props)(data);
};
onReady = (player, playerOrigin) => {
Object.keys(playerEvents).forEach(event => {
var err = post(
'addEventListener',
event.toLowerCase(),
player,
playerOrigin
);
if (err) {
this.onError(err);
}
});
};
playVideo = (e) => {
e.preventDefault();
this.setState({ showingVideo: true });
};
getIframeUrl = () => {
const { videoId } = this.props;
const query = this.getIframeUrlQuery();
return `//player.vimeo.com/video/${videoId}?${query}`;
};
getIframeUrlQuery = () => {
let str = [];
Object.keys(this.props.playerOptions).forEach(key => {
str.push(`${key}=${this.props.playerOptions[key]}`);
});
return str.join('&');
};
fetchVimeoData = () => {
if (this.state.imageLoaded) {
return;
}
const id = this.props.videoId;
jsonp(
`//vimeo.com/api/v2/video/${id}.json`,
{
prefix: 'vimeo'
},
(err, res) => {
if (err) {
debug('jsonp err: ', err.message);
this.onError(err);
}
debug('jsonp response', res);
this.setState({
thumb: res[0].thumbnail_large,
imageLoaded: true
});
}
);
};
renderImage = () => {
if (this.state.showingVideo || !this.state.imageLoaded) {
return;
}
const style = {
backgroundImage: `url(${this.state.thumb})`,
display: !this.state.showingVideo ? 'block' : 'none',
height: '100%',
width: '100%'
};
const playButton = this.props.playButton ?
cloneElement(this.props.playButton, { onClick: this.playVideo }) :
<PlayButton onClick={ this.playVideo } />;
return (
<div
className='vimeo-image'
style={ style }>
{playButton}
</div>
);
};
renderIframe = () => {
if (!this.state.showingVideo) {
return;
}
this.addMessageListener();
const embedVideoStyle = {
display: this.state.showingVideo ? 'block' : 'none',
height: '100%',
width: '100%'
};
return (
<div
className='vimeo-embed'
style={ embedVideoStyle }>
<iframe
frameBorder='0'
ref={el => {
this._player = el;
}}
src={ this.getIframeUrl() } />
</div>
);
};
renderLoading = (imageLoaded, loadingElement) => {
if (imageLoaded) {
return;
}
if (loadingElement) {
return loadingElement;
}
return (
<Spinner />
);
};
render() {
return (
<div className={ this.props.className } >
{ this.renderLoading(this.state.imageLoaded, this.props.loading) }
{ this.renderImage() }
{ this.renderIframe() }
</div>
);
}
}