封装像素流送功能为Vue组件

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

介绍

在本文中,我们将详细描述如何将UE(Unreal Engine)像素流送功能集成到前端工程中。首先,我将解释UE像素流送的工作原理,讲述如何提取UE像素流送功能的核心代码,并将其封装为一个可重用的插件或模块。我们将探讨UE引擎中的扩展机制和自定义功能的实现方式,以便将UE像素流送功能集成到前端工程中。

本文提到的Vue组件工程Pixel-streaming-layer我放到github上,以便后续大家下载交流。

使用工具

工具 版本号
UE 5.1 安装pixel-streaming后打包为应用
Pixel Streaming Interface 5.1 UE像素流送演示工程包
vue 3.X 前端框架
vue-cli 5.0.X Vue工程脚手架

实现思路

介绍

在进入开发之前,为了对组件的功能有更加充分的认知,有必要了解像素流送的整个执行过程,这里只做入门版介绍,专业版介绍请看这位老哥写的

整个过程可以分为5个阶段,客户浏览器端和UE应用端通过信令服务器进行协商,然后形成稳定的P2P连接,在WebRtc协议的保证下客户终端获取媒体流,捕获用户行为发送指令给UE应用端,UE根据指令调整画面形成反馈。后面我对每个阶段进行了更加详细的描述。

阶段描述

1. 准备阶段

  • UE项目配置:首先,需要在Unreal Engine项目中启用和配置像素流式传输插件,设置适当的视频质量和性能参数。
  • 信令服务器设置:为了建立UE服务器和客户端之间的连接,需要一个信令服务器。信令服务器负责交换网络配置信息(如IP地址和端口),以及初始化WebRTC会话。
  • 部署UE应用:将UE应用部署到支持像素流式传输的服务器上。服务器需要有足够的图形处理能力来渲染3D场景。

2. 建立连接

  • 客户端请求连接:客户端(通常是Web浏览器中的一个页面)通过信令服务器向UE服务器发送连接请求。
  • 协商WebRTC连接:使用信令服务器交换所需的WebRTC参数,包括SDP(会话描述协议)消息和ICE(交互式连接建立)候选,以建立一个稳定的P2P(点对点)视频流连接。

3. 流式传输

  • UE渲染和编码:UE服务器渲染3D场景,并将渲染的帧实时编码为视频流。
  • 通过WebRTC发送视频流:编码后的视频流通过建立的WebRTC连接发送给客户端。WebRTC技术确保了流的实时性和高效性,支持跨网络的低延迟传输。
  • 客户端解码和显示:客户端接收视频流,进行解码,并在用户界面中显示渲染的场景。

4. 交互

  • 客户端输入处理:客户端可以捕获用户输入(如键盘、鼠标或触摸事件)并通过WebRTC连接发送回UE服务器。
  • UE服务器响应:UE服务器根据收到的输入更新场景状态,下一帧渲染将反映这些更改,实现交互式体验。

5. 结束连接

  • 断开连接:当会话结束或用户离开时,客户端和服务器均可关闭WebRTC连接,同时信令服务器更新会话状态。

核心业务逻辑

从上文的描述我们可以知道,组件所要实现的,是客户终端持续获取视频音频媒体流和发送指令的这个阶段的功能。

建立流媒体连接具体的业务逻辑如下:

实现步骤

1. 下载工程包

从UE官方获取Pixel Streaming Infrastructure ,这个工程里包含了信令服务器和浏览器前端连接实例,本文使用的是UE5.1版本。有需要可以直接在github下载 各版本地址入口

2.创建工程

创建Vue3工程pixel-streaming-layer, 目录如下。关于vue组件库的小白开发教程可以看这里

3. 获取核心代码

在步骤1的工程中按下图目录找到两个文件app.js和webRtcPlayer.js,这是最终vue组件的核心文件。在vue3工程创建目录"src/components/pixel-stream-layer", 并把两个文件放到这里面。

(1)app.js 核心业务代码,更新为core.js

(2)webRtcPlayer 通用的WebRTC播放器

