h5使用vue实现直播(主播端和观众端)
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
观众端:我们是使用vue-video-player进行实现的
1.下载vue-video-player包和videojs-contrib-hls(播放插件)
npm install vue-video-player --save
npm install videojs-contrib-hls --save
遇到下载失败的情况,可参考这篇文章,下载低版本的包
Vue-video-player下载失败(npm i 报错)_npm安装video失败-CSDN博客
2.在main.js文件中引入
import VideoPlayer from 'vue-video-player'
import 'vue-video-player/src/custom-theme.css'
import 'video.js/dist/video-js.css'
import 'videojs-contrib-hls'
Vue.use(VideoPlayer)
3.组件中直接引入使用,下面为j基本完整组件代码
<template>
<div>
<videoPlayer @playing="onPlayerPlaying($event)" @ended="onPlayerEnded($event)" ref="videoPlayer" class="vjs-custom-skin videoPlayer" :options="playerOptions" :playsinline="true"></videoPlayer>
</div>
</template>
<script>
import { videoPlayer } from 'vue-video-player';
import 'videojs-flash';
import { antiShake } from '@/utils';
export default {
components: {
videoPlayer
},
data() {
return {
isShowPlayButton: true,
playerOptions: {
// 播放器功能按钮
controlBar: {
//是否显示音量条,默认显示
volumePanel: false,
// 当前时间和持续时间的分隔符'/'
timeDivider:false,
// 是否显示剩余时间的功能
remainingTimeDisplay:false,
// 是否显示全屏按钮
fullscreenToggle:true,
// 是否显示直播时长
durationDisplay:false,
// 暂停和播放键
playToggle:true,
// 当前时间
currentTimeDisplay:true,
// 进度条
progressControl:true,
},
height: '300',
sources: [
{
// 解析的类型(目前只支持m3u8的视频流)
type: 'application/x-mpegURL',
// 后台给到的视频流地址(我这里的是测试地址)
src: 'http://220.161.87.62:8800/hls/0/index.m3u8'
}
],
// techOrder: ['flash'],
aspectRatio: '16:9',
// 可选的播放速度
playbackRates: [0.5, 1.0, 1.5, 2.0], // 可选的播放速度
//自动播放,直播视频不支持,
autoplay: false,
// 默认情况下将会消除任何音频。
muted: false,
// 结束之后是否重新开始
loop: false,
// 直播封面
// poster:'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
//视频播放异常展现给用户的报错信息
notSupportedMessage: '此视频暂无法播放,请稍后再试',
}
};
},
methods: {
// 视频播完的回调
onPlayerEnded() {},
// 点击播放触发的回调函数
onPlayerPlaying() {
this.isShowPlayButton = true;
this.$forceUpdate();
}
}
};
</script>
<style lang="scss" scoped>
</style>
给他进行配置好就可以进行基本的直播播放了,在线直播源地址可通过此文章获取
常用m3u8,rtsp,rtmp,flv,mp4直播流在线测试地址_m3u8直播源-CSDN博客
如果觉得播放器按钮太多太繁琐,可以在playerOptions的controlBar功能对象进行配置
// 播放器功能按钮
controlBar: {
//是否显示音量条,默认显示
volumePanel: false,
// 当前时间和持续时间的分隔符'/'
timeDivider:false,
// 是否显示剩余时间的功能
remainingTimeDisplay:false,
// 是否显示全屏按钮
fullscreenToggle:true,
// 是否显示直播时长
durationDisplay:false,
// 暂停和播放键
playToggle:true,
// 当前时间
currentTimeDisplay:true,
// 进度条
progressControl:true,
},
当然也可以用css进行隐藏你不需要的
/* 时间 */
/deep/ .video-js .vjs-current-time,
.vjs-no-flex .vjs-current-time {
opacity: 0;
}
// 时间后分割线
/deep/ .vjs-custom-skin > .video-js .vjs-control-bar .vjs-time-divider {
opacity: 0;
}
// 播放按钮
.yesPlayButton {
/deep/ .vjs-custom-skin > .video-js .vjs-big-play-button {
opacity: 0;
}
}
4.设置自定义按钮(组件提供的功能按钮无法满足我们需求时候)
比如我这边需要一个刷新直播的按钮和一个收起直播的按钮
// 原播放器直播时长按钮,这里我直接直接设置为透明色,加上一个背景icon图,变成自定义按钮
/deep/ .vjs-duration-display {
background: url('../../../assets/images/retract.png') no-repeat;
background-size: 17px;
color: transparent;
}
// 原播放器live标识,这里我直接直接设置为透明色,加上一个背景icon图,变成自定义按钮
/deep/ .vjs-custom-skin > .video-js .vjs-control.vjs-live-control {
background: url('../../../assets/images/refresh.png') no-repeat;
background-size: 20px;
color: transparent;
position: relative;
top: 25%;
left: 6%;
}
最后页面点击需要判断点击的是我们自定义的哪一个,我们通过$events拿到点击的类名,这样我们就可以写我们自己的逻辑了
<div @click="handleVideoClick($event)" :class="isShowPlayButton?'yesPlayButton':'noPlayButton'">
<videoPlayer @playing="onPlayerPlaying($event)" @ended="onPlayerEnded($event)" ref="videoPlayer" class="vjs-custom-skin videoPlayer" :options="playerOptions" :playsinline="true"></videoPlayer>
</div>
handleVideoClick: antiShake(function (data) {
if (data.srcElement.classList[0] === 'vjs-live-display') {
// 点击刷新
this.$emit('refresh');
} else if (data.srcElement.classList[0] === 'vjs-duration-display') {
// 收起直播
this.$emit('playVedio',false)
}
}),
完整代码:(引入组件传入后台数据)
<template>
<div>
<videoPlayer @playing="onPlayerPlaying($event)" @ended="onPlayerEnded($event)" ref="videoPlayer" class="vjs-custom-skin videoPlayer" :options="playerOptions" :playsinline="true"></videoPlayer>
</div>
</template>
<script>
import { videoPlayer } from 'vue-video-player';
import 'videojs-flash';
import { antiShake } from '@/utils';
export default {
props: {
cloneTabsData: {
typeof: Object,
default: () => {}
},
vedioButtonStatus: {
typeof: Boolean,
default: true
},
isNum: {
typeof: Number,
default: 0
}
},
components: {
videoPlayer
},
watch: {
cloneTabsData: {
deep: true,
immediate: true,
handler(val) {
if (val.playUrl) {
this.playerOptions.sources[0].src = val.playUrl.replace('http:','https:');;
}
}
},
vedioButtonStatus: {
deep: true,
immediate: true,
handler(val) {
if (val && this.isNum !== 0) {
this.isShowPlayButton = false;
}
}
}
},
data() {
return {
isShowPlayButton: true,
playerOptions: {
// 播放器功能按钮
controlBar: {
//是否显示音量条,默认显示
volumePanel: false,
// 当前时间和持续时间的分隔符'/'
timeDivider:false,
// 是否显示剩余时间的功能
remainingTimeDisplay:false,
// 是否显示全屏按钮
fullscreenToggle:true,
// 是否显示直播时长
durationDisplay:false,
// 暂停和播放键
playToggle:true,
// 当前时间
currentTimeDisplay:true,
// 进度条
progressControl:true,
},
height: '300',
sources: [
{
// 解析的类型(目前只支持m3u8的视频流)
type: 'application/x-mpegURL',
// 后台给到的视频流地址(我这里的是测试地址)
src: 'http://220.161.87.62:8800/hls/0/index.m3u8'
}
],
// techOrder: ['flash'],
aspectRatio: '16:9',
// 可选的播放速度
playbackRates: [0.5, 1.0, 1.5, 2.0], // 可选的播放速度
//自动播放,直播视频不支持,
autoplay: false,
// 默认情况下将会消除任何音频。
muted: false,
// 结束之后是否重新开始
loop: false,
// 直播封面
// poster:'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
//视频播放异常展现给用户的报错信息
notSupportedMessage: '此视频暂无法播放,请稍后再试',
}
};
},
methods: {
// 视频播完的回调
onPlayerEnded() {},
// 点击播放触发的回调函数
onPlayerPlaying() {
this.isShowPlayButton = true;
this.$forceUpdate();
},
// 刷新直播进度
handleVideoClick: antiShake(function (data) {
if (data.srcElement.classList[0] === 'vjs-live-display') {
// 点击刷新
this.$emit('refresh');
} else if (data.srcElement.classList[0] === 'vjs-duration-display') {
// 收起直播
this.$emit('playVedio',false)
}
}),
}
};
</script>
<style lang="scss" scoped>
.custom-button {
position: absolute;
width: 15px;
top: 91%;
left: 56%;
transform: translate(-50%, -50%);
z-index: 1;
}
/deep/ .vjs-duration-display {
background: url('../../../assets/images/retract.png') no-repeat;
background-size: 17px;
color: transparent;
}
// 声音标识
/deep/ .video-js .vjs-mute-control .vjs-icon-placeholder:before, .vjs-icon-volume-high:before {
display: none;
}
// 直播live标识
/deep/ .vjs-custom-skin > .video-js .vjs-control.vjs-live-control {
background: url('../../../assets/images/refresh.png') no-repeat;
background-size: 20px;
color: transparent;
// border: 1px solid yellow;
position: relative;
top: 25%;
left: 6%;
// margin-top: 3%;
// transform: scale(0.5);
// margin-left: 1%;
}
/* 时间 */
/deep/ .video-js .vjs-current-time,
.vjs-no-flex .vjs-current-time {
opacity: 1;
}
// 时间后分割线
/deep/ .vjs-custom-skin > .video-js .vjs-control-bar .vjs-time-divider {
opacity: 1;
}
// 播放按钮
.yesPlayButton {
/deep/ .vjs-custom-skin > .video-js .vjs-big-play-button {
opacity: 1;
}
}
.noPlayButton {
/deep/ .vjs-custom-skin > .video-js .vjs-big-play-button {
opacity: 0;
}
}
</style>
主播端:主播端我们是使用阿里云进行直播的(h5端)
注意:需要现在阿里云购买推流服务器等进行一系列配置,本文章是直接讲述前端方面,已经拿到了后台给的推流地址之后的前端步骤
WebRTS推流SDK如何快速接入_视频直播(LIVE)-阿里云帮助中心
我这里有用html写一个demo,需要看的可以私聊我,给源码
在vue项目的用法就是
1.下载aliyun-rts-pusher包
npm install aliyun-rts-pusher --save
2.组件中使用
<template>
<div v-if="$route.query.play">
<div id="videoContainer"></div>
<!-- 悬浮组件 -->
<drag-icon ref="dragIconCom" :gapWidthPx="13" :coefficientHeight="0.66">
<div class="activityDrag" slot="icon" @click.stop="play()">
<img src="../../assets/images/icon/fz.png" alt="" />
</div>
</drag-icon>
<!-- 关闭直播 -->
<div class="closeVedio">
<span @click="releash" style="margin-right:10px">刷新直播</span>
<span @click="closeVedio">结束直播</span>
</div>
</div>
</template>
<script>
import { getpushstreamUrl } from '@/api/index.js';
import dragIcon from '@/components/dragIcon.vue';
import { AliRTSPusher } from 'aliyun-rts-pusher';
const pushClient = AliRTSPusher.createClient();
// 监听错误事件
pushClient.on('error', (err) => {
console.log(err.errorCode);
});
export default {
components: {
dragIcon
},
data() {
return {
options: [],
head: false,
pushUrl:''
};
},
created(){
this.mesPushUrl()
},
async mounted() {
const videoEl = pushClient.setRenderView('videoContainer');
// 获取摄像头麦克风列表
const deviceManager = await pushClient.getDeviceManager();
// 获取摄像头列表
const cameraList = await deviceManager.getCameraList();
// 获取麦克风列表
const micList = await deviceManager.getMicList();
this.options = cameraList;
console.log(cameraList[cameraList.length - 1].deviceId, '99');
// // 打开摄像头
await pushClient.startCamera(cameraList[cameraList.length - 1].deviceId);
// // 打开麦克风
await pushClient.startMicrophone(
cameraList[cameraList.length - 1].deviceId
);
pushClient.startPush(
this.pushUrl
);
},
methods: {
async getList() {
const pushClient = AliRTSPusher.createClient();
// 获取摄像头麦克风列表
const deviceManager = await pushClient.getDeviceManager();
// 获取摄像头列表
const cameraList = await deviceManager.getCameraList();
// 获取麦克风列表
const micList = await deviceManager.getMicList();
this.options = cameraList;
console.log(cameraList, 'cameraList');
},
// 获取推流地址
mesPushUrl(){
getpushstreamUrl().then(res=>{
this.pushUrl = res.data.pushUrl
}).catch(err=>{
console.log(err);
})
},
async play() {
this.head = !this.head;
console.log(this.head, 'this.head');
console.log(
this.options[this.options.length - 1].deviceId,
' this.options[this.options.length - 1].deviceId'
);
if (!this.head) {
// 后置
await pushClient.startCamera(
this.options[this.options.length - 1].deviceId
);
// 打开麦克风
await pushClient.startMicrophone(
this.options[this.options.length - 1].deviceId
);
pushClient.startPush(
this.pushUrl
);
} else {
// 前置
await pushClient.startCamera(this.options[0].deviceId);
// 打开麦克风
await pushClient.startMicrophone(this.options[0].deviceId);
pushClient.startPush(
this.pushUrl
);
}
},
// 关闭直播
closeVedio() {
// 停止推流
pushClient.stopPush();
// 关闭摄像头
pushClient.stopCamera();
// 关闭麦克风
pushClient.stopMicrophone();
pushClient.dispose();
pushClient.stopScreenCapture();
this.$router.push({
path: '/live',
query: { id: this.$route.query.id , type: this.$route.query.type }
});
},
releash() {
window.location.reload();
}
}
};
</script>
<style lang="scss" scoped>
.closeVedio {
width: 100%;
text-align: center;
span {
width: 100px;
text-align: center;
display: inline-block;
line-height: 36px;
background: #FF0000;
width: 120px;
height: 36px;
border-radius: 20px;
opacity: 1;
color: white;
}
}
</style>
dragIcon:图标悬浮组件,我这里是一个翻转摄像头icon,用户反转摄像头
附件:dragIcon组件代码
<template>
<div class="ys-float-btn" :style="{
width: itemWidth + 'px',
height: itemHeight + 'px',
left: left + 'px',
top: top + 'px',
}" ref="dragIcon" @click="onBtnClick" @touchstart.stop="handleTouchStart" @touchmove.prevent.stop="handleTouchMove($event)" @touchend.stop="handleTouchEnd">
<slot class="aaa" name="icon"></slot>
</div>
</template>
<script>
export default {
name: 'DragIcon',
props: {
// 距离屏幕边距的距离 传入px,进行处理
gapWidthPx: {
type: Number,
default: 0
},
// 页面初始化到顶部的百分比,小数形式展示
coefficientHeight: {
type: Number,
default: 0.65
}
},
computed: {
gapWidth() {
let num = this.gapWidthPx;
if (
document.getElementsByTagName('html')[0] &&
document.getElementsByTagName('html')[0].style.fontSize.split('px')[0]
) {
num =
(num / 100) *
document
.getElementsByTagName('html')[0]
.style.fontSize.split('px')[0];
}
return num;
},
itemWidth() {
return this.itemWidth1 || 40;
},
itemHeight() {
return this.itemHeight1 || 40;
}
},
watch: {
// 初始化计算组件宽度,获取组件距离左边的宽度
itemWidth: {
handler(newV, oldV) {
if (newV > 0 && newV != oldV) {
this.clientWidth = document.documentElement.clientWidth;
this.clientHeight = document.documentElement.clientHeight;
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
}
}
}
},
updated() {
this.$nextTick(() => {
if (
this.$refs.dragIcon &&
this.$refs.dragIcon.children[0] &&
this.$refs.dragIcon.children[0].clientHeight
) {
this.itemHeight1 = this.$refs.dragIcon.children[0].clientHeight;
this.itemWidth1 = this.$refs.dragIcon.children[0].clientWidth;
}
});
},
created() {
this.clientWidth = document.documentElement.clientWidth;
this.clientHeight = document.documentElement.clientHeight;
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
this.top = this.clientHeight * this.coefficientHeight;
},
methods: {
onBtnClick() {
this.$emit('onBtnClick');
},
handleTouchStart() {
this.startToMove = true;
this.$refs.dragIcon.style.transition = 'none';
},
handleTouchMove(e) {
if (this.startToMove && e.targetTouches.length === 1) {
const clientX = e.targetTouches[0].clientX; //手指相对视口的x
const clientY = e.targetTouches[0].clientY; //手指相对视口的y
this.left = clientX - this.itemWidth / 2;
this.top = clientY - this.itemHeight / 2;
}
},
handleTouchEnd() {
this.$refs.dragIcon.style.transition = 'all 0.3s';
if (this.left > this.clientWidth / 2) {
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
} else {
this.left = this.gapWidth;
}
if (this.top <= 36) {
this.top = 36 + this.gapWidth;
} else {
let bottom = this.clientHeight - 50 - this.itemHeight - this.gapWidth;
if (this.top >= bottom) {
this.top = bottom;
}
}
}
},
data() {
return {
currentTop: 0,
clientWidth: 0,
clientHeight: 0,
left: 0,
top: 0,
itemWidth1: 40,
itemHeight1: 40
};
}
};
</script>
<style lang="scss" scoped>
.ys-float-btn {
z-index: 20;
transition: all 0.3s;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: fixed;
bottom: 20vw;
}
</style>
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献6条内容
所有评论(0)