ดูวิธีใช้ requestVideoFrameCallback()
เพื่อให้ทำงานอย่างมีประสิทธิภาพยิ่งขึ้นกับวิดีโอในเบราว์เซอร์
เมธอด HTMLVideoElement.requestVideoFrameCallback()
ช่วยให้ผู้เขียนในเว็บลงทะเบียนโค้ดเรียกกลับที่ทำงานในขั้นตอนการแสดงผลเมื่อมีการส่งเฟรมวิดีโอใหม่ไปยังตัวจัดวางองค์ประกอบ
ซึ่งช่วยให้นักพัฒนาซอฟต์แวร์ดำเนินการกับวิดีโอต่อเฟรมได้อย่างมีประสิทธิภาพ เช่น การประมวลผลวิดีโอและการลงสีลงในผืนผ้าใบ การวิเคราะห์วิดีโอ หรือการซิงค์กับแหล่งที่มาของเสียงภายนอก
ความแตกต่างกับ requestAnimationFrame()
การดำเนินการต่างๆ เช่น การวาดเฟรมวิดีโอลงในผืนผ้าใบโดยใช้ drawImage()
ที่ดำเนินการผ่าน API นี้จะซิงค์กับอัตราเฟรมของวิดีโอที่เล่นบนหน้าจออย่างดีที่สุด
ซึ่งแตกต่างจาก
window.requestAnimationFrame()
ที่มักจะเริ่มทำงานประมาณ 60 ครั้งต่อวินาที
requestVideoFrameCallback()
จะผูกกับอัตราเฟรมวิดีโอจริงโดยมีข้อยกเว้นที่สำคัญดังนี้
อัตราที่แท้จริงในการเรียกใช้โค้ดเรียกกลับคืออัตราระหว่างอัตราของวิดีโอและอัตราของเบราว์เซอร์ที่น้อยกว่า ซึ่งหมายความว่าวิดีโอ 25 fps ที่เล่นในเบราว์เซอร์ที่แสดงผลที่ 60 Hz จะเรียกใช้โค้ดเรียกกลับที่ 25 Hz วิดีโอ 120fps ในเบราว์เซอร์ 60Hz เดียวกันจะเริ่มโค้ดเรียกกลับที่ 60Hz
ชื่อสื่อถึงอะไรบ้าง
เนื่องจากมีความคล้ายคลึงกับ window.requestAnimationFrame()
วิธีการนี้จึงเสนอเป็น video.requestAnimationFrame()
ในตอนแรกและเปลี่ยนชื่อเป็น requestVideoFrameCallback()
ซึ่งตกลงกันไว้หลังจากพูดคุยแลกเปลี่ยนกันเป็นระยะเวลานาน
การตรวจหาฟีเจอร์
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
การสนับสนุนเบราว์เซอร์
ใยโพลีเอสเตอร์
มี polyfill สำหรับเมธอด requestVideoFrameCallback()
ตาม Window.requestAnimationFrame()
และ HTMLVideoElement.getVideoPlaybackQuality()
โปรดคำนึงถึงข้อจำกัดที่ระบุไว้ใน README
ก่อนใช้ฟีเจอร์นี้
การใช้เมธอด requestVideoFrameCallback()
หากเคยใช้เมธอด requestAnimationFrame()
คุณจะรู้สึกคุ้นเคยกับเมธอด requestVideoFrameCallback()
ทันที
คุณจะต้องลงทะเบียนโค้ดเรียกกลับครั้งแรก 1 ครั้ง จากนั้นจะลงทะเบียนอีกครั้งทุกครั้งที่โค้ดเรียกกลับเริ่มทำงาน
const doSomethingWithTheFrame = (now, metadata) => {
// Do something with the frame.
console.log(now, metadata);
// Re-register the callback to be notified about the next frame.
video.requestVideoFrameCallback(doSomethingWithTheFrame);
};
// Initially register the callback to be notified about the first frame.
video.requestVideoFrameCallback(doSomethingWithTheFrame);
ในโค้ดเรียกกลับ now
คือ DOMHighResTimeStamp
และ metadata
คือพจนานุกรม VideoFrameMetadata
ที่มีพร็อพเพอร์ตี้ต่อไปนี้
presentationTime
ประเภทDOMHighResTimeStamp
: เวลาที่ User Agent ส่งเฟรมสำหรับการเรียบเรียงexpectedDisplayTime
ประเภทDOMHighResTimeStamp
: เวลาที่ User Agent คาดว่าจะเห็นเฟรมwidth
ประเภทunsigned long
: ความกว้างของเฟรมวิดีโอในหน่วยพิกเซลของสื่อheight
ประเภทunsigned long
: ความสูงของเฟรมวิดีโอในหน่วยพิกเซลของสื่อmediaTime
, ประเภทdouble
: การประทับเวลาการนำเสนอสื่อ (PTS) ในหน่วยวินาทีของเฟรมที่แสดง (เช่น การประทับเวลาบนไทม์ไลน์video.currentTime
)presentedFrames
ประเภทunsigned long
: จำนวนเฟรมที่ส่งสำหรับการจัดองค์ประกอบ อนุญาตให้ไคลเอ็นต์ตรวจสอบว่าเฟรมหายไประหว่างอินสแตนซ์ของVideoFrameRequestCallback
หรือไม่processingDuration
ประเภทdouble
: ระยะเวลาที่ผ่านไปเป็นหน่วยวินาทีนับตั้งแต่การส่งแพ็กเก็ตที่เข้ารหัสและมีการประทับเวลาการนำเสนอ (PTS) เดียวกันกับเฟรมนี้ (เช่น เหมือนกับmediaTime
) ไปยังตัวถอดรหัสจนกว่าเฟรมที่ถอดรหัสจะพร้อมสำหรับการนำเสนอ
สำหรับแอปพลิเคชัน WebRTC คุณสมบัติเพิ่มเติมอาจปรากฏขึ้น:
captureTime
ประเภทDOMHighResTimeStamp
: สำหรับเฟรมวิดีโอที่มาจากแหล่งที่มาในเครื่องหรือระยะไกล นี่คือเวลาที่กล้องจับภาพเฟรม สำหรับแหล่งที่มาระยะไกล ระบบจะประมาณเวลาในการบันทึกโดยใช้การซิงค์นาฬิกาและรายงานของผู้ส่ง RTCP เพื่อแปลงการประทับเวลา RTP เป็นเวลาบันทึกreceiveTime
ประเภทDOMHighResTimeStamp
: สำหรับเฟรมวิดีโอที่มาจากแหล่งที่มาระยะไกล นี่คือเวลาที่แพลตฟอร์มได้รับเฟรมที่เข้ารหัส ซึ่งก็คือเวลาที่ได้รับแพ็กเก็ตสุดท้ายของเฟรมนี้ผ่านทางเครือข่ายrtpTimestamp
ประเภทunsigned long
: การประทับเวลา RTP ที่เชื่อมโยงกับเฟรมวิดีโอนี้
รายการนี้มีความสนใจเป็นพิเศษคือ mediaTime
การใช้งาน Chromium ใช้นาฬิกาเสียงเป็นแหล่งที่มาของเวลาที่ทำงานหลัง video.currentTime
ขณะที่ mediaTime
จะป้อน presentationTimestamp
ของเฟรมโดยตรง
mediaTime
คือสิ่งที่คุณควรใช้หากต้องการระบุเฟรมอย่างชัดเจนด้วยวิธีที่ทำซ้ำได้ ซึ่งรวมถึงการระบุเฟรมที่คุณพลาดไป
ถ้าทุกอย่างดูออกแค่เฟรมเดียว...
การซิงค์แนวตั้ง (หรือเรียกสั้นๆ ว่า vsync) เป็นเทคโนโลยีกราฟิกที่ซิงโครไนซ์อัตราเฟรมของวิดีโอและอัตราการรีเฟรชของหน้าจอ
เนื่องจาก requestVideoFrameCallback()
ทำงานบนเทรดหลัก แต่ในเบื้องหลังการทำงาน การจัดองค์ประกอบวิดีโอจะเกิดขึ้นในเทรดของผู้ทำคอมโพเนนต์ ทุกอย่างจาก API นี้คือความพยายามอย่างดีที่สุด และเบราว์เซอร์ก็ไม่ได้ให้การรับประกันที่เข้มงวด
สิ่งที่จะเกิดขึ้นคือ API สามารถเป็น vsync ล่าช้า 1 อย่างเมื่อเทียบกับเมื่อเฟรมวิดีโอแสดงผล
ต้องใช้ vsync หนึ่งครั้งเพื่อให้การเปลี่ยนแปลงในหน้าเว็บผ่าน API ปรากฏบนหน้าจอ (เหมือนกับ window.requestAnimationFrame()
)
ดังนั้น ถ้าคุณอัปเดต mediaTime
หรือหมายเลขเฟรมในหน้าเว็บอยู่เรื่อยๆ และเปรียบเทียบกับเฟรมวิดีโอที่เรียงลำดับเลข วิดีโอจะดูเหมือนเป็นเฟรมข้างหน้า 1 เฟรม
สิ่งที่เกิดขึ้นคือเฟรมพร้อมใช้งานที่ vsync x โค้ดเรียกกลับเริ่มทำงานและเฟรมจะแสดงผลที่ vsync x+1 และการเปลี่ยนแปลงที่ทำในโค้ดเรียกกลับจะแสดงผลที่ vsync x+2
คุณสามารถตรวจสอบว่าโค้ดเรียกกลับเป็น vsync ล่าช้า (และแสดงผลเฟรมบนหน้าจออยู่แล้ว) โดยตรวจสอบว่า metadata.expectedDisplayTime
เป็นเวลาประมาณ now
หรือ vsync ในอนาคต
ถ้าอยู่ภายในช่วงประมาณ 5-10 ไมโครวินาทีของ now
เฟรมจะแสดงผลแล้ว
หาก expectedDisplayTime
อยู่ห่างออกไปประมาณ 16 มิลลิวินาที (สมมติว่าเบราว์เซอร์/หน้าจอรีเฟรชที่ 60 Hz)
แสดงว่าคุณทำข้อมูลให้ตรงกับเฟรมแล้ว
ข้อมูลประชากร
ฉันได้สร้างการสาธิตเล็กๆ น้อยๆ เกี่ยวกับ Glitch ที่แสดงวิธีการวาดเฟรมบนผืนผ้าใบที่อัตราเฟรมของวิดีโอตรงกับวิดีโอและบันทึกข้อมูลเมตาของเฟรมเพื่อวัตถุประสงค์ในการแก้ไขข้อบกพร่อง
let paintCount = 0;
let startTime = 0.0;
const updateCanvas = (now, metadata) => {
if (startTime === 0.0) {
startTime = now;
}
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const elapsed = (now - startTime) / 1000.0;
const fps = (++paintCount / elapsed).toFixed(3);
fpsInfo.innerText = `video fps: ${fps}`;
metadataInfo.innerText = JSON.stringify(metadata, null, 2);
video.requestVideoFrameCallback(updateCanvas);
};
video.requestVideoFrameCallback(updateCanvas);
บทสรุป
ผู้ใช้ได้ประมวลผลระดับเฟรมมานานระยะหนึ่งโดยไม่ต้องเข้าถึงเฟรมจริงโดยอิงตาม video.currentTime
เท่านั้น
เมธอด requestVideoFrameCallback()
ปรับปรุงวิธีแก้ปัญหานี้ได้อย่างมาก
ข้อความแสดงการยอมรับ
Thomas Guilbert เป็นผู้ระบุและติดตั้งใช้งาน API ของ requestVideoFrameCallback
โพสต์นี้ได้รับการตรวจสอบโดย Joe Medley และ Kayce Basques
รูปภาพหลักโดย
Denise Jans ใน Unsplash