useAudio

作者: skoll | 来源:发表于2020-10-15 21:08 被阅读0次

    音频可视化

    1 .https://github.com/goldfire/howler.js 添加空间声音,做音频兼容
    2 .测试音频地址https://m8.music.126.net/21180815163607/04976f67866d4b4d11575ab418904467/ymusic/515a/5508/520b/f0cf47930abbbb0562c9ea61707c4c0b.mp3?infoId=92001
    3 .状态码是206,表示分段加载
    4 .全部元素震动 https://okazari.github.io/Rythm.js/
    5 .我之前做的是采集的不同部位的频率数据,现在这个是把全部的都合成了一个,然后给一个元素添加样式
    6 .

    rythm.min.js 根据声音频率为元素添加样式

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/rythm.js/2.2.5/rythm.min.js"></script>
    </head>
    <body>
       <div class="a">
           哈哈哈
       </div>
       <div class="a">
           啦啦啦
       </div>
       <div class="a">
           你你你
       </div>
       <div class="a">
            好!好好
       </div>
       <button id="start">start</button>
       <button id="stop">stop</button>
       <ul>
           <li>
               好像不能给太激烈的音乐
           </li>
           <li>
               有三种自带的内建模式rythm-bass,rythm-high,rythm-medium直接加在class里面
           </li>
           <li>
               第三个,四个参数不知道具体是干啥的,有的是在1,10.有的是在0,200之间
           </li>
           <li>
               需要不同的搭配起来才比较好看
           </li>
    
       </ul>
       <script>
           const start=document.querySelector('#start')
           const stop=document.querySelector('#stop')
           var rythm=new Rythm()
           start.addEventListener('click',()=>{
                rythm.setMusic('./1.mp3')
                rythm.start()
                // rythm.addRythm('a', 'pulse', 0,2,{min:0.9,max:1.1})
                // 脉冲变化
    
                // rythm.addRythm('a', 'fontSize', 0, 2, { min: 0.5, max: 1.5 })
                // 字体大小变化
    
                // rythm.addRythm('a', 'jump', 0, 2, {  })
                // 上下
    
                // rythm.addRythm('a', 'shake', 0, 2, {  })
                // 左右小幅度变化
    
                // rythm.addRythm('a', 'twist', 0, 2, {  })
                // 旋转
    
                // rythm.addRythm('a', 'vanish', 0, 2, {  })
                // 透明度变化
    
                // rythm.addRythm('a','fontColor', 0,2, {from:[0,0,255],
                //   to:[255,0,255]}
                // )
                // 字体颜色
    
                // rythm.addRythm('a','color', 0,2, {from:[0,0,255],
                //   to:[255,0,255]}
                // )
                // 背景颜色
    
                // rythm.addRythm('a','borderColor', 0,2, {from:[0,0,255],
                //   to:[255,0,255]}
                // )
                // 边框颜色
    
                // rythm.addRythm('a','neon', 0,2,{from:[0,0,255],
                //   to:[255,0,255]})
                //   元素阴影
    
                // rythm.addRythm('a','tile', 0,10,)
                // 倾斜
    
                // rythm.addRythm('a','borderWidth', 0,2,{
                //     min:2,max:10
                // })
                
                // rythm.addRythm('a','kern', 0,10,{
                //     min:-5,
                //     max:5
                // })
                // 字间距
    
                // rythm.addRythm('a','blur', 0,10,{
                //     min:-5,
                //     max:5
                // })
                // 高斯模糊
    
                // rythm.addRythm('a','swing', 0,10,{
                //     curve:'up',
                //     direction:'left',
                //     radius:10,
                // })
                // 左右摇摆
    
                // rythm.addRythm('a','radius', 0,10,{
                //     min:0,
                //     max:5
                // })
    
                rythm.addRythm('a','radius', 0,10,{
                    min:0,
                    max:5
                })
           })
           stop.addEventListener('click',()=>{
               rythm.stop()
           })
            
       </script>
    </body>
    </html>
    

    额外功能

    1 .除了视频的基本功能,在传出频率的数据,来让别的东西根据频率生成可视化数据
    2 .audio和video标签有哪些不同,他们是否可以通用,或者说是否可以直接把audio的音频地址传到video,把他当成没有画面的视频来操作。
    3 .这样组件复用就会省很多的力气,甚至不用修改直接使用,唯一的就是多加一点判断条件
    4 .可以这样使用就是一定要区分video和audio的属性和方法是否公用。初步上来看,应该是格式可能不兼容,也就是地址那里。但是chrome尝试是可以的。以及返回值的差距
    5 .之前为了兼容都是一个video里面加好几个source标签
    6 ..ogg, .wav和.mp3,常见音频格式.也可以使用MP4视频文件,因为MP4视频也包含ACC编码音频,不过就是体积大了很多,不建议这么使用。
    7 .audio 属性

    1 .src 音频的地址链接
    2 .autoplay
    3 .loop
    4 .mute
    5 .preload :none:表示在点击播放按钮之前不加载任何信息。
    metadata: 下载音频的meta信息,就是视频长度,类型,还有作者(如果有)等信息。
    auto: 会尝试下载整个音频,如今5G都快来了,流量已经不值钱了,因此,我个人是更推荐使用auto的,体验更好一点。然后,通常浏览器自己也会优化加载策略,不会所有音频文件都加载下来,只是会加载一部分,保证点击播放按钮的时候,可以立即播放
    6 .controls
    7 .type:指定音频文件的mine type类型
    

    8 .Gecko内核浏览器速率范围是0.25到5.0,超出这个范围就静音 playbackRate
    9 .loadstart → durationchange → loadedmetadata → loadeddata → progress → canplay → canplaythrough 音频事件触发顺序
    10 .播放完毕
    11 .本来这里想要加一些事件触发函数,比如播放完毕,播放开始,但是发现可以根据state返回的数据在组件外部计算得知,那其实那可以算吧
    12 .

    import * as React from 'react'
    import {useEffect,useRef,useState,useCallback} 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,
        audioArray?:any[],
    }
    
    interface MediaProps{
        type:"audio|video",
        // 多媒体类型
        src:string,
        // 媒体资源的地址
        autoPlay:boolean,
        // 是否自动播放
        controls:boolean,
        // 是否显示媒体资源的组件
        loop:boolean,
        // 是否循环
        muted:boolean,
        // 是否静音
        preload:string,
        // 预加载模式
        poster?:string,
        // 预览海报
        fftsize?:number,
    }
    
    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,
            audioArray:[],
        })
        const [hasConnect,setConnect]=useState(false)
        // 是否注册声音绑定
        const ref=useRef<HTMLMediaElement|null>(null)
        const [isFullScreen,{setFull,exitFull,toggleFull}]=useFullScreen(ref)
        const audioAnimation=useRef<number>(0)
        const analyserRef=useRef<AnalyserNode>()
        var draw=function(){
            var bufferLength = analyserRef.current!.frequencyBinCount
            var dataArray = new Uint8Array(bufferLength)
            audioAnimation.current=requestAnimationFrame(draw)
            analyserRef.current!.getByteFrequencyData(dataArray)
            // // getByteFrequencyData得到的归一化数组的值在0到255之间
            // // Web音频api返回的bin数量是fftSize的一半。
            setState(Object.assign({},state,{audioArray:dataArray}))
        }
    
        const playAudio=useCallback(()=>{
            if(!hasConnect){
                var context = new(window.AudioContext)()
                var analyser = context.createAnalyser()
                analyser.fftSize = mediaConfig.fftSize
                var source = context.createMediaElementSource(ref.current!)
    
                source.connect(analyser)
                analyser.connect(context.destination)         
                analyserRef.current=analyser
                draw()
                // 为什么这个传传进去就不行了
            }else{
                draw()
            }
        },[ref.current,hasConnect])
        
        function onPlay(){
            setState(Object.assign({},state,{paused:false}))
            // 计算返回数据
            if(fftSize){
                console.log('playing')
                playAudio()
                setConnect(true)
            }
        }
    
        function onPause(){
            setState(Object.assign({},state,{paused:true}))
            // 对象的时候都必须这样写
            // 数组则是array.slice()
    
            // 关闭动画
            if(fftSize&&audioAnimation.current){
                window.cancelAnimationFrame(audioAnimation.current)
            }
        }
    
        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,seekable}=el
            // 音频被缓冲的部分
            // seekable:是否可以调到改媒体的部分,而不需要进一步缓冲
            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
    
        // delete mediaConfig['type']
        // delete mediaConfig['fftsize']
        // 这俩属性是别的地方用到的,并不需要在真实的video/audio里面传进去
    
        // console.log(mediaConfig)
    
        const {fftSize,...mediaProps}=mediaConfig
        if(fftSize){
            mediaProps["crossOrigin"]="anonymous"
        }
        el=React.createElement(type,{
                ref:ref,
                ...mediaProps,
                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
                if(mediaConfig['type']==='aduio')return
                // 音频不支持小窗口
                // 或者说可不可以还是创建视频的窗口,实际上来播音频呢。
                // 用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,])
        // 这里提示的补全有错误吧,还是按照自己的想法来,明确自己想要的效果是什么
    
        // console.log(state)
        return [el,state,controls,ref]    
    }
    export default useHTMLMedia;
    
    import useHtmlMedia from './useHTMLMedia'
    
    interface MediaProps{
        type:"audio",
        // 多媒体类型
        src:string,
        // 媒体资源的地址
        autoPlay:boolean,
        // 是否自动播放
        controls:boolean,
        // 是否显示媒体资源的组件
        loop:boolean,
        // 是否循环
        muted:boolean,
        // 是否静音
        preload:string,
        // 预加载模式
        fftSize?:number,
    
    }
    
    const initialMediaProps={
            controls:false,
            autoPlay:false,
            loop:false,
            muted:false,
            type:'audio',
            fftSize:1024,
    }
    
    function useAudio(mediaConfig:any){
        const mediaProps:MediaProps=Object.assign({},initialMediaProps,mediaConfig)
        return useHtmlMedia(mediaProps)
    }
    
    
    export default useAudio;
    
    //使用
    import useAudio from '../useAudio'
    
    
    export default function(){
      const [audio,state,controls,ref]=useAudio({
        src:'https://m8.music.126.net/21180815163607/04976f67866d4b4d11575ab418904467/ymusic/515a/5508/520b/f0cf47930abbbb0562c9ea61707c4c0b.mp3?infoId=92001',
        autoPlay:false,
        controls:true,
        fftSize:1024,
        // 这里应该加入一些回调函数
      })
      return (
        <>
            {audio}
            <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>
            
        </>
      );
    }
    

    相关文章

      网友评论

          本文标题:useAudio

          本文链接:https://www.haomeiwen.com/subject/hvuvpktx.html