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

Vue 3 + TypeScript 开发的视频直播页面组件

主要用于机场或无人机场景的 RTMP/RTSP/GB28181 协议直播

一、组件核心功能概览

该组件的核心是实现 “直播源选择 - 直播参数配置 - 直播播放 / 控制” 的完整流程,支持三种主流直播协议,具体功能如下:

功能模块具体能力
直播源管理加载机场 / 无人机设备列表、摄像头列表、镜头类型列表
协议与质量配置支持 RTMP(Web 播放)、RTSP(VLC 播放)、GB28181(安防协议),可选 5 档画质
播放控制直播启动、暂停、镜头切换、画质更新、播放状态监听
适配能力响应式布局(PC / 移动端视频尺寸适配)、多协议播放器兼容

二、核心变量定义

变量名类型用途
liveTypeListSelectOption[]直播协议选项(RTMP=1、RTSP=2、GB28181=3)
clarityListSelectOption[]画质选项(0 = 自适应、1 = 流畅、2 = 标清、3 = 高清、4 = 超清)
videowebrtcRef<HTMLDivElement>视频播放容器的 DOM 引用
jessibucaanyJessibuca 播放器实例(核心播放对象)
liveStateRef<boolean>直播状态(true = 正在播放、false = 已暂停)
droneSelected/cameraSelectedRef<any>已选择的无人机 / 摄像头 ID(用于拼接直播参数)
osdVisibleComputedRef从 Store 中获取 OSD(屏幕显示信息)状态

三、代码

