text: /**
   * Author: ひまわり(dtysky<dtysky@outlook.com>)
   * Github: https://github.com/dtysky
   * Created: 2017/6/1
   */
  import React, {Component} from 'react';
  import PropTypes from 'prop-types';
  import {findDOMNode} from 'react-dom';
  import cx from 'classnames';
  
  import {getKeyFromCode} from '../../utils';
  import Progress from './Progress';
  
  export default class VideoPlayer extends Component {
    static propTypes = {
      /**
       * @en
       * Switch the view to window or full-page.
       *
       * @cn
       * 切换窗口和全屏模式。
       */
      view: PropTypes.oneOf(['window', 'full']),
      /**
       * @en
       * 播放模式，可以设置为“随机”、“循环”、“单曲循环”、“通常”。
       *
       * @cn
       * Mode of playing, could be set to 'random', 'cycle', 'repeat', 'normal'.
       */
      mode: PropTypes.oneOf(['random', 'cycle', 'repeat', 'normal']),
      /**
       * @en
       * If current video can preload.
       *
       * @cn
       * 允许当前视频被预加载。
       */
      preload: PropTypes.string,
      /**
       * @en
       * If current video can auto play.
       *
       * @cn
       * 允许当前视频自动播放。
       */
      autoPlay: PropTypes.bool,
      /**
       * @en
       * Play current video.
       *
       * @cn
       * 播放当前视频。
       */
      play: PropTypes.bool,
      /**
       * @en
       * Set the time for current video.
       *
       * @cn
       * 设置当前视频的播放时间。
       */
      currentTime: PropTypes.number,
      /**
       * @en
       * Set volume.
       *
       * @cn
       * 设置音量。
       */
      volume: PropTypes.number,
      /**
       * @en
       * Video list.
       * It contains many items that describe the videos' information:
       * title: title of video.
       * desc: description of video.
       * sources: list of {src: link of source file, type: type of source}.
       * image: an image which will be shown when none of video sources is available.
       * text: an element which will be shown when none of video and image sources is available.
       * poster: poster of video.
       *
       * @cn
       * 视频列表。
       * 由一个被视频元素构造的列表组成：
       * title: 视频标题。
       * desc: 视频描述信息。
       * sources: 视频源列表：{src: 源文件链接，源类型}。
       * image: 当视频源文件都无效时，默认显示的图像。
       * text: 当视频源文件和默认图像都无效时，显示的元素。
       * poster: 视频封面。
       */
      list: PropTypes.arrayOf(
        PropTypes.shape({
          title: PropTypes.string.isRequired,
          desc: PropTypes.string,
          sources: PropTypes.arrayOf(
            PropTypes.shape({
              src: PropTypes.string.isRequired,
              type: PropTypes.string
            })
          ).isRequired,
          image: PropTypes.string,
          text: PropTypes.string,
          poster: PropTypes.string
        })
      ),
      /**
       * @en
       * Index of current video in list.
       *
       * @cn
       * 当前视频在列表中的序号。
       */
      currentItem: PropTypes.number,
      /**
       * @en
       * Default text of video.
       *
       * @cn
       * 视频默认介绍信息。
       */
      defaultText: PropTypes.string,
      /**
       * @en
       * Default image of video.
       *
       * @cn
       * 视频默认图片。
       */
      defaultImage: PropTypes.string,
      /**
       * @en
       * Default poster of video.
       *
       * @cn
       * 视频默认封面。
       */
      defaultPoster: PropTypes.string,
      /**
       * @en
       * Tooltip's style.
       *
       * @cn
       * 提示框的样式。
       */
      style: PropTypes.object,
      /**
       * @en
       * Tooltip's className.
       *
       * @cn
       * 提示框根元素的class。
       */
      className: PropTypes.string,
      /**
       * @en
       * Children.
       *
       * @cn
       * 子级元素。
       */
      children: PropTypes.node
    };
  
    static defaultProps = {
      view: 'window',
      mode: 'normal',
      autoPlay: false,
      preload: 'auto',
      list: [],
      defaultText: 'Not support !'
    };
  
    constructor(props) {
      super(props);
      this.parseSources(props.list);
      this.state = {
        // noList: props.list.length === 1,
        view: props.view,
        mode: props.mode,
        play: props.play || props.autoPlay || false,
        currentTime: props.currentTime || 0,
        currentPercent: 0,
        volume: props.volume || 1,
        currentItem: props.currentItem || 0,
        bufferedPercent: 0,
        showControllers: true,
        openList: false
      };
      this.timeoutId = 0;
      this.mouseTimeoutId = 0;
      this.durationStr = '00:00';
      this.currentStr = '00:00';
      this.refVideo = null;
      this.refRoot = null;
    }
  
    componentDidMount = () => {
      const dom = findDOMNode(this.refVideo);
      dom.addEventListener('canplaythrough', this.handleProgress);
      dom.addEventListener('timeupdate', this.handleTimeUpdate);
      dom.addEventListener('ended', this.handlePlayEnd);
      document.addEventListener('fullscreenchange', this.handleViewChange);
      document.addEventListener('webkitfullscreenchange', this.handleViewChange);
      document.addEventListener('mozfullscreenchange', this.handleViewChange);
      document.addEventListener('MSFullscreenChange', this.handleViewChange);
      if (this.state.play) {
        this.play();
      }
      this.handleMouseMove();
    }
  
    componentWillReceiveProps = nextProps => {
      this.setState(this.parseProps(nextProps), () => {
        if (this.state.play) {
          this.play();
        }
      });
    }
  
    componentWillUnmount = () => {
      clearTimeout(this.timeoutId);
      const dom = findDOMNode(this.refVideo);
      dom.removeEventListener('canplaythrough', this.handleProgress);
      dom.removeEventListener('timeupdate', this.handleTimeUpdate);
      dom.removeEventListener('ended', this.handlePlayEnd);
      document.removeEventListener('fullscreenchange', this.handleViewChange);
      document.removeEventListener('webkitfullscreenchange', this.handleViewChange);
      document.removeEventListener('mozfullscreenchange', this.handleViewChange);
      document.removeEventListener('MSFullscreenChange', this.handleViewChange);
    }
  
    parseProps = props => {
      this.parseSources(props.list);
      const {
        view,
        mode,
        play,
        currentTime,
        volume,
        currentItem
      } = this.state;
  
      return {
        // noList: props.list.length === 1,
        view: props.view || view,
        mode: props.mode || mode,
        play: props.play || play,
        currentTime: props.currentTime || currentTime,
        volume: props.volume || volume,
        currentItem: props.currentItem || currentItem
      };
    }
  
    parseSources = list => {
      const {
        defaultText,
        defaultImage,
        defaultPoster
      } = this.props;
  
      this.list = list;
      this.list.forEach(item => {
        if (!item.text) {
          item.text = defaultText;
        }
        if (!item.image && defaultImage) {
          item.image = defaultImage;
        }
        if (!item.poster && defaultPoster) {
          item.poster = defaultPoster;
        }
        item.sources.forEach(source => {
          if (!source.type) {
            source.type = this.genType(source.src);
          }
        });
      });
    }
  
    genType = url => {
      const temp = url.split('.');
      const ext = temp.pop();
      switch (ext) {
        case 'mp4':
          return 'video/mp4';
        case 'webm':
          return 'video/webm';
        case 'ogg':
        case 'ogv':
          return 'video/ogg';
        case 'mov':
          return 'video/quicktime';
        default:
          return 'text';
      }
    }
  
    smartFormatTime = time => {
      let s = ~~(time % 60);
      let m = time / 60;
      let h = 0;
      if (m >= 60) {
        h = h / 60;
        m = m % 60;
      }
  
      h = ~~h;
      m = ~~m;
  
      m = m < 10 ? `0${m}` : `${m}`;
      s = s < 10 ? `0${s}` : `${s}`;
  
      if (h) {
        return `${h}:${m}:${s}`;
      }
      return `${m}:${s}`;
    }
  
    play = () => {
      this.timeoutId = setTimeout(
        () => {
          this.refVideo.volume = this.state.volume;
          this.refVideo.play();
          this.setState({play: true});
        },
        10
      );
    }
  
    pause = () => {
      this.timeoutId = setTimeout(
        () => {
          this.refVideo.pause();
          this.setState({play: false});
        },
        10
      );
    }
  
    handleProgress = event => {
      const dom = event.target;
      if (dom.duration) {
        this.durationStr = this.smartFormatTime(dom.duration);
        this.setState({bufferedPercent: dom.buffered.end(0) / dom.duration * 100});
      }
    }
  
    handleTimeUpdate = event => {
      const duration = event.target.duration;
      const current = event.target.currentTime;
  
      this.currentStr = this.smartFormatTime(current);
      this.setState({
        currentTime: current,
        currentPercent: ~~((current / duration) * 100)
      });
    }
  
    handlePlayEnd = (event, pre = false) => {
      const {
        mode,
        currentItem
      } = this.state;
  
      // todo: really list, use another list for playing
      const max = this.list.length - 1;
      let next = currentItem;
  
      if (pre) {
        switch (mode) {
          case 'normal':
            if (currentItem === 0) {
              this.state.play = false;
              this.handleChangeItem(0);
              return;
            }
            next = currentItem - 1;
            break;
          case 'cycle':
            next = currentItem === 0 ? max : currentItem - 1;
            break;
          case 'repeat':
            break;
          case 'random':
            if (window.crypto) {
              next = ~~(window.crypto.getRandomValues(new Uint8Array([1]))[0] / 255 * (max + 1));
            } else {
              next = ~~(Math.random() * (max + 1));
            }
            if (next === currentItem) {
              next = currentItem === 0 ? max : currentItem - 1;
            }
            break;
          default:
            break;
        }
      } else {
        switch (mode) {
          case 'normal':
            if (currentItem === max) {
              this.state.play = false;
              this.handleChangeItem(max);
              return;
            }
            next = currentItem + 1;
            break;
          case 'cycle':
            next = currentItem === max ? 0 : currentItem + 1;
            break;
          case 'repeat':
            break;
          case 'random':
            if (window.crypto) {
              next = ~~(window.crypto.getRandomValues(new Uint8Array([1]))[0] / 255 * (max + 1));
            } else {
              next = ~~(Math.random() * (max + 1));
            }
            if (next === currentItem) {
              next = currentItem === 0 ? max : currentItem - 1;
            }
            break;
          default:
            break;
        }
      }
  
      this.handleChangeItem(next);
    }
  
    handlePressKey = e => {
      e.preventDefault();
      e.stopPropagation();
  
      const {
        currentPercent,
        volume
      } = this.state;
  
      switch (getKeyFromCode(e.keyCode || e.which)) {
        case 'LEFT':
          this.handleChangeCurrent(currentPercent - 1);
          break;
        case 'RIGHT':
          this.handleChangeCurrent(currentPercent + 1);
          break;
        case 'UP':
          this.handleChangeVolume(volume + 0.1);
          break;
        case 'DOWN':
          this.handleChangeVolume(volume - 0.1);
          break;
        case 'SPACE':
          this.handleSwitchPlay();
          break;
        default:
          break;
      }
    };
  
    handleSwitchPlay = () => {
      const {
        play
      } = this.state;
  
      if (play) {
        this.pause();
        return;
      }
      this.play();
    }
  
    handleSwitchVolume = () => {
      const {
        volume
      } = this.state;
  
      this.handleChangeVolume(volume ? 0 : 1);
    }
  
    handleChangeVolume = volume => {
      let nextVolume = volume;
      if (volume > 1) {
        nextVolume = 1;
      } else if (volume < 0) {
        nextVolume = 0;
      }
  
      this.setState({volume: nextVolume}, () => {
        this.refVideo.volume = this.state.volume;
      });
    }
  
    handleChangeCurrent = (current, cb) => {
      let nextCurrent = current;
      if (current > 100) {
        nextCurrent = 100;
      } else if (current < 0) {
        nextCurrent = 0;
      }
  
      const dom = findDOMNode(this.refVideo);
      dom.currentTime = dom.duration * nextCurrent / 100;
      this.setState({
        currentPercent: nextCurrent,
        currentTime: dom.currentTime
      }, cb ? cb() : () => {});
    }
  
    handleSwitchMode = () => {
      const {
        mode
      } = this.state;
      let nextMode = mode;
  
      switch (mode) {
        case 'normal':
          nextMode = 'cycle';
          break;
        case 'cycle':
          nextMode = 'repeat';
          break;
        case 'repeat':
          nextMode = 'random';
          break;
        case 'random':
          nextMode = 'normal';
          break;
        default:
          break;
      }
      this.setState({mode: nextMode});
    }
  
    handleSwitchView = () => {
      const {
        view
      } = this.state;
      const dom = findDOMNode(this.refRoot);
  
      if (view === 'full') {
        if (document.exitFullscreen) {
          document.exitFullscreen();
        } else if (document.msExitFullscreen) {
          document.msExitFullscreen();
        } else if (document.mozCancelFullScreen) {
          document.mozCancelFullScreen();
        } else if (document.webkitExitFullscreen) {
          document.webkitExitFullscreen();
        }
        return;
      }
  
      if (dom.requestFullscreen) {
        dom.requestFullscreen();
      } else if (dom.msRequestFullscreen) {
        dom.msRequestFullscreen();
      } else if (dom.mozRequestFullScreen) {
        dom.mozRequestFullScreen();
      } else if (dom.webkitRequestFullscreen) {
        dom.webkitRequestFullscreen();
      }
    }
  
    handleViewChange = () => {
      const fullscreen = document.isFullScreen
        || document.msIsFullScreen
        || document.mozIsFullScreen
        || document.webkitIsFullScreen;
  
      this.setState({view: fullscreen ? 'full' : 'window'});
    }
  
    handleSwitchList = () => {
      const {
        openList
      } = this.state;
  
      this.setState({openList: !openList});
    }
  
    handleChangeItem = item => {
      const {
        play
      } = this.state;
  
      const dom = findDOMNode(this.refVideo);
      this.setState({
        currentPercent: 0,
        currentTime: 0,
        currentItem: item
      });
      dom.load();
      if (play) {
        dom.play();
      } else {
        dom.pause();
      }
    }
  
    handleMouseMove = () => {
      clearTimeout(this.mouseTimeoutId);
      this.setState({showControllers: true});
      this.mouseTimeoutId = setTimeout(
        () => this.setState({showControllers: false}),
        2000
      );
    }
  
    render() {
      const {
        play,
        view
      } = this.state;
  
      return (
        <div
          className={cx(
            'hana-video-player',
            `hana-video-player-${view}`,
            this.props.className
          )}
          ref={ref => {
            this.refRoot = ref;
          }}
          tabIndex={-1}
          onKeyDown={this.handlePressKey}
          onMouseMove={this.handleMouseMove}
        >
          {this.renderVideo()}
          <div
            className={cx(
              'hana-video-player-mask',
              `hana-video-player-mask-${play ? 'pause' : 'play'}`
            )}
            onClick={this.handleSwitchPlay}
          />
          {this.renderList()}
          {this.renderControllers()}
          {this.renderProgress()}
        </div>
      );
    }
  
    renderVideo = () => {
      const {
        autoPlay,
        preload,
        defaultText,
        defaultImage,
        defaultPoster,
        style
      } = this.props;
  
      const {
        currentItem
      } = this.state;
  
      if (this.list.length === 0) {
        return this.props.children;
      }
  
      const item = this.list[currentItem];
      const poster = item.poster ? {poster: item.poster} : {poster: defaultPoster};
  
      return (
        <video
          key={item}
          className={cx('hana-video-player-video')}
          style={style}
          ref={ref => {
            this.refVideo = ref;
          }}
          {...poster}
          autoPlay={autoPlay}
          preload={preload}
        >
          {
            item.sources.map(({src, type}, index) => (
              <source key={index} src={src} type={type} />
            ))
          }
          <img src={item.image || defaultImage} alt={item.title} />
          <p>{item.text || defaultText}</p>
        </video>
      );
    }
  
    renderControllers = () => {
      const {
        play,
        volume,
        mode,
        showControllers
      } = this.state;
  
      if (this.list.length === 0) {
        return null;
      }
  
      return (
        <div
          className={cx(
            'hana-video-player-ctr',
            !showControllers && 'hana-video-player-ctr-hide'
          )}
        >
          <div className={cx('hana-video-player-ctr-bg')} />
          <div className={cx('hana-video-player-ctr-fg')} >
            <i
              className={cx(
                'hana-video-player-ctr-icon',
                'hana-video-player-ctr-mode',
                `hana-video-player-ctr-mode-${mode}`
              )}
              onClick={this.handleSwitchMode}
            />
            <div className={'hana-video-player-ctr-volume'}>
              <i
                className={cx(
                  'hana-video-player-ctr-icon',
                  `hana-video-player-ctr-volume-${volume === 0 ? 'z' : 'f'}`
                )}
                onClick={this.handleSwitchVolume}
              />
              <Progress
                className={cx('hana-video-player-progress-volume')}
                realTimeChange
                current={volume * 100}
                buffered={0}
                onChange={v => this.handleChangeVolume(v / 100)}
              />
            </div>
            <i
              className={cx(
                'hana-video-player-ctr-icon',
                'hana-video-player-ctr-pre'
              )}
              onClick={e => this.handlePlayEnd(e, true)}
            />
            <i
              className={cx(
                'hana-video-player-ctr-icon',
                `hana-video-player-ctr-${play ? 'pause' : 'play'}`
              )}
              onClick={this.handleSwitchPlay}
            />
            <i
              className={cx(
                'hana-video-player-ctr-icon',
                'hana-video-player-ctr-next'
              )}
              onClick={this.handlePlayEnd}
            />
            <i
              className={cx(
                'hana-video-player-ctr-icon',
                'hana-video-player-ctr-view-f'
              )}
              onClick={this.handleSwitchView}
            />
            <i
              className={cx(
                'hana-video-player-ctr-icon',
                'hana-video-player-ctr-list'
              )}
              onClick={this.handleSwitchList}
            />
          </div>
        </div>
      );
    }
  
    renderProgress = () => {
      const {
        currentPercent,
        bufferedPercent,
        showControllers
      } = this.state;
  
      if (this.list.length === 0) {
        return null;
      }
  
      return (
        <div
          className={cx(
            'hana-video-player-time-bar',
            !showControllers && 'hana-video-player-time-bar-hide'
          )}
        >
          <p>{this.currentStr}</p>
          <Progress
            className={cx('hana-video-player-progress-time')}
            current={currentPercent}
            buffered={bufferedPercent}
            onChange={this.handleChangeCurrent}
          />
          <p>{this.durationStr}</p>
        </div>
      );
    }
  
    renderList = () => {
      const {
        openList
      } = this.state;
  
      if (this.list.length <= 0) {
        return null;
      }
  
      return (
        <ul
          className={cx(
            'hana-video-player-list',
            `hana-video-player-list-${openList ? 'open' : 'close'}`
          )}
        >
          {
            this.list.map((item, index) => this.renderListItem(index, item))
          }
        </ul>
      );
    }
  
    renderListItem = (index, item) => {
      const {
        defaultImage,
        defaultPoster
      } = this.props;
  
      const {
        currentItem
      } = this.state;
  
      const {
        title,
        desc,
        image: realImage,
        poster: realPoster
      } = item;
  
      const image = realPoster || realImage || defaultPoster || defaultImage;
  
      return (
        <li
          key={index}
          className={cx(
            'hana-video-player-item',
            `hana-video-player-item-${index === currentItem ? 'active' : 'normal'}`
          )}
          onClick={() => {
            if (index !== currentItem) {
              this.handleChangeItem(index);
            }
          }}
        >
          <div
            className={cx('hana-video-player-item-poster')}
            style={{backgroundImage: `url(${image})`}}
          />
          <div className={cx('hana-video-player-item-info')}>
            <h3>{title}</h3>
            <p>{desc}</p>
          </div>
        </li>
      );
    }
  }
  