<!--
Copyright 2021 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>MxN Video playbacks</title>
<script>
const _defaultRows = 7;
const _defaultColumns = 7;
const _totalVideoWidth = 1600;
const _totalVideoHeight = 900;
let codec = 'vp9';
const parsedString = (function (names) {
const pairs = {};
for (let i = 0; i < names.length; ++i) {
const keyValue = names[i].split('=', 2);
if (keyValue.length === 1) {
pairs[keyValue[0]] = '';
} else {
pairs[keyValue[0]] = decodeURIComponent(keyValue[1].replace(/\+/g, ' '));
}
}
return pairs;
})(window.location.search.substr(1).split('&'));
function GetVideoSource(videoCount, index, codec) {
switch (codec) {
case 'vp8':
if (videoCount <= 4) {
return './teddy1_vp8_640x360_30fps.webm';
} else {
if (index < 4) {
return './teddy3_vp8_320x180_30fps.webm';
} else if (index < 16) {
return './teddy2_vp8_320x180_15fps.webm';
} else {
return './teddy1_vp8_320x180_7fps.webm';
}
}
break;
case 'vp9':
default:
if (videoCount <= 4) {
return './teddy1_vp9_640x360_30fps.webm';
} else {
if (index < 4) {
return './teddy3_vp9_320x180_30fps.webm';
} else if (index < 16) {
return './teddy2_vp9_320x180_15fps.webm';
} else {
return './teddy1_vp9_320x180_7fps.webm';
}
}
break;
}
}
function startMxNVideos() {
const container = document.getElementById('container');
// Get the video row count and the column count from the string.
// Example: videos_mxn.html?rows=9&columns=9
let videoRows = parsedString['rows'];
let videoColumns = parsedString['columns'];
if (videoRows === undefined) {
videoRows = _defaultRows;
}
if (videoColumns === undefined) {
videoColumns = _defaultColumns;
}
// Get the video source option from the string.
codecString = parsedString['codec'];
if (codecString === 'vp8') {
codec = 'vp8';
} else if (codecString !== 'vp9' && codecString !== undefined) {
console.warn('Unsupported video codec format! Switch to default VP9.');
}
// Limite the number of videos to 20x20.
// These videos will not load when the number is too big.
const maxColRow = Math.max(videoRows, videoColumns);
if (maxColRow > 20) {
const p = container.appendChild(document.createElement('p'));
p.style.position = "absolute";
p.style.top = 0;
p.style.left = 0;
p.style.width = _totalVideoWidth;
p.style.height = _totalVideoHeight;
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;
const iconHeight = videoHeight / 8;
// Create MxN videos.
let animatedBar;
let iconBottom;
const videoCount = videoRows * videoColumns;
for (let row = 0; row < videoRows; row++) {
for (let column = 0; column < videoColumns; column++) {
// Onscreen position.
const videoTop = row * videoHeight;
const videoLeft = column * videoWidth;
// Video source.
const i = row * videoColumns + column;
const videoSrc = GetVideoSource(videoCount, i, codec);
createOneVideo(videoTop, videoLeft, videoWidth, videoHeight,
videoSrc, /*hasBorder=*/ i === 0);
// Create an icon on top of each video.
createOneIcon(videoTop, videoLeft, videoWidth, videoHeight, iconHeight);
// For the voice animation on the last video.
if (i === 0) {
animatedBar = document.createElement('icon');
iconBottom = videoTop + iconHeight * 2;
createVoiceAnimationBar(animatedBar, videoTop, videoLeft,
videoWidth, videoHeight, iconHeight);
}
}
}
// Create one small video at the upper right corner to similate the one
// from the local camera (640x360).
createLocalVideoAndIcon(videoWidth, videoHeight, codec);
// Start the voice icon animation.
const frameTime30Fps = 32; // ms
let lastTimestamp = performance.now();
let barHeight = 0;
// Voice bar animation at 30 FPS
function voiceBarAnimation(timestamp) {
const elapsed = timestamp - lastTimestamp;
if (elapsed < frameTime30Fps) {
window.requestAnimationFrame(voiceBarAnimation);
return;
}
lastTimestamp = timestamp;
const maxBarHeight = iconHeight - 4;
barHeight = barHeight + (maxBarHeight / 10);
if (barHeight > maxBarHeight)
barHeight = maxBarHeight / 10;
animatedBar.style.top = iconBottom - barHeight;
animatedBar.style.height = barHeight;
window.requestAnimationFrame(voiceBarAnimation);
}
window.requestAnimationFrame(voiceBarAnimation);
}
function createOneVideo(top, left, width, height, videoSrc, hasBorder) {
const video = document.createElement('video');
video.loop = true;
video.autoplay = true;
video.muted = true;
video.src = videoSrc;
video.width = width;
video.height = height;
video.style.position = "absolute";
video.style.top = top;
video.style.left = left;
if (hasBorder) {
const borderWidth = 3;
video.style.borderWidth = borderWidth;
video.width = width - borderWidth * 2;
video.height = height - borderWidth * 2;
video.style.borderStyle = "solid";
video.style.borderColor = "rgba(90, 129, 193, 255)";
}
video.play();
container.appendChild(video);
}
function createOneIcon(videoTop, videoLeft, videoWidth, videoHeight, iconHeight) {
const icon = document.createElement('icon');
icon.style.backgroundColor = "rgba(29, 110, 216, 255)";
icon.style.position = "absolute";
icon.style.width = iconHeight;
icon.style.height = iconHeight;
icon.style.top = videoTop + iconHeight;
icon.style.left = videoLeft + videoWidth - iconHeight * 2;
container.appendChild(icon);
}
function createLocalVideoAndIcon(videoWidth, videoHeight, codec) {
const smallVideoWidth = videoWidth / 3;
const smallVideoHeight = videoHeight / 3;
const iconHeight = smallVideoHeight / 8;
const videoSrc = GetVideoSource(1, 1, codec);
createOneVideo(videoHeight - smallVideoHeight,
_totalVideoWidth - smallVideoWidth, smallVideoWidth,
smallVideoHeight, videoSrc, false);
createOneIcon(videoHeight - smallVideoHeight, _totalVideoWidth -
smallVideoWidth, smallVideoWidth, smallVideoHeight, iconHeight);
}
function createVoiceAnimationBar(bar, videoTop, videoLeft, videoWidth,
videoHeight, iconHeight) {
bar.style.backgroundColor = "white";
bar.style.position = "absolute";
bar.style.width = 5;
bar.style.height = iconHeight - 4;
bar.style.top = videoTop + iconHeight + 4;
bar.style.left = videoLeft + videoWidth - iconHeight * 1.5 - 2;
container.appendChild(bar);
}
</script>
</head>
<body>
<div id="container" style="position:absolute; top:0px; left:0px">
</div>
<script>startMxNVideos();</script>
</body>
</html>