<template><div class="flex-column flex-justify-start flex-align-center" style="position:relative;height:100%"><div id="video-webrtc" ref="videowebrtc"></div><div class="mt10 flex-row flex-justify-center flex-align-center"></div></div></template><script lang="ts" setup>
import { message } from 'ant-design-vue'
import { onMounted, reactive, ref, computed } from 'vue'
import { CURRENT_CONFIG as config } from '/@/api/http/config'
import {changeLivestreamLens,getLiveCapacity,setLivestreamQuality,startLivestream,stopLivestream,oneStartLivestream
} from '/@/api/manage'
import { getRoot } from '/@/root'
import jswebrtc from '/@/vendors/jswebrtc.min.js'
import Jessibuca from '../../public/js/jessibuca'
import { useMyStore } from '/@/store'
import { ELocalStorageKey, EBizCode } from '/@/types'
const store = useMyStore()
const osdVisible = computed(() => {return store.state.osdVisible
})
const root = getRoot()interface SelectOption {value: any,label: string,more?: any}const liveTypeList: SelectOption[] = [{value: 1,label: 'RTMP'},{value: 2,label: 'RTSP'},{value: 3,label: 'GB28181'}
]
const clarityList: SelectOption[] = [{value: 0,label: '自适应'},{value: 1,label: '流畅'},{value: 2,label: '标清'},{value: 3,label: '高清'},{value: 4,label: '超清'}
]
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
const deviceSn = localStorage.getItem(EBizCode.DeviceSn)!
const dock_sn = localStorage.getItem(EBizCode.DockSn)!
const videowebrtc = ref(null)
const livestreamSource = ref()
const droneList = ref()
const cameraList = ref()
const videoList = ref()
const droneSelected = ref()
const cameraSelected = ref()
const videoSelected = ref()
const claritySelected = ref()
const videoId = ref()
const liveState = ref<boolean>(false)
const livetypeSelected = ref()
const rtspData = ref()
const lensList = ref<string[]>([])
const lensSelected = ref<String>()
const isDockLive = ref(false)
const nonSwitchable = 'normal'
const cameraId = ref<string[]>([])
const wrjbianId = ref<string[]>([])
const onRefresh = async () => {// console.log('onRefresh进入')droneList.value = []cameraList.value = []videoList.value = []droneSelected.value = nullcameraSelected.value = nullvideoSelected.value = nullawait getLiveCapacity({}).then(res => {// console.log(res)if (res.code === 0) {if (res.data === null) {console.warn('warning: get live capacity is null!!!')return}const resData: Array<[]> = res.dataconsole.log('live_capacity:', resData)livestreamSource.value = resDatacameraId.value = resData[0].cameras_list[0].index // 相机id// wrjbianId.value = resData[1].cameras_list[1].index// console.log('相机编号', cameraId.value)const temp: Array<SelectOption> = []if (livestreamSource.value) {livestreamSource.value.forEach((ele: any) => {temp.push({ label: ele.name + '-' + ele.sn, value: ele.sn, more: ele.cameras_list })})droneList.value = temp}}onStart()}).catch(error => {message.error('数据获取异常', error)console.error(error)})
}
const type = ref(null)onMounted(() => {type.value = sessionStorage.getItem('type')// console.log('deviceSn', deviceSn)//   onRefresh()// setInterval(onOpen, 5000)onOpen()
})
function onOpen () {const params = {workspace_id: workspaceId,sn: deviceSn}oneStartLivestream(params).then(res => {play(res.data.url)console.log('直播指令', res)}).catch(error => {message.error('数据获取异常', error)console.error(error)})
}
let jessibuca: any = null
// const playUrl = ref('http://flv.bdplay.nodemedia.cn/live/bbb.flv')
const playing = ref(false)
const quieting = ref(true)
const loaded = ref(false)const createJessibuca = () => {jessibuca = new (window as any).Jessibuca({container: videowebrtc.value,videoBuffer: 0.2, // 缓存时长isResize: false,text: '',// background: "bg.jpg",loadingText: '加载中',// hasAudio:false,debug: true,showBandwidth: false, // showBandwidth.value, // 显示网速operateBtns: {fullscreen: false, // showOperateBtns.value,screenshot: false, // showOperateBtns.value,play: false, // showOperateBtns.value,audio: false, // showOperateBtns.value,},forceNoOffscreen: false, // forceNoOffscreen.value,isNotMute: false,decoder: '/js/decoder.js'}) as Jessibucajessibuca.on('load', function () {// console.log('on load')})jessibuca.on('log', function (msg: any) {// console.log('on log', msg)})jessibuca.on('record', function (msg: any) {// console.log('on record:', msg)})jessibuca.on('pause', function () {// console.log('on pause')playing.value = false})jessibuca.on('play', function () {// console.log('on play')playing.value = trueloaded.value = truequieting.value = jessibuca.isMute()})jessibuca.on('fullscreen', function (msg: any) {// console.log('on fullscreen', msg)})jessibuca.on('mute', function (msg: any) {// console.log('on mute', msg)quieting.value = msg})jessibuca.on('mute', function (msg: any) {// console.log('on mute2', msg)})jessibuca.on('audioInfo', function (msg: any) {// console.log('audioInfo', msg)})// jessibuca.on("bps", function (bps) {//   // console.log('bps', bps);// });// let _ts = 0;// jessibuca.on("timeUpdate", function (ts) {//   // console.log('timeUpdate,old,new,timestamp', _ts, ts, ts - _ts);//   // _ts = ts;// });jessibuca.on('videoInfo', function (info: any) {// console.log('videoInfo', info)})jessibuca.on('error', function (error: any) {console.log('error', error)})jessibuca.on('timeout', function () {// console.log('timeout')})jessibuca.on('start', function () {// console.log('start')})// jessibuca.on("stats", function (stats) {//   console.log('stats', JSON.stringify(stats));// });jessibuca.on('performance', function (performance: any) {let show = '卡顿'if (performance === 2) {show = '非常流畅'} else if (performance === 1) {show = '流畅'}})jessibuca.on('buffer', function (buffer: any) {// console.log('buffer', buffer)})jessibuca.on('stats', function (stats: any) {// console.log('stats', stats)})jessibuca.on('kBps', function (kBps: any) {// console.log('kBps', kBps)})// 显示时间戳 PTSjessibuca.on('videoFrame', function () {})//jessibuca.on('metadata', function () {})
}// const play = () => {
//   if (playUrl.value) {
//     jessibuca.play(playUrl.value)
//   }
// }
// const pause = () => {
//   jessibuca.pause()
//   playing.value = false
// }const play = (url: string) => {if (jessibuca) {jessibuca.destroy()}createJessibuca()playing.value = falseloaded.value = falseif (url) {jessibuca.play(url)}
}const onStart = async () => {// console.log(131231, osdVisible.value)// livetypeSelected.value = 1// isDockLive.value = true// console.log(//   'Param:',//   livetypeSelected.value,//   droneSelected.value,//   cameraSelected.value,//   videoSelected.value,//   claritySelected.value// )// 1 7CTDM1600BS774 165-0-7 normal-0 0livetypeSelected.value = 1 // 强制设置为rtmp类型droneSelected.value = osdVisible.value.gateway_sn// cameraSelected.value = '165-0-7'cameraSelected.value = cameraId.value// console.log('相机编号2', cameraSelected.value)videoSelected.value = 'normal-0'claritySelected.value = 0const timestamp = new Date().getTime().toString()if (livetypeSelected.value == null ||droneSelected.value == null ||cameraSelected.value == null ||claritySelected.value == null) {message.warn('waring: not select live para!!!')return}videoId.value =droneSelected.value + '/' + cameraSelected.value + '/' + (videoSelected.value || nonSwitchable + '-0')let liveURL = ''switch (livetypeSelected.value) {case 1: {// RTMP// liveURL = config.rtmpURL + timestampliveURL = config.rtmpURL + droneSelected.valuebreak}case 2: {// RTSP 浏览器和 Jessibuca 都不支持直接播放 RTSP 流,需要一个中间件来将 RTSP 流转成 HTTP-FLV 或 HLS 等 Web 可播放的格式。// liveURL = config.rtspURL + droneSelected.valueliveURL = config.rtspURL// liveURL = `userName=${config.rtspUserName}&password=${config.rtspPassword}&port=${config.rtspPort}`break}case 3: {liveURL = `serverIP=${config.gbServerIp}&serverPort=${config.gbServerPort}&serverID=${config.gbServerId}&agentID=${config.gbAgentId}&agentPassword=${config.gbPassword}&localPort=${config.gbAgentPort}&channel=${config.gbAgentChannel}`break}default:console.warn('warning: live type is not correct!!!')break}// console.log('是否进入')console.log(osdVisible.value.gateway_sn)console.log(osdVisible.value.sn)// console.log('编号', cameraSelected.value)const jichang = osdVisible.value.gateway_snconst wurenji = osdVisible.value.snconst bianId = cameraSelected.valueawait startLivestream({url: type.value === '1' ? `${config.rtmpURL}${jichang}` : `${config.rtmpURL}${wurenji}`,video_id: type.value === '1' ? jichang + '/' + bianId + '/normal-0' : wurenji + '/98-0-0/normal-0',url_type: 1, // 默认rtmp视频流,可更改选择video_quality: 0}).then(res => {if (res.code !== 0) {return}if (livetypeSelected.value === 3) {const url = res.data.urlconst videoElement = videowebrtc.value// gb28181,it will fail if not wait.message.loading({content: 'Loding...',duration: 4,onClose () {const player = new jswebrtc.Player(url, {video: videoElement,autoplay: true,onPlay: (obj: any) => {console.log('start play livestream')}})}})// } else if (livetypeSelected.value === 2) {//   console.log(res)//   rtspData.value =//     'url:' +//     res.data.url +//     '&username:' +//     res.data.username +//     '&password:' +//     res.data.password} else if (livetypeSelected.value === 1 || livetypeSelected.value === 2) {play(res.data.url)// const url = res.data.url// const videoElement = videowebrtc.value// console.log('start live:', url)// console.log(videoElement)// const player = new jswebrtc.Player(url, {//   video: videoElement,//   autoplay: true,//   onPlay: (obj: any) => {//     console.log('start play livestream')//   }// })}liveState.value = true}).catch(err => {console.error(err)})
}
const onStop = () => {videoId.value = droneSelected.value + '/' + cameraSelected.value + '/' + (videoSelected.value || nonSwitchable + '-0')stopLivestream({video_id: videoId.value}).then(res => {if (res.code === 0) {message.success(res.message)liveState.value = falselensSelected.value = undefinedconsole.log('stop play livestream')}})
}const onUpdateQuality = () => {if (!liveState.value) {message.info('Please turn on the livestream first.')return}setLivestreamQuality({video_id: videoId.value,video_quality: claritySelected.value}).then(res => {if (res.code === 0) {message.success('Set the clarity to ' + clarityList[claritySelected.value].label)}})
}const onLiveTypeSelect = (val: any) => {livetypeSelected.value = val
}
const onDroneSelect = (val: SelectOption) => {droneSelected.value = val.valueconst temp: Array<SelectOption> = []cameraList.value = []cameraSelected.value = undefinedvideoSelected.value = undefinedvideoList.value = []lensList.value = []if (!val.more) {return}val.more.forEach((ele: any) => {temp.push({ label: ele.name, value: ele.index, more: ele.videos_list })})cameraList.value = temp
}
const onCameraSelect = (val: SelectOption) => {cameraSelected.value = val.valueconst result: Array<SelectOption> = []videoSelected.value = undefinedvideoList.value = []lensList.value = []if (!val.more) {return}val.more.forEach((ele: any) => {result.push({ label: ele.type, value: ele.index, more: ele.switch_video_types })})videoList.value = resultif (videoList.value.length === 0) {return}const firstVideo: SelectOption = videoList.value[0]videoSelected.value = firstVideo.valuelensList.value = firstVideo.morelensSelected.value = firstVideo.labelisDockLive.value = lensList.value?.length > 0
}
const onVideoSelect = (val: SelectOption) => {videoSelected.value = val.valuelensList.value = val.morelensSelected.value = val.label
}
const onClaritySelect = (val: any) => {claritySelected.value = val
}
const onSwitch = () => {if (lensSelected.value === undefined || lensSelected.value === nonSwitchable) {message.info('The ' + nonSwitchable + ' lens cannot be switched, please select the lens to be switched.', 8)return}changeLivestreamLens({video_id: videoId.value,video_type: lensSelected.value}).then(res => {if (res.code === 0) {message.success('Switching live camera successfully.')}})
}
</script><style lang="scss" scoped>@import '/@/styles/index.scss';#video-webrtc {background: rgba(27, 39, 61, 0.8);width: 100%;height: 100%;}@media (max-width: 720px) {#video-webrtc {width: 90vw;height: 52.7vw;}}</style>

livetypeSelected.value 的值主要来自两个地方:

  1. 用户交互(主要来源):用户在界面上操作 a-select 下拉框时,Vue 的 v-model:value 指令会自动更新它的值。
  2. 代码逻辑(次要来源):在某些特定函数(如 onStart)中,代码会强制给它赋一个默认值。
http://www.dtcms.com/a/482879.html

相关文章:

  • 【开题答辩实录分享】以《智能体育训练助手的设计与实现》为例进行答辩实录分享
  • Vue + Element Plus 手动注册 v-loading 指令
  • docker elasticsearch端口映射解决端口冲突问题
  • SD:在一个 Ubuntu 系统安装 stable diffusion ComfyUI
  • 如何使用命令修改conda虚拟环境目录
  • 学习随笔-ES6和ES5的区别
  • 文件上传阿里云OSS以及本地图片服务器搭建
  • 企业网站建设需注意什么商务网站管理与建设
  • 威县做网站哪儿好个人网站建设的背景
  • Excel导出报Can not find ‘Converter‘ support class Map.
  • Linux osq_lock
  • SSM共享汽车管理系统300fw(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • Docker安装部署FileBrowser
  • 基于单片机频率周期脉宽测量系统Proteus仿真(含全部资料)
  • wap网站制作模板电影网站开发api
  • xss-labs通关(2)
  • 前后端分离项目前端页面开发远程调试代理解决跨域问题方法
  • 商城网站都有哪 些功能企业网站搭建方案
  • AI让404变品牌秀场:用提示词秒生成个性化错误文案
  • [2025.10.14]Win11.25H2企业版26220.6780深度精简优化 PIIS出品 1.9GB
  • Python路径操作革命:拥抱pathlib
  • 逻辑学是什么浅谈
  • 在阿里巴巴上做网站要多少钱阿里巴巴网站官网
  • 在 orin 上 安装了 miniconda 如何使用 orin 内置的 opencv
  • keil工具详细入门教学
  • 招聘网站开发方案doc蒸丞文化传媒有限公司网页设计
  • Linux中NUMA节点初始化内存相关参数的实现
  • [Backstage] 后端服务 | 示例插件
  • 黑马商城day2-Docker
  • 赤水市住房和城乡建设局网站网站建设费专票会计分录