ä½¿ç¨ WebRTC ç¼ç 转æ¢
åºçº¿
2025
æè¿å¯ç¨
èª October 2025 èµ·ï¼æ¤ç¹æ§å·²å¨ææ°æµè§å¨ä¸å¾å°æ¯æãä½å¨è¾æ§çè®¾å¤ææµè§å¨ä¸å¯è½æ æ³è¿è¡ã
WebRTC ç¼ç è½¬æ¢æä¾äºä¸ç§æºå¶ï¼å¯ä»¥å°é«æ§è½ç Stream API æ³¨å ¥å°ä¼ å ¥åä¼ åºç WebRTC 管éä¸ï¼ç¨äºä¿®æ¹ç¼ç çè§é¢åé³é¢å¸§ã使å¾ç¬¬ä¸æ¹ä»£ç è½å¤å®ç°å¯¹ç¼ç 帧ç端å°ç«¯å å¯çç¨ä¾ã
该 API å®ä¹äºä¸»çº¿ç¨å Worker ç对象ãä¸»çº¿ç¨æ¥å£æ¯ä¸ä¸ª RTCRtpScriptTransform å®ä¾ï¼å
¶å¨æé æ¶æå®äºè¦å®ç°è½¬æ¢å¨ä»£ç ç Workerãå¨ Worker ä¸è¿è¡ç转æ¢å¨éè¿åå«å° RTCRtpScriptTransform æ·»å å° RTCRtpReceiver.transform æ RTCRtpSender.transform ä¸ï¼æå
¥å°ä¼ å
¥æä¼ åºç WebRTC 管éä¸ã
å¨ Worker ä¸å建äºä¸ä¸ªå¯¹åºç RTCRtpScriptTransformer 对象ï¼å®å
·æä¸ä¸ª ReadableStream readable 屿§ï¼ä¸ä¸ª WritableStream writable 屿§ï¼ä»¥åä¸ä¸ªä»å
³èç RTCRtpScriptTransform æé 彿°ä¼ éç options å¯¹è±¡ãæ¥èª WebRTC 管éçç¼ç è§é¢å¸§ï¼RTCEncodedVideoFrameï¼æé³é¢å¸§ï¼RTCEncodedAudioFrameï¼ä¼è¢«å
¥éå° readable ä¸è¿è¡å¤çã
RTCRtpScriptTransformer ä½ä¸º rtctransform äºä»¶ç transformer 屿§åä»£ç æä¾ï¼è¯¥äºä»¶å¨æ¯æ¬¡ç¼ç 帧被å
¥éè¿è¡å¤çæ¶ï¼ä»¥åå¨ç¸åºç RTCRtpScriptTransform æé 彿°çåå§æ¶ï¼å¨ Worker å
¨å±ä½ç¨åå
触åãWorker 代ç å¿
é¡»å®ç°ä¸ä¸ªäºä»¶å¤çç¨åºï¼ä» transformer.readable ä¸è¯»åç¼ç å¸§ï¼æ ¹æ®éè¦å¯¹å
¶è¿è¡ä¿®æ¹ï¼å¹¶æç
§ç¸åç顺åºä¸ä¸éå¤å°å°å®ä»¬åå
¥ transformer.writableã
è½ç¶æ¥å£å¯¹å®ç°æ²¡æå
¶ä»éå¶ï¼ä½ä¸ç§èªç¶ç转æ¢å¸§çæ¹å¼æ¯å建ä¸ä¸ªé¾å¼ç®¡éï¼å°å¨ event.transformer.readable æµä¸å
¥éç帧éè¿ TransformStream åéå° event.transformer.writable æµãæä»¬å¯ä»¥ä½¿ç¨ event.transformer.options 屿§æ¥é
置任ä½åå³äºè½¬æ¢æ¯ä»å°å
å¨å
¥éä¼ å
¥å¸§ï¼è¿æ¯ä»ç¼è§£ç å¨åºéä¼ åºå¸§ç转æ¢ä»£ç ã
RTCRtpScriptTransformer æ¥å£è¿æä¾äºä¸äºæ¹æ³ï¼å¯å¨åéç¼ç è§é¢æ¶ä½¿ç¨ï¼ä»¥ä¾¿è®©ç¼è§£ç å¨çæä¸ä¸ªâå
³é®â帧ï¼å¨æ¥æ¶è§é¢æ¶è¯·æ±åéä¸ä¸ªæ°çå
³é®å¸§ã妿ï¼ä¾å¦ï¼å¨åéå¢é帧æ¶å å
¥ä¼è®®å¼å«ï¼åè¿äºæ¹æ³å¯è½å¾æç¨ï¼å
è®¸æ¥æ¶è
æ´å¿«å°å¼å§æ¥çè§é¢ã
以ä¸ç¤ºä¾æä¾äºå¦ä½ä½¿ç¨åºäº TransformStream çå®ç°æ¡æ¶çæ´å
·ä½ç¤ºä¾ã
æµè¯æ¯å¦æ¯æç¼ç 转æ¢
éè¿æ£æ¥ RTCRtpSender.transformï¼æ RTCRtpReceiver.transformï¼çå卿¥æµè¯æ¯å¦æ¯æç¼ç 转æ¢ã
const supportsEncodedTransforms =
window.RTCRtpSender && "transform" in RTCRtpSender.prototype;
æ·»å ç¨äºä¼ åºå¸§ç转æ¢
éè¿å°ç¸åºç RTCRtpScriptTransform åé
ç»ä¼ åºè½¨éç RTCRtpSender.transformï¼å°è¿è¡å¨ Worker ä¸çè½¬æ¢æå
¥å°ä¼ åºç WebRTC 管éä¸ã
以ä¸ç¤ºä¾å±ç¤ºäºå¦ä½ä»ç¨æ·çç½ç»æå头éè¿ WebRTC ä¼ è¾è§é¢ï¼å¹¶æ·»å ä¸ä¸ª WebRTC ç¼ç 转æ¢ä»¥ä¿®æ¹ä¼ åºæµã代ç åè®¾å·²ç»æä¸ä¸ªå为 peerConnection ç RTCPeerConnectionï¼å¹¶ä¸å·²ç»è¿æ¥å°è¿ç¨å¯¹ç端ã
é¦å
ï¼æä»¬ä½¿ç¨ getUserMedia() ä»åªä½è®¾å¤è·åè§é¢ MediaStreamï¼ç¶åä½¿ç¨ MediaStream.getTracks() æ¹æ³è·åæµä¸ç第ä¸ä¸ª MediaStreamTrackã
ä½¿ç¨ addTrack() å°è½¨éæ·»å å°å¯¹çè¿æ¥ï¼ä»èå¼å§å°å
¶æµå¼ä¼ è¾å°è¿ç¨å¯¹ç端ãaddTrack() æ¹æ³è¿åç¨äºåé轨éç RTCRtpSenderã
// è·åè§é¢æµååªä½è½¨é
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getTracks();
const videoSender = peerConnection.addTrack(track, stream);
æ¥ä¸æ¥æé ä¸ä¸ª RTCRtpScriptTransformï¼éè¦ä¸ä¸ª Worker èæ¬æ¥å®ä¹è½¬æ¢ï¼å¹¶ä¸è¿å¯ä»¥ä½¿ç¨ä¸ä¸ªå¯é对象æ¥å Worker ä¼ éä»»ææ¶æ¯ï¼å¨æ¬ä¾ä¸ï¼æä»¬ä½¿ç¨äºä¸ä¸ªå¼ä¸º "senderTransform" ç name 屿§æ¥åè¯ Workerï¼æ¤è½¬æ¢å°è¢«æ·»å å°ä¼ åºæµä¸ï¼ã
éè¿å°å
¶åé
ç» RTCRtpSender.transform 屿§ï¼æä»¬å°è½¬æ¢æ·»å å°ä¼ åºç®¡éä¸ã
// å建ä¸ä¸ªå
å« TransformStream ç Worker
const worker = new Worker("worker.js");
videoSender.transform = new RTCRtpScriptTransform(worker, {
name: "senderTransform",
});
ä¸é¢ç使ç¨åç¬çåéå¨åæ¥æ¶å¨è½¬æ¢é¨åæ¾ç¤ºäºå¨ Worker ä¸å¯è½å¦ä½ä½¿ç¨ nameã
请注æï¼ä½ å¯ä»¥å¨ä»»ä½æ¶åæ·»å 转æ¢ï¼ä½æ¯éè¿å¨è°ç¨ addTrack() åç«å³æ·»å 转æ¢ï¼è½¬æ¢å°è·å¾åéç第ä¸å¸§ç¼ç 帧ã
æ·»å ç¨äºä¼ å ¥å¸§ç转æ¢
è¿è¡å¨ Worker ä¸ç转æ¢éè¿å°ç¸åºç RTCRtpScriptTransform åé
ç»ä¼ å
¥è½¨éç RTCRtpReceiver.transform æ¥æå
¥å°ä¼ å
¥ç WebRTC 管éä¸ã
è¿ä¸ªä¾åå±ç¤ºäºå¦ä½æ·»å ä¸ä¸ªè½¬æ¢æ¥ä¿®æ¹ä¼ å
¥æµã该代ç åå®å·²ç»è¿æ¥å°è¿ç¨å¯¹ç端çå为 peerConnection ç RTCPeerConnectionã
é¦å
ï¼æä»¬æ·»å äºä¸ä¸ª RTCPeerConnection ç track äºä»¶å¤çç¨åºï¼ä»¥æè·å½å¯¹ç端å¼å§æ¥æ¶æ°è½¨éæ¶çäºä»¶ãå¨å¤çç¨åºå
é¨ï¼æä»¬æé äºä¸ä¸ª RTCRtpScriptTransform å¹¶å°å
¶æ·»å å° event.receiver.transformï¼event.receiver æ¯ä¸ä¸ª RTCRtpReceiverï¼ãä¸åä¸èç¸åï¼æé 彿°éç¨ä¸ä¸ªå
·æ name 屿§ç对象ï¼ä½æ¯å¨è¿éæä»¬ä½¿ç¨ receiverTransform ä½ä¸ºå¼ï¼åè¯ Worker æ£å¨ä¼ å
¥å¸§ã
peerConnection.ontrack = (event) => {
const worker = new Worker("worker.js");
event.receiver.transform = new RTCRtpScriptTransform(worker, {
name: "receiverTransform",
});
received_video.srcObject = event.streams[0];
};
忬¡æ³¨æï¼ä½ å¯ä»¥å¨ä»»ä½æ¶åæ·»å è½¬æ¢æµã使¯éè¿å¨ track äºä»¶å¤çå¨ä¸æ·»å å®ï¼å¯ä»¥ç¡®ä¿è½¬æ¢æµå°è·å¾è½¨éç第ä¸å¸§ç¼ç 帧ã
Worker å®ç°
Worker èæ¬å¿
é¡»å®ç°ä¸ä¸ªå¤ç rtctransform äºä»¶çå¤çç¨åºï¼å建ä¸ä¸ªé¾å¼ç®¡éï¼å° event.transformer.readableï¼ReadableStreamï¼æµéè¿ TransformStream ä¼ è¾å° event.transformer.writableï¼WritableStreamï¼æµä¸ã
Worker å¯è½æ¯æè½¬æ¢ä¼ å ¥æä¼ åºçç¼ç 帧ï¼ä¹å¯è½åæ¶æ¯æä¸¤è ï¼å¹¶ä¸è½¬æ¢å¯è½æ¯ç¡¬ç¼ç çï¼ä¹å¯è½æ¯å¨è¿è¡æ¶ä½¿ç¨ä» Web åºç¨ä¼ éçä¿¡æ¯é ç½®çã
åºæ¬ç WebRTC ç¼ç 转æ¢
ä¸é¢ç示ä¾å±ç¤ºäºä¸ä¸ªåºæ¬ç WebRTC ç¼ç 转æ¢ï¼å®å¯¹éåä¸çææå¸§è¿è¡ä½æ±åæä½ãå®ä¸ä½¿ç¨æéè¦ä»ä¸»çº¿ç¨ä¼ éçé项ï¼å 为ç¸åçç®æ³å¯ä»¥ç¨äºåéç®¡éæ¥å¯¹ä½è¿è¡æ±åï¼å¹¶ä¸å¨æ¥æ¶ç®¡éä¸è¿è¡è¿åã
该代ç å®ç°äºä¸ä¸ª rtctransform äºä»¶çäºä»¶å¤çå¨ãè¿ä¸ªå¤çç¨åºæå»ºäºä¸ä¸ª TransformStreamï¼ç¶åä½¿ç¨ ReadableStream.pipeThrough() è¿è¡ç®¡éä¼ è¾ï¼æåä½¿ç¨ ReadableStream.pipeTo() ä¼ è¾å° event.transformer.writableã
addEventListener("rtctransform", (event) => {
const transform = new TransformStream({
start() {}, // å¨å¯å¨æ¶è°ç¨
flush() {}, // 卿µå³å°å
³éæ¶è°ç¨
async transform(encodedFrame, controller) {
// é建åå§å¸§
const view = new DataView(encodedFrame.data);
// æå»ºä¸ä¸ªæ°çç¼å²åº
const newData = new ArrayBuffer(encodedFrame.data.byteLength);
const newView = new DataView(newData);
// å°ä¼ å
¥å¸§ä¸çææä½åå
for (let i = 0; i < encodedFrame.data.byteLength; ++i) {
newView.setInt8(i, ~view.getInt8(i));
}
encodedFrame.data = newData;
controller.enqueue(encodedFrame);
},
});
event.transformer.readable
.pipeThrough(transform)
.pipeTo(event.transformer.writable);
});
WebRTC ç¼ç 转æ¢çå®ç°ç±»ä¼¼äºâéç¨â TransformStreamï¼ä½åå¨ä¸äºéè¦çåºå«ãåéç¨æµä¸æ ·ï¼å®çæé 彿°æ¥åä¸ä¸ªå¯¹è±¡ï¼å
¶å®ä¹äºå¨æé æ¶è°ç¨çå¯é start() æ¹æ³ï¼å¨æµå³å°å
³éæ¶è°ç¨ç flush() æ¹æ³ï¼ä»¥å transform() æ¹æ³ï¼æ¯å½æä¸ä¸ªåéè¦å¤çæ¶é½ä¼è°ç¨ãä¸éç¨æé 彿°ä¸åï¼ä»»ä½å¨æé 彿°å¯¹è±¡ä¸ä¼ éç writableStrategy æ readableStrategy 屿§é½ä¼è¢«å¿½ç¥ï¼éåçç¥å®å
¨ç±ç¨æ·ä»£ç管çã
transform() æ¹æ³ä¹ä¸åï¼å®æ¥æ¶çæ¯ RTCEncodedVideoFrame æ RTCEncodedAudioFrameï¼è䏿¯éç¨çâåâãé¤äºå®å±ç¤ºäºå¦ä½å°å¸§è½¬æ¢ä¸ºå¯ä»¥ä¿®æ¹å¹¶å¨ä¹åæéå°æµä¸çå½¢å¼ä¹å¤ï¼æ¤å¤æ¾ç¤ºçæ¹æ³æ²¡æä»ä¹ç¹å«ä¹å¤ã
使ç¨åç¬çåéå¨åæ¥æ¶å¨è½¬æ¢
ä¹åçä¾åå¨åéåæ¥æ¶æ¶ä½¿ç¨ç¸åç转æ¢å½æ°æ¶å¯ä»¥å·¥ä½ï¼ä½å¨è®¸å¤æ åµä¸ï¼ç®æ³ä¼ææä¸åãä½ å¯ä»¥ä¸ºåéå¨åæ¥æ¶å¨ä½¿ç¨åç¬ç Worker èæ¬ï¼æè å¨ä¸ä¸ª Worker ä¸å¤çè¿ä¸¤ç§æ åµï¼å¦ä¸æç¤ºã
妿 Worker ç¨äºåéå¨åæ¥æ¶å¨ï¼å®éè¦ç¥éå½åçç¼ç å¸§æ¯æ¥èªç¼è§£ç å¨çä¼ åºå¸§ï¼è¿æ¯æ¥èªå°å
å¨çä¼ å
¥å¸§ãå¯ä»¥ä½¿ç¨ RTCRtpScriptTransform æé 彿°ç第äºä¸ªéé¡¹æ¥æå®æ¤ä¿¡æ¯ãä¾å¦ï¼æä»¬å¯ä»¥ä¸ºåéå¨åæ¥æ¶å¨å®ä¹ä¸ä¸ªåç¬ç RTCRtpScriptTransformï¼ä¼ éç¸åç Worker åä¸ä¸ª options 对象ï¼å
¶ä¸ç name 屿§æç¤ºè½¬æ¢æ¯ç¨äºåéè¿æ¯æ¥æ¶ï¼å¦ä¸é¢çåå èæç¤ºï¼ãç¶åå¨ Worker ä¸ï¼å¯ä»¥éè¿ event.transformer.options è·åå°æ¤ä¿¡æ¯ã
å¨è¿ä¸ªä¾åä¸ï¼æä»¬å¨å
¨å±ä¸ç¨ Worker çä½ç¨å对象ä¸å®ç°äº onrtctransform äºä»¶å¤çå¨ãname 屿§çå¼ç¨äºç¡®å®æé åªä¸ª TransformStreamï¼å®é
çæé æ¹æ³æ²¡ææ¾ç¤ºï¼ã
// å®ä¾å忢并å°å®ä»¬éå å°åéå¨/æ¥æ¶å¨ç®¡éç代ç
onrtctransform = (event) => {
let transform;
if (event.transformer.options.name == "senderTransform")
transform = createSenderTransform(); // è¿åä¸ä¸ª TransformStream
else if (event.transformer.options.name == "receiverTransform")
transform = createReceiverTransform(); // è¿åä¸ä¸ª TransformStream
else return;
event.transformer.readable
.pipeThrough(transform)
.pipeTo(event.transformer.writable);
};
请注æï¼å建管éé¾ç代ç ä¸ä¸ä¸ä¸ªç¤ºä¾ä¸ç代ç ç¸åã
è¿è¡æ¶ä¸åæ¢è¿è¡éä¿¡
RTCRtpScriptTransform æé 彿°å
è®¸ä½ ä¼ éé项åå¯¹è±¡å° Workerãå¨åé¢ç示ä¾ä¸ï¼æä»¬ä¼ éäºéæä¿¡æ¯ï¼ä½ææ¶ä½ å¯è½å¸æå¨è¿è¡æ¶ä¿®æ¹ Worker ä¸çåæ¢ç®æ³ï¼æè
ä» Worker ä¸è·åä¿¡æ¯ãä¾å¦ï¼æ¯æå å¯ç WebRTC ä¼è®®å¯è½éè¦ååæ¢ä½¿ç¨çç®æ³æ·»å ä¸ä¸ªæ°çå¯é¥ã
è½ç¶å¯ä»¥ä½¿ç¨ Worker.postMessage() å¨è¿è¡åæ¢ä»£ç ç Worker å主线ç¨ä¹é´å
±äº«ä¿¡æ¯ï¼ä½éå¸¸å° MessageChannel ä½ä¸º RTCRtpScriptTransform æé 彿°çé项æ´å®¹æï¼å 为å¨å¤çæ°çç¼ç 帧æ¶ï¼ééä¸ä¸æç´æ¥å¯å¨ event.transformer.options ä¸ä½¿ç¨ã
以ä¸ä»£ç å建äºä¸ä¸ª MessageChannel å¹¶å°å
¶ç¬¬äºä¸ªç«¯å£ä¼ è¾ç» Workerã主线ç¨å忢éåå¯ä»¥ä½¿ç¨ç¬¬ä¸ä¸ªå第äºä¸ªç«¯å£è¿è¡éä¿¡ã
// å建ä¸ä¸ªå
å« TransformStream ç Worker èæ¬
const worker = new Worker("worker.js");
// å建ä¸ä¸ª channel
// å° channel.port2 ä½ä¸ºæé 彿°éé¡¹ä¼ éç» transformï¼å¹¶å°å
¶ä¼ è¾å° Workerã
const channel = new MessageChannel();
const transform = new RTCRtpScriptTransform(
worker,
{ purpose: "encrypt", port: channel.port2 },
[channel.port2],
);
// ä½¿ç¨ port1 åéä¸ä¸ªå符串ãï¼æä»¬å¯ä»¥åéåä¼ è¾åºæ¬ç±»å/对象ï¼
channel.port1.postMessage("ç» Worker çæ¶æ¯");
channel.port1.start();
å¨ Worker ä¸ï¼ç«¯å£å¯ä½ä¸º event.transformer.options.port 使ç¨ãä¸é¢çä»£ç æ¾ç¤ºäºå¦ä½çå¬ç«¯å£ç message äºä»¶ä»¥ä»ä¸»çº¿ç¨è·åæ¶æ¯ãä½ è¿å¯ä»¥ä½¿ç¨è¯¥ç«¯å£å°æ¶æ¯åéå主线ç¨ã
event.transformer.options.port.onmessage = (event) => {
// æ¶æ¯è½½è·å¨âevent.dataâä¸
console.log(event.data);
};
触åå ³é®å¸§
åå§è§é¢å¾å°è¢«åéæåå¨ï¼å 为ç¨å®æ´å¾åæ¥è¡¨ç¤ºæ¯ä¸å¸§ä¼æ¶è大éç空é´å带宽ãç¸åï¼ç¼è§£ç å¨å®æçæä¸ä¸ªå å«è¶³å¤ä¿¡æ¯æå»ºå®æ´å¾åçâå ³é®å¸§âï¼å¨å ³é®å¸§ä¹é´åéâå¢é帧âï¼å®ä»¬åªå å«èªä¸ä¸ä¸ªå¢é帧以æ¥çååãè½ç¶è¿æ¯åéåå§è§é¢è¦é«æå¾å¤ï¼ä½è¿æå³çä¸ºäºæ¾ç¤ºä¸ç¹å®å¢é帧ç¸å ³èçå¾åï¼ä½ éè¦æåä¸ä¸ªå ³é®å¸§åææéåçå¢é帧ã
è¿å¯è½ä¼å¯¼è´æ°ç¨æ·å å ¥ WebRTC ä¼è®®åºç¨æ¶åºç°å»¶è¿ï¼å 为ä»ä»¬å¨æ¶å°ç¬¬ä¸ä¸ªå ³é®å¸§ä¹åæ æ³æ¾ç¤ºè§é¢ãåæ ·ï¼å¦æä½¿ç¨ç¼ç è½¬æ¢æ¥å å¯å¸§ï¼åæ¥æ¶æ¹å¨æ¶å°ä½¿ç¨å ¶å¯é¥å å¯ç第ä¸ä¸ªå ³é®å¸§ä¹åæ æ³æ¾ç¤ºè§é¢ã
为äºç¡®ä¿å¨éè¦æ¶å°½æ©åéæ°çå
³é®å¸§ï¼event.transformer ä¸ç RTCRtpScriptTransformer 对象æä¸¤ç§æ¹æ³ï¼RTCRtpScriptTransformer.generateKeyFrame()ï¼å®ä¼å¯¼è´ç¼è§£ç å¨çæä¸ä¸ªå
³é®å¸§ï¼å RTCRtpScriptTransformer.sendKeyFrameRequest()ï¼å®ä¼å¯¼è´æ¥æ¶æ¹å¯ä»¥ä»åéæ¹è¯·æ±ä¸ä¸ªå
³é®å¸§ã
ä¸é¢çç¤ºä¾æ¾ç¤ºäºä¸»çº¿ç¨å¦ä½å°å å¯å¯é¥ä¼ éç»åéæ¹è½¬æ¢ï¼å¹¶è§¦åç¼è§£ç å¨çæä¸ä¸ªå
³é®å¸§ã请注æï¼ä¸»çº¿ç¨æ æ³ç´æ¥è®¿é® RTCRtpScriptTransformer 对象ï¼å æ¤å®éè¦å°å¯é¥åéå¶æ è¯ç¬¦ï¼âridâæ¯æµ IDï¼æç¤ºå¿
é¡»çæå
³é®å¸§çç¼ç å¨ï¼ä¼ éç» Workerãå¨è¿éï¼æä»¬ä½¿ç¨äºä¸ä¸ª MessageChannelï¼ä½¿ç¨äºä¸åä¸èç¸åçæ¨¡å¼ã代ç åå®å·²ç»æä¸ä¸ªå¯¹çè¿æ¥ï¼å¹¶ä¸ videoSender æ¯ä¸ä¸ª RTCRtpSenderã
const worker = new Worker("worker.js");
const channel = new MessageChannel();
videoSender.transform = new RTCRtpScriptTransform(
worker,
{ name: "senderTransform", port: channel.port2 },
[channel.port2],
);
// å° rid åæ°å¯é¥åéå°åéæ¹
channel.port1.start();
channel.port1.postMessage({
rid: "1",
key: "93ae0927a4f8e527f1gce6d10bc6ab6c",
});
å¨ Worker ä¸ç rtctransform äºä»¶å¤çå¨è·å端å£ï¼å¹¶ä½¿ç¨å®æ¥ç嬿¥èªä¸»çº¿ç¨ç message äºä»¶ã妿æ¶å°äºä»¶ï¼åè·å rid å keyï¼ç¶åè°ç¨ generateKeyFrame()ã
event.transformer.options.port.onmessage = (event) => {
const { rid, key } = event.data;
// å¯é¥ç±è½¬æ¢å¨ç¨äºå å¯å¸§ï¼æªæ¾ç¤ºï¼
// ä½¿ç¨ rid è·åç¼è§£ç å¨çææ°å
³é®å¸§ãè¿éç 'rcevent' æ¯ rtctransform äºä»¶ã
rcevent.transformer.generateKeyFrame(rid);
};
æ¥æ¶æ¹è¯·æ±æ°å ³é®å¸§ç代ç å ä¹ç¸åï¼åªæ¯æ²¡ææå®âridâãè¿éæ¯ä» å å«ç«¯å£æ¶æ¯å¤çç¨åºç代ç ï¼
event.transformer.options.port.onmessage = (event) => {
const { key } = event.data;
// key ç±è½¬æ¢å¨ç¨äºè§£å¯å¸§ï¼æªæ¾ç¤ºï¼
// 请æ±åéå¨ååºä¸ä¸ªå
³é®å¸§
transformer.sendKeyFrameRequest();
};