未验证 提交 56c21ed9 编写于 作者: J junjun666 提交者: GitHub

feat: video 视频组件 (#174)

上级 45724aa5
......@@ -698,6 +698,16 @@
"sort": 20,
"show": true,
"author": "hx"
},
{
"version": "1.0.0",
"name": "Video",
"type": "component",
"cName": "视频播放器",
"desc": "原生video实现的视频播放器",
"sort": 20,
"show": true,
"author": "swag~jun"
}
]
},
......
......@@ -89,19 +89,6 @@ const progressOption = {
backColor: '#d9d9d9',
progressColor: 'red',
}
const demoBtnStyle = {
textAlign: 'center',
width: '100%',
height: '50px',
borderTop: '1px solid rgba(234, 240, 251, 1)',
paddingTop: '6px',
background: 'rgba(255, 255, 255, 1)'
}
const demoPieceStyle = {
display: 'flex',
justifyContent: 'center',
background: 'rgba(255, 255, 255, 1)'
}
const App = () => {
const [percent, setPercent] = useState(50)
const [strokeInnerWidth, setStrokeInnerWidth] = useState(10)
......@@ -122,14 +109,14 @@ const App = () => {
}
return (
<>
<div className="demo__piece" style={demoPieceStyle}>
<div className="demo__piece">
<CircleProgress
progress={percent}
progressOption={progressOption}
strokeInnerWidth={strokeInnerWidth}
/>
</div>
<div className="demo__btn" style={demoBtnStyle}>
<div className="demo__btn">
<Button type="primary" onClick={setReduceVal} style={{ marginRight: '10px' }}>
reduce
</Button>
......
......@@ -110,20 +110,6 @@ export default App;
import React, { useState } from "react";
import { Button, CircleProgress } from '@nutui/nutui-react';
const demoBtnStyle = {
textAlign: 'center',
width: '100%',
height: '50px',
borderTop: '1px solid rgba(234, 240, 251, 1)',
paddingTop: '6px',
background: 'rgba(255, 255, 255, 1)'
}
const demoPieceStyle = {
display: 'flex',
justifyContent: 'center',
background: 'rgba(255, 255, 255, 1)',
padding: '10px 0'
}
const App = () => {
const [percent, setPercent] = useState(30)
......@@ -143,10 +129,10 @@ const App = () => {
return (
<>
<div className="demo__piece" style={demoPieceStyle}>
<div className="demo__piece">
<CircleProgress progress={percent} />
</div>
<div className="demo__btn" style={demoBtnStyle}>
<div className="demo__btn">
<Button type="primary" onClick={setReduceVal}>
减少
</Button>
......
......@@ -88,19 +88,6 @@ const progressOption = {
backColor: '#d9d9d9',
progressColor: 'red',
}
const demoBtnStyle = {
textAlign: 'center',
width: '100%',
height: '50px',
borderTop: '1px solid rgba(234, 240, 251, 1)',
paddingTop: '6px',
background: 'rgba(255, 255, 255, 1)'
}
const demoPieceStyle = {
display: 'flex',
justifyContent: 'center',
background: 'rgba(255, 255, 255, 1)'
}
const App = () => {
const [percent, setPercent] = useState(50)
const [strokeInnerWidth, setStrokeInnerWidth] = useState(10)
......@@ -121,14 +108,14 @@ const App = () => {
}
return (
<>
<div className="demo__piece" style={demoPieceStyle}>
<div className="demo__piece">
<CircleProgress
progress={percent}
progressOption={progressOption}
strokeInnerWidth={strokeInnerWidth}
/>
</div>
<div className="demo__btn" style={demoBtnStyle}>
<div className="demo__btn">
<Button type="primary" onClick={setReduceVal} style={{ marginRight: '10px' }}>
減少
</Button>
......
......@@ -65,7 +65,7 @@ const App = () => {
<>
<h2>Basic Usage</h2>
<Cell>
<ul className="infiniteUl" id="scroll" style={InfiniteUlStyle}>
<ul id="scroll" style={InfiniteUlStyle}>
<Infiniteloading
containerId="scroll"
useWindow={false}
......@@ -74,7 +74,7 @@ const App = () => {
>
{defultList.map((item, index) => {
return (
<li className="infiniteLi" key={index} style={InfiniteLiStyle}>
<li key={index} style={InfiniteLiStyle}>
{item}
</li>
)
......@@ -151,7 +151,7 @@ const App = () => {
<>
<h2>Pull to refresh</h2>
<Cell>
<ul className="infiniteUl" id="refreshScroll" style={InfiniteUlStyle}>
<ul id="refreshScroll" style={InfiniteUlStyle}>
<Infiniteloading
pullIcon="JD"
containerId="refreshScroll"
......@@ -163,7 +163,7 @@ const App = () => {
>
{refreshList.map((item, index) => {
return (
<li className="infiniteLi" key={index} style={InfiniteLiStyle}>
<li key={index} style={InfiniteLiStyle}>
{item}
</li>
)
......@@ -232,7 +232,7 @@ const App = () => {
<>
<h2>Custom loading copywriting</h2>
<Cell>
<ul className="infiniteUl" id="customScroll" style={InfiniteUlStyle}>
<ul id="customScroll" style={InfiniteUlStyle}>
<Infiniteloading
containerId="customScroll"
useWindow={false}
......@@ -243,7 +243,7 @@ const App = () => {
>
{customList.map((item, index) => {
return (
<li className="infiniteLi" key={index} style={InfiniteLiStyle}>
<li key={index} style={InfiniteLiStyle}>
{item}
</li>
)
......
......@@ -64,7 +64,7 @@ const App = () => {
<>
<h2>基础用法</h2>
<Cell>
<ul className="infiniteUl" id="scroll" style={InfiniteUlStyle}>
<ul id="scroll" style={InfiniteUlStyle}>
<Infiniteloading
containerId="scroll"
useWindow={false}
......@@ -73,7 +73,7 @@ const App = () => {
>
{defultList.map((item, index) => {
return (
<li className="infiniteLi" key={index} style={InfiniteLiStyle}>
<li key={index} style={InfiniteLiStyle}>
{item}
</li>
)
......@@ -150,7 +150,7 @@ const App = () => {
<>
<h2>下拉刷新</h2>
<Cell>
<ul className="infiniteUl" id="refreshScroll" style={InfiniteUlStyle}>
<ul id="refreshScroll" style={InfiniteUlStyle}>
<Infiniteloading
pullIcon="JD"
containerId="refreshScroll"
......@@ -231,7 +231,7 @@ const App = () => {
<>
<h2>自定义加载文案</h2>
<Cell>
<ul className="infiniteUl" id="customScroll" style={InfiniteUlStyle}>
<ul id="customScroll" style={InfiniteUlStyle}>
<Infiniteloading
containerId="customScroll"
useWindow={false}
......@@ -242,7 +242,7 @@ const App = () => {
>
{customList.map((item, index) => {
return (
<li className="infiniteLi" key={index} style={InfiniteLiStyle}>
<li key={index} style={InfiniteLiStyle}>
{item}
</li>
)
......
......@@ -65,7 +65,7 @@ const App = () => {
<>
<h2>基礎用法</h2>
<Cell>
<ul className="infiniteUl" id="scroll" style={InfiniteUlStyle}>
<ul id="scroll" style={InfiniteUlStyle}>
<Infiniteloading
containerId="scroll"
useWindow={false}
......@@ -74,7 +74,7 @@ const App = () => {
>
{defultList.map((item, index) => {
return (
<li className="infiniteLi" key={index} style={InfiniteLiStyle}>
<li key={index} style={InfiniteLiStyle}>
{item}
</li>
)
......@@ -151,7 +151,7 @@ const App = () => {
<>
<h2>下拉刷新</h2>
<Cell>
<ul className="infiniteUl" id="refreshScroll" style={InfiniteUlStyle}>
<ul id="refreshScroll" style={InfiniteUlStyle}>
<Infiniteloading
pullIcon="JD"
containerId="refreshScroll"
......@@ -163,7 +163,7 @@ const App = () => {
>
{refreshList.map((item, index) => {
return (
<li className="infiniteLi" key={index} style={InfiniteLiStyle}>
<li key={index} style={InfiniteLiStyle}>
{item}
</li>
)
......@@ -232,7 +232,7 @@ const App = () => {
<>
<h2>自定義加載文案</h2>
<Cell>
<ul className="infiniteUl" id="customScroll" style={InfiniteUlStyle}>
<ul id="customScroll" style={InfiniteUlStyle}>
<Infiniteloading
containerId="customScroll"
useWindow={false}
......@@ -243,7 +243,7 @@ const App = () => {
>
{customList.map((item, index) => {
return (
<li className="infiniteLi" key={index} style={InfiniteLiStyle}>
<li key={index} style={InfiniteLiStyle}>
{item}
</li>
)
......
......@@ -91,48 +91,27 @@ export default App;
import React, { useState } from 'react'
import { Skeleton, Switch, Avatar } from '@nutui/nutui-react';
const NutSwitchStyle = {
display: 'flex',
margin: '0 16px 8px 0'
}
const RightContentStyle = {
marginLeft: '19px',
fontFamily: 'PingFangSC',
display: 'flex',
flexDirection: 'column'
}
const TitleStyle = {
fontSize: '14px',
color: 'rgba(51, 51, 51, 1)'
}
const DescStyle = {
marginTop: '10px',
fontSize: '13px',
color: 'rgba(154, 155, 157, 1)'
}
const App = () => {
const [checked, setChecked] = useState(false)
const changeStatus = (value: boolean, event: React.MouseEvent<Element, MouseEvent>) => {
console.log(`触发了change事件,开关状态:${value}`)
setChecked(value)
}
return (
<>
<div className="content">
<Switch size="15px" change={(value, event) => changeStatus(value, event)} className={NutSwitchStyle} />
<Skeleton width={250} height={15} title animated avatar row={3} loading={checked}>
<div className="container" style={{ display: 'flex' }}>
<Switch size="15px" change={(value, event) => changeStatus(value, event)} />
<Skeleton width="250px" height="15px" title animated avatar row={3} loading={checked}>
<div className="container">
<Avatar
size="50"
icon="https://img14.360buyimg.com/imagetools/jfs/t1/167902/2/8762/791358/603742d7E9b4275e3/e09d8f9a8bf4c0ef.png"
/>
<div className="right-content" className={RightContentStyle}>
<span className="title" className={TitleStyle}>NutUI-React</span>
<div className="desc" className={DescStyle}>
A set of JD style lightweight mobile terminal Vue group library provides rich basic components and business components to help developers quickly build mobile applications.
<div className="right-content">
<span className="title">NutUI-React</span>
<div className="desc">
一套京东风格的轻量级移动端React组件库,提供丰富的基础组件和业务组件,帮助开发者快速搭建移动应用。
</div>
</div>
</div>
......
......@@ -91,28 +91,6 @@ export default App;
import React, { useState } from 'react'
import { Skeleton, Switch, Avatar } from '@nutui/nutui-react';
const NutSwitchStyle = {
display: 'flex',
margin: '0 16px 8px 0'
}
const RightContentStyle = {
marginLeft: '19px',
fontFamily: 'PingFangSC',
display: 'flex',
flexDirection: 'column'
}
const TitleStyle = {
fontSize: '14px',
color: 'rgba(51, 51, 51, 1)'
}
const DescStyle = {
marginTop: '10px',
fontSize: '13px',
color: 'rgba(154, 155, 157, 1)'
}
const App = () => {
const [checked, setChecked] = useState(false)
......@@ -123,16 +101,16 @@ const App = () => {
return (
<>
<div className="content">
<Switch size="15px" change={(value, event) => changeStatus(value, event)} className={NutSwitchStyle} />
<Switch size="15px" change={(value, event) => changeStatus(value, event)} />
<Skeleton width="250px" height="15px" title animated avatar row={3} loading={checked}>
<div className="container" style={{ display: 'flex' }}>
<div className="container">
<Avatar
size="50"
icon="https://img14.360buyimg.com/imagetools/jfs/t1/167902/2/8762/791358/603742d7E9b4275e3/e09d8f9a8bf4c0ef.png"
/>
<div className="right-content" className={RightContentStyle}>
<span className="title" className={TitleStyle}>NutUI-React</span>
<div className="desc" className={DescStyle}>
<div className="right-content">
<span className="title">NutUI-React</span>
<div className="desc">
一套京东风格的轻量级移动端React组件库,提供丰富的基础组件和业务组件,帮助开发者快速搭建移动应用。
</div>
</div>
......
......@@ -91,48 +91,27 @@ export default App;
import React, { useState } from 'react'
import { Skeleton, Switch, Avatar } from '@nutui/nutui-react';
const NutSwitchStyle = {
display: 'flex',
margin: '0 16px 8px 0'
}
const RightContentStyle = {
marginLeft: '19px',
fontFamily: 'PingFangSC',
display: 'flex',
flexDirection: 'column'
}
const TitleStyle = {
fontSize: '14px',
color: 'rgba(51, 51, 51, 1)'
}
const DescStyle = {
marginTop: '10px',
fontSize: '13px',
color: 'rgba(154, 155, 157, 1)'
}
const App = () => {
const [checked, setChecked] = useState(false)
const changeStatus = (value: boolean, event: React.MouseEvent<Element, MouseEvent>) => {
console.log(`触发了change事件,开关状态:${value}`)
setChecked(value)
}
return (
<>
<div className="content">
<Switch size="15px" change={(value, event) => changeStatus(value, event)} className={NutSwitchStyle} />
<Skeleton width={250} height={15} title animated avatar row={3} loading={checked}>
<div className="container" style={{ display: 'flex' }}>
<Switch size="15px" change={(value, event) => changeStatus(value, event)} />
<Skeleton width="250px" height="15px" title animated avatar row={3} loading={checked}>
<div className="container">
<Avatar
size="50"
icon="https://img14.360buyimg.com/imagetools/jfs/t1/167902/2/8762/791358/603742d7E9b4275e3/e09d8f9a8bf4c0ef.png"
/>
<div className="right-content" className={RightContentStyle}>
<span className="title" className={TitleStyle}>NutUI-React</span>
<div className="desc" className={DescStyle}>
一套京東風格的輕量級移動端React組件庫,提供豐富的基礎組件和業務組件,幫助開發者快速搭建移動應用。
<div className="right-content">
<span className="title">NutUI-React</span>
<div className="desc">
一套京东风格的轻量级移动端React组件库,提供丰富的基础组件和业务组件,帮助开发者快速搭建移动应用。
</div>
</div>
</div>
......@@ -147,7 +126,6 @@ export default App;
## API
### Prop
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`video base info 1`] = `
<div>
<div
class="nut-video"
>
<video
autoplay=""
class="nut-video-player"
controls=""
loop=""
poster="https://img12.360buyimg.com/ling/s345x208_jfs/t1/168105/33/8417/54825/603df06dEfcddc4cb/21f9f5d0a1b3dad4.jpg.webp"
src="xxx.mp4"
>
<source
src="xxx.mp4"
type="video/mp4"
/>
</video>
</div>
</div>
`;
import * as React from 'react'
import { render } from '@testing-library/react'
import '@testing-library/jest-dom'
import { Video } from '../video'
test('video base info', () => {
const App = () => {
const source = {
src: 'xxx.mp4',
type: 'video/mp4',
}
const options = {
controls: true,
autoplay: true,
playsinline: true,
loop: true,
poster:
'https://img12.360buyimg.com/ling/s345x208_jfs/t1/168105/33/8417/54825/603df06dEfcddc4cb/21f9f5d0a1b3dad4.jpg.webp',
}
return <Video source={source} options={options} />
}
const { container } = render(<App />)
const sourceDom = container.querySelector('.nut-video source')
expect(sourceDom?.getAttribute('src')).toBe('xxx.mp4')
expect(container).toMatchSnapshot()
})
.demo-video {
padding: 57px 0 0 0 !important;
}
.nut-cell {
padding: 0;
&::after {
display: none;
}
}
.m-b {
margin-bottom: 60px;
}
import React, { useState } from 'react'
import { useTranslate } from '../../sites/assets/locale'
import Cell from '@/packages/cell'
import Button from '@/packages/button'
import { Video } from './video'
import './demo.scss'
interface T {
'84aa6bce': string
a5a25e88: string
'19875a3f': string
fcdac2ed: string
'200baa8c': string
'5ec0e561': string
a6e0b0cf: string
}
const VideoDemo = () => {
const [translated] = useTranslate<T>({
'zh-CN': {
'84aa6bce': '基础用法',
a5a25e88: '自动播放',
'19875a3f': '初始化静音',
fcdac2ed: '视频封面海报设置',
'200baa8c': '行内播放',
'5ec0e561': '设置视频为背景图',
a6e0b0cf: '视频切换',
},
'zh-TW': {
'84aa6bce': '基礎用法',
a5a25e88: '自動播放',
'19875a3f': '初始化靜音',
fcdac2ed: '視頻封面海報設置',
'200baa8c': '行內播放',
'5ec0e561': '設置視頻為背景圖',
a6e0b0cf: '視頻切換',
},
'en-US': {
'84aa6bce': 'Basic Usage',
a5a25e88: 'Auto play',
'19875a3f': 'Initialize mute',
fcdac2ed: 'Video cover poster settings',
'200baa8c': 'play inline',
'5ec0e561': 'Set video as background',
a6e0b0cf: 'Video switching',
},
})
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const [source1, setSource1] = useState({
src: 'https://storage.360buyimg.com/nutui/video/legao-%E6%9D%A8%E8%BF%9B%E5%86%9B.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
}
const options1 = {
autoplay: true,
muted: true,
controls: true,
}
const options2 = {
muted: true,
controls: true,
}
const options3 = {
controls: true,
poster:
'https://img12.360buyimg.com/ling/s345x208_jfs/t1/168105/33/8417/54825/603df06dEfcddc4cb/21f9f5d0a1b3dad4.jpg.webp',
}
const options4 = {
controls: true,
playsinline: true,
}
const options5 = {
controls: false,
autoplay: true,
disabled: true,
muted: true,
playsinline: true,
loop: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
const changeVideo = () => {
setSource1({ ...source1, src: 'https://vjs.zencdn.net/v/oceans.mp4' })
}
return (
<>
<div className="demo demo-video">
<h2>{translated['84aa6bce']}</h2>
<Cell className="cell">
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
/>
</Cell>
<h2>{translated.a5a25e88}</h2>
<Cell className="cell">
<Video
source={source}
options={options1}
play={play}
pause={pause}
playend={playend}
/>
</Cell>
<h2>{translated['19875a3f']}</h2>
<Cell className="cell">
<Video
source={source}
options={options2}
play={play}
pause={pause}
playend={playend}
/>
</Cell>
<h2>{translated.fcdac2ed}</h2>
<Cell className="cell">
<Video
source={source}
options={options3}
play={play}
pause={pause}
playend={playend}
/>
</Cell>
<h2>{translated['200baa8c']}</h2>
<Cell className="cell">
<Video
source={source}
options={options4}
play={play}
pause={pause}
playend={playend}
/>
</Cell>
<h2>{translated['5ec0e561']}</h2>
<Cell className="cell">
<Video
source={source}
options={options5}
play={play}
pause={pause}
playend={playend}
/>
</Cell>
<h2>{translated.a6e0b0cf}</h2>
<Cell className="cell">
<Video
source={source1}
options={options}
play={play}
pause={pause}
playend={playend}
/>
</Cell>
<Button type="primary" className="m-b" onClick={changeVideo}>
{translated.a6e0b0cf}
</Button>
</div>
</>
)
}
export default VideoDemo
# Video
### Intro
Video player implemented by native video
### Install
``` ts
import { Video } from '@nutui/nutui-react';
```
### Basic Usage
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>Basic Usage</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### Auto play
autoplay Property to set video autoplay
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
autoplay: true,
muted: true,
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>Auto play</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### Initialize mute
The muted property sets the initial mute of the video
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
muted: true,
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>Initialize mute</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### Video cover poster settings
The poster property sets the video poster
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
poster:
'https://img12.360buyimg.com/ling/s345x208_jfs/t1/168105/33/8417/54825/603df06dEfcddc4cb/21f9f5d0a1b3dad4.jpg.webp',
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>Video cover poster settings</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### play inline
The playsinline property sets the mobile terminal video to play in line and prevents the newly opened page from playing (compatible with IOS and some Android machines)
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
playsinline: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>play inline</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### Set video as background
When setting the video as the background image, it is necessary to set muted, disabled, operation prohibited, loop, loop and autoplay to true, and the mobile terminal needs to set playinline for in-line display
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: false,
autoplay: true,
disabled: true,
muted: true,
playsinline: true,
loop: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>Set video as background</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### Video switching
Reset the video when the video address changes
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video, Button } from '@nutui/nutui-react';
const App = () => {
const [source1, setSource1] = useState({
src: 'https://storage.360buyimg.com/nutui/video/legao-%E6%9D%A8%E8%BF%9B%E5%86%9B.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
const changeVideo = () => {
setSource1({...source1, src: 'https://vjs.zencdn.net/v/oceans.mp4'})
}
return (
<>
<h2>Video switching</h2>
<Cell className='cell'>
<Video
source={source1}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
<Button type="primary" className="m-b" onClick={changeVideo}>Video switching</Button>
</>
)
}
export default App;
```
:::
## API
### Props
| Attribute | Description | Type | Default |
| ------------------- | ------------------------------------------ | ------- | -------- |
| source | Video url and type settings | Object | - |
| options | Control video playback properties | Object | required |
| options.autoplay | Auto play | Boolean | false |
| options.poster | Poster settings | String | - |
| options.loop | Poster loop | Boolean | false |
| options.controls | Show operation control | Boolean | true |
| options.muted | Mute | Boolean | false |
| options.playsinline | Whether to set as inline playback element (solve Android compatibility problem) | Boolean | false |
### Events
| Event | Description | Arguments |
| -------- | ------------ | -------- |
| play | play event | -- |
| pause | pause event | -- |
| playend | Playback completion callback | -- |
\ No newline at end of file
# Video 视频播放器
### 介绍
原生video实现的视频播放器
### 安装
``` ts
import { Video } from '@nutui/nutui-react';
```
## 代码演示
### 基础用法
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>基础用法</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 自动播放
autoplay 属性设置视频自动播放
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
autoplay: true,
muted: true,
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>自动播放</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 初始化静音
muted属性设置视频初始化静音
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
muted: true,
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>初始化静音</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 视频封面海报设置
poster 属性设置视频海报
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
poster:
'https://img12.360buyimg.com/ling/s345x208_jfs/t1/168105/33/8417/54825/603df06dEfcddc4cb/21f9f5d0a1b3dad4.jpg.webp',
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>视频封面海报设置</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 行内播放
playsinline 属性设置移动端视频行内播放,阻止新打开页面播放(兼容 ios,兼容部分安卓机)
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
playsinline: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>行内播放</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 视频背景图
当设置视频为背景图时需要将 muted 静音、 disabled 禁止操作、loop 循环播放、autoplay 自动播放设置为 true,移动端需要设置 playsinline 行内展示
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: false,
autoplay: true,
disabled: true,
muted: true,
playsinline: true,
loop: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>设置视频为背景图</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 视频切换
当视频地址发生变化时,重置视频
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video, Button } from '@nutui/nutui-react';
const App = () => {
const [source1, setSource1] = useState({
src: 'https://storage.360buyimg.com/nutui/video/legao-%E6%9D%A8%E8%BF%9B%E5%86%9B.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
const changeVideo = () => {
setSource1({...source1, src: 'https://vjs.zencdn.net/v/oceans.mp4'})
}
return (
<>
<h2>视频切换</h2>
<Cell className='cell'>
<Video
source={source1}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
<Button type="primary" className="m-b" onClick={changeVideo}>视频切换</Button>
</>
)
}
export default App;
```
:::
## API
### Props
| 字段 | 说明 | 类型 | 默认值 |
| ------------------- | ------------------------------------------ | ------- | -------- |
| source | 视频地址和类型设置 | Object | - |
| options | 控制视频播放属性 | Object | required |
| options.autoplay | 是否自动播放 | Boolean | false |
| options.poster | 海报设置 | String | - |
| options.loop | 是否循环播放 | Boolean | false |
| options.controls | 是否展示操作栏 | Boolean | true |
| options.muted | 是否静音 | Boolean | false |
| options.playsinline | 是否设置为行内播放元素(解决安卓兼容问题) | Boolean | false |
### Events
| 事件名称 | 说明 | 回调参数 |
| -------- | ------------ | -------- |
| play | 播放 | -- |
| pause | 暂停 | -- |
| playend | 播放完成回调 | -- |
\ No newline at end of file
# Video 視頻播放器
### 介紹
原生video實現的視頻播放器
### 安裝
``` ts
import { Video } from '@nutui/nutui-react';
```
## 代碼演示
### 基礎用法
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: true,cd
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>基礎用法</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 自動播放
autoplay 屬性設置視頻自動播放
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
autoplay: true,
muted: true,
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>自動播放</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 初始化靜音
muted屬性設置視頻初始化靜音
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
muted: true,
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>初始化靜音</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 視頻封面海報設置
poster 屬性設置視頻海報
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
poster:
'https://img12.360buyimg.com/ling/s345x208_jfs/t1/168105/33/8417/54825/603df06dEfcddc4cb/21f9f5d0a1b3dad4.jpg.webp',
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>視頻封面海報設置</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 行內播放
playsinline 屬性設置移動端視頻行內播放,阻止新打開頁面播放(兼容 ios,兼容部分安卓機)
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
playsinline: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>行內播放</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 視頻背景圖
當設置視頻為背景圖時需要將 muted 靜音、 disabled 禁止操作、loop 循環播放、autoplay 自動播放設置為 true,移動端需要設置 playsinline 行內展示
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video } from '@nutui/nutui-react';
const App = () => {
const [source, setSource] = useState({
src: 'https://storage.360buyimg.com/nutui/video/video_NutUI.mp4',
type: 'video/mp4',
})
const options = {
controls: false,
autoplay: true,
disabled: true,
muted: true,
playsinline: true,
loop: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
return (
<>
<h2>設置視頻為背景圖</h2>
<Cell className='cell'>
<Video
source={source}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
</>
)
}
export default App;
```
:::
### 視頻切換
當視頻地址發生變化時,重置視頻
:::demo
```tsx
import React, { useState } from "react";
import { Cell, Video, Button } from '@nutui/nutui-react';
const App = () => {
const [source1, setSource1] = useState({
src: 'https://storage.360buyimg.com/nutui/video/legao-%E6%9D%A8%E8%BF%9B%E5%86%9B.mp4',
type: 'video/mp4',
})
const options = {
controls: true,
}
const play = (elm: HTMLVideoElement) => console.log('play', elm)
const pause = (elm: HTMLVideoElement) => console.log('pause', elm)
const playend = (elm: HTMLVideoElement) => console.log('playend', elm)
const changeVideo = () => {
setSource1({...source1, src: 'https://vjs.zencdn.net/v/oceans.mp4'})
}
return (
<>
<h2>視頻切換</h2>
<Cell className='cell'>
<Video
source={source1}
options={options}
play={play}
pause={pause}
playend={playend}
></Video>
</Cell>
<Button type="primary" className="m-b" onClick={changeVideo}>視頻切換</Button>
</>
)
}
export default App;
```
:::
## API
### Props
| 字段 | 說明 | 類型 | 默認值 |
| ------------------- | ------------------------------------------ | ------- | -------- |
| source | 視頻地址和類型設置 | Object | - |
| options | 控制視頻播放屬性 | Object | required |
| options.autoplay | 是否自動播放 | Boolean | false |
| options.poster | 海報設置 | String | - |
| options.loop | 是否循環播放 | Boolean | false |
| options.controls | 是否展示操作欄 | Boolean | true |
| options.muted | 是否靜音 | Boolean | false |
| options.playsinline | 是否設置為行內播放元素(解決安卓兼容問題) | Boolean | false |
### Events
| 事件名稱 | 說明 | 回調參數 |
| -------- | ------------ | -------- |
| play | 播放 | -- |
| pause | 暫停 | -- |
| playend | 播放完成回調 | -- |
\ No newline at end of file
import { Video } from './video'
export default Video
.nut-video {
width: 100%;
height: 100%;
position: relative;
display: flex;
&-player {
width: 100%;
background: #000;
&:focus {
outline: none;
}
}
video {
width: 100%;
height: 100%;
object-fit: fill;
}
}
import React, { useEffect, useRef, FunctionComponent } from 'react'
import classNames from 'classnames'
import bem from '@/utils/bem'
export interface VideoProps {
source: {
type: string
src: string
}
options: {
controls?: boolean
muted?: boolean
autoplay?: boolean
poster?: string
playsinline?: boolean
loop?: boolean
}
className: string
style: React.CSSProperties
play: (e: HTMLVideoElement) => void
pause: (e: HTMLVideoElement) => void
playend: (e: HTMLVideoElement) => void
}
const defaultProps = {
source: {
type: {},
src: '',
},
options: {
controls: true,
muted: false, // 默认不是静音
autoplay: false,
poster: '',
playsinline: false,
loop: false,
},
} as VideoProps
export const Video: FunctionComponent<
Partial<VideoProps> & React.HTMLAttributes<HTMLDivElement>
> = (props) => {
const {
children,
source,
options,
className,
play,
pause,
playend,
...restProps
} = {
...defaultProps,
...props,
}
const rootRef = useRef<HTMLVideoElement>(null)
const b = bem('video')
const classes = classNames(className, b(''))
useEffect(() => {
init()
}, [])
const init = () => {
if (rootRef.current) {
const videoRef = rootRef.current
if (options.autoplay) {
setTimeout(() => {
videoRef.play()
}, 200)
}
if (options.playsinline) {
videoRef.setAttribute('playsinline', String(options.playsinline))
videoRef.setAttribute('webkit-playsinline', String(options.playsinline))
videoRef.setAttribute('x5-video-player-type', 'h5-page')
videoRef.setAttribute('x5-video-player-fullscreen', 'false')
}
videoRef.addEventListener('play', () => {
play && play(videoRef)
})
videoRef.addEventListener('pause', () => {
pause && pause(videoRef)
})
videoRef.addEventListener('ended', () => {
videoRef.currentTime = 0
playend && playend(videoRef)
})
}
}
return (
<div className={classes} {...restProps}>
<video
className="nut-video-player"
muted={options.muted}
autoPlay={options.autoplay}
loop={options.loop}
poster={options.poster}
controls={options.controls}
ref={rootRef}
src={source.src}
>
<source src={source.src} type={source.type} />
<track kind="captions" />
</video>
</div>
)
}
Video.defaultProps = defaultProps
Video.displayName = 'NutVideo'
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册