草庐IT

video 自定义视频播放控件

贝小米在前端 2023-04-22 原文

ui设计的界面总是极具个性化的,要去修改插件中的视频控件的样式和布局太困难了,那就自己参照video原生事件,重写一个吧。

(效果图预览)


一、video标签的属性(props)

 html <video>标签 | 菜鸟教程

<video 
     ref="videoPlayer" 
     id="videoElement" 
     controls 
     autoplay
     :muted="isMute" 
     width="800px" 
     height="600px" 
>
     您的浏览器不支持video
</video>

参数说明:(更多属性参照上述菜鸟教程中的video标签)

  • controls:默认为true,即向用户展示视频控件(如播放、暂停按钮等)
  • autoplay:如果出现该属性,则视频在就绪后马上播放。
  • muted:是否静音,默认为true
  • width:设置视频播放器的宽度

二、video视频控件的触发事件 

video标签支持的多媒体事件(Media Events) | 菜鸟教程

<video 
    ref="videoPlayer" 
    id="videoElement" 
    controls 
    autoplay
    :muted="isMute" 
    width="100%" 
    height="100%" 
    @loadeddata="setVideoPoster($event)"
    @progress="videoProgress($event)"
    @pause="videoPause($event)"
    @play="videoPlay($event)"
    @timeupdate="videoTimeUpdate()"
    @ended="videoEnded()"
    @contextmenu="contextmenu"
>
    您的浏览器不支持video
</video>

<button @click="handlePlay">播放</button>
<button @click="handlePause">暂停</button>
<button @click="handleMute">切换静音</button>
<button @click="fullScreen">全屏</button>

data(){
    return{
        isMute: true // 默认静音 
    }
}

1、播放(onplay事件)

this.$refs.videoPlayer.play();

methods:{
    // 视频要开始播放时
    videoPlay(e){
        // ...触发该函数后视频会开始播放,我们可以做一些想做的事情,比如改变自定义播放按钮的样式等
    },

    // 自定义播放按钮中,触发视频的播放事件
    handelPlay(){
        this.$refs.videoPlayer.play();   // 会触发videoPlay()函数
    }

}

2、暂停(onpause事件)

this.$refs.videoPlayer.pause();

methods:{
    // 视频要暂停播放时
    videoPause(e){
        // ...触发该函数后视频会暂停播放
    },

    // 自定义播放按钮中,触发视频的播放事件
    handelPause(){
        this.$refs.videoPlayer.pause();   // 会触发videoPause()函数
    }

}

3、静音(muted属性)

(1)切换静音

 // 手动切换静音(点击(非拖拽)静音时,用户选择的音量不变)

handleMute() {

      this.isMute = !this.isMute;

},

