@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
461 lines • 17.1 kB
JavaScript
/* eslint-disable jsx-a11y/click-events-have-key-events */
import React from 'react';
import cls from 'classnames';
import BaseComponent from '../_base/baseComponent';
import { cssClasses, DEFAULT_PLAYBACK_RATE, numbers, strings } from '@douyinfe/semi-foundation/lib/es/videoPlayer/constants';
import VideoPlayerFoundation from '@douyinfe/semi-foundation/lib/es/videoPlayer/foundation';
import '@douyinfe/semi-foundation/lib/es/videoPlayer/videoPlayer.css';
import { IconPlay, IconPause, IconVolume1, IconVolume2, IconRestart, IconFlipHorizontal, IconMinimize, IconMaximize, IconMute, IconPlayCircle, IconMiniPlayer } from '@douyinfe/semi-icons';
import Button from '../button';
import Popover from '../popover';
import AudioSlider from '../audioPlayer/audioSlider';
import Dropdown from '../dropdown';
import VideoProgress from './videoProgress';
import { formatTime } from './utils';
import isNullOrUndefined from '@douyinfe/semi-foundation/lib/es/utils/isNullOrUndefined';
import LocaleConsumer from '../locale/localeConsumer';
import ErrorSVG from './ErrorSvg';
const prefixCls = cssClasses.PREFIX;
class VideoPlayer extends BaseComponent {
constructor(props) {
super(props);
this.handleMouseEnterWrapper = () => {
this.foundation.handleMouseEnterWrapper();
};
this.handleMouseLeaveWrapper = () => {
this.foundation.handleMouseLeaveWrapper();
};
this.handleTimeChange = value => {
this.foundation.handleTimeChange(value);
};
this.handleTimeUpdate = () => {
this.foundation.handleTimeUpdate();
};
this.handleError = () => {
this.foundation.handleError();
};
this.handlePlay = () => {
this.foundation.handlePlay();
};
this.handlePause = () => {
this.foundation.handlePause();
};
this.handleCanPlay = () => {
this.foundation.handleCanPlay();
};
this.handleWaiting = locale => {
this.foundation.handleWaiting(locale);
};
this.handleStalled = locale => {
this.foundation.handleStalled(locale);
};
this.handleProgress = () => {
this.foundation.handleProgress();
};
this.handleEnded = () => {
this.foundation.handleEnded();
};
this.handleDurationChange = () => {
this.foundation.handleDurationChange();
};
this.handleVolumeChange = value => {
this.foundation.handleVolumeChange(value);
};
this.handleVolumeSilent = () => {
this.foundation.handleVolumeSilent();
};
this.handleRateChange = (option, locale) => {
this.foundation.handleRateChange(option, locale);
};
this.handleQualityChange = (option, locale) => {
this.foundation.handleQualityChange(option, locale);
};
this.handleRouteChange = (option, locale) => {
this.foundation.handleRouteChange(option, locale);
};
this.handleMirror = locale => {
this.foundation.handleMirror(locale);
};
this.handleFullscreen = () => {
this.foundation.handleFullscreen();
};
this.handlePictureInPicture = () => {
this.foundation.handlePictureInPicture();
};
this.getVolumeIcon = () => {
const {
volume,
muted
} = this.state;
if (muted) {
return /*#__PURE__*/React.createElement(IconMute, null);
}
if (volume < 50) {
return /*#__PURE__*/React.createElement(IconVolume1, null);
}
return /*#__PURE__*/React.createElement(IconVolume2, null);
};
this.isResourceNotFound = () => {
const {
src
} = this.props;
return isNullOrUndefined(src);
};
this.renderTime = () => {
const {
currentTime,
totalTime
} = this.state;
if (this.foundation.shouldShowControlItem(strings.TIME)) {
return /*#__PURE__*/React.createElement("div", {
className: cls(`${cssClasses.PREFIX_CONTROLS}-time`)
}, formatTime(currentTime), " / ", formatTime(totalTime));
}
return null;
};
this.renderResourceNotFound = locale => {
return /*#__PURE__*/React.createElement("div", {
className: cls(`${prefixCls}-resource-not-found`)
}, locale.noResource);
};
this.renderPauseIcon = () => {
const {
isPlaying,
isError
} = this.state;
if (!isPlaying && !isError) {
return (
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
React.createElement("div", {
className: cls(`${prefixCls}-pause`)
}, /*#__PURE__*/React.createElement(IconPlayCircle, null))
);
}
return null;
};
this.renderError = locale => {
const {
isError
} = this.state;
const {
theme
} = this.props;
if (isError) {
return /*#__PURE__*/React.createElement("div", {
className: cls(`${prefixCls}-error`, {
[`${prefixCls}-error-${theme}`]: theme
})
}, /*#__PURE__*/React.createElement("div", {
className: cls(`${prefixCls}-error-svg`)
}, /*#__PURE__*/React.createElement(ErrorSVG, null)), locale.videoError);
}
return null;
};
this.renderPoster = () => {
const {
poster
} = this.props;
const {
isPlaying,
currentTime,
totalTime
} = this.state;
const isHide = currentTime > 0 && currentTime < totalTime;
if (!isPlaying && poster) {
return /*#__PURE__*/React.createElement("img", {
className: cls(`${prefixCls}-poster`, {
[`${prefixCls}-poster-hide`]: isHide
}),
src: poster,
alt: "poster"
});
}
return null;
};
this.renderNotification = () => {
const {
showNotification,
notificationContent
} = this.state;
if (!showNotification || !notificationContent) {
return null;
}
return /*#__PURE__*/React.createElement("div", {
className: cls(`${prefixCls}-notification`)
}, this.state.notificationContent);
};
this.renderVolume = () => {
const {
volume,
muted
} = this.state;
if (this.foundation.shouldShowControlItem(strings.VOLUME)) {
return /*#__PURE__*/React.createElement(Popover, {
autoAdjustOverflow: true,
position: 'top',
className: cls(`${cssClasses.PREFIX_CONTROLS}-popover`),
content: /*#__PURE__*/React.createElement("div", {
className: cls(`${cssClasses.PREFIX_CONTROLS}-volume`)
}, /*#__PURE__*/React.createElement("div", {
className: cls(`${cssClasses.PREFIX_CONTROLS}-volume-title`)
}, muted ? 0 : volume, "%"), /*#__PURE__*/React.createElement(AudioSlider, {
value: muted ? 0 : volume,
max: 100,
vertical: true,
height: 120,
showTooltip: false,
onChange: this.handleVolumeChange
}))
}, /*#__PURE__*/React.createElement(Button, {
className: cls(`${cssClasses.PREFIX_CONTROLS}-menu-item`, `${cssClasses.PREFIX_CONTROLS}-menu-button`),
theme: 'borderless',
icon: this.getVolumeIcon(),
onClick: this.handleVolumeSilent
}));
}
return null;
};
this.renderIconButton = (icon, onClick, name) => {
if (!this.foundation.shouldShowControlItem(name)) {
return null;
}
return /*#__PURE__*/React.createElement(Button, {
theme: 'borderless',
className: cls(`${cssClasses.PREFIX_CONTROLS}-menu-item`, `${cssClasses.PREFIX_CONTROLS}-menu-button`),
icon: icon,
onClick: onClick
});
};
this.renderDropdownButton = (currentValue, list, handleChange, name, locale) => {
var _a;
if (this.foundation.shouldShowControlItem(name)) {
return /*#__PURE__*/React.createElement(Dropdown, {
position: 'top',
className: cls(`${cssClasses.PREFIX_CONTROLS}-popup-menu`),
render: /*#__PURE__*/React.createElement(Dropdown.Menu, null, list.map(option => (/*#__PURE__*/React.createElement(Dropdown.Item, {
className: cls(`${cssClasses.PREFIX_CONTROLS}-popup-menu-item`),
key: option.value,
onClick: () => handleChange(option, locale),
active: option.value === currentValue
}, option.label)))),
onChange: option => handleChange(option, locale)
}, /*#__PURE__*/React.createElement("div", {
className: cls(`${cssClasses.PREFIX_CONTROLS}-menu-item`, `${cssClasses.PREFIX_CONTROLS}-popup`)
}, (_a = list.find(option => option.value === currentValue)) === null || _a === void 0 ? void 0 : _a.label));
}
return null;
};
this.state = {
bufferedValue: 0,
currentQuality: props.defaultQuality || '',
currentRoute: props.defaultRoute || '',
currentTime: 0,
isError: false,
isMirror: false,
isPlaying: false,
muted: props.muted,
notificationContent: '',
playbackRate: props.defaultPlaybackRate || 1,
playbackRateList: props.playbackRateList,
showNotification: false,
showControls: true,
src: props.src || '',
totalTime: 0,
volume: props.muted ? 0 : props.volume
};
this.videoRef = /*#__PURE__*/React.createRef();
this.videoWrapperRef = /*#__PURE__*/React.createRef();
this.foundation = new VideoPlayerFoundation(this.adapter);
}
get adapter() {
return Object.assign(Object.assign({}, super.adapter), {
getVideo: () => this.videoRef.current,
getVideoWrapper: () => this.videoWrapperRef.current,
notifyPause: () => {
var _a, _b;
return (_b = (_a = this.props).onPause) === null || _b === void 0 ? void 0 : _b.call(_a);
},
notifyPlay: () => {
var _a, _b;
return (_b = (_a = this.props).onPlay) === null || _b === void 0 ? void 0 : _b.call(_a);
},
notifyQualityChange: quality => {
var _a, _b;
return (_b = (_a = this.props).onQualityChange) === null || _b === void 0 ? void 0 : _b.call(_a, quality);
},
notifyRateChange: rate => {
var _a, _b;
return (_b = (_a = this.props).onRateChange) === null || _b === void 0 ? void 0 : _b.call(_a, rate);
},
notifyRouteChange: route => {
var _a, _b;
return (_b = (_a = this.props).onRouteChange) === null || _b === void 0 ? void 0 : _b.call(_a, route);
},
notifyVolumeChange: volume => {
var _a, _b;
return (_b = (_a = this.props).onVolumeChange) === null || _b === void 0 ? void 0 : _b.call(_a, volume);
},
setBufferedValue: bufferedValue => this.setState({
bufferedValue
}),
setCurrentTime: currentTime => this.setState({
currentTime
}),
setIsError: isError => this.setState({
isError
}),
setIsMirror: isMirror => this.setState({
isMirror
}),
setIsPlaying: isPlaying => this.setState({
isPlaying
}),
setMuted: muted => this.setState({
muted
}),
setNotificationContent: content => this.setState({
notificationContent: content
}),
setPlaybackRate: rate => this.setState({
playbackRate: rate
}),
setQuality: quality => this.setState({
currentQuality: quality
}),
setRoute: route => this.setState({
currentRoute: route
}),
setShowControls: showControls => this.setState({
showControls
}),
setShowNotification: showNotification => this.setState({
showNotification: showNotification
}),
setTotalTime: totalTime => this.setState({
totalTime
}),
setVolume: volume => this.setState({
volume
})
});
}
static getDerivedStateFromProps(props, state) {
const states = {};
if (!isNullOrUndefined(props.src) && props.src !== state.src) {
states.src = props.src;
}
return states;
}
componentDidMount() {
this.foundation.init();
}
componentWillUnmount() {
this.foundation.destroy();
}
render() {
const {
markers,
qualityList,
routeList,
width,
height,
autoPlay,
style,
className,
loop,
captionsSrc,
crossOrigin,
theme
} = this.props;
const {
isPlaying,
playbackRate,
playbackRateList,
isMirror,
currentTime,
totalTime,
currentQuality,
currentRoute,
src,
bufferedValue,
showControls
} = this.state;
return /*#__PURE__*/React.createElement(LocaleConsumer, {
componentName: "VideoPlayer"
}, locale => {
return /*#__PURE__*/React.createElement("div", {
className: cls(`${prefixCls}`, className, {
[`${prefixCls}-mirror`]: isMirror
}),
style: Object.assign({
width,
height
}, style),
ref: this.videoWrapperRef,
onMouseEnter: this.handleMouseEnterWrapper,
onMouseLeave: this.handleMouseLeaveWrapper
}, /*#__PURE__*/React.createElement("div", {
className: cls(`${prefixCls}-wrapper`, {
[`${cssClasses.PREFIX}-wrapper-${theme}`]: theme
})
}, /*#__PURE__*/React.createElement("video", {
ref: this.videoRef,
autoPlay: autoPlay,
loop: loop,
controls: false,
crossOrigin: crossOrigin,
src: src,
onTimeUpdate: this.handleTimeUpdate,
onDurationChange: this.handleDurationChange,
onClick: () => {
this.foundation.handlePlayOrPause();
},
// An error occurred while getting the media data, or the resource is in an unsupported format.
onError: this.handleError,
onCanPlay: this.handleCanPlay,
// Playback stopped due to temporary lack of data.
onWaiting: () => this.handleWaiting(locale),
// The user agent attempted to fetch media data but was unexpectedly unable to fetch the data.
onStalled: () => this.handleStalled(locale),
onProgress: this.handleProgress,
onEnded: this.handleEnded
}, /*#__PURE__*/React.createElement("track", {
kind: "captions",
src: captionsSrc
})), this.isResourceNotFound() && this.renderResourceNotFound(locale)), this.renderPoster(), this.renderPauseIcon(), this.renderError(locale), this.renderNotification(), /*#__PURE__*/React.createElement("div", {
className: cls(`${cssClasses.PREFIX_CONTROLS}`, {
[`${cssClasses.PREFIX_CONTROLS}-hide`]: !showControls
})
}, /*#__PURE__*/React.createElement(VideoProgress, {
key: totalTime,
value: currentTime,
max: totalTime,
onChange: this.handleTimeChange,
markers: markers,
bufferedValue: bufferedValue
}), /*#__PURE__*/React.createElement("div", {
className: cls(`${cssClasses.PREFIX_CONTROLS}-menu`)
}, /*#__PURE__*/React.createElement("div", {
className: cls(`${cssClasses.PREFIX_CONTROLS}-menu-left`)
}, this.renderIconButton(isPlaying ? /*#__PURE__*/React.createElement(IconPause, null) : /*#__PURE__*/React.createElement(IconPlay, null), isPlaying ? this.handlePause : this.handlePlay, strings.PLAY), this.renderIconButton(/*#__PURE__*/React.createElement(IconRestart, {
rotate: 180
}), isPlaying ? this.handlePause : this.handlePlay, strings.NEXT), this.renderTime(), this.renderVolume(), this.renderDropdownButton(playbackRate, playbackRateList, this.handleRateChange, strings.PLAYBACK_RATE, locale)), /*#__PURE__*/React.createElement("div", {
className: cls(`${cssClasses.PREFIX_CONTROLS}-menu-right`)
}, qualityList && qualityList.length > 0 && this.renderDropdownButton(currentQuality, qualityList, this.handleQualityChange, strings.QUALITY, locale), routeList && routeList.length > 0 && this.renderDropdownButton(currentRoute, routeList, this.handleRouteChange, strings.ROUTE, locale), this.renderIconButton(/*#__PURE__*/React.createElement(IconFlipHorizontal, null), () => this.handleMirror(locale), strings.MIRROR), this.renderIconButton(this.foundation.checkFullScreen() ? /*#__PURE__*/React.createElement(IconMinimize, null) : /*#__PURE__*/React.createElement(IconMaximize, null), this.handleFullscreen, strings.FULLSCREEN), this.renderIconButton(/*#__PURE__*/React.createElement(IconMiniPlayer, null), this.handlePictureInPicture, strings.PICTURE_IN_PICTURE)))));
});
}
}
VideoPlayer.defaultProps = {
autoPlay: false,
clickToPlay: true,
defaultPlaybackRate: numbers.DEFAULT_PLAYBACK_RATE,
controlsList: [strings.PLAY, strings.NEXT, strings.TIME, strings.VOLUME, strings.PLAYBACK_RATE, strings.QUALITY, strings.ROUTE, strings.MIRROR, strings.FULLSCREEN, strings.PICTURE_IN_PICTURE],
loop: false,
muted: false,
playbackRateList: DEFAULT_PLAYBACK_RATE,
seekTime: numbers.DEFAULT_SEEK_TIME,
theme: strings.DARK,
volume: numbers.DEFAULT_VOLUME
};
export default VideoPlayer;