[mirotalk] - add room duration
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
# ====================================================
|
||||
# MiroTalk P2P v.1.7.19 - Environment Configuration
|
||||
# MiroTalk P2P v.1.7.20 - Environment Configuration
|
||||
# ====================================================
|
||||
|
||||
# App environment
|
||||
|
||||
@@ -80,6 +80,7 @@ This project is proudly sponsored by
|
||||
- Push-to-talk functionality, similar to a walkie-talkie.
|
||||
- Advanced collaborative whiteboard for teachers.
|
||||
- Real-time sharing of YouTube embed videos, video files (MP4, WebM, OGG), and audio files (MP3).
|
||||
- Meeting Duration (HH:MM:SS): Set the meeting time in hours, minutes, and seconds for precise duration control.
|
||||
- Full-screen mode with one-click video element zooming and pin/unpin.
|
||||
- Customizable UI themes.
|
||||
- Right-click options on video elements for additional controls.
|
||||
@@ -120,21 +121,22 @@ This project is proudly sponsored by
|
||||
<br/>
|
||||
|
||||
- You can `directly join a room` by using links like:
|
||||
- https://p2p.mirotalk.com/join?room=test&name=random&avatar=0&audio=0&video=0&screen=0&chat=0&hide=0¬ify=0
|
||||
- https://mirotalk.up.railway.app/join?room=test&name=random&avatar=0&audio=0&video=0&screen=0&chat=0&hide=0¬ify=0
|
||||
- https://p2p.mirotalk.com/join?room=test&name=random&avatar=0&audio=0&video=0&screen=0&chat=0&hide=0¬ify=0&duration=unlimited
|
||||
- https://mirotalk.up.railway.app/join?room=test&name=random&avatar=0&audio=0&video=0&screen=0&chat=0&hide=0¬ify=0&duration=unlimited
|
||||
|
||||
| Params | Type | Description |
|
||||
| ------ | ------- | --------------- |
|
||||
| room | string | Room Id |
|
||||
| name | string | User name |
|
||||
| avatar | Mixed | User avatar |
|
||||
| audio | boolean | Audio stream |
|
||||
| video | boolean | Video stream |
|
||||
| screen | boolean | Screen stream |
|
||||
| chat. | boolean | Chat |
|
||||
| hide | boolean | Hide myself |
|
||||
| notify | boolean | Welcome message |
|
||||
| token | string | jwt token |
|
||||
| Params | Type | Description |
|
||||
| -------- | ------- | ------------------------- |
|
||||
| room | string | Room Id |
|
||||
| name | string | User name |
|
||||
| avatar | Mixed | User avatar |
|
||||
| audio | boolean | Audio stream |
|
||||
| video | boolean | Video stream |
|
||||
| screen | boolean | Screen stream |
|
||||
| chat. | boolean | Chat |
|
||||
| hide | boolean | Hide myself |
|
||||
| notify | boolean | Welcome message |
|
||||
| duration | string | Meeting duration HH:MM:SS |
|
||||
| token | string | jwt token |
|
||||
|
||||
> **Note**
|
||||
>
|
||||
@@ -353,9 +355,9 @@ curl -X POST "https://mirotalk.up.railway.app/api/v1/meeting" -H "authorization:
|
||||
### 4. Join Meeting (Basic)
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:3000/api/v1/join" -H "authorization: mirotalkp2p_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"random","avatar":false,"audio":true,"video":true,"screen":false,"chat":false,"hide":false,"notify":true}'
|
||||
curl -X POST "https://p2p.mirotalk.com/api/v1/join" -H "authorization: mirotalkp2p_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"random","avatar":false,"audio":true,"video":true,"screen":false,"chat":false,"hide":false,"notify":true}'
|
||||
curl -X POST "https://mirotalk.up.railway.app/api/v1/join" -H "authorization: mirotalkp2p_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"random","avatar":false,"audio":true,"video":true,"screen":false,"chat":false,"hide":false,"notify":true}'
|
||||
curl -X POST "http://localhost:3000/api/v1/join" -H "authorization: mirotalkp2p_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"random","avatar":false,"audio":true,"video":true,"screen":false,"chat":false,"hide":false,"notify":true,"duration":"unlimited"}'
|
||||
curl -X POST "https://p2p.mirotalk.com/api/v1/join" -H "authorization: mirotalkp2p_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"random","avatar":false,"audio":true,"video":true,"screen":false,"chat":false,"hide":false,"notify":true,"duration":"unlimited"}'
|
||||
curl -X POST "https://mirotalk.up.railway.app/api/v1/join" -H "authorization: mirotalkp2p_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"random","avatar":false,"audio":true,"video":true,"screen":false,"chat":false,"hide":false,"notify":true,"duration":"unlimited"}'
|
||||
```
|
||||
|
||||
### 5. Join Meeting with Token
|
||||
|
||||
@@ -28,6 +28,7 @@ async function getJoin() {
|
||||
chat: false,
|
||||
hide: false,
|
||||
notify: true,
|
||||
duration: 'unlimited',
|
||||
token: {
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
|
||||
@@ -27,6 +27,7 @@ $data = array(
|
||||
"chat" => false,
|
||||
"hide" => false,
|
||||
"notify" => true,
|
||||
"duration" => "unlimited",
|
||||
"token" => array(
|
||||
"username" => "username",
|
||||
"password" => "password",
|
||||
|
||||
@@ -22,6 +22,7 @@ data = {
|
||||
"chat": "false",
|
||||
"hide": "false",
|
||||
"notify": "true",
|
||||
"duration": "unlimited",
|
||||
"token": {
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
|
||||
@@ -18,6 +18,7 @@ REQUEST_DATA='{
|
||||
"chat": false,
|
||||
"hide": false,
|
||||
"notify": true,
|
||||
"duration": "unlimited",
|
||||
"token": {
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
|
||||
@@ -105,6 +105,9 @@ paths:
|
||||
notify:
|
||||
type: boolean
|
||||
default: false
|
||||
duration:
|
||||
type: string
|
||||
default: 'unlimited'
|
||||
token:
|
||||
type: object
|
||||
description: |
|
||||
|
||||
+3
-1
@@ -58,7 +58,7 @@ module.exports = class ServerApi {
|
||||
|
||||
getJoinURL(data) {
|
||||
// Get data
|
||||
const { room, name, avatar, audio, video, screen, chat, notify, hide, token } = data;
|
||||
const { room, name, avatar, audio, video, screen, chat, notify, hide, duration, token } = data;
|
||||
|
||||
const roomValue = room || uuidV4();
|
||||
const nameValue = name || 'User-' + this.getRandomNumber();
|
||||
@@ -69,6 +69,7 @@ module.exports = class ServerApi {
|
||||
const chatValue = chat || false;
|
||||
const hideValue = hide || false;
|
||||
const notifyValue = notify || false;
|
||||
const durationValue = duration || 'unlimited';
|
||||
const jwtToken = token ? '&token=' + this.getToken(token) : '';
|
||||
|
||||
const joinURL =
|
||||
@@ -84,6 +85,7 @@ module.exports = class ServerApi {
|
||||
`&chat=${chatValue}` +
|
||||
`&hide=${hideValue}` +
|
||||
`¬ify=${notifyValue}` +
|
||||
`&duration=${durationValue}` +
|
||||
jwtToken;
|
||||
|
||||
return joinURL;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* ==============================================
|
||||
* MiroTalk P2P v.1.7.19 - Configuration File
|
||||
* MiroTalk P2P v.1.7.20 - Configuration File
|
||||
* ==============================================
|
||||
*
|
||||
* Branding and customizations require a license:
|
||||
|
||||
+3
-3
@@ -45,7 +45,7 @@ dependencies: {
|
||||
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.7.19
|
||||
* @version 1.7.20
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -640,10 +640,10 @@ app.get('/join/', async (req, res) => {
|
||||
log.debug('Request Query', req.query);
|
||||
/*
|
||||
http://localhost:3000/join?room=test&name=mirotalk&audio=1&video=1&screen=0&chat=1¬ify=0&hide=0
|
||||
https://p2p.mirotalk.com/join?room=test&name=mirotalk&audio=1&video=1&screen=0&chat=1¬ify=0&hide=0
|
||||
https://p2p.mirotalk.com/join?room=test&name=mirotalk&audio=1&video=1&screen=0&chat=1¬ify=0&hide=0&duration=00:00:30
|
||||
https://mirotalk.up.railway.app/join?room=test&name=mirotalk&audio=1&video=1&screen=0&chat=1¬ify=0&hide=0
|
||||
*/
|
||||
const { room, name, audio, video, screen, chat, notify, hide, token } = checkXSS(req.query);
|
||||
const { room, name, audio, video, screen, chat, notify, hide, duration, token } = checkXSS(req.query);
|
||||
|
||||
if (!room) {
|
||||
log.warn('/join/params room empty', room);
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mirotalk",
|
||||
"version": "1.7.19",
|
||||
"version": "1.7.20",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mirotalk",
|
||||
"version": "1.7.19",
|
||||
"version": "1.7.20",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@mattermost/client": "11.3.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mirotalk",
|
||||
"version": "1.7.19",
|
||||
"version": "1.7.20",
|
||||
"description": "A free WebRTC browser-based video call",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ let brand = {
|
||||
},
|
||||
about: {
|
||||
imageUrl: '../images/mirotalk-logo.gif',
|
||||
title: 'WebRTC P2P v1.7.19',
|
||||
title: 'WebRTC P2P v1.7.20',
|
||||
html: `
|
||||
<button
|
||||
id="support-button"
|
||||
|
||||
+99
-21
@@ -15,7 +15,7 @@
|
||||
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.7.19
|
||||
* @version 1.7.20
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -28,6 +28,7 @@ const signalingServer = getSignalingServer();
|
||||
|
||||
// This room
|
||||
const myRoomId = getId('myRoomId');
|
||||
const roomSessionDuration = getRoomDuration();
|
||||
const roomId = getRoomId();
|
||||
const myRoomUrl = window.location.origin + '/join/' + roomId; // share room url
|
||||
|
||||
@@ -992,8 +993,7 @@ function getSignalingServer() {
|
||||
*/
|
||||
function getRoomId() {
|
||||
// check if passed as params /join?room=id
|
||||
let qs = new URLSearchParams(window.location.search);
|
||||
let queryRoomId = filterXSS(qs.get('room'));
|
||||
let queryRoomId = getQueryParam('room');
|
||||
|
||||
// skip /join/
|
||||
let roomId = queryRoomId ? queryRoomId : window.location.pathname.split('/join/')[1];
|
||||
@@ -1014,6 +1014,74 @@ function getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Room Session Duration
|
||||
*/
|
||||
function getRoomDuration() {
|
||||
const roomDuration = getQueryParam('duration');
|
||||
|
||||
if (isValidDuration(roomDuration)) {
|
||||
if (roomDuration === 'unlimited') {
|
||||
console.log('The room has no time limit');
|
||||
return roomDuration;
|
||||
}
|
||||
const timeLimit = timeToMilliseconds(roomDuration);
|
||||
setTimeout(() => {
|
||||
playSound('eject');
|
||||
Swal.fire({
|
||||
background: swBg,
|
||||
position: 'center',
|
||||
title: 'Time Limit Reached',
|
||||
text: 'The room has reached its time limit and will close shortly',
|
||||
icon: 'warning',
|
||||
timer: 6000, // 6 seconds
|
||||
timerProgressBar: true,
|
||||
showConfirmButton: false,
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
},
|
||||
willClose: () => {
|
||||
exitRoom();
|
||||
},
|
||||
});
|
||||
}, timeLimit);
|
||||
|
||||
console.log('Direct join', { duration: roomDuration, timeLimit: timeLimit });
|
||||
return roomDuration;
|
||||
}
|
||||
return 'unlimited';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HH:MM:SS to milliseconds
|
||||
* @param {string} timeString Time string in HH:MM:SS format
|
||||
* @returns {integer} milliseconds
|
||||
*/
|
||||
function timeToMilliseconds(timeString) {
|
||||
const [hours, minutes, seconds] = timeString.split(':').map(Number);
|
||||
return (hours * 3600 + minutes * 60 + seconds) * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate duration format
|
||||
* @param {string} duration Duration string
|
||||
* @returns {boolean} true/false
|
||||
*/
|
||||
function isValidDuration(duration) {
|
||||
if (duration === 'unlimited') return true;
|
||||
// Check if the format is HH:MM:SS
|
||||
const regex = /^(\d{2}):(\d{2}):(\d{2})$/;
|
||||
const match = duration.match(regex);
|
||||
if (!match) return false;
|
||||
const [hours, minutes, seconds] = match.slice(1).map(Number);
|
||||
// Validate ranges: hours, minutes, and seconds
|
||||
if (hours < 0 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random Id
|
||||
* @param {integer} length
|
||||
@@ -1049,8 +1117,7 @@ function getUUID() {
|
||||
* @returns {boolean} true/false (default true)
|
||||
*/
|
||||
function getNotify() {
|
||||
let qs = new URLSearchParams(window.location.search);
|
||||
let notify = filterXSS(qs.get('notify'));
|
||||
let notify = getQueryParam('notify');
|
||||
if (notify) {
|
||||
let queryNotify = notify === '1' || notify === 'true';
|
||||
if (queryNotify != null) {
|
||||
@@ -1068,8 +1135,7 @@ function getNotify() {
|
||||
* @returns {boolean} true/false
|
||||
*/
|
||||
function getChat() {
|
||||
let qs = new URLSearchParams(window.location.search);
|
||||
let chat = filterXSS(qs.get('chat'));
|
||||
let chat = getQueryParam('chat');
|
||||
if (chat) {
|
||||
let queryChat = chat === '1' || chat === 'true';
|
||||
if (queryChat != null) {
|
||||
@@ -1088,8 +1154,7 @@ function getChat() {
|
||||
*/
|
||||
function getPeerToken() {
|
||||
if (window.sessionStorage.peer_token) return window.sessionStorage.peer_token;
|
||||
let qs = new URLSearchParams(window.location.search);
|
||||
let token = filterXSS(qs.get('token'));
|
||||
let token = getQueryParam('token');
|
||||
let queryToken = false;
|
||||
if (token) {
|
||||
queryToken = token;
|
||||
@@ -1103,8 +1168,7 @@ function getPeerToken() {
|
||||
* @returns {string} Peer Name
|
||||
*/
|
||||
function getPeerName() {
|
||||
const qs = new URLSearchParams(window.location.search);
|
||||
const name = filterXSS(qs.get('name'));
|
||||
const name = getQueryParam('name');
|
||||
if (isHtml(name)) {
|
||||
console.log('Direct join', { name: 'Invalid name' });
|
||||
return 'Invalid name';
|
||||
@@ -1138,8 +1202,7 @@ function generateRandomName() {
|
||||
* @returns {string} Peer Avatar
|
||||
*/
|
||||
function getPeerAvatar() {
|
||||
const qs = new URLSearchParams(window.location.search);
|
||||
const avatar = filterXSS(qs.get('avatar'));
|
||||
const avatar = getQueryParam('avatar');
|
||||
const avatarDisabled = avatar === '0' || avatar === 'false';
|
||||
|
||||
console.log('Direct join', { avatar: avatar });
|
||||
@@ -1155,8 +1218,7 @@ function getPeerAvatar() {
|
||||
* @returns {boolean} true/false
|
||||
*/
|
||||
function getScreenEnabled() {
|
||||
let qs = new URLSearchParams(window.location.search);
|
||||
let screen = filterXSS(qs.get('screen'));
|
||||
let screen = getQueryParam('screen');
|
||||
if (screen) {
|
||||
screen = screen.toLowerCase();
|
||||
let queryPeerScreen = screen === '1' || screen === 'true';
|
||||
@@ -1172,8 +1234,7 @@ function getScreenEnabled() {
|
||||
* @returns {boolean} true/false
|
||||
*/
|
||||
function getHideMeActive() {
|
||||
let qs = new URLSearchParams(window.location.search);
|
||||
let hide = filterXSS(qs.get('hide'));
|
||||
let hide = getQueryParam('hide');
|
||||
let queryHideMe = false;
|
||||
if (hide) {
|
||||
hide = hide.toLowerCase();
|
||||
@@ -1183,6 +1244,16 @@ function getHideMeActive() {
|
||||
return queryHideMe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query parameter from URL
|
||||
* @param {string} param parameter name
|
||||
* @returns {string} parameter value
|
||||
*/
|
||||
function getQueryParam(param) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return filterXSS(urlParams.get(param));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is peer connections
|
||||
* @returns {boolean} true/false
|
||||
@@ -2161,9 +2232,8 @@ async function changeLocalMicrophone(deviceId) {
|
||||
* 1/true = enabled / 0/false = disabled
|
||||
*/
|
||||
function checkPeerAudioVideo() {
|
||||
let qs = new URLSearchParams(window.location.search);
|
||||
let audio = filterXSS(qs.get('audio'));
|
||||
let video = filterXSS(qs.get('video'));
|
||||
let audio = getQueryParam('audio');
|
||||
let video = getQueryParam('video');
|
||||
if (audio) {
|
||||
audio = audio.toLowerCase();
|
||||
let queryPeerAudio = useAudio ? audio === '1' || audio === 'true' : false;
|
||||
@@ -13570,7 +13640,7 @@ function showAbout() {
|
||||
Swal.fire({
|
||||
background: swBg,
|
||||
position: 'center',
|
||||
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.7.19',
|
||||
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.7.20',
|
||||
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
|
||||
customClass: { image: 'img-about' },
|
||||
html: `
|
||||
@@ -13631,6 +13701,14 @@ function leaveRoom() {
|
||||
surveyActive ? leaveFeedback() : redirectOnLeave();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the Room
|
||||
*/
|
||||
function exitRoom() {
|
||||
checkRecording();
|
||||
redirectOnLeave();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for feedback when room exit
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Custom Room page: build /join URL from form settings.
|
||||
*
|
||||
* Query params used by client.js:
|
||||
* - room, name, avatar, audio, video, screen, chat, hide, notify
|
||||
* - room, name, avatar, audio, video, screen, chat, hide, notify, duration, token
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -19,6 +19,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const avatarEl = document.getElementById('avatar');
|
||||
const tokenEl = document.getElementById('token');
|
||||
|
||||
const durationEl = document.getElementById('duration');
|
||||
|
||||
const audioEl = document.getElementById('audio');
|
||||
const videoEl = document.getElementById('video');
|
||||
const screenEl = document.getElementById('screen');
|
||||
@@ -53,12 +55,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const boolToFlag = (checked) => (checked ? '1' : '0');
|
||||
|
||||
const normalizeDuration = (raw) => {
|
||||
const value = safe(raw);
|
||||
if (!value) return 'unlimited';
|
||||
if (value.toLowerCase() === 'unlimited') return 'unlimited';
|
||||
// Accept HH:MM:SS format only
|
||||
const re = /^(\d{2}):(\d{2}):(\d{2})$/;
|
||||
if (!re.test(value)) {
|
||||
throw new Error('Duration must be HH:MM:SS (e.g. 12:30:00) or left empty for unlimited');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const buildJoinUrl = () => {
|
||||
const room = safe(roomEl?.value) || 'random';
|
||||
const name = safe(nameEl?.value) || 'random';
|
||||
const avatarRaw = safe(avatarEl?.value);
|
||||
const avatar = avatarRaw ? avatarRaw : '0';
|
||||
const token = safe(tokenEl?.value);
|
||||
const duration = normalizeDuration(durationEl?.value);
|
||||
|
||||
const url = new URL('/join', window.location.origin);
|
||||
url.searchParams.set('room', room);
|
||||
@@ -72,6 +87,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
url.searchParams.set('hide', boolToFlag(!!hideEl?.checked));
|
||||
url.searchParams.set('notify', boolToFlag(!!notifyEl?.checked));
|
||||
|
||||
url.searchParams.set('duration', duration);
|
||||
|
||||
if (token) url.searchParams.set('token', token);
|
||||
|
||||
return url;
|
||||
|
||||
@@ -9,6 +9,7 @@ class IframeApi {
|
||||
chat: false,
|
||||
hide: false,
|
||||
notify: false,
|
||||
duration: 'unlimited',
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
token: null,
|
||||
@@ -53,6 +54,7 @@ class IframeApi {
|
||||
chat: this.options.chat ? 1 : 0,
|
||||
hide: this.options.hide ? 1 : 0,
|
||||
notify: this.options.notify ? 1 : 0,
|
||||
duration: this.options.duration || 'unlimited',
|
||||
});
|
||||
|
||||
if (this.options.token) {
|
||||
|
||||
@@ -136,6 +136,21 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="cr-field">
|
||||
<label class="cr-label" for="duration">Duration (optional)</label>
|
||||
<input
|
||||
id="duration"
|
||||
name="duration"
|
||||
class="cr-input"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
placeholder="12:30:00"
|
||||
pattern="^\d{2}:\d{2}:\d{2}$"
|
||||
maxlength="8"
|
||||
/>
|
||||
<p class="cr-hint">Format HH:MM:SS. Leave empty for unlimited (uses duration=unlimited).</p>
|
||||
</div>
|
||||
|
||||
<div class="cr-field">
|
||||
<label class="cr-label" for="crPreviewUrl">Join link</label>
|
||||
<div class="cr-preview-row">
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
screen: 0,
|
||||
hide: 0,
|
||||
notify: 0,
|
||||
duration: 'unlimited', // HH:MM:SS
|
||||
token: null,
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
|
||||
+3
-2
@@ -143,6 +143,7 @@ describe('test-api', () => {
|
||||
chat: false,
|
||||
hide: false,
|
||||
notify: false,
|
||||
duration: '00:30:00',
|
||||
token: { username: 'user', password: 'pass', presenter: true, expire: '1h' },
|
||||
};
|
||||
|
||||
@@ -150,7 +151,7 @@ describe('test-api', () => {
|
||||
|
||||
const result = serverApi.getJoinURL(data);
|
||||
result.should.equal(
|
||||
'https://example.com/join?room=room1&name=John%20Doe&avatar=avatar.jpg&audio=true&video=false&screen=false&chat=false&hide=false¬ify=false&token=testToken'
|
||||
'https://example.com/join?room=room1&name=John%20Doe&avatar=avatar.jpg&audio=true&video=false&screen=false&chat=false&hide=false¬ify=false&duration=00:30:00&token=testToken'
|
||||
);
|
||||
|
||||
tokenStub.restore();
|
||||
@@ -168,7 +169,7 @@ describe('test-api', () => {
|
||||
|
||||
const result = serverApi.getJoinURL({});
|
||||
result.should.equal(
|
||||
'https://example.com/join?room=room1&name=User-123456&avatar=false&audio=false&video=false&screen=false&chat=false&hide=false¬ify=false'
|
||||
'https://example.com/join?room=room1&name=User-123456&avatar=false&audio=false&video=false&screen=false&chat=false&hide=false¬ify=false&duration=unlimited'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
chat: 0,
|
||||
hide: 0,
|
||||
notify: 0,
|
||||
duration: 'unlimited', // HH:MM:SS
|
||||
token: null,
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
|
||||
Reference in New Issue
Block a user