Is it possible to host multiple meetings with different licenses and embed their respective videos onto a single website?
Currently, we are only able to embed one video.
We have cloned the following GitHub repository and implemented the sample source code found in the sample-app-web/Components folder as a reference: GitHub - zoom/meetingsdk-web-sample: Zoom Meeting SDK Web Sample App.
Details
①zoomtest2.aspx
Join the meeting here.
<!DOCTYPE html>
<head>
<title>Zoom WebSDK Embedded Demo Nav</title>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="https://source.zoom.us/2.9.7/css/bootstrap.css" />
<link type="text/css" rel="stylesheet" href="https://source.zoom.us/2.9.7/css/react-select.css" />
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<script src="./tools/jsrsasign-all-min.js"></script>
<script src="./tools/tool.js"></script>
<script src="./tools/token-tool.js"></script>
<script src="./tools/vconsole.min.js"></script>
<script src="./tools/nav.js"></script>
</head>
<body>
<style>
.sdk-select {
height: 34px;
border-radius: 4px;
}
.websdktest button {
float: right;
margin-left: 5px;
}
#nav-tool {
margin-bottom: 0px;
}
#show-test-tool {
position: absolute;
top: 100px;
left: 0;
display: block;
z-index: 99999;
}
#display_name {
width: 250px;
}
#websdk-iframe {
width: 700px;
height: 500px;
border: 1px;
border-color: red;
border-style: dashed;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
left: 50%;
margin: 0;
}
</style>
<nav id="nav-tool" class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Zoom WebSDK Embedded</a>
</div>
<div id="navbar" class="websdktest">
<form class="navbar-form navbar-right" id="meeting_form">
<button type="submit" class="btn btn-primary" id="join_meeting">Join</button>
</form>
</div>
<!--/.navbar-collapse -->
</div>
</nav>
</body>
</html>
②nav.js
I have customized it so that when you press the Join button, it will navigate to zoomtest3.aspx.
/* eslint-disable no-undef */
window.addEventListener('DOMContentLoaded', function(event) {
console.log('DOM fully loaded and parsed');
websdkready();
});
function websdkready() {
var testTool = window.testTool;
if (testTool.isMobileDevice()) {
// eslint-disable-next-line no-undef
var vConsole = new VConsole();
}
console.log("checkSystemRequirements");
// console.log(JSON.stringify(ZoomMtgEmbedded.checkSystemRequirements()));
var SDK_KEY = "<SDK_KEY>";
/**
* NEVER PUT YOUR ACTUAL API SECRET IN CLIENT SIDE CODE, THIS IS JUST FOR QUICK PROTOTYPING
* The below generateSignature should be done server side as not to expose your api secret in public
* You can find an eaxmple in here: https://marketplace.zoom.us/docs/sdk/native-sdks/web/signature
*/
var SDK_SECRET = "<SDK_SECRET>";
// some help code, remember mn, pwd, lang to cookie, and autofill.
//document.getElementById("display_name").value =
// testTool.detectOS() +
// "#" +
// testTool.getBrowserInfo();
//document.getElementById("meeting_number").value = testTool.getCookie(
// "meeting_number"
//);
//document.getElementById("meeting_pwd").value = testTool.getCookie(
// "meeting_pwd"
//);
//if (testTool.getCookie("meeting_lang"))
// document.getElementById("meeting_lang").value = testTool.getCookie(
// "meeting_lang"
// );
//document
// .getElementById("meeting_lang")
// .addEventListener("change", function (e) {
// testTool.setCookie(
// "meeting_lang",
// document.getElementById("meeting_lang").value
// );
// testTool.setCookie(
// "_zm_lang",
// document.getElementById("meeting_lang").value
// );
// });
// copy zoom invite link to mn, autofill mn and pwd.
//document
// .getElementById("meeting_number")
// .addEventListener("input", function (e) {
// var tmpMn = e.target.value.replace(/([^0-9])+/i, "");
// if (tmpMn.match(/([0-9]{9,11})/)) {
// tmpMn = tmpMn.match(/([0-9]{9,11})/)[1];
// }
// var tmpPwd = e.target.value.match(/pwd=([\d,\w]+)/);
// if (tmpPwd) {
// document.getElementById("meeting_pwd").value = tmpPwd[1];
// testTool.setCookie("meeting_pwd", tmpPwd[1]);
// }
// document.getElementById("meeting_number").value = tmpMn;
// testTool.setCookie(
// "meeting_number",
// document.getElementById("meeting_number").value
// );
// });
//document.getElementById("clear_all").addEventListener("click", function (e) {
// testTool.deleteAllCookies();
// document.getElementById("display_name").value = "";
// document.getElementById("meeting_number").value = "";
// document.getElementById("meeting_pwd").value = "";
// document.getElementById("meeting_lang").value = "en-US";
// document.getElementById("meeting_role").value = 0;
// window.location.href = "/index.html";
//});
// click join meeting button
document
.getElementById("join_meeting")
.addEventListener("click", function (e) {
e.preventDefault();
var meetingConfig = testTool.getMeetingConfig();
if (!meetingConfig.mn || !meetingConfig.name) {
alert("Meeting number or username is empty");
return false;
}
//testTool.setCookie("meeting_number", meetingConfig.mn);
//testTool.setCookie("meeting_pwd", meetingConfig.pwd);
// generateSDKSignature define in token-tool.js
var signature = generateSDKSignature({
meetingNumber: meetingConfig.mn,
sdkKey: SDK_KEY,
sdkSecret: SDK_SECRET,
role: meetingConfig.role,
success: function (res) {
console.log(res);
meetingConfig.signature = res;
meetingConfig.sdkKey = SDK_KEY;
//20230213 加藤 遷移先変更テスト
var joinUrl = "/actninfo/zoomtest3.aspx?" + testTool.serialize(meetingConfig);
console.log(joinUrl);
window.open(joinUrl, "_blank");
},
});
});
function copyToClipboard(elementId) {
var aux = document.createElement("input");
aux.setAttribute("value", document.getElementById(elementId).getAttribute('link'));
document.body.appendChild(aux);
aux.select();
document.execCommand("copy");
document.body.removeChild(aux);
}
// click copy jon link button
window.copyJoinLink = function (element) {
var meetingConfig = testTool.getMeetingConfig();
if (!meetingConfig.mn || !meetingConfig.name) {
alert("Meeting number or username is empty");
return false;
}
var signature = generateSDKSignature({
meetingNumber: meetingConfig.mn,
sdkKey: SDK_KEY,
sdkSecret: SDK_SECRET,
role: meetingConfig.role,
success: function (res) {
console.log(res);
meetingConfig.signature = res;
meetingConfig.sdkKey = SDK_KEY;
if (document.getElementById('demoType').value === 'cdn') {
var joinUrl =
testTool.getCurrentDomain() +
"/cdn.html?" +
testTool.serialize(meetingConfig);
document.getElementById('copy_link_value').setAttribute('link', joinUrl);
copyToClipboard('copy_link_value');
} else{
var joinUrl =
testTool.getCurrentDomain() +
"/index.html?" +
testTool.serialize(meetingConfig);
document.getElementById('copy_link_value').setAttribute('link', joinUrl);
copyToClipboard('copy_link_value');
}
},
});
};
}
③tool.js
Customize the getMeetingConfig
method here to add “mn2” so that you can set the second meeting number.
var testTool = {
b64EncodeUnicode: function (str) {
// first we use encodeURIComponent to get percent-encoded UTF-8,
// then we convert the percent encodings into raw bytes which
// can be fed into btoa.
return btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(
match,
p1
) {
return String.fromCharCode("0x" + p1);
})
);
},
b64DecodeUnicode: function (str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(
atob(str)
.split("")
.map(function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
},
isMobileDevice: function () {
return (
navigator.userAgent.match(/Android/i) ||
navigator.userAgent.match(/webOS/i) ||
navigator.userAgent.match(/iPhone/i) ||
navigator.userAgent.match(/iPod/i) ||
navigator.userAgent.match(/BlackBerry/i) ||
navigator.userAgent.match(/Windows Phone/i)
);
},
//nav.jsで呼び出される。ミーティングconfigを取得
getMeetingConfig: function () {
return {
//meeting_number,name,password,role,mail,言語,グローバル固定
mn: "95054073126",
name: "testuser",
pwd: "",
role: 0,
email: "test@gmail.com",
lang: "jp-JP",
signature: "",
china: 0,
mn2: "96977640334",
};
},
createZoomNode: function (id, url) {
const zoomIframe = document.createElement("iframe");
zoomIframe.id = id;
zoomIframe.sandbox =
"allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox";
zoomIframe.allow = "microphone; camera; fullscreen;";
zoomIframe.src = url;
zoomIframe.style = "";
if (typeof document.body.append === "function") {
document.body.append(zoomIframe);
} else {
document.body.appendChild(zoomIframe);
}
},
getCurrentDomain: function () {
return (
window.location.protocol +
"//" +
window.location.hostname +
":" +
window.location.port
);
},
parseQuery: function () {
return (function () {
var href = window.location.href;
var queryString = href.substr(href.indexOf("?"));
var query = {};
var pairs = (queryString[0] === "?"
? queryString.substr(1)
: queryString
).split("&");
for (var i = 0; i < pairs.length; i += 1) {
var pair = pairs[i].split("=");
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
}
return query;
})();
},
serialize: function (obj) {
// eslint-disable-next-line no-shadow
var keyOrderArr = ["name", "mn", "email", "pwd", "role", "lang", "signature", "china"];
Array.intersect = function () {
var result = new Array();
var obj = {};
for (var i = 0; i < arguments.length; i++) {
for (var j = 0; j < arguments[i].length; j++) {
var str = arguments[i][j];
if (!obj[str]) {
obj[str] = 1;
} else {
obj[str]++;
if (obj[str] == arguments.length) {
result.push(str);
}
}
}
}
return result;
};
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, "includes", {
enumerable: false,
value: function (obj) {
var newArr = this.filter(function (el) {
return el === obj;
});
return newArr.length > 0;
},
});
}
var tmpInterArr = Array.intersect(keyOrderArr, Object.keys(obj));
var sortedObj = [];
keyOrderArr.forEach(function (key) {
if (tmpInterArr.includes(key)) {
sortedObj.push([key, obj[key]]);
}
});
Object.keys(obj)
.sort()
.forEach(function (key) {
if (!tmpInterArr.includes(key)) {
sortedObj.push([key, obj[key]]);
}
});
var tmpSortResult = (function (sortedObj) {
var str = [];
for (var p in sortedObj) {
if (typeof sortedObj[p][1] !== "undefined") {
str.push(
encodeURIComponent(sortedObj[p][0]) +
"=" +
encodeURIComponent(sortedObj[p][1])
);
}
}
return str.join("&");
})(sortedObj);
return tmpSortResult;
},
detectOS: function () {
var sUserAgent = navigator.userAgent;
var isWin =
navigator.platform === "Win32" || navigator.platform === "Windows";
var isMac =
navigator.platform === "Mac68K" ||
navigator.platform === "MacPPC" ||
navigator.platform === "Macintosh" ||
navigator.platform === "MacIntel";
if (isMac) return "Mac";
var isUnix = navigator.platform === "X11" && !isWin && !isMac;
if (isUnix) return "Unix";
var isLinux = String(navigator.platform).indexOf("Linux") > -1;
if (isLinux) return "Linux";
if (isWin) {
var isWin2K =
sUserAgent.indexOf("Windows NT 5.0") > -1 ||
sUserAgent.indexOf("Windows 2000") > -1;
if (isWin2K) return "Win2000";
var isWinXP =
sUserAgent.indexOf("Windows NT 5.1") > -1 ||
sUserAgent.indexOf("Windows XP") > -1;
if (isWinXP) return "WinXP";
var isWin2003 =
sUserAgent.indexOf("Windows NT 5.2") > -1 ||
sUserAgent.indexOf("Windows 2003") > -1;
if (isWin2003) return "Win2003";
var isWinVista =
sUserAgent.indexOf("Windows NT 6.0") > -1 ||
sUserAgent.indexOf("Windows Vista") > -1;
if (isWinVista) return "WinVista";
var isWin7 =
sUserAgent.indexOf("Windows NT 6.1") > -1 ||
sUserAgent.indexOf("Windows 7") > -1;
if (isWin7) return "Win7";
var isWin10 =
sUserAgent.indexOf("Windows NT 10") > -1 ||
sUserAgent.indexOf("Windows 10") > -1;
if (isWin10) return "Win10";
}
return "other";
},
detectIE: function () {
var ua = window.navigator.userAgent;
// Test values; Uncomment to check result …
// IE 10
// ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';
// IE 11
// ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
// Edge 12 (Spartan)
// ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';
// Edge 13
// ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586';
var msie = ua.indexOf("MSIE ");
if (msie > 0) {
// IE 10 or older => return version number
return "IE" + parseInt(ua.substring(msie + 5, ua.indexOf(".", msie)), 10);
}
var trident = ua.indexOf("Trident/");
if (trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf("rv:");
return "IE" + parseInt(ua.substring(rv + 3, ua.indexOf(".", rv)), 10);
}
var edge = ua.indexOf("Edge/");
if (edge > 0) {
// Edge (IE 12+) => return version number
return (
"Edge" + parseInt(ua.substring(edge + 5, ua.indexOf(".", edge)), 10)
);
}
// other browser
return false;
},
getBrowserInfo: function () {
var agent = navigator.userAgent.toLowerCase();
var regStr_ff = /firefox\/[\d.]+/gi;
var regStr_chrome = /chrome\/[\d.]+/gi;
var regStrChrome2 = /ipad; cpu os (\d+_\d+)/gi;
var regStr_saf = /version\/[\d.]+/gi;
var regStr_saf2 = /safari\/[\d.]+/gi;
var regStr_edg = /edg\/[\d.]+/gi;
// firefox
if (agent.indexOf("firefox") > 0) {
return agent.match(regStr_ff);
}
// Safari
if (agent.indexOf("safari") > 0 && agent.indexOf("chrome") < 0) {
var tmpInfo = "safari/unknow";
var tmpInfo2;
tmpInfo = agent.match(regStr_saf);
tmpInfo2 = agent.match(regStr_saf2);
if (tmpInfo) {
tmpInfo = tmpInfo.toString().replace("version", "safari");
}
if (tmpInfo2) {
tmpInfo = tmpInfo2.toString().replace("version", "safari");
}
return tmpInfo;
}
// IE / Eege
var tmpIsIE = testTool.detectIE();
if (tmpIsIE) {
return tmpIsIE;
}
// Chrome
if (agent.indexOf("chrome") > 0) {
return agent.match(regStr_chrome);
}
return "other";
},
getRandomInt: function (max) {
return Math.floor(Math.random() * Math.floor(max));
},
extractHostname: function (url) {
var hostname;
if (url.indexOf("//") > -1) {
hostname = url.split("/")[2];
} else {
hostname = url.split("/")[0];
}
hostname = hostname.split(":")[0];
hostname = hostname.split("?")[0];
return hostname;
},
getDomainName: function (hostName) {
return hostName.substring(
hostName.lastIndexOf(".", hostName.lastIndexOf(".") - 1) + 1
);
},
setCookie: function (cname, cvalue) {
var exdays = 1;
var d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
var expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
},
getCookie: function (cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(";");
for (var i = 0; i < ca.length; i += 1) {
var c = ca[i];
while (c.charAt(0) === " ") {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return "";
},
deleteAllCookies: function () {
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
var eqPos = cookie.indexOf("=");
var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
}
},
};
window.testTool = testTool;
④zoomtest3.aspx
I would like to set it up so that two videos are displayed here.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSDK Embedded CDN demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://source.zoom.us/2.9.7/lib/vendor/react.min.js"></script>
<script src="https://source.zoom.us/2.9.7/lib/vendor/react-dom.min.js"></script>
<script src="https://source.zoom.us/2.9.7/zoom-meeting-embedded-2.9.7.min.js"></script>
<script src="./tools/tool.js"></script>
<script src="./tools/vconsole.min.js"></script>
<script src="./tools/token-tool.js"></script>
<script src="./tools/cdn.js"></script>
</head>
<body>
<div>テスト1</div>
<div id="ZoomEmbeddedApp"></div>
<div>テスト2</div>
<div id="ZoomEmbeddedApp2"></div>
</body>
</html>
⑤cdn.js
/* eslint-disable no-undef */
window.addEventListener('DOMContentLoaded', function (event) {
console.log('DOM fully loaded and parsed');
websdkready();
});
function websdkready() {
var testTool = window.testTool;
// get meeting args from url
var tmpArgs = testTool.parseQuery();
var meetingConfig = {
sdkKey: tmpArgs.sdkKey,
meetingNumber: tmpArgs.mn,
//2つ目のミーティングNo
meetingNumber2: tmpArgs.mn2,
userName: (function () {
if (tmpArgs.name) {
try {
return testTool.b64DecodeUnicode(tmpArgs.name);
} catch (e) {
return tmpArgs.name;
}
}
return (
"CDN#" +
tmpArgs.version +
"#" +
testTool.detectOS() +
"#" +
testTool.getBrowserInfo()
);
})(),
passWord: tmpArgs.pwd,
leaveUrl: "/index.html",
role: parseInt(tmpArgs.role, 10),
userEmail: (function () {
try {
return testTool.b64DecodeUnicode(tmpArgs.email);
} catch (e) {
return tmpArgs.email;
}
})(),
lang: tmpArgs.lang,
signature: tmpArgs.signature || "",
china: tmpArgs.china === "1",
};
// a tool use debug mobile device
if (testTool.isMobileDevice()) {
vConsole = new VConsole();
}
if (!meetingConfig.signature) {
//API認証
window.location.href = "./zoomtest2.aspx";
}
// WebSDK Embedded init
var rootElement = document.getElementById('ZoomEmbeddedApp');
var zmClient = ZoomMtgEmbedded.createClient();
zmClient.init({
debug: true,
zoomAppRoot: rootElement,
webEndpoint: meetingConfig.webEndpoint,
language: meetingConfig.lang,
customize: {
meetingInfo: ['topic', 'host', 'mn', 'pwd', 'telPwd', 'invite', 'participant', 'dc', 'enctype'],
toolbar: {
buttons: [
{
text: 'CustomizeButton',
className: 'CustomizeButton',
onClick: () => {
console.log('click Customer Button');
}
}
]
}
}
}).then((e) => {
console.log('success', e);
}).catch((e) => {
console.log('error', e);
});
// 2つめの映像
var rootElement2 = document.getElementById('ZoomEmbeddedApp2');
var zmClient2 = ZoomMtgEmbedded.createClient();
zmClient2.init({
debug: true,
zoomAppRoot: rootElement2,
webEndpoint: meetingConfig.webEndpoint,
language: meetingConfig.lang,
customize: {
meetingInfo: ['topic', 'host', 'mn', 'pwd', 'telPwd', 'invite', 'participant', 'dc', 'enctype'],
toolbar: {
buttons: [
{
text: 'CustomizeButton',
className: 'CustomizeButton',
onClick: () => {
console.log('click Customer Button');
}
}
]
}
}
}).then((e) => {
console.log('success', e);
}).catch((e) => {
console.log('error', e);
});
// WebSDK Embedded join
zmClient.join({
sdkKey: meetingConfig.sdkKey,
signature: meetingConfig.signature,
meetingNumber: meetingConfig.meetingNumber,
userName: meetingConfig.userName,
password: meetingConfig.passWord,
userEmail: meetingConfig.userEmail,
}).then((e) => {
console.log('success', e);
}).catch((e) => {
console.log('error', e);
});
// WebSDK Embedded2 join
zmClient2.join({
sdkKey: meetingConfig.sdkKey,
signature: meetingConfig.signature,
meetingNumber: meetingConfig.meetingNumber2,
userName: meetingConfig.userName,
password: meetingConfig.passWord,
userEmail: meetingConfig.userEmail,
}).then((e) => {
console.log('success', e);
}).catch((e) => {
console.log('error', e);
});
};
I tried what you suggested, but currently, I am encountering an error and only one video is being displayed.