Limited availability
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 èæ¬å¿
é¡»å®ç°ä¸ä¸ªå¤ç 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();
};
æµè§å¨å
¼å®¹æ§ åè§
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4