4. 代码封装

  1. 在目录"src/components/pixel-stream-layer" 新建index.vue作为组件的入口,引入core.js。core.js对初始化、调整画面、播放、暂停等方法做了封装,因此可以在index.vue直接引用。

    jsx 复制代码
    import * as Core from './core.js'
    //...
    mo1unted () {
      this.videoInstance = Core.init()
    },
    methods:{
      /**
       * 向UE场景派发指令
       * @param {String} message 指令内容,比如'openDoor ID1'
       */
      emitMessageToUE (message) {
        Core.emitUIInteraction(message)
      },
      /**
       * 播放视频
       * @public
       */
      play () {
        Core.play()
      },
      //...
    }
  2. core.js内部则直接引入了webRtcPlayer.js

    jsx 复制代码
    import webRtcPlayer from './webRtcPlayer'
    
    export function init (config) {
    	// ...
      return webRtcPlayerObj
    }
    
    /**
     * 初始化WebRTC播放器实例
     * @param {HTML} htmlElement 容器标签
     * @param {*} config 配置参数
     * @returns {DOM}
     */
    function setupWebRtcPlayer (htmlElement, config) {
      webRtcPlayerObj = new webRtcPlayer({ ...config, startVideoMuted: true })
      //...
    }

5. 打包组件

  1. 打包入口文件为 src/components/index.js

  2. 由于我这边是以组件库的形式创建的工程,所以在入口文件中,会以组件库中一个组件的方式引入

    jsx 复制代码
    import PixelStreamLayer from './pixel-stream-layer/index.vue'
    
    const components = {
      PixelStreamLayer
    }
    
    function install (Vue) {
      const keys = Object.keys(components)
      keys.forEach((name) => {
        const component = components[name]
        Vue.component(component.name || name, component)
      })
    }
    
    export default {
      install,
      ...components
    }
  3. package.json打包指令如下

    -target lib:指定构建的目标为库,即将组件构建为可被其他项目引用的独立库。

    -dest lib:指定构建输出的目录为 lib,即构建后的文件将被输出到 lib 目录下。

    jsx 复制代码
    "build:component": "vue-cli-service build --target lib --dest lib src/components/index.js",

6. 在实际项目中使用组件

  1. 全局引入

    jsx 复制代码
    // main.js全局注册
    import { createApp } from 'vue'
    const app = createApp(App)
    
    // npm部署到私有库了
    import PixelStreamLayer from '@zkzc/pixel-streaming-layer' 
    app.use(PixelStreamLayer)
    
    // 引入样式
    import '@zkzc/pixel-streaming-layer/lib/pixel-streaming-layer.css'
    
    <pixel-stream-layer ref="pslayer"  :server-url="serverURL"/>
  2. 创建实例

    html 复制代码
     <pixel-stream-layer ref="pslayer" server-url="http://192.168.1.254"/>

7. 测试功能

核心代码改造

