UNPKG

@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
/* 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;