(2)改变音量(volume属性

this.$refs.videoPlayer.volume = a; (a为从 0~1的数字)

// 这里用element的进度条写音量大小调节条
<el-slider v-model="curVolume" :show-tooltip="false" @input="changeVolume"></el-slider>

data(){
    return{
        curVolume: 0, // 默认音量为0
    }
},

methods:{
    changeVolume(val){
        this.curVolume = val;
        // 由于h5规定volum的值在0-1之间,所以这里要对获取到的val做一个处理(滑块的val是从0-100)
        this.$refs.videoPlayer.volume = val / 100;
        
        // 音量为0的时候,video控件为静音
        if ( val == 0 ) {
           this.isMute = true;
        } else {
           this.isMute = false;
        }
    }
}

4、全屏

fullScreen() {
    this.$refs.videoPlayer.webkitRequestFullScreen();
},

5、播放进度条

获取视频总时长(duration

var videoObj = this.$refs.videoPlayer;

videoObj.addEventListener('canplay', () => {

        this.totalT = videoObj.duration;

})

获取视频加载进度

HTML5视频 - 加载百分比?

HTML5视频 - 加载百分比?

// 获取视频加载进度
videoProgress(e){
       var bf = this.$refs.videoPlayer.buffered;  
       var time = this.$refs.videoPlayer.currentTime;
       if ( bf.length != 0 ){
           var range = 0;
           while( !( bf.start(range) <= time && time <= bf.end(range) ) ) {
               range += 1;
           }
           var loadEndPercentage = ( bf.end(range) / this.playerVideo.duration ) * 100;  // 结束加载的百分比
           this.persentLoad = loadEndPercentage;
       }
},

(1)父组件调用

<template>
    <video 
      ref="videoPlayer" 
      @progress="videoProgress($event)"
      @timeupdate="videoTimeUpdate()"
    >
      您的浏览器不支持video
    </video>

    // 视频播放、加载进度条
    <ProgressLine 
      :presentT="presentT" 
      :totalT="totalT" 
      :persentLoad="persentLoad" 
      @changeCurrentTime="changeCurrentTime($event)"
      @changeCurrentWord="changeCurrentWord($event)"
    >
    </ProgressLine>

    // 播放时长、视频总时长
    <p>
      <span id="currentTime" ref="progressTimer">{{ videoCurrentTime }}</span>
      <span style="color: #ffffff;opacity: 0.3;">&nbsp;/&nbsp;</span>
      <span id="durationTime" ref="durationTimer">{{ videoTotalTime }}</span>
    </p>
</template>

import ProgressLine from './ProgressLine.vue';
export default {
    name: 'videoPage',
    components: {
        ProgressLine
    },
    data(){
        return{
            presentT: 0,  // 进度条的当前值,必须为number
            totalT: 0,    // 进度条的最大值,必须为number
            persentLoad: 0, // 视频加载进度 
            videoCurrentTime: '00:00', // 当前视频已播放时长
            videoTotalTime: '00:00',  // 视频总时长
        }
    },
    methods:{
        // 子组件传入的时间修改
        changeCurrentTime(data) {
            this.$refs.videoPlayer.currentTime = data;  // 点击进度条设置视频当前播放点
        },
        changeCurrentWord(data) {
            this.videoCurrentTime = this.formatTime(data);  // 当前播放时间显示文字
        },
        // 获取视频加载进度
        videoProgress(e){
            var bf = this.playerVideo.buffered;  
            var time = this.playerVideo.currentTime;
            if ( bf.length != 0 ){
                var range = 0;
                while( !( bf.start(range) <= time && time <= bf.end(range) ) ) {
                    range += 1; 
                }
                
                var loadEndPercentage = ( bf.end(range) / this.playerVideo.duration ) * 100;    // 结束加载的百分比
                this.persentLoad = loadEndPercentage;
            }
        },
        // 视频自动播放时
        videoTimeUpdate(){
            this.presentT = this.playerVideo.currentTime;   // 获取当前播放时长
            this.videoCurrentTime = this.formatTime(this.presentT);  // 时间格式化
        },
        // 时间格式化
        formatTime(t) {
            var m = parseInt(t % 3600 / 60)
            m = m < 10 ? '0' + m : m
            var s = parseInt(t % 60)
            s = s < 10 ? '0' + s : s
            return m + ':' + s
        },
    }
}

(2)进度条组件(播放进度条 和 加载进度条)

// ProgressLine.vue 进度条组件
<template>
    <div>
        <div class="line-background">
            <div class="time-line" @click="adjustProgress($event)">
                <div class="progress-round" ref="progressRound">
                    <div class="loading" ref="persentLoad" style="width: 0;"></div>   <!-- 加载进度条 -->
                    <div class="progress" ref="progress" @click="adjustProgress"></div>
                    <div class="round" ref="round" @mousedown="roundDrag"></div>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
export default {
    name: 'ProgressLine',
    props: {
        presentT: {},
        totalT: {},
        persentLoad: { default : 0 }
    },
    data() {
        return {
            // 进度条拖拽
            dragClick: false,
            // 鼠标/手指按下
            clickDown: false,
        }
    },
    created() {

    },
    watch: {
        // 侦听当前播放时长设置进度条
        presentT: {
            handler(newValue, oldValue) {
                // 未点击进度条
                if (this.dragClick == false && this.clickDown == false) {
                    this.$refs.progress.style.width = newValue / this.totalT * 100 + '%'
                    if ((newValue / this.totalT * 100 - 1.23) < 0) {
                        this.$refs.round.style.left = 0 + '%'
                    } else {
                        this.$refs.round.style.left = (newValue / this.totalT * 100) - 1.23 + '%'
                    }
                } else if (this.dragClick == true) {
                    this.dealWidth()
                    this.dragClick = false
                }
            }
        },
        persentLoad: {
            handler(newValue, oldValue) {
                this.$refs.persentLoad.style.width = ( newValue / 100 ) * 1300 + 'px'; 
            }
        }
    },
    methods: {
        progressData(data) {
            this.$emit('changeCurrentTime', data)
            this.$emit('changeCurrentWord', data)
        },
        // 进度条位置和圆点定位处理
        dealWidth() {
            this.$refs.progress.style.width = this.progressWidth / this.$refs.progressRound.offsetWidth * 100 + '%'
            if ((this.progressWidth / this.$refs.progressRound.offsetWidth * 100) - 1.23 < 0) {  // 圆点定位
                this.$refs.round.style.left = 0 + '%'
            } else {
                this.$refs.round.style.left = (this.progressWidth / this.$refs.progressRound.offsetWidth * 100) - 1.23 + '%'
            }
        },
        // 进度条点击
        adjustProgress(e) {
            this.dragClick = true
            e.preventDefault()
            const { left, width } = this.$refs.progressRound.getBoundingClientRect()  // 进度条到屏幕距离及进度条的宽度
            this.progressWidth = e.clientX - left
            if (this.progressWidth < 0) {//进度条边界值计算情况
                this.progressWidth = 0
            } else if (this.progressWidth >= width) {
                this.progressWidth = width
            } else {
                this.progressWidth = e.clientX - left // e.clientX:鼠标点击的位置到屏幕最左侧的距离
            }
            this.dealWidth()
            this.progressData((this.progressWidth / width) * this.totalT)
        },
        // 进度条圆点拖拽
        roundDrag(event) {
            event.preventDefault()
            const offsetX = event.offsetX
            this.dragClick = true
            this.clickDown = true  // 解决圆点拖拽进度条长度抖动
            document.onmousemove = (e) => {  // 给圆点添加移动事件
                e.preventDefault()// 阻止进度条拖拽时屏幕原有的滑动功能
                const X = e.clientX  // 获取圆点离屏幕的距离
                const { left, width } = this.$refs.progressRound.getBoundingClientRect()
                const ml = X - left  // 进度条长度:圆点离屏幕的距离减去进度条最左边离屏幕的距离
                if (ml <= 0) {  // 进度条长度最小和最大值的界定
                    this.progressWidth = 0
                } else if (ml >= width) {
                    this.progressWidth = width
                } else {
                    this.progressWidth = ml
                }
                this.progressData((this.progressWidth / width) * this.totalT)  //视频播放时间
                this.dealWidth()
            }
            // 抬起鼠标,结束移动事件
            document.onmouseup = () => {
                document.onmousemove = null
                document.onmouseup = null
                this.clickDown = false
            }
        },
    }
}
</script>
<style lang="less" scoped>
.line-background {
    width: 100%;
    height: 10px;
    background-color: rgba(255, 255, 255, 0.3);

    .time-line {
        width: 100%;
        height: 10px;
        background-color: #565651;

        .progress-round {
            cursor: pointer;
            width: 100%;
            position: relative;
            display: flex;

            .loading {
                height: 10px;
                background-color: rgba(255, 255, 255, 0.3);
            }

            .progress {
                position: absolute;
                top: 0;
                left: 0;
                width: 00%;
                height: 10px;
                background-color: #3d7eff;
            }

            .round {
                position: absolute;
                top: 50%;
                left: 0;
                transform: translateY(-50%);
                width: 16px;
                height: 16px;
                border-radius: 16px;
                background: #ffffff;
                box-shadow: -2px 0px 2px 2px rgba(3, 0, 0, 0.30);
            }
        }
    }
}
</style>

6、视频中禁用右键(可以禁止用户下载视频)

// 在视频中禁用右键(禁止用户下载)
contextmenu(e){
    e.returnValue = false;
},

7、设置倍速播放

 this.$refs.videoPlayer.playbackRate = rate;   // rate 一般在[2.0,1.75,1.5,1.0,0.75,0.5]范围

有关video 自定义视频播放控件的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  3. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  4. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  5. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  6. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  7. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

  8. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

  9. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  10. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

随机推荐