预置
基本介绍
利用像素流送可以在用户不可见的电脑上远程运行虚幻引擎应用程序。举例而言,这台电脑可以是机构中的一台实体电脑,也可以是云端服务提供的虚拟机。虚幻引擎将使用该电脑可用的资源(CPU、GPU、内存等)来运行游戏逻辑并渲染每一帧。它会不断将此渲染输出编码到一个媒体流送中,再通过一个轻量级的网页服务堆栈进行传递。用户即可在其他电脑和移动设备上运行的标准网页浏览器中查看直播流送。
本文将重点阐述前端与UE的交互过程。
背景
在像素流送过程中,不仅需要考虑 UE 如和展示在界面上,往往还需要前端与UE交互,但是官方提供的接口,有一种类似UDP,那种让人不安全的感觉,如果只是单发送,单接收还好,但涉及到逐步交互,就需要将逻辑拆开来处理,比如打开 UE 场景中的一些模型,操作这个模型,结束操作,获得这个信息,用户的注意力会从前端转到UE,再从UE转到前端,而我们更加习惯使用异步或者回调的方式去理解接口。因此便有了本次封装。
相关接口预置知识
像素流送,分为三个端去理解(准确来说,WebRTC 这种模式都需要三个端去理解)
- Browser 浏览器
- Signal 信令服务器(不过多描述)
- UE Application UE 应用(不过多描述)
浏览器与 UE 应用 通信
在浏览器中,会引入对应的库,目前是直接挂在 Windows 对象下,可以直接调用
- 发送消息:
window.emitUIInteraction
函数向 UE 应用 发送消息, - 接收消息:使用
window.addResponseEventListener
,监听 UE 应用发送过来的消息,完成分步交互。
接口封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
|
export interface IUECommandStruct<T> { commandType: string; commandData: T; } export const event_map = new Map<string, Set<Function>>();
export const sendToUe = (commandType: string, commandData: any, receiveCommandType?: string): Promise<IUECommandStruct<any>> => { let reject; const promisify = new Promise<IUECommandStruct<any>>((resolve, _reject) => { window.emitUIInteraction({ commandType, commandData, }); if (!receiveCommandType) return resolve({ commandType, commandData: 'Success' }); const unlisten = listenUe(receiveCommandType, (data, destroy) => { resolve(data as IUECommandStruct<any>); setTimeout(()=>{ destroy(); }) }); reject = () => { _reject(); unlisten(); }; }); promisify.cancel = reject; return promisify; };
export function listenUe(command: string, fn: (data: any, destroy: Function) => void) { const callback = (data: any, destroy: Function) => { fn(data, destroy); }; listen(command, callback); return () => { try { event_map.get(command)?.delete(callback); } catch { console.error(command, "it has destroyed"); } }; }
function listen(command: string, callback: Function) { const fns = event_map.get(command); if (!fns) event_map.set(command, new Set<Function>()); return event_map.get(command)!.add(callback); }
export function emit(command: string, data: any) { const fns = event_map.get(command); fns?.forEach((callback) => { return callback(data, () => { setTimeout(() => { fns.delete(callback); }); }); }); }
|
使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13
|
export const sendAndWaitResponse = (ID: string) => sendToUe('Test', ID, 'Line');
export const send = (ID: string) => sendToUe("Test", ID);
export const listen = (fn: Function) => listenUe("Line", fn);
|
关键函数讲解
函数会返回一个 Promise
对象,并额外增加了 cancel
函数,争对返回的结果进行取消等待,正如sendAndWaitResponse
注释所描述,这里会等待第三个参数,也就是 Line
这个对应的事件接收到的时候,才会进行 resolve
,如果没有第三个参数,为了保证统一,也返回了一个Promise
对象。
listenUe
函数会返回一个用于主动 unlisten 的函数,用于该任务还没发起,但是组件即将卸载,也就是这个函数没用了,所以需要提前释放掉,传入参数为需要监听的命令类型和一个函数,用户执行完相关数据,需要手动 destroy
这个函数,类似与 unlisten
,一般在达到某个条件的时候,不需要监听了,而这个组件仍然还需要使用的时候,手动注销。这是为了避免函数反复注册,从出现内存泄露。
总结
本文简要介绍了像素流送、前端到 UE 的交互方式及其二次封装的方法。解决了分步调用UE的异步逻辑问题,实现了类 axios 的交互方式。