| <!-- |
| Copyright 2022 The Chromium Authors |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| <html> |
| |
| <head> |
| <title>WebGL MxN Video playbacks</title> |
| <style> |
| #message { |
| position: absolute; |
| width: 1600px; |
| height: 900px; |
| top: 200px; |
| left: 400px; |
| } |
| </style> |
| <script src="../third_party/stats-js/stats.js"></script> |
| <script src="video_utils.js"></script> |
| <script src="webgl_video.js"></script> |
| <script> |
| const _defaultRows = 7; |
| const _defaultColumns = 7; |
| const _totalVideoWidth = 1600; |
| const _totalVideoHeight = 900; |
| let hasUIOnTop = true; |
| let hasFPSOnTop = true; |
| let capUIFPS = true; |
| let useLargeSizeVideo = false; |
| let codec = 'vp9'; |
| let useLocalCamera = false; |
| |
| async function startMxNVideos() { |
| const container = document.getElementById('container'); |
| const p = document.getElementById('message'); |
| |
| // Initialize WebGL |
| |
| const gl = await webglInit(_totalVideoWidth, _totalVideoHeight); |
| if (!gl) { |
| p.innerHTML = "WebGL not supported!"; |
| return; |
| } |
| |
| // Get the UI rendering options from the string. |
| const uiOption = parsedString['ui']; |
| if (uiOption === 'none') { |
| hasUIOnTop = false; |
| } |
| |
| const fpsOption = parsedString['fps']; |
| if (fpsOption === 'none') { |
| hasFPSOnTop = false; |
| } |
| |
| codecString = parsedString['codec']; |
| if (codecString === 'vp8') { |
| codec = 'vp8'; |
| } else if (codecString === 'avc') { |
| codec = 'avc'; |
| } else if (codecString !== 'vp9' && codecString !== undefined) { |
| console.warn('Unsupported video codec format! Switch to default VP9.'); |
| } |
| |
| const capFPSOption = parsedString['cap_ui_fps']; |
| if (capFPSOption === '0') { |
| capUIFPS = false; |
| } else if (capFPSOption === '1') { |
| capUIFPS = true; |
| } |
| |
| const useLargeSizeVideoOption = parsedString['use_large_size_video']; |
| if (useLargeSizeVideoOption === '0') { |
| useLargeSizeVideo = false; |
| } else if (useLargeSizeVideoOption === '1') { |
| useLargeSizeVideo = true; |
| } |
| |
| const useLocalCameraOption = parsedString['use_local_camera']; |
| if (useLocalCameraOption === '0') { |
| useLocalCamera = false; |
| } else if (useLocalCameraOption === '1') { |
| useLocalCamera = true; |
| } |
| |
| // Get the number of video rows and columns from the string. |
| let videoRows = parsedString['rows']; |
| let videoColumns = parsedString['columns']; |
| if (videoRows === undefined) { |
| videoRows = _defaultRows; |
| } |
| if (videoColumns === undefined) { |
| videoColumns = _defaultColumns; |
| } |
| |
| // Limit the number of videos to 20x20. |
| // The video will not load when the number is too big. |
| const maxColRow = Math.max(videoRows, videoColumns); |
| if (maxColRow > 20) { |
| p.innerHTML = "Cannot support videos more than 20 x 20 !" + "<br />" + |
| "Please change the number of rows/columns."; |
| return; |
| } |
| |
| // Calculate the video onscreen size |
| const videoWidth = _totalVideoWidth / maxColRow; |
| const videoHeight = _totalVideoHeight / maxColRow; |
| |
| // Create MxN videos and a small video (size = 89x50) at the upper right |
| // corner to similate the one from the local camera. |
| p.innerHTML = "Uploading videos..."; |
| const videos = []; |
| const videoCount = videoRows * videoColumns; |
| for (let i = 0; i < videoCount + 1; i++) { |
| const video = document.createElement('video'); |
| video.id = i; |
| video.loop = true; |
| video.autoplay = true; |
| video.muted = true; |
| video.display = "none"; |
| video.src = GetVideoSource(videoCount, i, codec, useLargeSizeVideo); |
| video.width = videoWidth; |
| video.height = videoHeight; |
| video.crossorigin = "anonymous"; |
| videos.push(video); |
| } |
| // For the small video at the upper right corner. |
| videos[videoRows * videoColumns].width = videoWidth / 3; |
| videos[videoRows * videoColumns].height = videoHeight / 3; |
| |
| if (useLocalCamera) { |
| const video = videos[videoRows * videoColumns]; |
| video.src = ""; |
| const constraints = { |
| audio: false, |
| video: { width: 640, height: 360 } |
| }; |
| await navigator.mediaDevices.getUserMedia(constraints) |
| .then((mediaStream) => { |
| video.srcObject = mediaStream; |
| video.onloadedmetadata = () => { |
| video.play(); |
| }; |
| }) |
| .catch((err) => { |
| console.error(`${err.name}: ${err.message}`); |
| }); |
| } else { |
| // Use the 640x360 source to simulate the local camera. |
| videos[videoRows * videoColumns].src = GetVideoSource(1, 1, codec); |
| } |
| |
| await Promise.all(videos.map(video => video.play())); |
| p.remove(); |
| |
| // Simulate video playback by WebGL rendering. |
| webglDrawVideoFrames(gl, videos, videoRows, videoColumns, |
| hasUIOnTop, hasFPSOnTop, capUIFPS); |
| } |
| |
| </script> |
| </head> |
| |
| <body> |
| <div id="container" style="position:absolute; top:0px; left:0px"> |
| <p id="message"></p> |
| </div> |
| <script>startMxNVideos();</script> |
| </body> |
| |
| </html> |