h5使用video标签简单实现播放视频
近期做了一个需求,h5 页面,展示视频列表,点击视频,全屏播放。开始还以为全屏是不是需要读写 app 的标题栏,事实证明多想了,video 标签本身全屏的时候,可以让视频之外的地方全黑,ios 和 android 都一样。
但 ios 播放视频的时候,会自动将 video 放在最高层,然后全屏播放。
为了统一效果,播放视频的时候,统一用一个黑色遮罩层。
video,其实本身还是挺多事的,本文只是简单的播放,以后再涉及 video 的时候,心里有个数。video 的所有属性和事件可以参考MDN 的介绍。
如果是 PC 端的,推荐DPlayer。移动端,效果可能没那么好。
播放效果和简单逻辑
网络异常,图片无法展示
|
可能全屏效果比很多插件要好些,不然样式问题,也是闹心。
实现其实没啥大逻辑。
播放组件关键逻辑就是,播放的时候,先检测是否能播放,若不能则显示加载中。
播放组件的逻辑:
- 判断是否能播放,不能播放显示加载中
- 播放中出错的话,提示
- 播放结束,通知父组件
网络异常,图片无法展示
|
列表的逻辑:
- 展示列表
- 传递必要属性
- 接受子组件的事件
网络异常,图片无法展示
|
列表项组件项组件的逻辑:
- 属于展示组件,额外增加一个图片失败的监测
网络异常,图片无法展示
|
附注代码
videoPlayer 的代码
<template> <div class="video-box" v-if="isShowPlayer"> <video controls :src="info.url" :poster="info.cover" ref="video" @canplay="canPlay" @error="error" @ended="ended" controlslist="nodownload" disablePictureInPicture ></video> <div class="close-box" @click="clickClose"> <img src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/close.png" alt="关闭" /> </div> <div class="loadingBox" v-if="isLoading"> <img class="loading-icon" src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/loading.gif" alt="正在加载..." /> </div> </div> </template> <script> export default { props: { info: { required: true, type: Object }, // url cover属性 isShowPlayer: { default: false } }, data() { return { isCanPlay: false, isLoading: false }; }, mounted() { if (this.isCanPlay) { this.$refs.video.play(); return; } this.isLoading = true; }, methods: { canPlay() { this.isCanPlay = true; this.isLoading = false; this.$refs.video.play(); }, error() { alert("视频出错了,请联系客服人员"); this.showLoading = false; }, ended() { this.$emit("ended"); }, clickClose() { this.$emit("update:isShowPlayer", false); } } }; </script> <style scoped> .video-box { position: fixed; z-index: 9999; top: 0; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; background-color: #000; } video { width: 100%; } .close-box { position: fixed; z-index: 10000; top: 0px; right: 0; padding: 20px; } .close-box img { width: 24px; } .loadingBox { position: fixed; z-index: 9999; top: 0; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; background-color: transparent; } .loading-icon { width: 50px; padding: 10px; border-radius: 10px; background-color: rgba(0, 0, 0); } </style>
列表 的代码
<template> <div class="paper"> <div class="list"> <play-item :info="item" class="item" @clickItem="clickItem(index)" v-for="(item, index) in playList" :key="index" ></play-item> </div> <videoPlayer v-if="isShowPlayer" :isShowPlayer.sync="isShowPlayer" :info="curVideo" @ended="ended" ></videoPlayer> <div class="loadingBox" v-if="isLoading"> <img class="loading-icon" src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/loading.gif" alt="正在加载..." /> </div> </div> </template> <script> import PlayItem from "./components/PlayItem"; import VideoPlayer from "./components/VideoPlayer"; export default { components: { PlayItem, VideoPlayer }, data() { return { // 播放列表 playList: [], // 是否正在加载 isLoading: true, // 是否显示播放器 isShowPlayer: false, // 当前播放视频的索引 curVideoIndex: null }; }, computed: { curVideo() { return this.playList[this.curVideoIndex]; } }, mounted() { this.getPlayList(); }, methods: { getPlayList() { setTimeout(() => { this.isLoading = false; const list = [ { title: "黑色毛衣", url: "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/video.mp4" }, { title: "发如雪", url: "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/video2.mp4" }, { title: "千里之外", url: "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/video.mp4" } ]; list.forEach( item => (item.cover = "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/cover.png") ); this.playList = list; }, 20); }, clickItem(index) { this.curVideoIndex = index; this.isShowPlayer = true; }, ended() { const isLast = this.curVideoIndex === this.playList.length - 1; if (isLast) { alert("这是最后一个视频了~~"); return; } this.curVideoIndex++; } } }; </script> <style scoped> .list { padding: 16px; } .item { margin-bottom: 16px; } .loadingBox { position: fixed; z-index: 9999; top: 0; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; background-color: transparent; } .loading-icon { width: 50px; padding: 10px; border-radius: 10px; background-color: rgba(0, 0, 0); } </style>
列表项组件 的代码
<template> <div class="play-item-box" @click="clickItem"> <div class="left"> <img class="resource" :src="info.cover" @error="errorImg" alt="" /> <img class="play-icon" src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/play_icon.png" alt="" /> </div> <div class="right"> <div class="title"> {{ info.title }} </div> <div class="time">发送时间:2021-10-21 10:10:10</div> </div> </div> </template> <script> export default { name: "playItem", props: { info: Object }, data() { return { defaultCover: "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/cover.png" }; }, methods: { clickItem() { this.$emit("clickItem"); }, errorImg() { this.info.coverImage = this.defaultCover; } } }; </script> <style scoped> .play-item-box { display: flex; border-radius: 6px; } .left { width: 33%; position: relative; height: 70px; } .left .resource { display: block; width: 100%; border-radius: 6px; height: 100%; } .play-icon { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1; width: 33px; } .right { flex: 1; margin-left: 10px; display: flex; flex-direction: column; justify-content: space-between; padding: 2px 0; } .title { font-size: 16px; font-weight: bold; color: #333; line-height: 1.3; } .time { font-size: 12px; color: #999; } .video { position: absolute; width: 100%; height: 100%; top: 0; opacity: 0; left: 0; bottom: 0; right: 0; z-index: 11; } </style>