uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度
WebSocket+图片帧定时拍照+Base64传输✅ 完全免费无需服务器
纯前端实现
高延迟高流量
帧率极低
个人demo测试
超低频监控
500ms-2s⭐⭐
RTMP推流TRTC/即构SDK推流❌ 付费方案
(部分有免费额度)
专业直播方案
支持高并发
需流媒体服务器
SDK可能收费
中小型直播场景1-3s⭐⭐⭐⭐
开源WebRTC自建coturn+mediasoup✅ 开源免费超低延迟
完全可控
需自建信令服务器
维护成本高
技术团队内网项目200-500ms⭐⭐⭐⭐⭐
商业WebRTC腾讯TRTC/声网Agora❌ 付费方案
(免费试用)
企业级服务
全球节点
按流量/时长计费
绑定厂商
商业视频通话应用200-800ms⭐⭐⭐⭐
HLS切片方案FFmpeg切片+nginx✅ 服务器可自建免费兼容所有浏览器
支持CDN分发
延迟10秒以上非实时录播场景10s+⭐⭐⭐
UDP自定义协议开发原生插件✅ 协议层免费
❌ 人力成本高
完全自定义优化需原生开发能力
过审风险
军工/工业特殊场景200-500ms⭐⭐⭐⭐⭐⭐

免费方案选择建议:

  1. 完全零成本​:

    • WebSocket图片帧(仅适合原型验证)
    • 开源WebRTC(需技术储备)
  2. 轻度付费​:

    • 腾讯云RTMP(免费10GB/月流量)
    • 阿里云直播(免费20GB/月流量)
  3. 企业级推荐​:

    • 声网Agora(首月赠送1万分钟)
    • 即构科技(首月免费)

下面我将介绍WebSocket+图片帧的实现方法:

 

WebSocket + 图片帧传输方案详解

该方案是 ​Uniapp微信小程序 + PC端视频实时预览​ 的一种 ​低成本、纯前端实现​ 的技术方案,适用于 ​低帧率、非严格实时​ 的场景。


🔹 方案原理

  1. 小程序端​:

    • 使用 <camera> 组件获取实时画面。
    • 通过 uni.createCameraContext().takePhoto() ​定时拍照​(如300ms/次)。
    • 将图片转为 ​Base64​ 格式,通过 ​WebSocket​ 发送到服务器。
  2. PC端​:

    • 建立 WebSocket 连接,接收 Base64 图片数据。
    • 使用 <img> 或 <canvas> ​连续渲染图片,模拟视频流效果。

uniapp微信小程序端:

<template><view><camera :device-position="devicePosition" :flash="flash" @error="error" style="width:100%; height:300px;"></camera><button @click="startPushing">开始推流</button><button @click="stopPushing">停止推流</button><button @click="switchFlash">切换闪光灯</button><button @click="flipCamera">翻转摄像头</button><button style="font-size: 24rpx;">webscoket连接状态:{{pushState}}</button></view>
</template><script>
export default {data() {return {pushState: "未连接",devicePosition: 'front',flash: 'off',timer: null,ws: null}},methods: {flipCamera() {this.devicePosition = this.devicePosition === 'back' ? 'front' : 'back';},switchFlash() {this.flash = this.flash === 'off' ? 'torch' : 'off';},startPushing() {// 如果已连接,则不再重复连接if (this.pushState === '连接成功') return;const randomToken = new Date().getTime();const url = 'ws://192.168.1.34:7097/liveWebSocket?linkInfo=a-' + randomToken;this.ws = uni.connectSocket({url,success: () => {console.log('正在尝试连接WebSocket', url);}});this.ws.onOpen(() => {uni.showToast({ title: '连接成功' });this.pushState = '连接成功';this.startCapture();});this.ws.onError((err) => {uni.showToast({ title: '连接异常', icon: 'none' });this.pushState = '连接异常';this.stopPushing();});this.ws.onClose(() => {this.pushState = '已关闭';this.stopPushing();});},stopPushing() {if (this.timer) {clearInterval(this.timer);this.timer = null;}if (this.ws) {this.ws.close();this.ws = null;this.pushState = "未连接";}},startCapture() {const context = uni.createCameraContext(this);// 调整为300ms间隔,减轻设备压力this.timer = setInterval(() => {context.takePhoto({quality: 'low',success: (res) => {this.processAndSendImage(res.tempImagePath);},fail: (err) => {console.error('拍照失败:', err);}});}, 300);},processAndSendImage(tempImagePath) {uni.getFileSystemManager().readFile({filePath: tempImagePath,encoding: 'base64',success: (res) => {const base64Image = `data:image/jpeg;base64,${res.data}`;if (this.ws) {this.ws.send({data: base64Image,success: () => {console.log('图片发送成功');this.cleanTempFile(tempImagePath);},fail: (err) => {console.warn('图片发送失败:', err);}});}},fail: (err) => {console.warn('读取图片失败:', err);}});},cleanTempFile(filePath) {setTimeout(() => {uni.getFileSystemManager().removeSavedFile({filePath,success: () => {console.log('临时文件已删除');},fail: (err) => {console.warn('删除临时文件失败:', err);}});}, 2000);},error(e) {console.error('摄像头错误:', e);}},onUnload() {this.stopPushing();}
}
</script>

pc端预览:

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>管理员监控页面</title><script src="./vue2.js"></script>
</head><body><div id="app"><button @click="toSend">开始请求</button><div v-if="videos && videos.length> 0" style="display: flex;"><div v-for="item in videos" :key="item.sessionId" style="margin: 10px;display: flex;flex-flow: column;":id="item.sessionId">状态:{{item.status}}<img :src="item.videoSrc" style="width: 200px; height: 200px; border: 1px solid red;" alt=""></div></div><div style="background-color: green;margin: 20px 0;display: flex;width: 50%;word-wrap: break-word">接口数据:<div v-html="datas"></div></div><div style="background-color: red;width: 50%">视频列表:<template v-if="videos && videos.length> 0"><p v-for="item2 in videos">{{item2}}</p></template></div></div><script>new Vue({el: '#app',data: {datas: "",videos: [// {//     sessionId: '1',//     status: '未连接',//     videoSrc: '' //图片帧// }]},mounted() {},methods: {// 开始请求toSend() {//断开所有webscoket连接if (this.videos && this.videos.length > 0) {this.videos.forEach(item => {if (item.ws) {item.ws.close();}});}this.datas = "";this.videos = [];// 请求直播人员列表fetch('http://192.168.1.34:7097/liveWebStock/getAcceptList').then(response => response.json()).then(data => {if (data.code == 200) {// console.log(6666, data.data); this.datas = data.data;// 初始化每个视频流对象并建立 WebSocket this.videos = data.data.map(item => ({...item,status: '未连接',videoSrc: '',ws: null}));// 建立 WebSocket 连接this.videos.forEach(item => {this.initWebSocket(item.sessionId);});}}).catch(error => {console.error('请求直播人员列表失败:', error);});},initWebSocket(sessionId) {if (!sessionId) return;const wsUrl = `ws://192.168.1.34:7097/liveWebSocket?linkInfo=b-${sessionId}`;const index = this.videos.findIndex(v => v.sessionId === sessionId);if (index === -1) return;const ws = new WebSocket(wsUrl);ws.onopen = () => {this.$set(this.videos, index, {...this.videos[index],status: '已连接到服务器',ws});};//  处理接收到的数据ws.onmessage = (event) => {console.log("接收到base64图片", event);// 假设是 base64 数据const base64Data = event.data;const url = base64Data;this.$set(this.videos, index, {...this.videos[index],videoSrc: url});};ws.onerror = (error) => {this.$set(this.videos, index, {...this.videos[index],status: `WebSocket 错误: ${error.message}`});console.error(`WebSocket 错误 (${sessionId}):`, error);};ws.onclose = () => {this.$set(this.videos, index, {...this.videos[index],status: 'WebSocket 连接已关闭'});};}}});</script>
</body></html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/86507.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分布式锁实战:Redisson vs. Redis 原生指令的性能对比

分布式锁实战&#xff1a;Redisson vs. Redis 原生指令的性能对比 引言 在DIY主题模板系统中&#xff0c;用户可自定义聊天室的背景、图标、动画等元素。当多个运营人员或用户同时修改同一模板时&#xff0c;若没有锁机制&#xff0c;可能出现“甲修改了背景色&#xff0c;乙…

C++ 设计模式《复制粘贴的奇迹:小明的原型工厂》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;原型模式&#xff08;Prototype Pattern&#xff09; &#x1f4d6; 背景故事 创业初期&#xff0c;小明每天加班写配送路线、配送策略、营销套餐。可当业务做大后&#xff0c;他发现大家常常下单“上次那个套餐”—— “老…

【Elasticsearch】映射:fielddata 详解

映射&#xff1a;fielddata 详解 1.fielddata 是什么2.fielddata 的工作原理3.主要用法3.1 启用 fielddata&#xff08;通常在 text 字段上&#xff09;3.2 监控 fielddata 使用情况3.3 清除 fielddata 缓存 4.使用场景示例示例 1&#xff1a;对 text 字段进行聚合示例 2&#…

开源 vGPU 方案:HAMi,实现细粒度 GPU 切分

本文主要分享一个开源的 GPU 虚拟化方案&#xff1a;HAMi&#xff0c;包括如何安装、配置以及使用。 相比于上一篇分享的 TimeSlicing 方案&#xff0c;HAMi 除了 GPU 共享之外还可以实现 GPU core、memory 得限制&#xff0c;保证共享同一 GPU 的各个 Pod 都能拿到足够的资源。…

PlayDiffusion上线:AI语音编辑进入“无痕时代”

在语音合成与语音编辑领域&#xff0c;一个长期存在的挑战是如何在修改语音内容的同时&#xff0c;保持原始语音的自然性、连贯性和说话人特征。近日&#xff0c;一款名为 PlayDiffusion 的新型 AI 语音修复模型应运而生&#xff0c;成功实现了这一目标。 PlayDiffusion 是一个…

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)

2025年能源电力系统与流体力学国际会议&#xff08;EPSFD 2025&#xff09;将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会&#xff0c;EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…

微信小程序前端面经

一、技术栈与编码能力&#xff08;10min&#xff09; 1. Vue 3 & Composition API Q1&#xff1a;请解释一下 ref 和 reactive 的区别&#xff1f;你在项目中是如何使用的&#xff1f; 答&#xff1a;ref是包装一个原始值或对象&#xff0c;通过.value访问&#xff0c;r…

rknn toolkit2搭建和推理

安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 &#xff0c;不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源&#xff08;最常用&#xff09; conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…

Houdini POP入门学习07 - 分组

使用PopGroup可对粒子进行分组操作&#xff0c;并通过表达式从而更灵活的处理粒子行为。 1.创建box作为发射器&#xff0c;连接popnet节点。 2.双击进入popnet&#xff0c;添加popwind添加向上风力。现在播放粒子可见粒子向上方移动。 3.添加popgroup进行分组&#xff0c;开启…

机器学习复习3--模型评估

误差与过拟合 我们将学习器对样本的实际预测结果与样本的真实值之间的差异称为&#xff1a;误差&#xff08;error&#xff09;。 误差定义&#xff1a; ①在训练集上的误差称为训练误差&#xff08;training error&#xff09;或经验误差&#xff08;empirical error&#x…

Docker 镜像上传到 AWS ECR:从构建到推送的全流程

一、在 EC2 实例中安装 Docker&#xff08;适用于 Amazon Linux 2&#xff09; 步骤 1&#xff1a;连接到 EC2 实例 ssh -i your-key.pem ec2-useryour-ec2-public-ip步骤 2&#xff1a;安装 Docker sudo yum update -y sudo amazon-linux-extras enable docker sudo yum in…

MobileNet 改进:基于MobileNetV2和SSPP的图像分类

1.创新点分析 在计算机视觉领域,高效的图像分类模型一直是研究热点。 本文将详细解析一个结合了MobileNetV2和空间金字塔池化(SSPP)的深度学习模型实现。 模型概述 这个代码实现了一个轻量级但功能强大的图像分类器,主要包含两个核心组件: MobileNetV2作为特征提取器 自定…

Java中List的forEach用法详解

在 Java 中&#xff0c;List.forEach() 是 Java 8 引入的一种简洁的遍历集合元素的方法。它基于函数式编程思想&#xff0c;接受一个 Consumer 函数式接口作为参数&#xff0c;用于对集合中的每个元素执行操作。 基本语法 java 复制 下载 list.forEach(consumer); 使用示…

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…

计算机视觉与深度学习 | 基于MATLAB的相机标定

基于MATLAB的相机标定:原理、步骤与代码实现 相机标定 基于MATLAB的相机标定:原理、步骤与代码实现MATLAB相机标定完整流程1. 准备工作2. 采集标定图像3. 导入图像并检测角点4. 生成世界坐标5. 执行相机标定6. 分析标定结果7. 应用标定结果校正图像相机标定关键概念相机参数类…

物联网专业核心课程以及就业方向

物联网专业作为信息技术与产业应用深度融合的交叉学科&#xff0c;其课程体系覆盖硬件、软件、网络、数据等全链条技术&#xff0c;就业方向则随智能技术普及呈现多元化趋势。以下是基于最新行业动态与教育实践的系统分析&#xff1a; &#x1f4da; 一、物联网专业核心课程体系…

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…

线性代数证明:把行列式的某一行(列)的k倍加到另一行(列),行列式的值不变

线性代数证明 把行列式的某一行&#xff08;列&#xff09;的k倍加到另一行&#xff08;列&#xff09;&#xff0c;行列式的值不变&#xff1a; 注意五角星的位置要用到另一条性质&#xff1a;若行列式的某一行&#xff08;列&#xff09;的元素都是两数之和&#xff0c;则可以…