# Video 视频播放

- 来源：[E10 UI 组件库](https://www.e-cology.com.cn/ui/video/freepass)
- 平台：PC
- 维护者：屈正巍
- 设计稿：http://pd.e-cology.com.cn/pd/E10/interface/e10xiaoguotu/PC/E10zjys/#s5
- Issue：http://gitlab.ecology10.cn/FRONTEND/weapp-ui/-/issues?state=all&search=[Video]

## 代码演示

### 基础用法

直接引入组件进行使用

```jsx
import { Component } from 'react'
import { Video, Button, constants } from '@weapp/ui'

const prefixCls = `${constants.uiPcClsPrefix}-demo-video`;

class VideoDemo extends Component<any, any> {

  state = {
    baseSrc: 'https://www.w3schools.com/html/mov_bbb.mp4',
  }

  showVideo = () => {
    const { baseSrc } = this.state
    Video.showVideo(baseSrc, { useVideoJs: false })
  }

  customRenderTrigger = (_this: any, content: any) => (
    <Button
     
      type={'primary'}
    >
      预览弹层视频
    </Button>
  )

  render() {
    const { baseSrc } = this.state
    return (
      <div className={prefixCls}>
        <h3>注意：</h3>
        <h3>1.原生的video组件在没有指定宽高的情况下会按照视频的原始宽高展示</h3>
        <h3>2.如需根据容器的宽高来动态适配，请在style中给上宽高属性100%</h3>
        <h4>直接使用</h4>
        <Video
         
          src={baseSrc}
          useVideoJs={false}
        />
        <h4>弹层预览模式</h4>
        <Video
          isDialog
          customRender={this.customRenderTrigger}
          src={baseSrc}
          useVideoJs={false}
        />
        <h4>调用Video.showVideo</h4>
        <Button
         
          onClick={this.showVideo}
          type={'success'}
        >
          showVideo预览视频
        </Button>
      </div>
    );
  }
}

export default VideoDemo;
```

### 视频配置

通过video标签原生属性及api对视频内容进行控制制

```jsx
import { Component, ReactText } from 'react'
import { Video, constants, Button, Input, Checkbox } from '@weapp/ui'

const { InputNumber } = Input

const prefixCls = `${constants.uiPcClsPrefix}-demo-video`;
class VideoDemo extends Component<any, any> {

  state = {
    baseSrc: 'https://www.w3schools.com/html/mov_bbb.mp4',
    fluid: true,
    width: void 0,
    height: void 0,
    fullScreenScale: 100,
  }

  getVideoSize = () => {
    const { fluid, width, height } = this.state
    return fluid ? { width: '100%', height: '100%' } : { width, height }
  }

  onFullScreenScaleChange = (value: ReactText) => this.setState({ fullScreenScale: Number(value) })
  handleChangeFluid = (value: any) => {
    this.setState({ fluid: value })
  }
  handleWidthChange = (value: ReactText) => this.setState({ width: value })
  handleHeightChange = (value: ReactText) => this.setState({ height: value })

  customRenderTrigger1 = (_this: any, content: any) => <Button type={'primary'} style={{ marginLeft: 10 }}>弹层模式</Button>
  customRenderTrigger2 = (_this: any, content: any) => <Button type={'success'} style={{ marginLeft: 10 }}>弹层模式（无遮罩层）</Button>

  customRenderCloseBtn = () => <div style={{ width: '50px', height: '50px', border: '1px solid red' }}>自定义关闭按钮</div>

  render() {
    const { baseSrc, fluid, width, height, fullScreenScale } = this.state
    return (
      <div className={prefixCls}>
        <h4>配置项：视频宽高、比例、自动播放、静音、播放首帧图、各操作按钮的显隐、弹层右上角关闭按钮</h4>
        <h4>注意：因为原生video组件的性质，在给定poster的时候请给上宽高为100%以适配video组件大小</h4>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span>流式布局:</span>
          <Checkbox
            style={{ marginLeft: 10 }}
            value={fluid}
            onChange={this.handleChangeFluid} />
          <span>（注意：容器宽高只有在关闭流式布局时生效）</span>
        </div>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span>视频宽度:</span>
          <InputNumber
            style={{ width: 50, marginLeft: 10 }}
            value={width}
            onChange={this.handleWidthChange}
            max={9999}
            min={0}
          />
        </div>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span>视频高度:</span>
          <InputNumber
            style={{ width: 50, marginLeft: 10 }}
            value={height}
            onChange={this.handleHeightChange}
            max={9999}
            min={0}
          />
        </div>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span>弹层全屏比例:</span>
          <Input
            style={{ width: 50, marginLeft: 10 }}
            value={String(fullScreenScale)}
            onChange={this.onFullScreenScaleChange}
          />
          <span>（弹层全屏比例仅控制在80-100%，非合法范围按照100%处理）</span>
        </div>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span>弹层模式:</span>
          <Video
            useVideoJs={false}
            isDialog
            src={baseSrc}
            width={width}
            height={height}
            fullScreenScale={fullScreenScale}
            customRender={this.customRenderTrigger1}
          />
          <Video
            useVideoJs={false}
            isDialog
            needMask={false}
            src={baseSrc}
            width={width}
            height={height}
            fullScreenScale={fullScreenScale}
            customRender={this.customRenderTrigger2}
          />
        </div>
        <Video
          useVideoJs={false}
          src={baseSrc}
          // width={'100%'}
          // height={'100%'}
          {...this.getVideoSize()}
          controls
          autoPlay={false}
          controlsList={['nodownload']}
          muted
          customRenderCloseBtn={this.customRenderCloseBtn}
        />
      </div>
    )
  }
}

export default VideoDemo;
```

### videojs模式

引入三方媒体插件

```jsx
import { Component } from 'react'
import { Video, Button, constants } from '@weapp/ui'

const prefixCls = `${constants.uiPcClsPrefix}-demo-video`;

class VideoDemo extends Component<any, any> {

  state = {
    baseSrc: 'https://www.w3schools.com/html/mov_bbb.mp4',
    // baseSrc: '/api/file/preview?fileId=1144200109673226256&type=video&authModule=im&weappAuthStr=eyJ0YWciOiIxIiwibXNnaWQiOiIxNzQ5NjA0ODQ5MTQwNTAwNDQxIiwiZmxhZyI6MCwidXNlciI6eyJjaWQiOiIxNjY5OTg3NDU2MjEwMDAwMTA1IiwidWlkIjoiODAyMjQ2OTM0MDE4NzQ0MzIxIn19',
    // baseSrc: '111'
  }

  showVideo = () => {
    const { baseSrc } = this.state
    Video.showVideo(baseSrc, {
      useVideoJs: true,
      downloadSrc: baseSrc,
      // ...otherParams
    })
  }

  customRenderTrigger = (_this: any, content: any) => (
    <Button
     
      type={'primary'}
    >
      预览弹层视频
    </Button>
  )

  render() {
    const { baseSrc } = this.state
    return (
      <div className={prefixCls}>
        <h3>注意：</h3>
        <h3>1.为兼容历史使用的原生video配置，videojs组件在没有指定宽高时会默认按照视频原始宽高展示</h3>
        <h3>2.如需根据容器的宽高来动态适配，请在videoJsOptions中配置流式布局fluid：true</h3>
        <h3>3.目前的videojs模式暂不支持ie和国产化浏览器</h3>
        <h4>直接使用</h4>
        {/* <Video
         
          useVideoJs
          src={baseSrc}
        /> */}
        <h4>弹层预览模式</h4>
        <Video
          isDialog
          useVideoJs
          customRender={this.customRenderTrigger}
          src={baseSrc}
        />
        <h4>调用Video.showVideo</h4>
        <Button
         
          onClick={this.showVideo}
          type={'success'}
        >
          showVideo预览视频
        </Button>
      </div>
    );
  }
}

export default VideoDemo;
```

### videojs配置

根据ref获取组件内部的player元素，并调用相对应的api对视频内容进行控制

```jsx
import { Component, ReactText } from 'react'
import { Video, constants, Input, Button, Select, SingleType, SelectValueType, Checkbox } from '@weapp/ui'
const { InputNumber } = Input

const prefixCls = `${constants.uiPcClsPrefix}-demo-video`;

const options = [
  { id: 'opus', content: 'opus', },
  { id: 'ogv', content: 'ogv', },
  { id: 'mp4', content: 'mp4', },
  { id: 'mov', content: 'mov', },
  { id: 'm4v', content: 'm4v', },
  { id: 'mkv', content: 'mkv', },
  { id: 'm4a', content: 'm4a', },
  { id: 'mp3', content: 'mp3', },
  { id: 'aac', content: 'aac', },
  { id: 'caf', content: 'caf', },
  { id: 'flac', content: 'flac', },
  { id: 'oga', content: 'oga', },
  { id: 'wav', content: 'wav', },
  { id: 'm3u8', content: 'm3u8', },
  { id: 'mpd', content: 'mpd', },
  { id: 'jpg', content: 'jpg', },
  { id: 'jpeg', content: 'jpeg', },
  { id: 'gif', content: 'gif', },
  { id: 'png', content: 'png', },
  { id: 'svg', content: 'svg', },
  { id: 'webp', content: 'webp', },
]

class VideoDemo extends Component<any, any> {
  videoRef: any;
  getVideoRef = (el: any) => this.videoRef = el

  state = {
    baseSrc: 'https://www.w3schools.com/html/mov_bbb.mp4',
    baseSrcType: 'mp4',
    fluid: true,
    width: void 0,
    height: void 0,
  }

  onBaseSrcChange = (value: ReactText) => this.setState({ baseSrc: String(value) })
  handleChangeBaseSrcType = (value: SingleType<SelectValueType>) => this.setState({ baseSrcType: value })

  updateSrc = () => {
    const { baseSrc, baseSrcType } = this.state
    const src = [{ src: baseSrc || '', type: Video.getVideoType(baseSrcType || '') }]
    this.videoRef.player?.src(src)
  }
  pause = () => this.videoRef.player?.pause()
  play = () => this.videoRef.player?.play()
  reset = () => this.videoRef.player?.reset()
  requestFullscreen = () => this.videoRef.player?.requestFullscreen()
  handleChangeFluid = (value: any) => {
    this.setState({ fluid: value })
    this.videoRef.player?.fluid(value)
  }
  handleWidthChange = (value: ReactText) => this.setState({ width: value })
  handleHeightChange = (value: ReactText) => this.setState({ height: value })

  getVideoJsOptions = () => {
    const { fluid } = this.state
    const options: any = {
      // 是否使用流式布局，使播放器宽度适应父容器 boolean 
      // 播放器的的大小将不再是一个固定的值，它会根据宽高比（aspectRatio）动态计算出来，以适应其容器的大小。
      fluid: fluid,
      // 是否自动播放 boolean / 'muted' / 'play' / 'any'
      autoplay: false,
      // 是否静音 boolean
      muted: true,
      // 播放海报 string
      poster: '',
      // 控制操作按钮的显隐 boolean
      controls: true,
      // 视频预加载方式 'auto' / 'metadata' / 'none'
      preload: 'auto',
      // 控制栏 any
      controlBar: {
        'currentTimeDisplay': true,
        'timeDivider': true,
        'durationDisplay': true,
        'remainingTimeDisplay': true,
        // children -> 用于设置播放器可以包含的组件类型和顺序。
        // children: [
        //   { name: 'playToggle' },
        //   { name: 'currentTimeDisplay' },
        //   { name: 'progressControl' },
        //   { name: 'durationDisplay' },
        //   { name: 'playbackRateMenuButton' },
        //   { name: 'volumePanel' },
        //   { name: 'FullscreenToggle' }
        // ]
      },
    }
    return options
  }

  render() {
    const { baseSrc, baseSrcType, fluid, width, height } = this.state
    return (
      <div className={prefixCls}>
        <h4>注意：目前的videojs模式暂不支持ie和国产化浏览器</h4>
        <h4>配置项：视频宽高、比例、自动播放、静音、播放首帧图、各操作按钮的显隐类型以及顺序</h4>
        <h4>配置方法：根据ref拿到player，通过调用videojs内部api来进行控制</h4>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span style={{ display: 'flex', alignItems: 'center' }}>视频链接：</span>
          <Input
            value={baseSrc}
            onChange={this.onBaseSrcChange}
            style={{ flex: 1 }}
          />
          <Select
            data={options}
            value={baseSrcType}
            placeholder={'请选择视频格式'}
            style={{ display: 'flex', alignItems: 'center' }}
            onSelect={this.handleChangeBaseSrcType}
          />
          <Button
            onClick={this.updateSrc}
            type={'primary'}
            style={{ display: 'flex', alignItems: 'center' }}
          >
            更新视频链接
          </Button>
        </div>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span style={{ display: 'flex', alignItems: 'center' }}>控制播放：</span>
          <Button onClick={this.play} type={'success'}>播放</Button>
          <Button onClick={this.pause} type={'danger'}>暂停</Button>
          <Button onClick={this.reset} type={'warning'}>重置</Button>
          <Button onClick={this.requestFullscreen} type={'default'}>全屏</Button>
        </div>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span>流式布局:</span>
          <Checkbox
            style={{ marginLeft: 10 }}
            value={fluid}
            onChange={this.handleChangeFluid} />
          <span>（注意：容器宽高只有在关闭流式布局时生效）</span>
        </div>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span>视频宽度:</span>
          <InputNumber
            style={{ width: 50, marginLeft: 10 }}
            value={width}
            onChange={this.handleWidthChange}
            max={9999}
            min={0}
          />
        </div>
        <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
          <span>视频高度:</span>
          <InputNumber
            style={{ width: 50, marginLeft: 10 }}
            value={height}
            onChange={this.handleHeightChange}
            max={9999}
            min={0}
          />
        </div>
        <Video
          ref={this.getVideoRef}
          useVideoJs
          src={baseSrc}
          width={width}
          height={height}
          videoJsOptions={this.getVideoJsOptions()}
        />
        <h4>原生video组件类比</h4>
        <Video
          src={baseSrc}
          width={'100%'}
        />
      </div>
    )
  }
}

export default VideoDemo;
```

## API

### 弹窗模式下：

```typescript
1.如果设置了 width，将 video 高度将根据 width 自适应；
2.如果设置了 heigth，video 高度将根据 height 自适应;
3.两者同时设置，以 width 优先；两者都没有设置，将使用视频本身的高度。
```

### 使用videojs说明：

```typescript
1.如果采用videojs模式，可以参考demo4中，采用ref的形式拿到Video中的player，并调用相对应的方法对视频播放进行控制
2.兼容性问题：videojs的兼容文件类型如下（针对video与audio）
  mp4: 'video/mp4',
  mov: 'video/mp4',
  m4v: 'video/mp4',
  mkv: 'video/x-matroska',
  m4a: 'audio/mp4',
  mp3: 'audio/mpeg',
  aac: 'audio/aac',
  flac: 'audio/flac',
  oga: 'audio/ogg',
  wav: 'audio/wav',
  // m3u8: 'application/x-mpegURL',
  // mpd: 'application/dash+xml',
  // jpg: 'image/jpeg',
  // jpeg: 'image/jpeg',
  // gif: 'image/gif',
  // png: 'image/png',
  // svg: 'image/svg+xml',
  // webp: 'image/webp'
3.ios设备以及safari浏览器的兼容问题：
mac 和 ios 端只能接受分段流进行视频播放，后端直接通过接口返回流给组件，如果直接给视频链接会出现意外情况
4.目前文档上传有默认的最大文件限制，如需要上传高清的体积比较大的视频，则需要在后台管理中心中解开限制
```

### 参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| className | 自定义类名 | string | 否 |  |
| style | 自定义样式 | CSSProperties | 否 |  |
| isDialog | 是否为弹出视频 | boolean | 否 |  |
| src | 视频 url | string | 否 |  |
| poster | 海报帧 url | string | 否 |  |
| width | video 宽度 | number | 否 |  |
| height | video 高度 | number | 否 |  |
| controls | 是否提供用户控制 | boolean | 否 | true |
| autoPlay | 视频是否马上自动开始播放 | boolean | 否 | false |
| loop | 是否循环播放 | boolean | 否 | false |
| muted | 是否默认静音 | boolean | 否 | false |
| preload | 预加载配置项 | 'auto/metadata/none' | 否 | 'auto' |
| dialogProps | 弹窗自定义配置 | 参考 Dialog 组件 Props | 否 |  |
| onOpen | 弹窗模式打开回调 | () => void | 否 |  |
| onClose | 弹窗模式关闭回调 | () => void | 否 |  |
| showCloseBtn | 是否显示关闭按钮（仅在弹层模式下生效） | boolean | 否 | true |
| customRenderCloseBtn | 自定义渲染关闭按钮 | (_: any, ele: ReactNode) => ReactNode | 否 |  |
| useVideoJs | 是否采用三方的video.js | boolean | 否 | true |
| videoJsOptions | 三方的video.js配置options | VideoJsPlayerOptions | 否 |  |
| fullScreenScale | 全屏的比例 -> 最好控制在80-100% | number | 否 | 85 |
| needMask | 是否含有遮罩层 -> 关闭按钮位置不同 | boolean | 否 | true |
### Video.showVideo

通过 function 调用方式预览音频

```typescript
//其中otherParams为需要传递的参数
function showVideo(src?: string, otherParams?: AudioProps){
  ...
}
```
