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

news2025/6/12 20:47:23
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度
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.coloradmin.cn/o/2407597.html

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

相关文章

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…

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

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

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践

6月5日&#xff0c;2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席&#xff0c;并作《智能体在安全领域的应用实践》主题演讲&#xff0c;分享了在智能体在安全领域的突破性实践。他指出&#xff0c;百度通过将安全能力…

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…

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

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

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…