视频组件
1 .后续可以加上支持流式访问,现在应该只能传入MP4格式
2 .可以封装下flv.js,西瓜视频那个
import * as React from 'react'
import {useEffect,useRef,useState} from 'react'
import parseTimeRanges from './parseTimeRanges'
import useFullScreen from './useFullscreen'
// 这个全屏的应该放在外面,因为hook是不能嵌套的
interface HTMLMediaProps extends React.AudioHTMLAttributes<any>,React.VideoHTMLAttributes<any>{
src:string,
}
// xgplayer
interface HTMLMediaState{
buffered:any[],
duration:number,
paused:boolean,
muted:boolean,
time:number,
volume:number,
isFullScreen:boolean,
}
interface MediaProps{
type:"audio|video",
// 多媒体类型
src:string,
// 媒体资源的地址
autoPlay:boolean,
// 是否自动播放
controls:boolean,
// 是否显示媒体资源的组件
loop:boolean,
// 是否循环
muted:boolean,
// 是否静音
preload:string,
// 预加载模式
poster?:string,
// 预览海报
}
interface HTMLMediaControls{
play:()=>Promise<void>|void,
pause:()=>void,
mute:()=>void,
unmute:()=>void,
volume:(volume:number)=>void,
seek:(time:number)=>void,
pip:()=>void,
speed:(value:number)=>void,
setFull:()=>void,
}
type createHTMLMediaHookReturn=[
React.ReactElement<HTMLMediaProps>,
HTMLMediaState,
HTMLMediaControls,
{current:HTMLMediaElement|null}
]
function useHTMLMedia(mediaConfig:any):createHTMLMediaHookReturn{
let el:React.ReactElement<any>|undefined
let props:HTMLMediaProps
const [state,setState]=useState<HTMLMediaState>({
buffered:[],
time:0,
duration:0,
paused:true,
muted:false,
volume:1,
isFullScreen:false
})
const ref=useRef<HTMLMediaElement|null>(null)
const [isFullScreen,{setFull,exitFull,toggleFull}]=useFullScreen(ref)
function onPlay(){
console.log('play')
setState(Object.assign({},state,{paused:false}))
}
function onPause(){
setState(Object.assign({},state,{paused:true}))
// 对象的时候都必须这样写
// 数组则是array.slice()
}
function onVolumeChange(){
const el=ref.current
if(!el)return
setState(Object.assign({},state,{muted:el.muted,volume:el.volume}))
}
function onDurationChange(){
const el=ref.current
if(!el)return
const {duration,buffered}=el
setState(Object.assign({},state,{
duration,
buffered:parseTimeRanges(buffered)
}))
}
// 这些只是用来更新状态的,具体的操作逻辑应该在她之前,这些只是作为钩子函数来记录变化的数值
function onTimeUpdate(){
const el=ref.current
if(!el)return
setState(Object.assign({},state,{time:el.currentTime}))
}
function onProgress(){
const el=ref.current
if(!el)return
setState(Object.assign({},state,{buffered:parseTimeRanges(el.buffered)}))
}
let type=mediaConfig.type
el=React.createElement(type,{
ref:ref,
...mediaConfig,
onPlay,
onPause,
onVolumeChange,
onDurationChange,
onTimeUpdate,
onProgress,
},'对不起,你的浏览器不支持播放 video !')
let loclPlay:boolean=false
const controls={
play(){
const el:any=ref.current
if(!el)return
if(!loclPlay){
// 都需要强制转换
const promise=el.play()
const isPromise=typeof promise==='object'
if(isPromise){
loclPlay=true
const resetLock=()=>{
loclPlay=false
}
promise.then(resetLock,resetLock)
}
return promise
}
return undefined
},
pause(){
const el=ref.current
if(el&&!loclPlay){
return el.pause()
}
},
seek(time:number){
const el=ref.current
if(!el||state.duration===undefined)return
time=Math.min(state.duration,Math.max(0,time))
el.currentTime=time
},
volume(volume:number){
const el=ref.current
if(!el)return
volume=Math.min(1,Math.max(0,volume))
el.volume=volume
setState(Object.assign({},state,{volume:volume}))
},
mute(){
const el=ref.current
if(!el)return
el.muted=true
},
unmute(){
const el=ref.current
if(!el)return
el.muted=false
},
pip(){
let el:any=ref.current
let doc:any=document
// 用any承接,不然会提示没有这个属性
if(el!==doc.pictureInPictureElement){
el.requestPictureInPicture()
.catch((error:any)=>{
console.log('视频无法进入画中画模式!')
})
}else{
doc.exitPictureInPicture()
.catch((error:any)=>{
console.log('视频无法退出画中画模式!')
})
}
},
speed(value:number){
const el=ref.current
if(!el)return
let speeds=[0.5,1,2,3]
if(speeds.includes(value)){
el.playbackRate=value
}else{
console.log('不能修改限定之外的速度')
}
},
setFull,
}
useEffect(()=>{
const el=ref.current!;
if(!el){
return
}
setState(Object.assign({},state,{
volume:el.volume,
muted:el.muted,
paused:el.paused,
}))
if(mediaConfig.autoPlay&&el.paused){
controls.play()
}
},[mediaConfig.src,])
// 这里提示的补全有错误吧,还是按照自己的想法来,明确自己想要的效果是什么
return [el,state,controls,ref]
}
export default useHTMLMedia;
useVideo
import useHtmlMedia from './useHTMLMedia'
interface MediaProps{
type:"audio|video",
// 多媒体类型
src:string,
// 媒体资源的地址
autoPlay:boolean,
// 是否自动播放
controls:boolean,
// 是否显示媒体资源的组件
loop:boolean,
// 是否循环
muted:boolean,
// 是否静音
preload:string,
// 预加载模式
poster?:string,
// 预览海报
}
const initialMediaProps={
controls:false,
autoPlay:false,
loop:false,
muted:true,
preload:"metadata",
type:'video',
poster:'',
}
function useVideo(mediaConfig:any){
const mediaProps:MediaProps=Object.assign({},initialMediaProps,mediaConfig)
return useHtmlMedia(mediaProps)
}
export default useVideo;
//parseTime
//当前已经缓存的数据
function parseTimeRanges(ranges:any){
const result:{start:number,end:number}[]=[]
for(let i=0 ; i<ranges.length; i++){
result.push({
start:ranges.start(i),
end:ranges.end(i),
})
}
return result
}
export default parseTimeRanges
使用
import useVideo from '../useVideo'
export default function(){
const [video,state,controls,ref]=useVideo({
src:'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4',
autoPlay:false,
controls:false,
})
return (
<>
{video}
<hr/>
<pre>{JSON.stringify(state,null,2)}</pre>
<button onClick={controls.pause}>Pause</button>
<button onClick={controls.play}>Play</button>
<button onClick={controls.mute}>Mute</button>
<button onClick={controls.unmute}>unMute</button>
<br/>
{/* 传值的函数需要这么写 */}
<button onClick={() => controls.volume(.1)}>Volume: 10%</button>
<button onClick={() => controls.volume(.5)}>Volume: 50%</button>
<button onClick={() => controls.volume(1)}>Volume: 100%</button>
<br/>
<button onClick={() => controls.seek(state.time - 5)}>-5 sec</button>
<button onClick={() => controls.seek(state.time + 5)}>+5 sec</button>
<br/>
<button onClick={controls.pip}>pip</button>
<br/>
<button onClick={()=>controls.speed(0.5)}>speed:0.5</button>
<button onClick={()=>controls.speed(1)}>speed:1</button>
<button onClick={()=>controls.speed(2)}>speed:2</button>
<button onClick={()=>controls.speed(3)}>speed:3</button>
<br/>
<button onClick={controls.setFull}>fullScreen</button>
</>
);
}
1 .ondurationchange 事件在视频/音频(audio/video)的时长发生变化时触发
2 . 当视频/音频(audio/video)已经加载后,视频/音频(audio/video)的时长从 "NaN" 修改为正在的时长
网友评论