因业务需要,我对核心业务代码core.js进行了改造。

  1. 调整init,将流媒体地址和可配置项变成构造参数

    jsx 复制代码
    /**
     * 初始化功能
     * @param {Object} [config={}]
     * @param {String} serverUrl 视频流服务地址
     * @param {Boolean} [autoOfferToReceive=true] 是否前端主动发起offer
     * @return webRtcPlayer
     */
    export function init (config) {
      // 流服务连接地址
      connectURL = config.serverUrl
      // 是否前端主动发起offer
      autoOfferToReceive = setDefaultTrue(config.autoOfferToReceive)
    
      // 监听各种stream消息并处理
      registerMessageHandlers()
      // 声明各种与Stream交流的Message类型
      populateDefaultProtocol()
      // 初始化冻结层,当视频画面停止更新时会出现
      setupFreezeFrameOverlay()
      // 将每个按键操作写入到操作序列,等待逐个执行
      registerKeyboardEvents()
      // 开始核心逻辑
      start(false)
    
      return webRtcPlayerObj
    }
  2. 暴露公共方法

    jsx 复制代码
    /**
     * 调整画面分辨率以适应当前容器尺寸
     * @public
     */
    export function updateViewToContainer () {
      const playerElement = document.getElementById('player')
      const descriptor = {
        'Resolution.Width': playerElement.clientWidth,
        'Resolution.Height': playerElement.clientHeight
      }
      emitCommand(descriptor)
    }
    
    /**
     * 开始播放
     */
    export function play () {
      connect()
      startAfkWarningTimer()
    }
    
    /**
     * 停止播放
     */
    export function stop () {
      if (webRtcPlayerObj) {
        webRtcPlayerObj.close()
      }
    }
    /**
     * 发起一个指令,针对UE关卡蓝图
     * @param {String} descriptor 
     */
    function emitCommand (descriptor) {
      emitDescriptor('Command', descriptor)
    }
    /**
     * 发起一个交互操作,针对UE暴露的方法
     * @param {String} descriptor 
     */
    export function emitUIInteraction (descriptor) {
      emitDescriptor('UIInteraction', descriptor)
    }
  3. 组件入口代码,基于前两步骤的改造,组件就可以提供对应的配置参数和公共方法

    jsx 复制代码
    import * as Core from './core.js'
    
    export default {
      name: 'PixelStreamLayer',
      props: {
        serverUrl: {
          // 流媒体地址
          type: String,
          required: true
        },
        config: {
          // 各种配置参数
          type: Object
        }
      },
      data () {
        return {
          videoInstance: null
        }
      },
      mounted () {
        this.videoInstance = Core.init({
          serverUrl: this.serverUrl
          //...其他配置项
        })
      },
      methods: {
        /**
         * 向UE场景派发指令
         * @param {String} message 指令内容,比如'openDoor ID1'
         */
        emitMessageToUE (message) {
          Core.emitUIInteraction(message)
        },
        // 将画面填满窗口
        fillView () {
          Core.updateViewToContainer()
        },
        // 页面在加载后可自动播放
        play () {
          Core.play()
        },
        // 组件销毁前可自动停止
        stop () {
          Core.stop()
        }
      }
    }

待改进内容

  1. 更多的配置项

    事实上在本文使用的UE5.1版本,提供以下的初始配置项,都可以调整后作为构造参数,看具体项目需要

    Label描述 参数名 说明
    Use microphone useMic 是否使用麦克风,语音录入功能只能在localhost和https协议下才能进行
    prefer SFU preferSFU 媒体流是否使用SFU作为媒体流传输方式
    Force TURN ForceTURN 强制使用 TURN服务器作为中继来传输媒体流
    Force mono audio ForceMonoAudio 强制单声道
    Control Scheme controlScheme 控制模式有2种。 "Hoving Mouse":玩家可以在操作游戏时使用鼠标进行其他操作,如切换窗口、调整音量等。
    "Locked Mouse":玩家在操作游戏时鼠标不能离开游戏窗口
    Hide Browser Cursor hideBrowserCursor 隐藏浏览器光标
    Request KeyFrame 要求视频编码器生成关键帧
    offerToReceive 主动发起offer
    noWatermark 是否去除UE水印
  2. 控制权限

    由于UE应用实例在运行中是相当耗资源的,在目前的情况下不可能给所有访问的用户开单独的实例,所以会有多用户操作同一个实例的情况。因此需要增加配置项,去除一部分用户的控制权限,即只能看而不能控制。这块实现不难,只要把发送指令相关的逻辑加屏蔽条件即可。

  3. 增加调试模式

    目前是调试面板,后续可以考虑通过is-debug配置属性的方式增加调试面板,将画面和操作调整到最佳状态。

相关链接

pixel-streaming-layer源代码

UE5.1 + Vue3像素流,保姆级教程

一文看懂WebRtc建连过程

GPT3.5+MetaHuman 实现工程

从零开始:Vue cli3 库模式搭建组件库并发布到npm

相关推荐
Fan_web15 分钟前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常16 分钟前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇1 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr1 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho2 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常3 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记4 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java4 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele4 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范