当前位置: 首页 > news >正文

【西瓜播放器+Vue】前端实现网页短视频:上下滑动、自动播放、显示视频信息等

文章目录

    • 目标
    • 依赖
    • 逻辑
      • 上下滑动:swipe
      • 播放器实例
        • 事件
        • 配置
      • 隐藏控制栏
      • 暂停全部
      • 播放完后自动播放下一个
      • 滑动后自动播放
      • 页面不可见时暂停播放
      • 播放器显示视频信息
    • 代码
      • 播放器组件
      • 外部调用

目标

前端实现像抖音短视频那样上下滑动播放视频等效果。
使用:西瓜播放器 | 快速上手

大致效果:
在这里插入图片描述

依赖

上下滑动使用走马灯组件。这里使用vant的走马灯。
Swipe 轮播 - Vant 4

播放器使用西瓜播放器:
西瓜播放器 | 快速上手

需要注意的是,在pc端,swipe的上下滑动效果无响应,需要这样处理:进阶用法 - Vant 4

# 安装模块
npm i @vant/touch-emulator -S
// 引入模块后自动生效
import '@vant/touch-emulator';

逻辑

提示:本部分边写代码边写文档,因此代码片段仅是当前功能片段,非完整代码。

上下滑动:swipe

使用走马灯swipe组件实现上下滑动,需要设置:

  • 竖直滑动
  • 隐藏指示器
  • 循环轮播
  <van-swiperef="swipeRef"vertical:show-indicators="false"style="height: 700px; width: 490px":stop-propagation="false":loop="true"@change="handleChange"><van-swipe-item v-for="(item, index) in playList" :key="index"><playerDemo:id="item.id":url="item.url":poster="item.poster":on-ended="onEnded":bind-player="bindPlayer"></playerDemo></van-swipe-item></van-swipe>

封装一个播放器组件:playerDemo,传入视频相关参数:

const playList = ref([{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '1',poster: 'https://vcg00.cfp.cn/creative/vcg/800/new/VCG211432661212.jpg',},{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '2',poster: 'https://vcg02.cfp.cn/creative/vcg/800/new/VCG211555535113-MDJ.jpg',},{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '3',poster: 'https://vcg01.cfp.cn/creative/vcg/800/new/VCG211513792984.jpg',},{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '4',poster: 'https://vcg03.cfp.cn/creative/vcg/800/new/VCG211563273611.jpg',},
])

播放器实例

  <div class="main"><div ref="videoRef"></div></div>
import { defineProps } from 'vue'
import { ref, onMounted, onUnmounted } from 'vue'
import Player from 'xgplayer'
import 'xgplayer/dist/index.min.css'
import { Events } from 'xgplayer'
import XgPlayer from 'xgplayer'interface PlayerProps {url: string // 视频链接id: string // 视频idposter: string // 视频封面onEnded: () => voidbindPlayer: (id: string, player: XgPlayer) => void
}
const props = defineProps<PlayerProps>()
const videoRef = ref<HTMLElement>()let player: Player | nullonMounted(() => {if (!videoRef.value) return//   https://v3.h5player.bytedance.com/config/player = new Player({el: videoRef.value,url: props.url,width: '390px',height: '700px',videoInit: true,autoplay: true,poster: props.poster,videoFillMode: 'auto',marginControls: false,closeVideoDblclick: true, // 禁止双击全屏commonStyle: {progressColor: 'rgba(255, 255, 255, 0.2)',playedColor: '#fff',cachedColor: 'rgba(255, 255, 255, 0.6)',},// https://v3.h5player.bytedance.com/plugins/icons.html// icons: {},start: {isShowPause: true,disableAnimate: true,},// https://v3.h5player.bytedance.com/pluginsplugins: [],})//   绑定播放器事件,事件从组件外传入player.on(Events.ENDED, handleEnded)props.bindPlayer(props.id, player)
})onUnmounted(() => {player?.off(Events.ENDED, handleEnded)player?.destroy()
})
事件

要给组件定义事件方法,如:

  onEnded: () => voidbindPlayer: (id: string, player: XgPlayer) => void

西瓜播放器 | 事件
从组件外传入一个事件的回调函数,在组件内绑定此事件。
onEnded,在组件外传入onEnded事件:

const onEnded = () => {// ...
}
<playerDemo:id="item.id":url="item.url":poster="item.poster":on-ended="onEnded":bind-player="bindPlayer"></playerDemo>

在组件内绑定onEnded视频播放结束的事件:

// 处理传入的播放器事件
const handleEnded = () => {props.onEnded()
}
onMounted(() => {// ...player.on(Events.ENDED, handleEnded)
})

销毁:

onUnmounted(() => {player?.off(Events.ENDED, handleEnded)player?.destroy()
})

这样一来,播放器内视频结束后,就会触发传入的onEnded了。

我们希望播放器组件只处理播放器相关的逻辑,具体对应事件的业务逻辑都从组件外传入。

配置

指的是创建播放器实例的配置:
西瓜播放器 | 配置

player = new Player({el: videoRef.value,url: props.url,width: '390px',height: '700px',videoInit: true,autoplay: true,poster: props.poster,videoFillMode: 'auto',marginControls: false,closeVideoDblclick: true, // 禁止双击全屏commonStyle: {progressColor: 'rgba(255, 255, 255, 0.2)',playedColor: '#fff',cachedColor: 'rgba(255, 255, 255, 0.6)',},// https://v3.h5player.bytedance.com/plugins/icons.html// icons: {},start: {isShowPause: true,disableAnimate: true,},// https://v3.h5player.bytedance.com/pluginsplugins: [],})

主要是看文档写即可。此文档很清晰,需要啥功能直接搜配置。

隐藏控制栏

我们想要做短视频的效果,需要保留进度条,隐藏控制栏,因此不能设置:

controls:false

我们隐藏除了进度条外的其他类即可。

在这里插入图片描述
在这里插入图片描述

<!-- 全局样式,see:https://cn.vuejs.org/api/sfc-css-features.html#global-selectors -->
<style>
.xg-left-grid,
.xg-right-grid {display: none !important;
}
</style>

暂停全部

场景:滑动后,发现被划走的视频(不可见)依旧在播放。
需求:不可见的视频暂停播放。

播放器组件只知道当前播放器的情况,因此此逻辑需要在组件外实现。

定义一个对外暴露、绑定播放器的方法:

bindPlayer: (id: string, player: XgPlayer) => void

在实例化完播放器后:

onMounted(() => {if (!videoRef.value) return//   https://v3.h5player.bytedance.com/config/player = new Player({// ...})props.bindPlayer(props.id, player)
})

组件外绑定:

import XgPlayer from 'xgplayer'
const playersRef = ref<XgPlayer[]>([])// 绑定对应视频与播放器实例
const bindPlayer = (id: string, player: XgPlayer) => {const index = playList.value.findIndex((item) => item.id === id)if (index != -1) {playersRef.value[index] = player}
}const pauseAll = () => {playersRef.value.forEach((player) => {player.pause()})
}

在滑动时暂停全部pauseAll() 即可。

播放完后自动播放下一个

这里有一个问题。
播放完后自动播放下一个,此时往上滑,回到上一个播放器,我们的预期是重播此视频,然而实际效果是:触发完播自动播下一个。

一开始处理为,滑动播放器时,若视频播完,则重播:

const handleChange = (index: number) => {pauseAll()//   如果当前视频已结束,重播const currentPlayer = playersRef.value[index]if (currentPlayer.ended) {currentPlayer.retry()}console.log(index, playList.value[index], '当前', currentPlayer)
}

但是无效。
场景:视频A播完后,自动跳到视频B。此时回到视频A,currentPlayer.endedfalse
无法准确判断视频是否播完。因此处理方法为:视频播完时,直接调用重播,然后暂停,切到下一个视频。这样就不需要判断currentPlayer.ended

组件内绑定的handleEnded

// 处理传入的播放器事件
const handleEnded = () => {// 需求:视频结束时,自动跳到下一个。此时若回到当前视频,需要重播// 但是,此时ended状态为false,无法判断是否是结束后重播。因此需要在视频结束时直接重播,然后全部暂停,跳到下一集// 此时上滑回到本集,即继续播放,效果与重播一致player?.retry()props.onEnded()
}

组件外,触发滑动时:

const handleChange = (index: number) => {pauseAll()
}// 传入播放器事件到组件
const onEnded = () => {pauseAll()swipeRef?.value?.next()
}

效果:

在这里插入图片描述

滑动后自动播放

const handleChange = (index: number) => {pauseAll()const currentPlayer = playersRef.value[index]currentPlayer.play()
}

页面不可见时暂停播放

需求:页面不可见时暂停播放,回到页面时继续播放当前视频。

const currentPlayer = ref<XgPlayer | null>(null)
const activeIndexRef = ref(0)onMounted(() => {document.addEventListener('visibilitychange', handleVisibilitychange)
})onUnmounted(() => {document.removeEventListener('visibilitychange', handleVisibilitychange)
})const handleVisibilitychange = () => {if (document.hidden) {pauseAll()} else {playCurrent()}
}// 播放当前视频
const playCurrent = () => {currentPlayer.value = playersRef.value[activeIndexRef.value]currentPlayer.value?.play()
}const handleChange = (index: number) => {pauseAll()activeIndexRef.value = indexplayCurrent()
}

在这里插入图片描述

播放器显示视频信息

传入信息,新写样式即可。

<playerDemo:id="item.id":url="item.url":poster="item.poster":info="item.info":on-ended="onEnded":bind-player="bindPlayer"
></playerDemo>
const playList = ref([{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '1',poster: 'https://vcg00.cfp.cn/creative/vcg/800/new/VCG211432661212.jpg',info: {cover: 'https://vcg00.cfp.cn/creative/vcg/800/new/VCG211432661212.jpg',name: '不重要的名字1',},},{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '2',poster: 'https://vcg02.cfp.cn/creative/vcg/800/new/VCG211555535113-MDJ.jpg',info: {cover: 'https://vcg00.cfp.cn/creative/vcg/800/new/VCG211432661212.jpg',name: '不重要的名字2',},},{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '3',poster: 'https://vcg01.cfp.cn/creative/vcg/800/new/VCG211513792984.jpg',},{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '4',poster: 'https://vcg03.cfp.cn/creative/vcg/800/new/VCG211563273611.jpg',},
])

组件内:

<div v-if="props.info" class="info"><img class="cover" :src="props.info.cover" alt="" /><div class="name">{{ props.info.name }}</div>
</div>
.main {position: relative;
}.info {position: absolute;bottom: 65px;left: 15px;display: flex;align-items: center;justify-content: center;.cover {width: 30px;height: 55px;margin-right: 10px;border-radius: 8px;}.name {color: #fff;}
}

效果:

在这里插入图片描述

代码

播放器组件

player.vue

<template><div class="main"><div ref="videoRef"></div><div v-if="props.info" class="info"><img class="cover" :src="props.info.cover" alt="" /><div class="name">{{ props.info.name }}</div></div></div>
</template><script setup lang="ts">
import { defineProps } from 'vue'
import { ref, onMounted, onUnmounted } from 'vue'
import Player from 'xgplayer'
import 'xgplayer/dist/index.min.css'
import { Events } from 'xgplayer'
import XgPlayer from 'xgplayer'interface PlayerInfo {name: stringcover: string
}interface PlayerProps {url: string // 视频链接id: string // 视频idposter: string // 视频封面info?: PlayerInfoonEnded: () => voidbindPlayer: (id: string, player: XgPlayer) => void
}
const props = defineProps<PlayerProps>()
const videoRef = ref<HTMLElement>()let player: Player | nullonMounted(() => {if (!videoRef.value) return//   https://v3.h5player.bytedance.com/config/player = new Player({el: videoRef.value,url: props.url,width: '390px',height: '700px',videoInit: true,autoplay: true,poster: props.poster,videoFillMode: 'auto',marginControls: false,closeVideoDblclick: true, // 禁止双击全屏commonStyle: {progressColor: 'rgba(255, 255, 255, 0.2)',playedColor: '#fff',cachedColor: 'rgba(255, 255, 255, 0.6)',},// https://v3.h5player.bytedance.com/plugins/icons.html// icons: {},start: {isShowPause: true,disableAnimate: true,},// https://v3.h5player.bytedance.com/pluginsplugins: [],})//   绑定播放器事件,事件从组件外传入player.on(Events.ENDED, handleEnded)props.bindPlayer(props.id, player)
})onUnmounted(() => {player?.off(Events.ENDED, handleEnded)player?.destroy()
})// 处理传入的播放器事件
const handleEnded = () => {// 需求:视频结束时,自动跳到下一个。此时若回到当前视频,需要重播// 但是,此时ended状态为false,无法判断是否是结束后重播。因此需要在视频结束时直接重播,然后全部暂停,跳到下一集// 此时上滑回到本集,即继续播放,效果与重播一致player?.retry()props.onEnded()
}
</script><!-- 全局样式,see:https://cn.vuejs.org/api/sfc-css-features.html#global-selectors -->
<style>
.xg-left-grid,
.xg-right-grid {display: none !important;
}
</style><style lang="less" scoped>
.main {position: relative;
}.info {position: absolute;bottom: 65px;left: 15px;display: flex;align-items: center;justify-content: center;.cover {width: 30px;height: 55px;margin-right: 10px;border-radius: 8px;}.name {color: #fff;}
}
</style>

外部调用

<template><h3>封装西瓜播放器,上下滑动视频</h3><p><a href="https://v3.h5player.bytedance.com/guide/#%E5%AE%89%E8%A3%85">https://v3.h5player.bytedance.com/guide/#%E5%AE%89%E8%A3%85</a></p><van-swiperef="swipeRef"vertical:show-indicators="false"style="height: 700px; width: 490px":stop-propagation="false":loop="true"@change="handleChange"><van-swipe-item v-for="(item, index) in playList" :key="index"><playerDemo:id="item.id":url="item.url":poster="item.poster":info="item.info":on-ended="onEnded":bind-player="bindPlayer"></playerDemo></van-swipe-item></van-swipe>
</template><script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import playerDemo from '@/components/player.vue'
// 适配pc端-vant-Swipe,see : https://vant-ui.github.io/vant/#/zh-CN/advanced-usage#zhuo-mian-duan-gua-pei
import '@vant/touch-emulator'
import type { SwipeInstance } from 'vant'
import XgPlayer from 'xgplayer'
const swipeRef = ref<SwipeInstance>()
const playersRef = ref<XgPlayer[]>([])
const currentPlayer = ref<XgPlayer | null>(null)
const activeIndexRef = ref(0)const playList = ref([{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '1',poster: 'https://vcg00.cfp.cn/creative/vcg/800/new/VCG211432661212.jpg',info: {cover: 'https://vcg00.cfp.cn/creative/vcg/800/new/VCG211432661212.jpg',name: '不重要的名字1',},},{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '2',poster: 'https://vcg02.cfp.cn/creative/vcg/800/new/VCG211555535113-MDJ.jpg',info: {cover: 'https://vcg00.cfp.cn/creative/vcg/800/new/VCG211432661212.jpg',name: '不重要的名字2',},},{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '3',poster: 'https://vcg01.cfp.cn/creative/vcg/800/new/VCG211513792984.jpg',},{url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',id: '4',poster: 'https://vcg03.cfp.cn/creative/vcg/800/new/VCG211563273611.jpg',},
])onMounted(() => {document.addEventListener('visibilitychange', handleVisibilitychange)
})onUnmounted(() => {document.removeEventListener('visibilitychange', handleVisibilitychange)
})const handleVisibilitychange = () => {if (document.hidden) {pauseAll()} else {playCurrent()}
}// 播放当前视频
const playCurrent = () => {currentPlayer.value = playersRef.value[activeIndexRef.value]currentPlayer.value?.play()
}const handleChange = (index: number) => {pauseAll()activeIndexRef.value = indexplayCurrent()
}// 绑定对应视频与播放器实例
const bindPlayer = (id: string, player: XgPlayer) => {const index = playList.value.findIndex((item) => item.id === id)if (index != -1) {playersRef.value[index] = player}
}const pauseAll = () => {playersRef.value.forEach((player) => {player.pause()})
}// 传入播放器事件到组件
const onEnded = () => {pauseAll()swipeRef?.value?.next()
}
</script><style scoped lang="less"></style>
http://www.dtcms.com/a/490907.html

相关文章:

  • 软件下载网站模版html软件下载手机版
  • 哪些平台可以免费推广广州百度提升优化
  • Redis-缓存问题(穿透、击穿、雪崩)
  • Mysql数据库系统库数据恢复
  • 服务器数据恢复—RAID5硬盘掉线,热备盘未启用如何恢复raid5阵列数据?
  • 在 Linux 服务器上配置 SFTP 的完整指南(2025 最新安全实践)
  • pytorch 数据加载加速
  • 网站建设平台设备荣耀手机官网
  • 调用apisix admin 接口创建资源
  • 迅为RK3568开发板OpenHarmony系统南向驱动开发手册-pdf配置 rk3568_uart_config.hcs
  • 中兴通讯的网站建设分析wordpress安装后要删除哪些文件
  • 建设银行对账单查询网站简述电子商务网站开发的主要步骤
  • ARMA模型
  • 智慧园区:引领城市未来发展新趋势
  • python命名约定 私有变量 保护变量 公共变量
  • 气泡图 vs 散点图:什么时候加第三维?
  • 西安网站开发工程师wordpress+中文版
  • 网页设计网站源代码淘宝网站的建设目的
  • 分布式系统的幂等性设计:从理论到生产实践
  • Advanced Port Scanner,极速端口扫描利器
  • 字节面试题
  • 个人项目开发(2) 基于MFA实现的双重登录验证
  • 邢台做移动网站公司电话号码中国设计之家
  • 丹阳高铁站对面的规划打开这个网站你会回来感谢我的
  • 2025年--Lc194-516. 最长回文子序列(动态规划在字符串的应用,需要二刷)--Java版
  • [HTML]播放wav格式音频
  • IntentService 的应用场景和使用方式?
  • 【开题答辩实录分享】以《基于大数据技术的二手车交易数据分析与设计》为例进行答辩实录分享
  • 基础开发工具(上)
  • k8s lngress与安全机制