mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-25 20:17:47 +00:00
Compare commits
8 Commits
6758
...
openresty-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a08da498dc | ||
|
|
a995b33753 | ||
|
|
bfb15a2523 | ||
|
|
1d59c8122d | ||
|
|
31766c891b | ||
|
|
7a3b8d6ac4 | ||
|
|
edf5e1c094 | ||
|
|
7cd39b7983 |
@@ -62,12 +62,14 @@ import {
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
getAvailableDevices,
|
||||
getDefaultDeviceId,
|
||||
notifyCameraError,
|
||||
notifyMicError,
|
||||
setAudioOutputDeviceId,
|
||||
updateDeviceList
|
||||
} from './react/features/base/devices';
|
||||
} from './react/features/base/devices/actions.web';
|
||||
import {
|
||||
getDefaultDeviceId,
|
||||
setAudioOutputDeviceId
|
||||
} from './react/features/base/devices/functions.web';
|
||||
import {
|
||||
JitsiConferenceErrors,
|
||||
JitsiConferenceEvents,
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@@ -20,7 +20,7 @@ Description: WebRTC JavaScript video conferences
|
||||
|
||||
Package: jitsi-meet-web-config
|
||||
Architecture: all
|
||||
Depends: openssl, nginx | nginx-full | nginx-extras | apache2, curl
|
||||
Depends: openssl, nginx | nginx-full | nginx-extras | openresty | apache2, curl
|
||||
Description: Configuration for web serving of Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
Videobridge to provide high quality, scalable video conferences.
|
||||
|
||||
42
debian/jitsi-meet-web-config.postinst
vendored
42
debian/jitsi-meet-web-config.postinst
vendored
@@ -57,6 +57,10 @@ case "$1" in
|
||||
|| [ "$NGINX_EXTRAS_INSTALL_CHECK" = "unpacked" ] ; then
|
||||
FORCE_NGINX="true"
|
||||
fi
|
||||
OPENRESTY_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'openresty' 2>/dev/null | awk '{print $3}' || true)"
|
||||
if [ "$OPENRESTY_INSTALL_CHECK" = "installed" ] || [ "$OPENRESTY_INSTALL_CHECK" = "unpacked" ] ; then
|
||||
FORCE_OPENRESTY="true"
|
||||
fi
|
||||
APACHE_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'apache2' 2>/dev/null | awk '{print $3}' || true)"
|
||||
if [ "$APACHE_INSTALL_CHECK" = "installed" ] || [ "$APACHE_INSTALL_CHECK" = "unpacked" ] ; then
|
||||
FORCE_APACHE="true"
|
||||
@@ -182,21 +186,41 @@ case "$1" in
|
||||
echo "config.flags.receiveMultipleVideoStreams = true;" >> $JITSI_MEET_CONFIG
|
||||
fi
|
||||
|
||||
if [[ "$FORCE_NGINX" = "true" && ( -z "$JVB_HOSTNAME_OLD" || "$RECONFIGURING" = "true" ) ]] ; then
|
||||
if [[ "$FORCE_OPENRESTY" = "true" ]]; then
|
||||
NGX_COMMON_CONF_PATH="/usr/local/openresty/nginx/conf/$JVB_HOSTNAME.conf"
|
||||
NGX_SVC_NAME=openresty
|
||||
OPENRESTY_NGX_CONF="/usr/local/openresty/nginx/conf/nginx.conf"
|
||||
else
|
||||
NGX_COMMON_CONF_PATH="/etc/nginx/sites-available/$JVB_HOSTNAME.conf"
|
||||
NGX_SVC_NAME=nginx
|
||||
fi
|
||||
|
||||
if [[ ( "$FORCE_NGINX" = "true" || "$FORCE_OPENRESTY" = "true" ) && ( -z "$JVB_HOSTNAME_OLD" || "$RECONFIGURING" = "true" ) ]] ; then
|
||||
|
||||
# this is a reconfigure, lets just delete old links
|
||||
if [ "$RECONFIGURING" = "true" ] ; then
|
||||
rm -f /etc/nginx/sites-enabled/$JVB_HOSTNAME_OLD.conf
|
||||
rm -f /etc/jitsi/meet/$JVB_HOSTNAME_OLD-config.js
|
||||
if [[ "$FORCE_OPENRESTY" = "true" ]]; then
|
||||
sed -i "/include.*$JVB_HOSTNAME_OLD/d" "$OPENRESTY_NGX_CONF"
|
||||
fi
|
||||
fi
|
||||
|
||||
# nginx conf
|
||||
if [ ! -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf ] ; then
|
||||
cp /usr/share/jitsi-meet-web-config/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ] ; then
|
||||
ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
|
||||
if [ ! -f "$NGX_COMMON_CONF_PATH" ] ; then
|
||||
cp /usr/share/jitsi-meet-web-config/jitsi-meet.example "$NGX_COMMON_CONF_PATH"
|
||||
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ] && ! [[ "$FORCE_OPENRESTY" = "true" ]] ; then
|
||||
ln -s "$NGX_COMMON_CONF_PATH" /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" "$NGX_COMMON_CONF_PATH"
|
||||
|
||||
if [[ "$FORCE_OPENRESTY" = "true" ]]; then
|
||||
OPENRESTY_NGX_CONF_MD5_ORIG=$(dpkg-query -s openresty | sed -n '/\/nginx\.conf /{s@.* @@;p}')
|
||||
OPENRESTY_NGX_CONF_MD5_USERS=$(md5sum "$OPENRESTY_NGX_CONF" | sed 's@ .*@@')
|
||||
if [[ "$OPENRESTY_NGX_CONF_MD5_USERS" = "$OPENRESTY_NGX_CONF_MD5_ORIG" ]]; then
|
||||
sed -i "/^http \x7b/,/^\x7d/s@^\x7d@\tinclude $NGX_COMMON_CONF_PATH;\n\x7d@" "$OPENRESTY_NGX_CONF"
|
||||
fi
|
||||
fi
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
|
||||
if [ "$CERT_CHOICE" = "$UPLOADED_CERT_CHOICE" ] ; then
|
||||
@@ -204,14 +228,14 @@ case "$1" in
|
||||
CERT_KEY_ESC=$(echo $CERT_KEY | sed 's/\./\\\./g')
|
||||
CERT_KEY_ESC=$(echo $CERT_KEY_ESC | sed 's/\//\\\//g')
|
||||
sed -i "s/ssl_certificate_key\ \/etc\/jitsi\/meet\/.*key/ssl_certificate_key\ $CERT_KEY_ESC/g" \
|
||||
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
"$NGX_COMMON_CONF_PATH"
|
||||
CERT_CRT_ESC=$(echo $CERT_CRT | sed 's/\./\\\./g')
|
||||
CERT_CRT_ESC=$(echo $CERT_CRT_ESC | sed 's/\//\\\//g')
|
||||
sed -i "s/ssl_certificate\ \/etc\/jitsi\/meet\/.*crt/ssl_certificate\ $CERT_CRT_ESC/g" \
|
||||
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
"$NGX_COMMON_CONF_PATH"
|
||||
fi
|
||||
|
||||
invoke-rc.d nginx reload || true
|
||||
invoke-rc.d $NGX_SVC_NAME reload || true
|
||||
elif [[ "$FORCE_APACHE" = "true" && ( -z "$JVB_HOSTNAME_OLD" || "$RECONFIGURING" = "true" ) ]] ; then
|
||||
|
||||
# this is a reconfigure, lets just delete old links
|
||||
|
||||
4
debian/jitsi-meet-web-config.postrm
vendored
4
debian/jitsi-meet-web-config.postrm
vendored
@@ -24,6 +24,9 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
remove)
|
||||
if [ -x "/etc/init.d/openresty" ]; then
|
||||
invoke-rc.d openresty reload || true
|
||||
fi
|
||||
if [ -x "/etc/init.d/nginx" ]; then
|
||||
invoke-rc.d nginx reload || true
|
||||
fi
|
||||
@@ -38,6 +41,7 @@ case "$1" in
|
||||
rm -f /etc/jitsi/meet/$JVB_HOSTNAME-config.js
|
||||
rm -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
rm -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
|
||||
rm -f /usr/local/openresty/nginx/conf/$JVB_HOSTNAME.conf
|
||||
rm -f /etc/apache2/sites-available/$JVB_HOSTNAME.conf
|
||||
rm -f /etc/apache2/sites-enabled/$JVB_HOSTNAME.conf
|
||||
rm -f /etc/jitsi/meet/$JVB_HOSTNAME.key
|
||||
|
||||
14
globals.native.d.ts
vendored
14
globals.native.d.ts
vendored
@@ -12,8 +12,21 @@ interface IWindow {
|
||||
JITSI_MEET_LITE_SDK: boolean;
|
||||
JitsiMeetJS: any;
|
||||
config: IConfig;
|
||||
document: any;
|
||||
innerHeight: number;
|
||||
innerWidth: number;
|
||||
interfaceConfig: any;
|
||||
location: ILocation;
|
||||
self: any;
|
||||
top: any;
|
||||
|
||||
onerror: (event: string, source: any, lineno: any, colno: any, e: Error) => void;
|
||||
onunhandledrejection: (event: any) => void;
|
||||
|
||||
setTimeout: typeof setTimeout;
|
||||
clearTimeout: typeof clearTimeout;
|
||||
setImmediate: typeof setImmediate;
|
||||
clearImmediate: typeof clearImmediate;
|
||||
}
|
||||
|
||||
interface INavigator {
|
||||
@@ -22,6 +35,7 @@ interface INavigator {
|
||||
|
||||
declare global {
|
||||
const APP: any;
|
||||
const document: any;
|
||||
const interfaceConfig: any;
|
||||
const navigator: INavigator;
|
||||
const window: IWindow;
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"buttonLabel": "驾驶模式",
|
||||
"title": "安全驾驶模式",
|
||||
"title": "驾驶模式",
|
||||
"videoStopped": "你的视频已停止"
|
||||
}
|
||||
},
|
||||
@@ -104,6 +104,7 @@
|
||||
},
|
||||
"noMessagesMessage": "会议中还没有消息,在这里开始谈话吧!",
|
||||
"privateNotice": "与{{recipient}}的私聊",
|
||||
"sendButton": "发送",
|
||||
"smileysPanel": "表情符号面板",
|
||||
"tabs": {
|
||||
"chat": "聊天",
|
||||
@@ -270,6 +271,7 @@
|
||||
"gracefulShutdown": "我们目前正在维护中,请稍后再试。",
|
||||
"grantModeratorDialog": "你确定要授予{{participantName}}主持人权限吗?",
|
||||
"grantModeratorTitle": "授予主持人权限",
|
||||
"hide": "隐藏",
|
||||
"hideShareAudioHelper": "不要再显示",
|
||||
"incorrectPassword": "错误的用户名或者密码",
|
||||
"incorrectRoomLockPassword": "密码错误",
|
||||
@@ -387,6 +389,7 @@
|
||||
"shareYourScreenDisabled": "共享屏幕已禁用。",
|
||||
"sharedVideoDialogError": "错误:网址无效",
|
||||
"sharedVideoLinkPlaceholder": "YouTube或视频链接",
|
||||
"show": "显示",
|
||||
"start": "开始",
|
||||
"startLiveStreaming": "开始直播",
|
||||
"startRecording": "开始录制",
|
||||
@@ -435,7 +438,7 @@
|
||||
"search": "搜索GIPHY"
|
||||
},
|
||||
"helpView": {
|
||||
"header": "帮助中心"
|
||||
"title": "帮助中心"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "接听",
|
||||
@@ -553,6 +556,7 @@
|
||||
"signedInAs": "你当前登录为:",
|
||||
"start": "开始直播",
|
||||
"streamIdHelp": "这是什么?",
|
||||
"title": "直播",
|
||||
"unavailableTitle": "直播不可用",
|
||||
"youtubeTerms": "YouTube服务条款"
|
||||
},
|
||||
@@ -595,6 +599,7 @@
|
||||
"passwordJoinButton": "加入",
|
||||
"reject": "拒绝",
|
||||
"rejectAll": "拒绝全部",
|
||||
"title": "大厅",
|
||||
"toggleLabel": "开启大厅模式"
|
||||
},
|
||||
"localRecording": {
|
||||
@@ -621,6 +626,7 @@
|
||||
"no": "否",
|
||||
"participant": "参会者",
|
||||
"participantStats": "参会者状态",
|
||||
"selectTabTitle": "🎥请选择此标签页进行录制",
|
||||
"sessionToken": "会话Token",
|
||||
"start": "开始录制",
|
||||
"stop": "停止录制",
|
||||
@@ -737,13 +743,13 @@
|
||||
"videoModeration": "开启视频"
|
||||
},
|
||||
"close": "关闭",
|
||||
"header": "参会者",
|
||||
"headings": {
|
||||
"lobby": "大厅(({{count}}人)",
|
||||
"participantsList": "会议参会者({{count}}人)",
|
||||
"waitingLobby": "在大厅等待({{count}}人)"
|
||||
},
|
||||
"search": "搜索参会者"
|
||||
"search": "搜索参会者",
|
||||
"title": "参会者"
|
||||
},
|
||||
"passwordDigitsOnly": "最多{{number}}位数字",
|
||||
"passwordSetRemotely": "由其他参会者设置",
|
||||
@@ -853,7 +859,7 @@
|
||||
"ringing": "响铃中……"
|
||||
},
|
||||
"privacyView": {
|
||||
"header": "隐私"
|
||||
"title": "隐私"
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "头像",
|
||||
@@ -925,6 +931,7 @@
|
||||
"signIn": "登录",
|
||||
"signOut": "注销",
|
||||
"surfaceError": "请选择当前标签页",
|
||||
"title": "录制中",
|
||||
"unavailable": "{{serviceName}}目前无法使用,我们正在努力解决这个问题,请稍后再试。",
|
||||
"unavailableTitle": "录制不可用",
|
||||
"uploadToCloud": "上传至云端"
|
||||
@@ -936,8 +943,8 @@
|
||||
"security": {
|
||||
"about": "你可以为会议添加一个$t(lockRoomPassword),参会者需要输入$t(lockRoomPassword)才能加入会议。",
|
||||
"aboutReadOnly": "主持人可以为会议添加一个$t(lockRoomPassword)),参会者需要输入$t(lockRoomPassword)才能加入会议。",
|
||||
"header": "安全选项",
|
||||
"insecureRoomNameWarning": "会议室名称过于简单,任何人都可以加入此会议,请考虑使用安全选项以确保你的会议安全。"
|
||||
"insecureRoomNameWarning": "会议室名称过于简单,任何人都可以加入此会议,请考虑使用安全选项以确保你的会议安全。",
|
||||
"title": "安全选项"
|
||||
},
|
||||
"settings": {
|
||||
"buttonLabel": "设置",
|
||||
@@ -953,7 +960,7 @@
|
||||
"desktopShareWarning": "你需要重新启动共享屏幕以使新设置生效。",
|
||||
"devices": "设备",
|
||||
"followMe": "所有人跟随",
|
||||
"framesPerSecond": "帧率",
|
||||
"framesPerSecond": "帧",
|
||||
"incomingMessage": "新消息",
|
||||
"language": "语言",
|
||||
"loggedIn": "以{{name}}登录",
|
||||
@@ -1004,6 +1011,7 @@
|
||||
"profileSection": "简介",
|
||||
"serverURL": "服务器网址",
|
||||
"showAdvanced": "显示高级设置",
|
||||
"startCarModeInLowBandwidthMode": "同时开启驾驶模式和省流模式",
|
||||
"startWithAudioMuted": "关闭音频并启动",
|
||||
"startWithVideoMuted": "关闭视频并启动",
|
||||
"terms": "条款",
|
||||
@@ -1042,7 +1050,7 @@
|
||||
"title": "由于你的电脑进入休眠模式,视频通话已经中断。"
|
||||
},
|
||||
"termsView": {
|
||||
"header": "条款"
|
||||
"title": "条款"
|
||||
},
|
||||
"toggleTopPanelLabel": "打开/关闭顶部面板",
|
||||
"toolbar": {
|
||||
@@ -1218,6 +1226,8 @@
|
||||
"labelToolTip": "会议正在转录中",
|
||||
"off": "转录已停止",
|
||||
"pending": "准备转录会议中……",
|
||||
"sourceLanguageDesc": "当前会议语言设置为<b>{{sourceLanguage}}</b><br/>你可以在这里",
|
||||
"sourceLanguageHere": "更改",
|
||||
"start": "开启显示字幕",
|
||||
"stop": "停止显示字幕",
|
||||
"subtitles": "字幕",
|
||||
@@ -1337,12 +1347,12 @@
|
||||
"jitsiOnMobile": "手机版Jitsi – 下载我们的APP,随时随地都能开始会议",
|
||||
"join": "创建/加入",
|
||||
"logo": {
|
||||
"calendar": "日历logo",
|
||||
"calendar": "日历图标",
|
||||
"desktopPreviewThumbnail": "桌面预览缩略图",
|
||||
"googleLogo": "谷歌logo",
|
||||
"logoDeepLinking": "Jitsi meet logo",
|
||||
"microsoftLogo": "微软logo",
|
||||
"policyLogo": "政策logo"
|
||||
"googleLogo": "谷歌图标",
|
||||
"logoDeepLinking": "Jitsi Meet图标",
|
||||
"microsoftLogo": "微软图标",
|
||||
"policyLogo": "政策图标"
|
||||
},
|
||||
"mobileDownLoadLinkAndroid": "从Google Play下载安卓版手机APP",
|
||||
"mobileDownLoadLinkFDroid": "从F-Droid下载安卓版手机APP",
|
||||
@@ -1353,7 +1363,7 @@
|
||||
"recentListDelete": "删除",
|
||||
"recentListEmpty": "近期会议为空,与你的团队参会者聊天后,历史会议记录会出现在这里。",
|
||||
"reducedUIText": "欢迎使用{{app}}!",
|
||||
"roomNameAllowedChars": "会议室名称不应包含以下任何字符:? & : ' \" % #",
|
||||
"roomNameAllowedChars": "会议室名称不应包含以下字符:? & : ' \" % #",
|
||||
"roomname": "请输入会议室名称",
|
||||
"roomnameHint": "输入你想加入的会议室的名称或网址,你也可以使用不同的名称创建会议室,其他人只需输入相同的名称即可加入。",
|
||||
"sendFeedback": "发送反馈",
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "增加會議連結",
|
||||
"confirmAddLink": "您要為此活動加入 Jitsi 連結嗎?",
|
||||
"confirmAddLink": "您要為此活動加入Jitsi連結嗎?",
|
||||
"error": {
|
||||
"appConfiguration": "行事曆整合尚未正確設定。",
|
||||
"generic": "發生錯誤,請檢查行事曆設定,或是重新整理行事曆。",
|
||||
@@ -82,7 +82,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"buttonLabel": "駕駛模式",
|
||||
"title": "安全駕駛模式",
|
||||
"title": "駕駛模式",
|
||||
"videoStopped": "您的視訊已停用"
|
||||
}
|
||||
},
|
||||
@@ -104,6 +104,7 @@
|
||||
},
|
||||
"noMessagesMessage": "此會議尚無訊息,在此開始對話聊天!",
|
||||
"privateNotice": "傳送私人訊息至{{recipient}}",
|
||||
"sendButton": "傳送",
|
||||
"smileysPanel": "Emoji 面板",
|
||||
"tabs": {
|
||||
"chat": "聊天",
|
||||
@@ -118,7 +119,7 @@
|
||||
"buttonTextEdge": "安裝 Edge 外掛程式",
|
||||
"close": "關閉",
|
||||
"dontShowAgain": "不要再問了",
|
||||
"installExtensionText": "安裝適用於 Google 行事曆及 Office 365 整合的擴充功能"
|
||||
"installExtensionText": "安裝適用於Google行事曆及Office 365整合的擴充功能"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "正在將您連接至您的會議……"
|
||||
@@ -140,7 +141,7 @@
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "位址:",
|
||||
"audio_ssrc": "音訊 SSRC:",
|
||||
"audio_ssrc": "音訊SSRC:",
|
||||
"bandwidth": "估計頻寬:",
|
||||
"bitrate": "連線速率:",
|
||||
"bridgeCount": "伺服器數量:",
|
||||
@@ -155,7 +156,7 @@
|
||||
"maxEnabledResolution": "最大傳輸",
|
||||
"more": "顯示更多",
|
||||
"packetloss": "丟包率:",
|
||||
"participant_id": "與會者 ID:",
|
||||
"participant_id": "與會者ID:",
|
||||
"quality": {
|
||||
"good": "很好",
|
||||
"inactive": "未啟用",
|
||||
@@ -172,7 +173,7 @@
|
||||
"status": "連接:",
|
||||
"transport": "傳輸協定:",
|
||||
"transport_plural": "傳輸:",
|
||||
"video_ssrc": "視訊 SSRC:"
|
||||
"video_ssrc": "視訊SSRC:"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "稍早",
|
||||
@@ -183,10 +184,10 @@
|
||||
"appNotInstalled": "您需要在手機上安裝{{app}}行動應用程式才能加入這場會議。",
|
||||
"description": "甚麼事情都沒發生?我們已嘗試在您的{{app}}桌面應用程式開啟會議。請再試一次,或是在{{app}}網路應用程式開啟會議。",
|
||||
"descriptionWithoutWeb": "甚麼事情都沒發生?我們已試著將您的會議在桌面應用程式{{app}}中啟動。",
|
||||
"downloadApp": "下載 App",
|
||||
"ifDoNotHaveApp": "如果您尚未安裝 App:",
|
||||
"ifHaveApp": "如果您已經有 App 了:",
|
||||
"joinInApp": "使用 App 加入會議",
|
||||
"downloadApp": "下載App",
|
||||
"ifDoNotHaveApp": "如果您尚未安裝App:",
|
||||
"ifHaveApp": "如果您已經此App:",
|
||||
"joinInApp": "使用App加入會議",
|
||||
"launchWebButton": "在瀏覽器開啟",
|
||||
"title": "正在{{app}}發起您的會議……",
|
||||
"tryAgainButton": "在桌面上再試一次",
|
||||
@@ -270,6 +271,7 @@
|
||||
"gracefulShutdown": "我們目前正在維護中,請稍後再試。",
|
||||
"grantModeratorDialog": "您確定要授予{{participantName}}主持人權限嗎?",
|
||||
"grantModeratorTitle": "授予主持人權限",
|
||||
"hide": "隱藏",
|
||||
"hideShareAudioHelper": "不再顯示",
|
||||
"incorrectPassword": "錯誤的用戶名稱或密碼",
|
||||
"incorrectRoomLockPassword": "密碼不符",
|
||||
@@ -387,6 +389,7 @@
|
||||
"shareYourScreenDisabled": "畫面分享已停用。",
|
||||
"sharedVideoDialogError": "錯誤:網址無效",
|
||||
"sharedVideoLinkPlaceholder": "YouTube或影片網址",
|
||||
"show": "顯示",
|
||||
"start": "開始",
|
||||
"startLiveStreaming": "啟動直播串流",
|
||||
"startRecording": "啟動錄製作業",
|
||||
@@ -435,7 +438,7 @@
|
||||
"search": "搜尋 GIPHY"
|
||||
},
|
||||
"helpView": {
|
||||
"header": "說明中心"
|
||||
"title": "說明中心"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "接通",
|
||||
@@ -553,6 +556,7 @@
|
||||
"signedInAs": "您目前登入名稱為:",
|
||||
"start": "啟動直播串流",
|
||||
"streamIdHelp": "這是什麼?",
|
||||
"title": "直播串流",
|
||||
"unavailableTitle": "直播串流無法使用",
|
||||
"youtubeTerms": "YouTube服務條款"
|
||||
},
|
||||
@@ -595,6 +599,7 @@
|
||||
"passwordJoinButton": "加入",
|
||||
"reject": "拒絕",
|
||||
"rejectAll": "拒絕所有人",
|
||||
"title": "大廳",
|
||||
"toggleLabel": "啟用大廳模式"
|
||||
},
|
||||
"localRecording": {
|
||||
@@ -621,6 +626,7 @@
|
||||
"no": "否",
|
||||
"participant": "與會者",
|
||||
"participantStats": "與會者狀態",
|
||||
"selectTabTitle": "🎥請選擇此分頁進行錄製",
|
||||
"sessionToken": "工作階段Token",
|
||||
"start": "啟動錄製",
|
||||
"stop": "停用錄製",
|
||||
@@ -737,13 +743,13 @@
|
||||
"videoModeration": "開啟視訊"
|
||||
},
|
||||
"close": "關閉",
|
||||
"header": "與會者",
|
||||
"headings": {
|
||||
"lobby": "大廳({{count}}人)",
|
||||
"participantsList": "會議與會者({{count}}人)",
|
||||
"waitingLobby": "於大廳等候({{count}}人)"
|
||||
},
|
||||
"search": "搜尋與會者"
|
||||
"search": "搜尋與會者",
|
||||
"title": "與會者"
|
||||
},
|
||||
"passwordDigitsOnly": "上限為{{number}}位數",
|
||||
"passwordSetRemotely": "由其他與會者設定",
|
||||
@@ -824,7 +830,7 @@
|
||||
"initiated": "通話已初始化",
|
||||
"joinAudioByPhone": "使用手機音訊裝置加入",
|
||||
"joinMeeting": "加入會議",
|
||||
"joinMeetingInLowBandwidthMode": "以低帶寬模式加入",
|
||||
"joinMeetingInLowBandwidthMode": "以低頻寬模式加入",
|
||||
"joinWithoutAudio": "無音訊情況下加入",
|
||||
"keyboardShortcuts": "啟用鍵盤快捷鍵",
|
||||
"linkCopied": "連結已複製到剪貼簿",
|
||||
@@ -853,7 +859,7 @@
|
||||
"ringing": "鈴鈴鈴……"
|
||||
},
|
||||
"privacyView": {
|
||||
"header": "隱私權"
|
||||
"title": "隱私權"
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "頭像",
|
||||
@@ -925,6 +931,7 @@
|
||||
"signIn": "登入",
|
||||
"signOut": "登出",
|
||||
"surfaceError": "請選擇當前分頁",
|
||||
"title": "錄製中",
|
||||
"unavailable": "喔哦!{{serviceName}}目前無法使用,我們正在解決此問題,請稍後再試。",
|
||||
"unavailableTitle": "錄製無法使用",
|
||||
"uploadToCloud": "上傳至雲端"
|
||||
@@ -936,8 +943,8 @@
|
||||
"security": {
|
||||
"about": "您可以添加$t(lockRoomPassword)至您的會議,與會者在加入會議前必須先輸入$t(lockRoomPassword)。",
|
||||
"aboutReadOnly": "主持人可以添加$t(lockRoomPassword)至會議,與會者在加入會議前必須先輸入$t(lockRoomPassword)。",
|
||||
"header": "安全性選項",
|
||||
"insecureRoomNameWarning": "會議室名稱過於簡單,任何人都可以加入此會議,請考慮使用安全性選項以保護您的會議安全。"
|
||||
"insecureRoomNameWarning": "會議室名稱過於簡單,任何人都可以加入此會議,請考慮使用安全性選項以保護您的會議安全。",
|
||||
"title": "安全性選項"
|
||||
},
|
||||
"settings": {
|
||||
"buttonLabel": "設定",
|
||||
@@ -953,7 +960,7 @@
|
||||
"desktopShareWarning": "您必須重新啟動桌面畫面分享以套用新的設定。",
|
||||
"devices": "裝置",
|
||||
"followMe": "全部人跟隨我",
|
||||
"framesPerSecond": "影格率",
|
||||
"framesPerSecond": "fps",
|
||||
"incomingMessage": "新訊息",
|
||||
"language": "語言",
|
||||
"loggedIn": "以{{name}}登入",
|
||||
@@ -1004,6 +1011,7 @@
|
||||
"profileSection": "簡介",
|
||||
"serverURL": "伺服器網址",
|
||||
"showAdvanced": "顯示進階設定",
|
||||
"startCarModeInLowBandwidthMode": "同時啟用駕駛模式與低頻寬模式",
|
||||
"startWithAudioMuted": "啟動並靜音",
|
||||
"startWithVideoMuted": "啟動並關閉影像",
|
||||
"terms": "條款",
|
||||
@@ -1042,7 +1050,7 @@
|
||||
"title": "由於電腦進入休眠,您的視訊通話已經中斷。"
|
||||
},
|
||||
"termsView": {
|
||||
"header": "條款"
|
||||
"title": "條款"
|
||||
},
|
||||
"toggleTopPanelLabel": "啟用/停用頂部面板",
|
||||
"toolbar": {
|
||||
@@ -1218,6 +1226,8 @@
|
||||
"labelToolTip": "此會議正在轉錄",
|
||||
"off": "轉錄已停用",
|
||||
"pending": "準備轉錄會議……",
|
||||
"sourceLanguageDesc": "會議語言當前設定為<b>{{sourceLanguage}}</b><br/>您可以在這裡",
|
||||
"sourceLanguageHere": "修改",
|
||||
"start": "開始顯示字幕",
|
||||
"stop": "停用顯示字幕",
|
||||
"subtitles": "字幕",
|
||||
@@ -1338,10 +1348,10 @@
|
||||
"join": "建立/加入",
|
||||
"logo": {
|
||||
"calendar": "行事曆圖示",
|
||||
"desktopPreviewThumbnail": "桌面畫面分享縮圖",
|
||||
"googleLogo": "Google 商標",
|
||||
"logoDeepLinking": "Jitsi meet 商標",
|
||||
"microsoftLogo": "Microsoft 商標",
|
||||
"desktopPreviewThumbnail": "桌面預覽縮圖",
|
||||
"googleLogo": "Google圖示",
|
||||
"logoDeepLinking": "Jitsi Meet圖示",
|
||||
"microsoftLogo": "Microsoft圖示",
|
||||
"policyLogo": "政策圖示"
|
||||
},
|
||||
"mobileDownLoadLinkAndroid": "下載 Android 版本的手機應用程式",
|
||||
@@ -1353,7 +1363,7 @@
|
||||
"recentListDelete": "刪除",
|
||||
"recentListEmpty": "目前最近使用是空白的,與您的團隊成員聊天,即會在此處找到最近使用過的會議。",
|
||||
"reducedUIText": "歡迎使用{{app}}!",
|
||||
"roomNameAllowedChars": "會議室名稱不應包含以下字元:? & : '「 % #",
|
||||
"roomNameAllowedChars": "會議室名稱不應包含以下字元:? & : ' \" % #",
|
||||
"roomname": "輸入會議室名稱",
|
||||
"roomnameHint": "請輸入您想加入的會議室名稱或網址,您可以用個名稱來建立會議室,只要其他人輸入相同的名稱就能加入會議室喔。",
|
||||
"sendFeedback": "傳送回饋",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/* global APP, JitsiMeetJS */
|
||||
|
||||
import {
|
||||
getAudioOutputDeviceId,
|
||||
notifyCameraError,
|
||||
notifyMicError
|
||||
} from '../../react/features/base/devices';
|
||||
} from '../../react/features/base/devices/actions.web';
|
||||
import {
|
||||
getAudioOutputDeviceId
|
||||
} from '../../react/features/base/devices/functions.web';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId,
|
||||
|
||||
24
package-lock.json
generated
24
package-lock.json
generated
@@ -31,7 +31,7 @@
|
||||
"@jitsi/js-utils": "2.0.4",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.3.0",
|
||||
"@jitsi/rtcstats": "9.4.0",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.10.2",
|
||||
@@ -74,7 +74,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1528.0.0+23644901/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1530.0.0+f2af389e/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -3780,9 +3780,9 @@
|
||||
"integrity": "sha512-JujivPbOUvdRYa2xqByHYKfKGNGa7ZPyNLaNuh8hEp9XsiNfjaJAHdboq6M1VY9TP+765nyxC0LjpAw1VkikOQ=="
|
||||
},
|
||||
"node_modules/@jitsi/rtcstats": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.3.0.tgz",
|
||||
"integrity": "sha512-aipr1Tt/vfouMmgISCSu64Np3pD1u51y/2SztYNDt5bd6f79Qrieceu0JFqZWxC9KQRsamoJL7Mb9qxo2KkULg==",
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.4.0.tgz",
|
||||
"integrity": "sha512-NZXgJUAX6Mvexes7zAnHOiU+F2O7NIdyRUcir1YUD85mvBV0DMjuwUnIL5XaYkCzDuE3rTcV2FX9B80BTRlnLQ==",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "^2.0.0",
|
||||
"sdp": "^3.0.3",
|
||||
@@ -13497,8 +13497,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1528.0.0+23644901/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-pE11TMMGVAkuFncsh2Z7SaYt6EetVv8AlU/ZfT5+W5vKqw1R0aKrTm2Lvz2cGtzJ4ooCkghsZwQYHQo6MuuZ9Q==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1530.0.0+f2af389e/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-gqsNJblQ5wgYZJzhbkI7iBbg5Ddn9/EyfiCOwYdB9lHe07yDYco7H/vUH/TxTFTurEHtyV8LKb5KMEhJIKVhpw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
@@ -23208,9 +23208,9 @@
|
||||
"integrity": "sha512-JujivPbOUvdRYa2xqByHYKfKGNGa7ZPyNLaNuh8hEp9XsiNfjaJAHdboq6M1VY9TP+765nyxC0LjpAw1VkikOQ=="
|
||||
},
|
||||
"@jitsi/rtcstats": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.3.0.tgz",
|
||||
"integrity": "sha512-aipr1Tt/vfouMmgISCSu64Np3pD1u51y/2SztYNDt5bd6f79Qrieceu0JFqZWxC9KQRsamoJL7Mb9qxo2KkULg==",
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.4.0.tgz",
|
||||
"integrity": "sha512-NZXgJUAX6Mvexes7zAnHOiU+F2O7NIdyRUcir1YUD85mvBV0DMjuwUnIL5XaYkCzDuE3rTcV2FX9B80BTRlnLQ==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "^2.0.0",
|
||||
"sdp": "^3.0.3",
|
||||
@@ -30510,8 +30510,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1528.0.0+23644901/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-pE11TMMGVAkuFncsh2Z7SaYt6EetVv8AlU/ZfT5+W5vKqw1R0aKrTm2Lvz2cGtzJ4ooCkghsZwQYHQo6MuuZ9Q==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1530.0.0+f2af389e/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-gqsNJblQ5wgYZJzhbkI7iBbg5Ddn9/EyfiCOwYdB9lHe07yDYco7H/vUH/TxTFTurEHtyV8LKb5KMEhJIKVhpw==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"@jitsi/js-utils": "2.0.4",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.3.0",
|
||||
"@jitsi/rtcstats": "9.4.0",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.10.2",
|
||||
@@ -79,7 +79,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1528.0.0+23644901/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1530.0.0+f2af389e/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -199,7 +199,7 @@
|
||||
"tsc:web": "tsc --noEmit --project tsconfig.web.json",
|
||||
"tsc:native": "tsc --noEmit --project tsconfig.native.json",
|
||||
"tsc:ci": "npm run tsc:web && npm run tsc:native",
|
||||
"lint:ci": "eslint --ext .js,.ts,.tsx --max-warnings 0 . && npm run tsc:web",
|
||||
"lint:ci": "eslint --ext .js,.ts,.tsx --max-warnings 0 . && npm run tsc:ci",
|
||||
"lang-sort": "./resources/lang-sort.sh",
|
||||
"lint-fix": "eslint --ext .js,.ts,.tsx --max-warnings 0 --fix .",
|
||||
"postinstall": "patch-package --error-on-fail && jetify",
|
||||
|
||||
@@ -3,8 +3,8 @@ import { API_ID } from '../../../modules/API/constants';
|
||||
import { getName as getAppName } from '../app/functions';
|
||||
import { IStore } from '../app/types';
|
||||
import { getAnalyticsRoomName } from '../base/conference/functions';
|
||||
import checkChromeExtensionsInstalled from '../base/environment/checkChromeExtensionsInstalled';
|
||||
import {
|
||||
checkChromeExtensionsInstalled,
|
||||
isMobileBrowser
|
||||
} from '../base/environment/utils';
|
||||
import JitsiMeetJS, {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import logger from '../logger';
|
||||
|
||||
import AbstractHandler, { IEvent } from './AbstractHandler';
|
||||
@@ -63,7 +65,7 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
* @param {Object} userProps - The user portperties.
|
||||
* @returns {void}
|
||||
*/
|
||||
setUserProperties(userProps: Object) {
|
||||
setUserProperties(userProps: any) {
|
||||
if (this._enabled) {
|
||||
amplitude.getInstance().setUserProperties(userProps);
|
||||
}
|
||||
@@ -82,6 +84,7 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
amplitude.getInstance().logEvent(this._extractName(event) ?? '', event);
|
||||
}
|
||||
|
||||
@@ -100,7 +103,9 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
|
||||
return {
|
||||
sessionId: amplitude.getInstance().getSessionId(),
|
||||
// @ts-ignore
|
||||
deviceId: amplitude.getInstance().options.deviceId,
|
||||
// @ts-ignore
|
||||
userId: amplitude.getInstance().options.userId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,14 +162,10 @@ export function appNavigate(uri?: string) {
|
||||
* If we have a close page enabled, redirect to it without
|
||||
* showing any other dialog.
|
||||
*
|
||||
* @param {Object} _options - Used to decide which particular close page to show
|
||||
* or if close page is disabled, whether we should show the thankyou dialog.
|
||||
* @param {boolean} options.showThankYou - Whether we should
|
||||
* show thank you dialog.
|
||||
* @param {boolean} options.feedbackSubmitted - Whether feedback was submitted.
|
||||
* @param {Object} options - Ignored.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function maybeRedirectToWelcomePage(_options: { feedbackSubmitted?: boolean; showThankYou?: boolean; } = {}) {
|
||||
export function maybeRedirectToWelcomePage(options: any) { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
// Dummy.
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { IAudioOnlyState } from '../base/audio-only/reducer';
|
||||
import { IConferenceState } from '../base/conference/reducer';
|
||||
import { IConfigState } from '../base/config/reducer';
|
||||
import { IConnectionState } from '../base/connection/reducer';
|
||||
import { IDevicesState } from '../base/devices/reducer';
|
||||
import { IDevicesState } from '../base/devices/types';
|
||||
import { IDialogState } from '../base/dialog/reducer';
|
||||
import { IFlagsState } from '../base/flags/reducer';
|
||||
import { IJwtState } from '../base/jwt/reducer';
|
||||
|
||||
@@ -52,6 +52,7 @@ export interface IJitsiConference {
|
||||
getLocalTracks: Function;
|
||||
getMeetingUniqueId: Function;
|
||||
getParticipantById: Function;
|
||||
getParticipants: Function;
|
||||
grantOwner: Function;
|
||||
isAVModerationSupported: Function;
|
||||
isCallstatsEnabled: Function;
|
||||
@@ -68,6 +69,7 @@ export interface IJitsiConference {
|
||||
on: Function;
|
||||
removeTrack: Function;
|
||||
replaceTrack: Function;
|
||||
room: IJitsiConferenceRoom;
|
||||
sendCommand: Function;
|
||||
sendCommandOnce: Function;
|
||||
sendEndpointMessage: Function;
|
||||
@@ -111,6 +113,11 @@ export interface IConferenceState {
|
||||
subject?: string;
|
||||
}
|
||||
|
||||
export interface IJitsiConferenceRoom {
|
||||
myroomjid: string;
|
||||
roomjid: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for actions that contain the conference object, so that it can be
|
||||
* stored for use by other action creators.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
import { getUserSelectedOutputDeviceId } from '../settings/functions.any';
|
||||
import { getUserSelectedOutputDeviceId } from '../settings/functions.web';
|
||||
|
||||
import {
|
||||
ADD_PENDING_DEVICE_REQUEST,
|
||||
19
react/features/base/devices/functions.any.ts
Normal file
19
react/features/base/devices/functions.any.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { IReduxState } from '../../app/types';
|
||||
|
||||
/**
|
||||
* Returns true if there are devices of a specific type or on native platform.
|
||||
*
|
||||
* @param {Object} state - The state of the application.
|
||||
* @param {string} type - The type of device: VideoOutput | audioOutput | audioInput.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasAvailableDevices(state: IReduxState, type: string) {
|
||||
if (state['features/base/devices'] === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const availableDevices = state['features/base/devices'].availableDevices;
|
||||
|
||||
return Number(availableDevices[type as keyof typeof availableDevices]?.length) > 0;
|
||||
}
|
||||
1
react/features/base/devices/functions.native.ts
Normal file
1
react/features/base/devices/functions.native.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './functions.any';
|
||||
@@ -5,10 +5,9 @@ import { ISettingsState } from '../settings/reducer';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
|
||||
import logger from './logger';
|
||||
import { IDevicesState } from './reducer';
|
||||
import { IDevicesState } from './types';
|
||||
|
||||
|
||||
declare const APP: any;
|
||||
export * from './functions.any';
|
||||
|
||||
const webrtcKindToJitsiKindTranslator = {
|
||||
audioinput: 'audioInput',
|
||||
@@ -240,24 +239,6 @@ export function getVideoDeviceIds(state: IReduxState) {
|
||||
return state['features/base/devices'].availableDevices.videoInput?.map(({ deviceId }) => deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are devices of a specific type or on native platform.
|
||||
*
|
||||
* @param {Object} state - The state of the application.
|
||||
* @param {string} type - The type of device: VideoOutput | audioOutput | audioInput.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasAvailableDevices(state: IReduxState, type: string) {
|
||||
if (state['features/base/devices'] === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const availableDevices = state['features/base/devices'].availableDevices;
|
||||
|
||||
return Number(availableDevices[type as keyof typeof availableDevices]?.length) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set device id of the audio output device which is currently in use.
|
||||
* Empty string stands for default device.
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
setAudioOutputDeviceId
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { IDevicesState } from './reducer';
|
||||
import { IDevicesState } from './types';
|
||||
|
||||
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
|
||||
microphone: {
|
||||
|
||||
@@ -8,8 +8,9 @@ import {
|
||||
SET_VIDEO_INPUT_DEVICE,
|
||||
UPDATE_DEVICE_LIST
|
||||
} from './actionTypes';
|
||||
import { groupDevicesByKind } from './functions';
|
||||
import { groupDevicesByKind } from './functions.web';
|
||||
import logger from './logger';
|
||||
import { IDevicesState } from './types';
|
||||
|
||||
|
||||
const DEFAULT_STATE: IDevicesState = {
|
||||
@@ -25,19 +26,6 @@ const DEFAULT_STATE: IDevicesState = {
|
||||
}
|
||||
};
|
||||
|
||||
export interface IDevicesState {
|
||||
availableDevices: {
|
||||
audioInput?: MediaDeviceInfo[];
|
||||
audioOutput?: MediaDeviceInfo[];
|
||||
videoInput?: MediaDeviceInfo[];
|
||||
};
|
||||
pendingRequests: Object[];
|
||||
permissions: {
|
||||
audio: boolean;
|
||||
video: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for actions which changes the state of known and used devices.
|
||||
*
|
||||
17
react/features/base/devices/types.ts
Normal file
17
react/features/base/devices/types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
export interface IDevicesState {
|
||||
availableDevices: {
|
||||
// @ts-ignore
|
||||
audioInput?: MediaDeviceInfo[];
|
||||
// @ts-ignore
|
||||
audioOutput?: MediaDeviceInfo[];
|
||||
// @ts-ignore
|
||||
videoInput?: MediaDeviceInfo[];
|
||||
};
|
||||
pendingRequests: any[];
|
||||
permissions: {
|
||||
audio: boolean;
|
||||
video: boolean;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
/**
|
||||
* Checks whether the chrome extensions defined in the config file are installed or not.
|
||||
*
|
||||
* @param {Object} config - Objects containing info about the configured extensions.
|
||||
*
|
||||
* @returns {Promise[]}
|
||||
*/
|
||||
export default function checkChromeExtensionsInstalled(config: any = {}) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Checks whether the chrome extensions defined in the config file are installed or not.
|
||||
*
|
||||
* @param {Object} config - Objects containing info about the configured extensions.
|
||||
*
|
||||
* @returns {Promise[]}
|
||||
*/
|
||||
export default function checkChromeExtensionsInstalled(config: any = {}) {
|
||||
const isExtensionInstalled = (info: any) => new Promise(resolve => {
|
||||
const img = new Image();
|
||||
|
||||
img.src = `chrome-extension://${info.id}/${info.path}`;
|
||||
img.setAttribute('aria-hidden', 'true');
|
||||
img.onload = function() {
|
||||
resolve(true);
|
||||
};
|
||||
img.onerror = function() {
|
||||
resolve(false);
|
||||
};
|
||||
});
|
||||
const extensionInstalledFunction = (info: any) => isExtensionInstalled(info);
|
||||
|
||||
return Promise.all(
|
||||
(config.chromeExtensionsInfo || []).map((info: any) => extensionInstalledFunction(info))
|
||||
);
|
||||
}
|
||||
@@ -18,30 +18,3 @@ export function isMobileBrowser() {
|
||||
export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the chrome extensions defined in the config file are installed or not.
|
||||
*
|
||||
* @param {Object} config - Objects containing info about the configured extensions.
|
||||
*
|
||||
* @returns {Promise[]}
|
||||
*/
|
||||
export function checkChromeExtensionsInstalled(config: any = {}) {
|
||||
const isExtensionInstalled = (info: any) => new Promise(resolve => {
|
||||
const img = new Image();
|
||||
|
||||
img.src = `chrome-extension://${info.id}/${info.path}`;
|
||||
img.setAttribute('aria-hidden', 'true');
|
||||
img.onload = function() {
|
||||
resolve(true);
|
||||
};
|
||||
img.onerror = function() {
|
||||
resolve(false);
|
||||
};
|
||||
});
|
||||
const extensionInstalledFunction = (info: any) => isExtensionInstalled(info);
|
||||
|
||||
return Promise.all(
|
||||
(config.chromeExtensionsInfo || []).map((info: any) => extensionInstalledFunction(info))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { MEET_FEATURES } from './constants';
|
||||
* @returns {string} The JSON Web Token (JWT), if any, defined by the specified
|
||||
* {@code url}; otherwise, {@code undefined}.
|
||||
*/
|
||||
export function parseJWTFromURLParams(url: URL | Location = window.location) {
|
||||
export function parseJWTFromURLParams(url: URL | typeof window.location = window.location) {
|
||||
// @ts-ignore
|
||||
return parseURLParams(url, true, 'search').jwt;
|
||||
}
|
||||
|
||||
@@ -51,5 +51,9 @@ export interface ILocalParticipant extends IParticipant {
|
||||
}
|
||||
|
||||
export interface IJitsiParticipant {
|
||||
getDisplayName: () => string;
|
||||
getId: () => string;
|
||||
getJid: () => string;
|
||||
getRole: () => string;
|
||||
isHidden: () => boolean;
|
||||
}
|
||||
|
||||
@@ -103,160 +103,6 @@ export function getServerURL(stateful: IStateful) {
|
||||
return state['features/base/settings'].serverURL || DEFAULT_SERVER_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches known devices for a matching deviceId and fall back to matching on
|
||||
* label. Returns the stored preferred cameraDeviceId if a match is not found.
|
||||
*
|
||||
* @param {Object|Function} stateful - The redux state object or
|
||||
* {@code getState} function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getUserSelectedCameraDeviceId(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
userSelectedCameraDeviceId,
|
||||
userSelectedCameraDeviceLabel
|
||||
} = state['features/base/settings'];
|
||||
const { videoInput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return _getUserSelectedDeviceId({
|
||||
availableDevices: videoInput,
|
||||
|
||||
// Operating systems may append " #{number}" somewhere in the label so
|
||||
// find and strip that bit.
|
||||
matchRegex: /\s#\d*(?!.*\s#\d*)/,
|
||||
userSelectedDeviceId: userSelectedCameraDeviceId,
|
||||
userSelectedDeviceLabel: userSelectedCameraDeviceLabel,
|
||||
replacement: ''
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches known devices for a matching deviceId and fall back to matching on
|
||||
* label. Returns the stored preferred micDeviceId if a match is not found.
|
||||
*
|
||||
* @param {Object|Function} stateful - The redux state object or
|
||||
* {@code getState} function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getUserSelectedMicDeviceId(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
userSelectedMicDeviceId,
|
||||
userSelectedMicDeviceLabel
|
||||
} = state['features/base/settings'];
|
||||
const { audioInput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return _getUserSelectedDeviceId({
|
||||
availableDevices: audioInput,
|
||||
|
||||
// Operating systems may append " ({number}-" somewhere in the label so
|
||||
// find and strip that bit.
|
||||
matchRegex: /\s\(\d*-\s(?!.*\s\(\d*-\s)/,
|
||||
userSelectedDeviceId: userSelectedMicDeviceId,
|
||||
userSelectedDeviceLabel: userSelectedMicDeviceLabel,
|
||||
replacement: ' ('
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches known devices for a matching deviceId and fall back to matching on
|
||||
* label. Returns the stored preferred audioOutputDeviceId if a match is not found.
|
||||
*
|
||||
* @param {Object|Function} stateful - The redux state object or
|
||||
* {@code getState} function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getUserSelectedOutputDeviceId(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
userSelectedAudioOutputDeviceId,
|
||||
userSelectedAudioOutputDeviceLabel
|
||||
} = state['features/base/settings'];
|
||||
const { audioOutput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return _getUserSelectedDeviceId({
|
||||
availableDevices: audioOutput,
|
||||
matchRegex: undefined,
|
||||
userSelectedDeviceId: userSelectedAudioOutputDeviceId,
|
||||
userSelectedDeviceLabel: userSelectedAudioOutputDeviceLabel,
|
||||
replacement: undefined
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to abstract the logic for choosing which device ID to
|
||||
* use. Falls back to fuzzy matching on label if a device ID match is not found.
|
||||
*
|
||||
* @param {Object} options - The arguments used to match find the preferred
|
||||
* device ID from available devices.
|
||||
* @param {Array<string>} options.availableDevices - The array of currently
|
||||
* available devices to match against.
|
||||
* @param {Object} options.matchRegex - The regex to use to find strings
|
||||
* appended to the label by the operating system. The matches will be replaced
|
||||
* with options.replacement, with the intent of matching the same device that
|
||||
* might have a modified label.
|
||||
* @param {string} options.userSelectedDeviceId - The device ID the participant
|
||||
* prefers to use.
|
||||
* @param {string} options.userSelectedDeviceLabel - The label associated with the
|
||||
* device ID the participant prefers to use.
|
||||
* @param {string} options.replacement - The string to use with
|
||||
* options.matchRegex to remove identifies added to the label by the operating
|
||||
* system.
|
||||
* @private
|
||||
* @returns {string} The preferred device ID to use for media.
|
||||
*/
|
||||
function _getUserSelectedDeviceId(options: {
|
||||
availableDevices: MediaDeviceInfo[] | undefined;
|
||||
matchRegex?: RegExp;
|
||||
replacement?: string;
|
||||
userSelectedDeviceId?: string;
|
||||
userSelectedDeviceLabel?: string;
|
||||
}) {
|
||||
const {
|
||||
availableDevices,
|
||||
matchRegex = '',
|
||||
userSelectedDeviceId,
|
||||
userSelectedDeviceLabel,
|
||||
replacement = ''
|
||||
} = options;
|
||||
|
||||
// If there is no label at all, there is no need to fall back to checking
|
||||
// the label for a fuzzy match.
|
||||
if (!userSelectedDeviceLabel || !userSelectedDeviceId) {
|
||||
return userSelectedDeviceId;
|
||||
}
|
||||
|
||||
const foundMatchingBasedonDeviceId = availableDevices?.find(
|
||||
candidate => candidate.deviceId === userSelectedDeviceId);
|
||||
|
||||
// Prioritize matching the deviceId
|
||||
if (foundMatchingBasedonDeviceId) {
|
||||
return userSelectedDeviceId;
|
||||
}
|
||||
|
||||
const strippedDeviceLabel
|
||||
= matchRegex ? userSelectedDeviceLabel.replace(matchRegex, replacement)
|
||||
: userSelectedDeviceLabel;
|
||||
const foundMatchBasedOnLabel = availableDevices?.find(candidate => {
|
||||
const { label } = candidate;
|
||||
|
||||
if (!label) {
|
||||
return false;
|
||||
} else if (strippedDeviceLabel === label) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const strippedCandidateLabel
|
||||
= label.replace(matchRegex, replacement);
|
||||
|
||||
return strippedDeviceLabel === strippedCandidateLabel;
|
||||
});
|
||||
|
||||
return foundMatchBasedOnLabel
|
||||
? foundMatchBasedOnLabel.deviceId : userSelectedDeviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we hide the helper dialog when a user tries to do audio only screen sharing.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { IStateful } from '../app/types';
|
||||
import { toState } from '../redux/functions';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
@@ -58,3 +59,157 @@ function getDeviceIdByType(state: IReduxState, isType: string) {
|
||||
export function getDisplayName(state: IReduxState): string {
|
||||
return state['features/base/settings'].displayName || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches known devices for a matching deviceId and fall back to matching on
|
||||
* label. Returns the stored preferred cameraDeviceId if a match is not found.
|
||||
*
|
||||
* @param {Object|Function} stateful - The redux state object or
|
||||
* {@code getState} function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getUserSelectedCameraDeviceId(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
userSelectedCameraDeviceId,
|
||||
userSelectedCameraDeviceLabel
|
||||
} = state['features/base/settings'];
|
||||
const { videoInput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return _getUserSelectedDeviceId({
|
||||
availableDevices: videoInput,
|
||||
|
||||
// Operating systems may append " #{number}" somewhere in the label so
|
||||
// find and strip that bit.
|
||||
matchRegex: /\s#\d*(?!.*\s#\d*)/,
|
||||
userSelectedDeviceId: userSelectedCameraDeviceId,
|
||||
userSelectedDeviceLabel: userSelectedCameraDeviceLabel,
|
||||
replacement: ''
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches known devices for a matching deviceId and fall back to matching on
|
||||
* label. Returns the stored preferred micDeviceId if a match is not found.
|
||||
*
|
||||
* @param {Object|Function} stateful - The redux state object or
|
||||
* {@code getState} function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getUserSelectedMicDeviceId(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
userSelectedMicDeviceId,
|
||||
userSelectedMicDeviceLabel
|
||||
} = state['features/base/settings'];
|
||||
const { audioInput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return _getUserSelectedDeviceId({
|
||||
availableDevices: audioInput,
|
||||
|
||||
// Operating systems may append " ({number}-" somewhere in the label so
|
||||
// find and strip that bit.
|
||||
matchRegex: /\s\(\d*-\s(?!.*\s\(\d*-\s)/,
|
||||
userSelectedDeviceId: userSelectedMicDeviceId,
|
||||
userSelectedDeviceLabel: userSelectedMicDeviceLabel,
|
||||
replacement: ' ('
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches known devices for a matching deviceId and fall back to matching on
|
||||
* label. Returns the stored preferred audioOutputDeviceId if a match is not found.
|
||||
*
|
||||
* @param {Object|Function} stateful - The redux state object or
|
||||
* {@code getState} function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getUserSelectedOutputDeviceId(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
userSelectedAudioOutputDeviceId,
|
||||
userSelectedAudioOutputDeviceLabel
|
||||
} = state['features/base/settings'];
|
||||
const { audioOutput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return _getUserSelectedDeviceId({
|
||||
availableDevices: audioOutput,
|
||||
matchRegex: undefined,
|
||||
userSelectedDeviceId: userSelectedAudioOutputDeviceId,
|
||||
userSelectedDeviceLabel: userSelectedAudioOutputDeviceLabel,
|
||||
replacement: undefined
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to abstract the logic for choosing which device ID to
|
||||
* use. Falls back to fuzzy matching on label if a device ID match is not found.
|
||||
*
|
||||
* @param {Object} options - The arguments used to match find the preferred
|
||||
* device ID from available devices.
|
||||
* @param {Array<string>} options.availableDevices - The array of currently
|
||||
* available devices to match against.
|
||||
* @param {Object} options.matchRegex - The regex to use to find strings
|
||||
* appended to the label by the operating system. The matches will be replaced
|
||||
* with options.replacement, with the intent of matching the same device that
|
||||
* might have a modified label.
|
||||
* @param {string} options.userSelectedDeviceId - The device ID the participant
|
||||
* prefers to use.
|
||||
* @param {string} options.userSelectedDeviceLabel - The label associated with the
|
||||
* device ID the participant prefers to use.
|
||||
* @param {string} options.replacement - The string to use with
|
||||
* options.matchRegex to remove identifies added to the label by the operating
|
||||
* system.
|
||||
* @private
|
||||
* @returns {string} The preferred device ID to use for media.
|
||||
*/
|
||||
function _getUserSelectedDeviceId(options: {
|
||||
availableDevices: MediaDeviceInfo[] | undefined;
|
||||
matchRegex?: RegExp;
|
||||
replacement?: string;
|
||||
userSelectedDeviceId?: string;
|
||||
userSelectedDeviceLabel?: string;
|
||||
}) {
|
||||
const {
|
||||
availableDevices,
|
||||
matchRegex = '',
|
||||
userSelectedDeviceId,
|
||||
userSelectedDeviceLabel,
|
||||
replacement = ''
|
||||
} = options;
|
||||
|
||||
// If there is no label at all, there is no need to fall back to checking
|
||||
// the label for a fuzzy match.
|
||||
if (!userSelectedDeviceLabel || !userSelectedDeviceId) {
|
||||
return userSelectedDeviceId;
|
||||
}
|
||||
|
||||
const foundMatchingBasedonDeviceId = availableDevices?.find(
|
||||
candidate => candidate.deviceId === userSelectedDeviceId);
|
||||
|
||||
// Prioritize matching the deviceId
|
||||
if (foundMatchingBasedonDeviceId) {
|
||||
return userSelectedDeviceId;
|
||||
}
|
||||
|
||||
const strippedDeviceLabel
|
||||
= matchRegex ? userSelectedDeviceLabel.replace(matchRegex, replacement)
|
||||
: userSelectedDeviceLabel;
|
||||
const foundMatchBasedOnLabel = availableDevices?.find(candidate => {
|
||||
const { label } = candidate;
|
||||
|
||||
if (!label) {
|
||||
return false;
|
||||
} else if (strippedDeviceLabel === label) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const strippedCandidateLabel
|
||||
= label.replace(matchRegex, replacement);
|
||||
|
||||
return strippedDeviceLabel === strippedCandidateLabel;
|
||||
});
|
||||
|
||||
return foundMatchBasedOnLabel
|
||||
? foundMatchBasedOnLabel.deviceId : userSelectedDeviceId;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
// @ts-ignore
|
||||
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
|
||||
@@ -11,6 +12,8 @@ import { getLocalVideoTrack, isLocalVideoTrackDesktop } from './functions';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
/**
|
||||
* Signals that the local participant is ending screensharing or beginning the screensharing flow.
|
||||
*
|
||||
@@ -37,6 +40,8 @@ export function toggleScreensharing(enabled: boolean, _ignore1?: boolean, _ignor
|
||||
};
|
||||
}
|
||||
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
|
||||
/**
|
||||
* Creates desktop track and replaces the local one.
|
||||
*
|
||||
|
||||
@@ -1,29 +1,18 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { IStateful } from '../app/types';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import {
|
||||
getMultipleVideoSendingSupportFeatureFlag,
|
||||
getMultipleVideoSupportFeatureFlag
|
||||
} from '../config/functions.any';
|
||||
import { isMobileBrowser } from '../environment/utils';
|
||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted } from '../media/actions';
|
||||
import { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
|
||||
import {
|
||||
getVirtualScreenshareParticipantOwnerId,
|
||||
isScreenShareParticipant
|
||||
} from '../participants/functions';
|
||||
import { IParticipant } from '../participants/types';
|
||||
import { toState } from '../redux/functions';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId
|
||||
} from '../settings/functions.any';
|
||||
|
||||
// @ts-ignore
|
||||
import loadEffects from './loadEffects';
|
||||
import logger from './logger';
|
||||
import { ITrack } from './reducer';
|
||||
import { ITrackOptions } from './types';
|
||||
import { ITrack } from './types';
|
||||
|
||||
/**
|
||||
* Returns root tracks state.
|
||||
@@ -79,223 +68,6 @@ export function isParticipantVideoMuted(participant: IParticipant, state: IRedux
|
||||
return isParticipantMediaMuted(participant, MEDIA_TYPE.VIDEO, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a local video track for presenter. The constraints are computed based
|
||||
* on the height of the desktop that is being shared.
|
||||
*
|
||||
* @param {Object} options - The options with which the local presenter track
|
||||
* is to be created.
|
||||
* @param {string|null} [options.cameraDeviceId] - Camera device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {number} desktopHeight - The height of the desktop that is being
|
||||
* shared.
|
||||
* @returns {Promise<JitsiLocalTrack>}
|
||||
*/
|
||||
export async function createLocalPresenterTrack(options: ITrackOptions, desktopHeight: number) {
|
||||
const { cameraDeviceId } = options;
|
||||
|
||||
// compute the constraints of the camera track based on the resolution
|
||||
// of the desktop screen that is being shared.
|
||||
const cameraHeights = [ 180, 270, 360, 540, 720 ];
|
||||
const proportion = 5;
|
||||
const result = cameraHeights.find(
|
||||
height => (desktopHeight / proportion) < height);
|
||||
const constraints = {
|
||||
video: {
|
||||
aspectRatio: 4 / 3,
|
||||
height: {
|
||||
ideal: result
|
||||
}
|
||||
}
|
||||
};
|
||||
const [ videoTrack ] = await JitsiMeetJS.createLocalTracks(
|
||||
{
|
||||
cameraDeviceId,
|
||||
constraints,
|
||||
devices: [ 'video' ]
|
||||
});
|
||||
|
||||
videoTrack.type = MEDIA_TYPE.PRESENTER;
|
||||
|
||||
return videoTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create local tracks of specific types.
|
||||
*
|
||||
* @param {Object} options - The options with which the local tracks are to be
|
||||
* created.
|
||||
* @param {string|null} [options.cameraDeviceId] - Camera device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {string[]} options.devices - Required track types such as 'audio'
|
||||
* and/or 'video'.
|
||||
* @param {string|null} [options.micDeviceId] - Microphone device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
|
||||
* @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* should check for a {@code getUserMedia} permission prompt and fire a
|
||||
* corresponding event.
|
||||
* @param {IStore} store - The redux store in the context of which the function
|
||||
* is to execute and from which state such as {@code config} is to be retrieved.
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
*/
|
||||
export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore) {
|
||||
let { cameraDeviceId, micDeviceId } = options;
|
||||
const {
|
||||
desktopSharingSourceDevice,
|
||||
desktopSharingSources,
|
||||
firePermissionPromptIsShownEvent,
|
||||
timeout
|
||||
} = options;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
// TODO The app's settings should go in the redux store and then the
|
||||
// reliance on the global variable APP will go away.
|
||||
if (!store) {
|
||||
store = APP.store; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
|
||||
cameraDeviceId = getUserSelectedCameraDeviceId(state);
|
||||
}
|
||||
if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
|
||||
micDeviceId = getUserSelectedMicDeviceId(state);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const state = store.getState();
|
||||
const {
|
||||
desktopSharingFrameRate,
|
||||
firefox_fake_device, // eslint-disable-line camelcase
|
||||
resolution
|
||||
} = state['features/base/config'];
|
||||
const constraints = options.constraints ?? state['features/base/config'].constraints;
|
||||
|
||||
return (
|
||||
loadEffects(store).then((effectsArray: Object[]) => {
|
||||
// Filter any undefined values returned by Promise.resolve().
|
||||
const effects = effectsArray.filter(effect => Boolean(effect));
|
||||
|
||||
return JitsiMeetJS.createLocalTracks(
|
||||
{
|
||||
cameraDeviceId,
|
||||
constraints,
|
||||
desktopSharingFrameRate,
|
||||
desktopSharingSourceDevice,
|
||||
desktopSharingSources,
|
||||
|
||||
// Copy array to avoid mutations inside library.
|
||||
devices: options.devices?.slice(0),
|
||||
effects,
|
||||
firefox_fake_device, // eslint-disable-line camelcase
|
||||
firePermissionPromptIsShownEvent,
|
||||
micDeviceId,
|
||||
resolution,
|
||||
timeout
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
logger.error('Failed to create local tracks', options.devices, err);
|
||||
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing a promise which resolves with the created tracks &
|
||||
* the errors resulting from that process.
|
||||
*
|
||||
* @returns {Promise<JitsiLocalTrack>}
|
||||
*
|
||||
* @todo Refactor to not use APP.
|
||||
*/
|
||||
export function createPrejoinTracks() {
|
||||
const errors: any = {};
|
||||
const initialDevices = [ 'audio' ];
|
||||
const requestedAudio = true;
|
||||
let requestedVideo = false;
|
||||
const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics even if the user joins the
|
||||
// conference muted. Previous implementation would only acquire the handle when the user first unmuted,
|
||||
// which would results in statistics ( such as "No audio input" or "Are you trying to speak?") being available
|
||||
// only after that point.
|
||||
if (startWithAudioMuted) {
|
||||
APP.store.dispatch(setAudioMuted(true));
|
||||
}
|
||||
|
||||
if (!startWithVideoMuted && !startAudioOnly) {
|
||||
initialDevices.push('video');
|
||||
requestedVideo = true;
|
||||
}
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
|
||||
if (!requestedAudio && !requestedVideo) {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}, APP.store)
|
||||
.catch((err: Error) => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
// Try audio only...
|
||||
errors.audioAndVideoError = err;
|
||||
|
||||
return (
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
return [];
|
||||
} else if (requestedVideo && !requestedAudio) {
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
}
|
||||
logger.error('Should never happen');
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
// Log this just in case...
|
||||
if (!requestedAudio) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
// Try video only...
|
||||
return requestedVideo
|
||||
? createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
: [];
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
// Log this just in case...
|
||||
if (!requestedVideo) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tryCreateLocalTracks,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns local audio track.
|
||||
*
|
||||
@@ -667,16 +439,3 @@ export function setTrackMuted(track: any, muted: boolean, state: IReduxState) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether toggle camera should be enabled or not.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux store or {@code getState} function.
|
||||
* @returns {boolean} - Whether toggle camera should be enabled.
|
||||
*/
|
||||
export function isToggleCameraEnabled(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const { videoInput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return isMobileBrowser() && Number(videoInput?.length) > 1;
|
||||
}
|
||||
45
react/features/base/tracks/functions.native.ts
Normal file
45
react/features/base/tracks/functions.native.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
|
||||
import { ITrackOptions } from './types';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
/**
|
||||
* Create local tracks of specific types.
|
||||
*
|
||||
* @param {Object} options - The options with which the local tracks are to be
|
||||
* created.
|
||||
* @param {string|null} [options.cameraDeviceId] - Camera device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {string[]} options.devices - Required track types such as 'audio'
|
||||
* and/or 'video'.
|
||||
* @param {string|null} [options.micDeviceId] - Microphone device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
|
||||
* @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* should check for a {@code getUserMedia} permission prompt and fire a
|
||||
* corresponding event.
|
||||
* @param {IStore} store - The redux store in the context of which the function
|
||||
* is to execute and from which state such as {@code config} is to be retrieved.
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
*/
|
||||
export function createLocalTracksF(options: ITrackOptions = {}, store: IStore) {
|
||||
const { cameraDeviceId, micDeviceId } = options;
|
||||
const state = store.getState();
|
||||
const {
|
||||
resolution
|
||||
} = state['features/base/config'];
|
||||
const constraints = options.constraints ?? state['features/base/config'].constraints;
|
||||
|
||||
return JitsiMeetJS.createLocalTracks(
|
||||
{
|
||||
cameraDeviceId,
|
||||
constraints,
|
||||
|
||||
// Copy array to avoid mutations inside library.
|
||||
devices: options.devices?.slice(0),
|
||||
micDeviceId,
|
||||
resolution
|
||||
});
|
||||
}
|
||||
242
react/features/base/tracks/functions.web.ts
Normal file
242
react/features/base/tracks/functions.web.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { IStateful } from '../app/types';
|
||||
import { isMobileBrowser } from '../environment/utils';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { setAudioMuted } from '../media/actions';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import { toState } from '../redux/functions';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId
|
||||
} from '../settings/functions.web';
|
||||
|
||||
// @ts-ignore
|
||||
import loadEffects from './loadEffects';
|
||||
import logger from './logger';
|
||||
import { ITrackOptions } from './types';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
/**
|
||||
* Create local tracks of specific types.
|
||||
*
|
||||
* @param {Object} options - The options with which the local tracks are to be
|
||||
* created.
|
||||
* @param {string|null} [options.cameraDeviceId] - Camera device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {string[]} options.devices - Required track types such as 'audio'
|
||||
* and/or 'video'.
|
||||
* @param {string|null} [options.micDeviceId] - Microphone device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
|
||||
* @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* should check for a {@code getUserMedia} permission prompt and fire a
|
||||
* corresponding event.
|
||||
* @param {IStore} store - The redux store in the context of which the function
|
||||
* is to execute and from which state such as {@code config} is to be retrieved.
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
*/
|
||||
export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore) {
|
||||
let { cameraDeviceId, micDeviceId } = options;
|
||||
const {
|
||||
desktopSharingSourceDevice,
|
||||
desktopSharingSources,
|
||||
firePermissionPromptIsShownEvent,
|
||||
timeout
|
||||
} = options;
|
||||
|
||||
// TODO The app's settings should go in the redux store and then the
|
||||
// reliance on the global variable APP will go away.
|
||||
store = store || APP.store; // eslint-disable-line no-param-reassign
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
|
||||
cameraDeviceId = getUserSelectedCameraDeviceId(state);
|
||||
}
|
||||
if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
|
||||
micDeviceId = getUserSelectedMicDeviceId(state);
|
||||
}
|
||||
|
||||
const {
|
||||
desktopSharingFrameRate,
|
||||
firefox_fake_device, // eslint-disable-line camelcase
|
||||
resolution
|
||||
} = state['features/base/config'];
|
||||
const constraints = options.constraints ?? state['features/base/config'].constraints;
|
||||
|
||||
return (
|
||||
loadEffects(store).then((effectsArray: Object[]) => {
|
||||
// Filter any undefined values returned by Promise.resolve().
|
||||
const effects = effectsArray.filter(effect => Boolean(effect));
|
||||
|
||||
return JitsiMeetJS.createLocalTracks(
|
||||
{
|
||||
cameraDeviceId,
|
||||
constraints,
|
||||
desktopSharingFrameRate,
|
||||
desktopSharingSourceDevice,
|
||||
desktopSharingSources,
|
||||
|
||||
// Copy array to avoid mutations inside library.
|
||||
devices: options.devices?.slice(0),
|
||||
effects,
|
||||
firefox_fake_device, // eslint-disable-line camelcase
|
||||
firePermissionPromptIsShownEvent,
|
||||
micDeviceId,
|
||||
resolution,
|
||||
timeout
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
logger.error('Failed to create local tracks', options.devices, err);
|
||||
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a local video track for presenter. The constraints are computed based
|
||||
* on the height of the desktop that is being shared.
|
||||
*
|
||||
* @param {Object} options - The options with which the local presenter track
|
||||
* is to be created.
|
||||
* @param {string|null} [options.cameraDeviceId] - Camera device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {number} desktopHeight - The height of the desktop that is being
|
||||
* shared.
|
||||
* @returns {Promise<JitsiLocalTrack>}
|
||||
*/
|
||||
export async function createLocalPresenterTrack(options: ITrackOptions, desktopHeight: number) {
|
||||
const { cameraDeviceId } = options;
|
||||
|
||||
// compute the constraints of the camera track based on the resolution
|
||||
// of the desktop screen that is being shared.
|
||||
const cameraHeights = [ 180, 270, 360, 540, 720 ];
|
||||
const proportion = 5;
|
||||
const result = cameraHeights.find(
|
||||
height => (desktopHeight / proportion) < height);
|
||||
const constraints = {
|
||||
video: {
|
||||
aspectRatio: 4 / 3,
|
||||
height: {
|
||||
ideal: result
|
||||
}
|
||||
}
|
||||
};
|
||||
const [ videoTrack ] = await JitsiMeetJS.createLocalTracks(
|
||||
{
|
||||
cameraDeviceId,
|
||||
constraints,
|
||||
devices: [ 'video' ]
|
||||
});
|
||||
|
||||
videoTrack.type = MEDIA_TYPE.PRESENTER;
|
||||
|
||||
return videoTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing a promise which resolves with the created tracks &
|
||||
* the errors resulting from that process.
|
||||
*
|
||||
* @returns {Promise<JitsiLocalTrack>}
|
||||
*
|
||||
* @todo Refactor to not use APP.
|
||||
*/
|
||||
export function createPrejoinTracks() {
|
||||
const errors: any = {};
|
||||
const initialDevices = [ 'audio' ];
|
||||
const requestedAudio = true;
|
||||
let requestedVideo = false;
|
||||
const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics even if the user joins the
|
||||
// conference muted. Previous implementation would only acquire the handle when the user first unmuted,
|
||||
// which would results in statistics ( such as "No audio input" or "Are you trying to speak?") being available
|
||||
// only after that point.
|
||||
if (startWithAudioMuted) {
|
||||
APP.store.dispatch(setAudioMuted(true));
|
||||
}
|
||||
|
||||
if (!startWithVideoMuted && !startAudioOnly) {
|
||||
initialDevices.push('video');
|
||||
requestedVideo = true;
|
||||
}
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
|
||||
if (!requestedAudio && !requestedVideo) {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}, APP.store)
|
||||
.catch((err: Error) => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
// Try audio only...
|
||||
errors.audioAndVideoError = err;
|
||||
|
||||
return (
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
return [];
|
||||
} else if (requestedVideo && !requestedAudio) {
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
}
|
||||
logger.error('Should never happen');
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
// Log this just in case...
|
||||
if (!requestedAudio) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
// Try video only...
|
||||
return requestedVideo
|
||||
? createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
: [];
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
// Log this just in case...
|
||||
if (!requestedVideo) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
tryCreateLocalTracks,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether toggle camera should be enabled or not.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux store or {@code getState} function.
|
||||
* @returns {boolean} - Whether toggle camera should be enabled.
|
||||
*/
|
||||
export function isToggleCameraEnabled(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const { videoInput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return isMobileBrowser() && Number(videoInput?.length) > 1;
|
||||
}
|
||||
@@ -2,11 +2,9 @@ import { batch } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||
import { hideNotification } from '../../notifications/actions';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
|
||||
import { getAvailableDevices } from '../devices/actions';
|
||||
import {
|
||||
SET_AUDIO_MUTED,
|
||||
SET_CAMERA_FACING_MODE,
|
||||
@@ -14,43 +12,31 @@ import {
|
||||
SET_VIDEO_MUTED,
|
||||
TOGGLE_CAMERA_FACING_MODE
|
||||
} from '../media/actionTypes';
|
||||
import { setScreenshareMuted, toggleCameraFacingMode } from '../media/actions';
|
||||
import { toggleCameraFacingMode } from '../media/actions';
|
||||
import {
|
||||
CAMERA_FACING_MODE,
|
||||
MEDIA_TYPE,
|
||||
MediaType,
|
||||
SCREENSHARE_MUTISM_AUTHORITY,
|
||||
VIDEO_MUTISM_AUTHORITY,
|
||||
VIDEO_TYPE
|
||||
VIDEO_MUTISM_AUTHORITY
|
||||
} from '../media/constants';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
|
||||
import {
|
||||
TRACK_ADDED,
|
||||
TRACK_MUTE_UNMUTE_FAILED,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_REMOVED,
|
||||
TRACK_STOPPED,
|
||||
TRACK_UPDATED
|
||||
} from './actionTypes';
|
||||
import {
|
||||
createLocalTracksA,
|
||||
destroyLocalTracks,
|
||||
showNoDataFromSourceVideoError,
|
||||
toggleScreensharing,
|
||||
trackMuteUnmuteFailed,
|
||||
trackNoDataFromSourceNotificationInfoChanged,
|
||||
trackRemoved
|
||||
} from './actions';
|
||||
import {
|
||||
getLocalTrack,
|
||||
getTrackByJitsiTrack,
|
||||
isUserInteractionRequiredForUnmute,
|
||||
setTrackMuted
|
||||
} from './functions';
|
||||
import { ITrack } from './reducer';
|
||||
|
||||
import './subscriber';
|
||||
|
||||
/**
|
||||
@@ -63,29 +49,6 @@ import './subscriber';
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case TRACK_ADDED: {
|
||||
const { local } = action.track;
|
||||
|
||||
// The devices list needs to be refreshed when no initial video permissions
|
||||
// were granted and a local video track is added by umuting the video.
|
||||
if (local) {
|
||||
store.dispatch(getAvailableDevices());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case TRACK_NO_DATA_FROM_SOURCE: {
|
||||
const result = next(action);
|
||||
|
||||
_handleNoDataFromSourceErrors(store, action);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case TRACK_REMOVED: {
|
||||
_removeNoDataFromSourceNotification(store, action.track);
|
||||
break;
|
||||
}
|
||||
case SET_AUDIO_MUTED:
|
||||
if (!action.muted
|
||||
&& isUserInteractionRequiredForUnmute(store.getState())) {
|
||||
@@ -153,82 +116,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TRACK_MUTE_UNMUTE_FAILED: {
|
||||
const { jitsiTrack } = action.track;
|
||||
const muted = action.wasMuted;
|
||||
const isVideoTrack = jitsiTrack.getType() !== MEDIA_TYPE.AUDIO;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
if (isVideoTrack && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
||||
&& getMultipleVideoSendingSupportFeatureFlag(store.getState())) {
|
||||
store.dispatch(setScreenshareMuted(!muted));
|
||||
} else if (isVideoTrack) {
|
||||
APP.conference.setVideoMuteStatus();
|
||||
} else {
|
||||
APP.conference.setAudioMuteStatus(!muted);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TRACK_STOPPED: {
|
||||
const { jitsiTrack } = action.track;
|
||||
|
||||
if (typeof APP !== 'undefined'
|
||||
&& getMultipleVideoSendingSupportFeatureFlag(store.getState())
|
||||
&& jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
store.dispatch(toggleScreensharing(false));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TRACK_UPDATED: {
|
||||
// TODO Remove the following calls to APP.UI once components interested
|
||||
// in track mute changes are moved into React and/or redux.
|
||||
if (typeof APP !== 'undefined') {
|
||||
const result = next(action);
|
||||
const state = store.getState();
|
||||
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const { jitsiTrack } = action.track;
|
||||
const muted = jitsiTrack.isMuted();
|
||||
const participantID = jitsiTrack.getParticipantId();
|
||||
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
|
||||
|
||||
if (isVideoTrack) {
|
||||
// Do not change the video mute state for local presenter tracks.
|
||||
if (jitsiTrack.type === MEDIA_TYPE.PRESENTER) {
|
||||
APP.conference.mutePresenter(muted);
|
||||
} else if (jitsiTrack.isLocal() && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) {
|
||||
APP.conference.setVideoMuteStatus();
|
||||
} else if (jitsiTrack.isLocal() && muted && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
!getMultipleVideoSendingSupportFeatureFlag(state)
|
||||
&& store.dispatch(toggleScreensharing(false, false, true));
|
||||
} else {
|
||||
APP.UI.setVideoMuted(participantID);
|
||||
}
|
||||
} else if (jitsiTrack.isLocal()) {
|
||||
APP.conference.setAudioMuteStatus(muted);
|
||||
} else {
|
||||
APP.UI.setAudioMuted(participantID, muted);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Mobile.
|
||||
const { jitsiTrack, local } = action.track;
|
||||
|
||||
if (local && jitsiTrack.isMuted()
|
||||
&& jitsiTrack.type === MEDIA_TYPE.VIDEO && jitsiTrack.videoType === VIDEO_TYPE.DESKTOP) {
|
||||
store.dispatch(toggleScreensharing(false));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
@@ -259,53 +146,6 @@ StateListenerRegistry.register(
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles no data from source errors.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified action is
|
||||
* dispatched.
|
||||
* @param {Action} action - The redux action dispatched in the specified store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleNoDataFromSourceErrors(store: IStore, action: any) {
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], action.track.jitsiTrack);
|
||||
|
||||
if (!track || !track.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { jitsiTrack } = track;
|
||||
|
||||
if (track.mediaType === MEDIA_TYPE.AUDIO && track.isReceivingData) {
|
||||
_removeNoDataFromSourceNotification(store, action.track);
|
||||
}
|
||||
|
||||
if (track.mediaType === MEDIA_TYPE.VIDEO) {
|
||||
const { noDataFromSourceNotificationInfo = {} } = track;
|
||||
|
||||
if (track.isReceivingData) {
|
||||
if (noDataFromSourceNotificationInfo.timeout) {
|
||||
clearTimeout(noDataFromSourceNotificationInfo.timeout);
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, undefined));
|
||||
}
|
||||
|
||||
// try to remove the notification if there is one.
|
||||
_removeNoDataFromSourceNotification(store, action.track);
|
||||
} else {
|
||||
if (noDataFromSourceNotificationInfo.timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => dispatch(showNoDataFromSourceVideoError(jitsiTrack)), 5000);
|
||||
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, { timeout }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the local track associated with a specific {@code MEDIA_TYPE} in a
|
||||
* specific redux store.
|
||||
@@ -334,23 +174,6 @@ function _getLocalTrack(
|
||||
includePending));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the no data from source notification associated with the JitsiTrack if displayed.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @param {Track} track - The redux action dispatched in the specified store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _removeNoDataFromSourceNotification({ getState, dispatch }: IStore, track: ITrack) {
|
||||
const t = getTrackByJitsiTrack(getState()['features/base/tracks'], track.jitsiTrack);
|
||||
const { jitsiTrack, noDataFromSourceNotificationInfo = {} } = t || {};
|
||||
|
||||
if (noDataFromSourceNotificationInfo?.uid) {
|
||||
dispatch(hideNotification(noDataFromSourceNotificationInfo.uid));
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, undefined));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes or unmutes a local track with a specific media type.
|
||||
*
|
||||
38
react/features/base/tracks/middleware.native.ts
Normal file
38
react/features/base/tracks/middleware.native.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
VIDEO_TYPE
|
||||
} from '../media/constants';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import {
|
||||
TRACK_UPDATED
|
||||
} from './actionTypes';
|
||||
import {
|
||||
toggleScreensharing
|
||||
} from './actions.native';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
/**
|
||||
* Middleware that captures LIB_DID_DISPOSE and LIB_DID_INIT actions and,
|
||||
* respectively, creates/destroys local media tracks. Also listens to
|
||||
* media-related actions and performs corresponding operations with tracks.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case TRACK_UPDATED: {
|
||||
const { jitsiTrack, local } = action.track;
|
||||
|
||||
if (local && jitsiTrack.isMuted()
|
||||
&& jitsiTrack.type === MEDIA_TYPE.VIDEO && jitsiTrack.videoType === VIDEO_TYPE.DESKTOP) {
|
||||
store.dispatch(toggleScreensharing(false));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
198
react/features/base/tracks/middleware.web.ts
Normal file
198
react/features/base/tracks/middleware.web.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { hideNotification } from '../../notifications/actions';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
|
||||
import { getAvailableDevices } from '../devices/actions.web';
|
||||
import { setScreenshareMuted } from '../media/actions';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
VIDEO_TYPE
|
||||
} from '../media/constants';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import {
|
||||
TRACK_ADDED,
|
||||
TRACK_MUTE_UNMUTE_FAILED,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_REMOVED,
|
||||
TRACK_STOPPED,
|
||||
TRACK_UPDATED
|
||||
} from './actionTypes';
|
||||
import {
|
||||
showNoDataFromSourceVideoError,
|
||||
toggleScreensharing,
|
||||
trackNoDataFromSourceNotificationInfoChanged
|
||||
} from './actions.web';
|
||||
import {
|
||||
getTrackByJitsiTrack
|
||||
} from './functions.web';
|
||||
import { ITrack } from './types';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
/**
|
||||
* Middleware that captures LIB_DID_DISPOSE and LIB_DID_INIT actions and,
|
||||
* respectively, creates/destroys local media tracks. Also listens to
|
||||
* media-related actions and performs corresponding operations with tracks.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case TRACK_ADDED: {
|
||||
const { local } = action.track;
|
||||
|
||||
// The devices list needs to be refreshed when no initial video permissions
|
||||
// were granted and a local video track is added by umuting the video.
|
||||
if (local) {
|
||||
store.dispatch(getAvailableDevices());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case TRACK_NO_DATA_FROM_SOURCE: {
|
||||
const result = next(action);
|
||||
|
||||
_handleNoDataFromSourceErrors(store, action);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case TRACK_REMOVED: {
|
||||
_removeNoDataFromSourceNotification(store, action.track);
|
||||
break;
|
||||
}
|
||||
|
||||
case TRACK_MUTE_UNMUTE_FAILED: {
|
||||
const { jitsiTrack } = action.track;
|
||||
const muted = action.wasMuted;
|
||||
const isVideoTrack = jitsiTrack.getType() !== MEDIA_TYPE.AUDIO;
|
||||
|
||||
if (isVideoTrack && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
||||
&& getMultipleVideoSendingSupportFeatureFlag(store.getState())) {
|
||||
store.dispatch(setScreenshareMuted(!muted));
|
||||
} else if (isVideoTrack) {
|
||||
APP.conference.setVideoMuteStatus();
|
||||
} else {
|
||||
APP.conference.setAudioMuteStatus(!muted);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case TRACK_STOPPED: {
|
||||
const { jitsiTrack } = action.track;
|
||||
|
||||
if (getMultipleVideoSendingSupportFeatureFlag(store.getState())
|
||||
&& jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
store.dispatch(toggleScreensharing(false));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TRACK_UPDATED: {
|
||||
// TODO Remove the following calls to APP.UI once components interested
|
||||
// in track mute changes are moved into React and/or redux.
|
||||
|
||||
const result = next(action);
|
||||
const state = store.getState();
|
||||
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const { jitsiTrack } = action.track;
|
||||
const muted = jitsiTrack.isMuted();
|
||||
const participantID = jitsiTrack.getParticipantId();
|
||||
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
|
||||
|
||||
if (isVideoTrack) {
|
||||
// Do not change the video mute state for local presenter tracks.
|
||||
if (jitsiTrack.type === MEDIA_TYPE.PRESENTER) {
|
||||
APP.conference.mutePresenter(muted);
|
||||
} else if (jitsiTrack.isLocal() && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) {
|
||||
APP.conference.setVideoMuteStatus();
|
||||
} else if (jitsiTrack.isLocal() && muted && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||
!getMultipleVideoSendingSupportFeatureFlag(state)
|
||||
&& store.dispatch(toggleScreensharing(false, false, true));
|
||||
} else {
|
||||
APP.UI.setVideoMuted(participantID);
|
||||
}
|
||||
} else if (jitsiTrack.isLocal()) {
|
||||
APP.conference.setAudioMuteStatus(muted);
|
||||
} else {
|
||||
APP.UI.setAudioMuted(participantID, muted);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles no data from source errors.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified action is
|
||||
* dispatched.
|
||||
* @param {Action} action - The redux action dispatched in the specified store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleNoDataFromSourceErrors(store: IStore, action: any) {
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], action.track.jitsiTrack);
|
||||
|
||||
if (!track || !track.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { jitsiTrack } = track;
|
||||
|
||||
if (track.mediaType === MEDIA_TYPE.AUDIO && track.isReceivingData) {
|
||||
_removeNoDataFromSourceNotification(store, action.track);
|
||||
}
|
||||
|
||||
if (track.mediaType === MEDIA_TYPE.VIDEO) {
|
||||
const { noDataFromSourceNotificationInfo = {} } = track;
|
||||
|
||||
if (track.isReceivingData) {
|
||||
if (noDataFromSourceNotificationInfo.timeout) {
|
||||
clearTimeout(noDataFromSourceNotificationInfo.timeout);
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, undefined));
|
||||
}
|
||||
|
||||
// try to remove the notification if there is one.
|
||||
_removeNoDataFromSourceNotification(store, action.track);
|
||||
} else {
|
||||
if (noDataFromSourceNotificationInfo.timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => dispatch(showNoDataFromSourceVideoError(jitsiTrack)), 5000);
|
||||
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, { timeout }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the no data from source notification associated with the JitsiTrack if displayed.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @param {Track} track - The redux action dispatched in the specified store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _removeNoDataFromSourceNotification({ getState, dispatch }: IStore, track: ITrack) {
|
||||
const t = getTrackByJitsiTrack(getState()['features/base/tracks'], track.jitsiTrack);
|
||||
const { jitsiTrack, noDataFromSourceNotificationInfo = {} } = t || {};
|
||||
|
||||
if (noDataFromSourceNotificationInfo?.uid) {
|
||||
dispatch(hideNotification(noDataFromSourceNotificationInfo.uid));
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, undefined));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { MediaType } from '../media/constants';
|
||||
import { PARTICIPANT_ID_CHANGED } from '../participants/actionTypes';
|
||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||
import { set } from '../redux/functions';
|
||||
@@ -14,48 +13,7 @@ import {
|
||||
TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
||||
TRACK_WILL_CREATE
|
||||
} from './actionTypes';
|
||||
|
||||
export interface ITrack {
|
||||
isReceivingData: boolean;
|
||||
jitsiTrack: any;
|
||||
lastMediaEvent?: string;
|
||||
local: boolean;
|
||||
mediaType: MediaType;
|
||||
mirror: boolean;
|
||||
muted: boolean;
|
||||
noDataFromSourceNotificationInfo?: {
|
||||
timeout?: number;
|
||||
uid?: string;
|
||||
};
|
||||
participantId: string;
|
||||
streamingStatus?: string;
|
||||
videoStarted: boolean;
|
||||
videoType?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track type.
|
||||
*
|
||||
* @typedef {object} Track
|
||||
* @property {JitsiLocalTrack|JitsiRemoteTrack} jitsiTrack - The associated
|
||||
* {@code JitsiTrack} instance. Optional for local tracks if those are still
|
||||
* being created (ie {@code getUserMedia} is still in progress).
|
||||
* @property {Promise} [gumProcess] - If a local track is still being created,
|
||||
* it will have no {@code JitsiTrack}, but a {@code gumProcess} set to a
|
||||
* {@code Promise} with and extra {@code cancel()}.
|
||||
* @property {boolean} local=false - If the track is local.
|
||||
* @property {MEDIA_TYPE} mediaType=false - The media type of the track.
|
||||
* @property {boolean} mirror=false - The indicator which determines whether the
|
||||
* display/rendering of the track should be mirrored. It only makes sense in the
|
||||
* context of video (at least at the time of this writing).
|
||||
* @property {boolean} muted=false - If the track is muted.
|
||||
* @property {(string|undefined)} participantId - The ID of the participant whom
|
||||
* the track belongs to.
|
||||
* @property {boolean} videoStarted=false - If the video track has already
|
||||
* started to play.
|
||||
* @property {(VIDEO_TYPE|undefined)} videoType - The type of video track if
|
||||
* any.
|
||||
*/
|
||||
import { ITrack } from './types';
|
||||
|
||||
/**
|
||||
* Reducer function for a single track.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { MediaType } from '../media/constants';
|
||||
|
||||
export interface ITrackOptions {
|
||||
cameraDeviceId?: string | null;
|
||||
constraints?: {
|
||||
@@ -18,6 +20,47 @@ export interface ITrackOptions {
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track type.
|
||||
*
|
||||
* @typedef {object} Track
|
||||
* @property {JitsiLocalTrack|JitsiRemoteTrack} jitsiTrack - The associated
|
||||
* {@code JitsiTrack} instance. Optional for local tracks if those are still
|
||||
* being created (ie {@code getUserMedia} is still in progress).
|
||||
* @property {Promise} [gumProcess] - If a local track is still being created,
|
||||
* it will have no {@code JitsiTrack}, but a {@code gumProcess} set to a
|
||||
* {@code Promise} with and extra {@code cancel()}.
|
||||
* @property {boolean} local=false - If the track is local.
|
||||
* @property {MEDIA_TYPE} mediaType=false - The media type of the track.
|
||||
* @property {boolean} mirror=false - The indicator which determines whether the
|
||||
* display/rendering of the track should be mirrored. It only makes sense in the
|
||||
* context of video (at least at the time of this writing).
|
||||
* @property {boolean} muted=false - If the track is muted.
|
||||
* @property {(string|undefined)} participantId - The ID of the participant whom
|
||||
* the track belongs to.
|
||||
* @property {boolean} videoStarted=false - If the video track has already
|
||||
* started to play.
|
||||
* @property {(VIDEO_TYPE|undefined)} videoType - The type of video track if
|
||||
* any.
|
||||
*/
|
||||
export interface ITrack {
|
||||
isReceivingData: boolean;
|
||||
jitsiTrack: any;
|
||||
lastMediaEvent?: string;
|
||||
local: boolean;
|
||||
mediaType: MediaType;
|
||||
mirror: boolean;
|
||||
muted: boolean;
|
||||
noDataFromSourceNotificationInfo?: {
|
||||
timeout?: number;
|
||||
uid?: string;
|
||||
};
|
||||
participantId: string;
|
||||
streamingStatus?: string;
|
||||
videoStarted: boolean;
|
||||
videoType?: string | null;
|
||||
}
|
||||
|
||||
export interface IToggleScreenSharingOptions {
|
||||
audioOnly: boolean;
|
||||
enabled?: boolean;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './native';
|
||||
@@ -1 +0,0 @@
|
||||
export * from './web';
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React from 'react';
|
||||
import { TouchableRipple } from 'react-native-paper';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
// @ts-ignore
|
||||
import styles from '../../../react/components/native/styles';
|
||||
import { IIconButtonProps } from '../../../react/types';
|
||||
import { BUTTON_TYPES } from '../../constants';
|
||||
|
||||
@@ -7,6 +7,8 @@ import { ISwitchProps } from '../types';
|
||||
|
||||
interface IProps extends ISwitchProps {
|
||||
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* Id of the toggle.
|
||||
*/
|
||||
@@ -78,7 +80,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
};
|
||||
});
|
||||
|
||||
const Switch = ({ id, checked, disabled, onChange }: IProps) => {
|
||||
const Switch = ({ className, id, checked, disabled, onChange }: IProps) => {
|
||||
const { classes: styles, cx } = useStyles();
|
||||
const isMobile = isMobileBrowser();
|
||||
|
||||
@@ -89,7 +91,7 @@ const Switch = ({ id, checked, disabled, onChange }: IProps) => {
|
||||
return (
|
||||
<label
|
||||
className = { cx('toggle-container', styles.container, checked && styles.containerOn,
|
||||
isMobile && 'is-mobile', disabled && 'disabled') }>
|
||||
isMobile && 'is-mobile', disabled && 'disabled', className) }>
|
||||
<input
|
||||
type = 'checkbox'
|
||||
{ ...(id ? { id } : {}) }
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default as Button } from './Button';
|
||||
@@ -63,7 +63,7 @@ export function escapeRegexp(s: string) {
|
||||
* @param {Object} w - Window object to use instead of the built in one.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getBaseUrl(w: Window = window) {
|
||||
export function getBaseUrl(w: typeof window = window) {
|
||||
const doc = w.document;
|
||||
const base = doc.querySelector('base');
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { Linking } from 'react-native';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
@@ -2,11 +2,18 @@ import _ from 'lodash';
|
||||
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { getParticipantById, getParticipantCount, isLocalParticipantModerator } from '../base/participants/functions';
|
||||
import { IJitsiConference } from '../base/conference/reducer';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
getParticipantCount,
|
||||
isLocalParticipantModerator
|
||||
} from '../base/participants/functions';
|
||||
import { IJitsiParticipant } from '../base/participants/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
|
||||
import { FEATURE_KEY } from './constants';
|
||||
import { IRoom, IRooms } from './types';
|
||||
import { IRoom, IRoomInfo, IRoomInfoParticipant, IRooms, IRoomsInfo } from './types';
|
||||
|
||||
/**
|
||||
* Returns the rooms object for breakout rooms.
|
||||
@@ -30,9 +37,16 @@ export const getMainRoom = (stateful: IStateful) => {
|
||||
return _.find(rooms, room => Boolean(room.isMainRoom));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the rooms info.
|
||||
*
|
||||
* @param {IStateful} stateful - The redux store, the redux.
|
||||
|
||||
* @returns {IRoomsInfo} The rooms info.
|
||||
*/
|
||||
export const getRoomsInfo = (stateful: IStateful) => {
|
||||
const breakoutRooms = getBreakoutRooms(stateful);
|
||||
const conference = getCurrentConference(stateful);
|
||||
const conference: IJitsiConference = getCurrentConference(stateful);
|
||||
|
||||
const initialRoomsInfo = {
|
||||
rooms: []
|
||||
@@ -40,27 +54,45 @@ export const getRoomsInfo = (stateful: IStateful) => {
|
||||
|
||||
// only main roomn
|
||||
if (!breakoutRooms || Object.keys(breakoutRooms).length === 0) {
|
||||
// filter out hidden participants
|
||||
const conferenceParticipants = conference?.getParticipants()
|
||||
.filter((participant: IJitsiParticipant) => !participant.isHidden());
|
||||
|
||||
const localParticipant = getLocalParticipant(stateful);
|
||||
let localParticipantInfo;
|
||||
|
||||
if (localParticipant) {
|
||||
localParticipantInfo = {
|
||||
role: localParticipant.role,
|
||||
displayName: localParticipant.name,
|
||||
avatarUrl: localParticipant.loadableAvatarUrl,
|
||||
id: localParticipant.id
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...initialRoomsInfo,
|
||||
rooms: [ {
|
||||
isMainRoom: true,
|
||||
id: conference?.room?.roomjid,
|
||||
jid: conference?.room?.myroomjid,
|
||||
participants: conference?.participants && Object.keys(conference.participants).length
|
||||
? Object.keys(conference.participants).map(participantId => {
|
||||
const participantItem = conference?.participants[participantId];
|
||||
const storeParticipant = getParticipantById(stateful, participantItem._id);
|
||||
participants: conferenceParticipants?.length > 0
|
||||
? [
|
||||
localParticipantInfo,
|
||||
...conferenceParticipants.map((participantItem: IJitsiParticipant) => {
|
||||
const storeParticipant = getParticipantById(stateful, participantItem.getId());
|
||||
|
||||
return {
|
||||
jid: participantItem._jid,
|
||||
role: participantItem._role,
|
||||
displayName: participantItem._displayName,
|
||||
avatarUrl: storeParticipant?.loadableAvatarUrl,
|
||||
id: participantItem._id
|
||||
};
|
||||
}) : []
|
||||
} ]
|
||||
};
|
||||
return {
|
||||
jid: participantItem.getJid(),
|
||||
role: participantItem.getRole(),
|
||||
displayName: participantItem.getDisplayName(),
|
||||
avatarUrl: storeParticipant?.loadableAvatarUrl,
|
||||
id: participantItem.getId()
|
||||
} as IRoomInfoParticipant;
|
||||
}) ]
|
||||
: [ localParticipantInfo ]
|
||||
} as IRoomInfo ]
|
||||
} as IRoomsInfo;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -86,11 +118,11 @@ export const getRoomsInfo = (stateful: IStateful) => {
|
||||
avatarUrl: storeParticipant?.loadableAvatarUrl,
|
||||
id: storeParticipant ? storeParticipant.id
|
||||
: participantLongId
|
||||
};
|
||||
} as IRoomInfoParticipant;
|
||||
}) : []
|
||||
};
|
||||
} as IRoomInfo;
|
||||
})
|
||||
};
|
||||
} as IRoomsInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,3 +15,22 @@ export interface IRoom {
|
||||
export interface IRooms {
|
||||
[jid: string]: IRoom;
|
||||
}
|
||||
|
||||
export interface IRoomInfo {
|
||||
id: string;
|
||||
isMainRoom: boolean;
|
||||
jid: string;
|
||||
participants: IRoomInfoParticipant[];
|
||||
}
|
||||
|
||||
export interface IRoomsInfo {
|
||||
rooms: IRoomInfo[];
|
||||
}
|
||||
|
||||
export interface IRoomInfoParticipant {
|
||||
avatarUrl: string;
|
||||
displayName: string;
|
||||
id: string;
|
||||
jid: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { IStore } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
import { updateSettings } from '../../../base/settings/actions';
|
||||
import { Button } from '../../../base/ui/components/web';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { getCurrentConference } from '../../base/conference/functions';
|
||||
import checkChromeExtensionsInstalled from '../../base/environment/checkChromeExtensionsInstalled';
|
||||
import {
|
||||
checkChromeExtensionsInstalled,
|
||||
isMobileBrowser
|
||||
} from '../../base/environment/utils';
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { JitsiParticipantConnectionStatus, JitsiTrackStreamingStatus } from '../base/lib-jitsi-meet';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { ITrack } from '../base/tracks/reducer';
|
||||
import { ITrack } from '../base/tracks/types';
|
||||
|
||||
/**
|
||||
* Checks if the passed track's streaming status is active.
|
||||
|
||||
@@ -8,7 +8,7 @@ import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
|
||||
import { isSupportedBrowser } from '../../base/environment';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import { Button } from '../../base/ui/components/web';
|
||||
import Button from '../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../base/ui/constants';
|
||||
import {
|
||||
openDesktopApp,
|
||||
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
setAudioInputDeviceAndUpdateSettings,
|
||||
setAudioOutputDevice,
|
||||
setVideoInputDeviceAndUpdateSettings
|
||||
} from '../base/devices/actions';
|
||||
} from '../base/devices/actions.web';
|
||||
import {
|
||||
areDeviceLabelsInitialized,
|
||||
getAudioOutputDeviceId,
|
||||
getDeviceIdByLabel,
|
||||
groupDevicesByKind
|
||||
} from '../base/devices/functions';
|
||||
} from '../base/devices/functions.web';
|
||||
import { isIosMobileBrowser } from '../base/environment/utils';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { toState } from '../base/redux/functions';
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId,
|
||||
getUserSelectedOutputDeviceId
|
||||
} from '../base/settings/functions.any';
|
||||
} from '../base/settings/functions.web';
|
||||
|
||||
/**
|
||||
* Returns the properties for the device selection dialog from Redux state.
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
KICKED_OUT
|
||||
} from '../base/conference';
|
||||
import { SET_CONFIG } from '../base/config';
|
||||
import { NOTIFY_CAMERA_ERROR, NOTIFY_MIC_ERROR } from '../base/devices';
|
||||
import { NOTIFY_CAMERA_ERROR, NOTIFY_MIC_ERROR } from '../base/devices/actionTypes';
|
||||
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
DOMINANT_SPEAKER_CHANGED,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export type DetectInput = {
|
||||
|
||||
// @ts-ignore
|
||||
image: ImageBitmap | ImageData;
|
||||
threshold: number;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -7,10 +8,13 @@ import { IconHorizontalPoints } from '../../../base/icons/svg';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import IconButton from '../../../base/ui/components/native/IconButton';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants';
|
||||
// @ts-ignore
|
||||
import MuteEveryoneDialog from '../../../video-menu/components/native/MuteEveryoneDialog';
|
||||
import { isMoreActionsVisible, isMuteAllVisible } from '../../functions';
|
||||
|
||||
// @ts-ignore
|
||||
import { ContextMenuMore } from './ContextMenuMore';
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import Icon from '../../../../base/icons/components/Icon';
|
||||
import { IconArrowLeft } from '../../../../base/icons/svg';
|
||||
import { Button } from '../../../../base/ui/components/web';
|
||||
import Button from '../../../../base/ui/components/web/Button';
|
||||
// @ts-ignore
|
||||
import { getCountryCodeFromPhone } from '../../../utils';
|
||||
// @ts-ignore
|
||||
|
||||
@@ -8,7 +8,7 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import Icon from '../../../../base/icons/components/Icon';
|
||||
import { IconClose } from '../../../../base/icons/svg';
|
||||
import { Button } from '../../../../base/ui/components/web';
|
||||
import Button from '../../../../base/ui/components/web/Button';
|
||||
// @ts-ignore
|
||||
import Label from '../Label';
|
||||
// @ts-ignore
|
||||
|
||||
@@ -113,7 +113,7 @@ export interface IProps extends WithTranslation {
|
||||
/**
|
||||
* Callback to change the local recording only self setting.
|
||||
*/
|
||||
onLocalRecordingSelfChange: Function;
|
||||
onLocalRecordingSelfChange: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked on sharing setting change.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// XXX CSS is used on Web, JavaScript styles are use only for mobile. Export an
|
||||
// (empty) object so that styles[*] statements on Web don't trigger errors.
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
|
||||
export default {};
|
||||
|
||||
@@ -13,5 +12,3 @@ export const ICON_CLOUD = 'images/icon-cloud.png';
|
||||
export const ICON_INFO = 'images/icon-info.png';
|
||||
|
||||
export const ICON_USERS = 'images/icon-users.png';
|
||||
|
||||
export const TRACK_COLOR = BaseTheme.palette.ui15;
|
||||
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
Container,
|
||||
Image,
|
||||
LoadingIndicator,
|
||||
Switch,
|
||||
Text
|
||||
// @ts-ignore
|
||||
} from '../../../../base/react';
|
||||
import { connect } from '../../../../base/redux/functions';
|
||||
import Button from '../../../../base/ui/components/web/Button';
|
||||
import Switch from '../../../../base/ui/components/web/Switch';
|
||||
import { BUTTON_TYPES } from '../../../../base/ui/constants';
|
||||
import { RECORDING_TYPES } from '../../../constants';
|
||||
// @ts-ignore
|
||||
@@ -25,8 +25,7 @@ import {
|
||||
ICON_CLOUD,
|
||||
ICON_INFO,
|
||||
ICON_USERS,
|
||||
LOCAL_RECORDING,
|
||||
TRACK_COLOR
|
||||
LOCAL_RECORDING
|
||||
// @ts-ignore
|
||||
} from '../styles.web';
|
||||
|
||||
@@ -76,11 +75,10 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
|
||||
= integrationsEnabled || _localRecordingAvailable
|
||||
? (
|
||||
<Switch
|
||||
checked = { selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE }
|
||||
className = 'recording-switch'
|
||||
disabled = { isValidating }
|
||||
onValueChange = { this._onRecordingServiceSwitchChange }
|
||||
trackColor = {{ false: TRACK_COLOR }}
|
||||
value = { selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE } />
|
||||
onChange = { this._onRecordingServiceSwitchChange } />
|
||||
) : null;
|
||||
|
||||
const label = isVpaas ? t('recording.serviceDescriptionCloud') : t('recording.serviceDescription');
|
||||
@@ -141,11 +139,10 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
|
||||
{ t('recording.fileSharingdescription') }
|
||||
</Text>
|
||||
<Switch
|
||||
checked = { sharingSetting }
|
||||
className = 'recording-switch'
|
||||
disabled = { isValidating }
|
||||
onValueChange = { onSharingSettingChanged }
|
||||
trackColor = {{ false: TRACK_COLOR }}
|
||||
value = { sharingSetting } />
|
||||
onChange = { onSharingSettingChanged } />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -282,12 +279,11 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
|
||||
if (fileRecordingsServiceEnabled || _localRecordingAvailable) {
|
||||
switchContent = (
|
||||
<Switch
|
||||
checked = { selectedRecordingService
|
||||
=== RECORDING_TYPES.DROPBOX }
|
||||
className = 'recording-switch'
|
||||
disabled = { isValidating }
|
||||
onValueChange = { this._onDropboxSwitchChange }
|
||||
trackColor = {{ false: TRACK_COLOR }}
|
||||
value = { selectedRecordingService
|
||||
=== RECORDING_TYPES.DROPBOX } />
|
||||
onChange = { this._onDropboxSwitchChange } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -351,12 +347,11 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
|
||||
{ t('recording.saveLocalRecording') }
|
||||
</Text>
|
||||
<Switch
|
||||
checked = { selectedRecordingService
|
||||
=== RECORDING_TYPES.LOCAL }
|
||||
className = 'recording-switch'
|
||||
disabled = { isValidating }
|
||||
onValueChange = { this._onLocalRecordingSwitchChange }
|
||||
trackColor = {{ false: TRACK_COLOR }}
|
||||
value = { selectedRecordingService
|
||||
=== RECORDING_TYPES.LOCAL } />
|
||||
onChange = { this._onLocalRecordingSwitchChange } />
|
||||
</Container>
|
||||
</Container>
|
||||
{selectedRecordingService === RECORDING_TYPES.LOCAL && (
|
||||
@@ -373,11 +368,10 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
|
||||
{t('recording.onlyRecordSelf')}
|
||||
</Text>
|
||||
<Switch
|
||||
checked = { localRecordingOnlySelf }
|
||||
className = 'recording-switch'
|
||||
disabled = { isValidating }
|
||||
onValueChange = { onLocalRecordingSelfChange }
|
||||
trackColor = {{ false: TRACK_COLOR }}
|
||||
value = { localRecordingOnlySelf } />
|
||||
onChange = { onLocalRecordingSelfChange } />
|
||||
</Container>
|
||||
</Container>
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { Button } from '../../../base/ui/components/web';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import {
|
||||
CALENDAR_TYPE,
|
||||
MicrosoftSignInButton,
|
||||
|
||||
@@ -11,7 +11,7 @@ import { AbstractDialogTab } from '../../../base/dialog';
|
||||
// @ts-ignore
|
||||
import type { Props as AbstractDialogTabProps } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { Button } from '../../../base/ui/components/web';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
// @ts-ignore
|
||||
import { openLogoutDialog } from '../../actions';
|
||||
|
||||
@@ -4,11 +4,13 @@ import React from 'react';
|
||||
|
||||
import { areAudioLevelsEnabled } from '../../../../base/config/functions';
|
||||
import {
|
||||
getAudioInputDeviceData,
|
||||
getAudioOutputDeviceData,
|
||||
setAudioInputDeviceAndUpdateSettings,
|
||||
setAudioOutputDevice as setAudioOutputDeviceAction
|
||||
} from '../../../../base/devices';
|
||||
} from '../../../../base/devices/actions.web';
|
||||
import {
|
||||
getAudioInputDeviceData,
|
||||
getAudioOutputDeviceData
|
||||
} from '../../../../base/devices/functions.web';
|
||||
import Popover from '../../../../base/popover/components/Popover.web';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { SMALL_MOBILE_WIDTH } from '../../../../base/responsive-ui/constants';
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
getVideoDeviceIds,
|
||||
setVideoInputDeviceAndUpdateSettings
|
||||
} from '../../../../base/devices';
|
||||
} from '../../../../base/devices/actions.web';
|
||||
import {
|
||||
getVideoDeviceIds
|
||||
} from '../../../../base/devices/functions.web';
|
||||
import Popover from '../../../../base/popover/components/Popover.web';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { SMALL_MOBILE_WIDTH } from '../../../../base/responsive-ui/constants';
|
||||
|
||||
@@ -5,7 +5,6 @@ import { isNameReadOnly } from '../base/config/functions';
|
||||
import { SERVER_URL_CHANGE_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
import i18next, { DEFAULT_LANGUAGE, LANGUAGES } from '../base/i18n/i18next';
|
||||
import { createLocalTrack } from '../base/lib-jitsi-meet/functions';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
isLocalParticipantModerator
|
||||
@@ -256,67 +255,6 @@ export function getSoundsTabProps(stateful: IStateful) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise which resolves with a list of objects containing
|
||||
* all the video jitsiTracks and appropriate errors for the given device ids.
|
||||
*
|
||||
* @param {string[]} ids - The list of the camera ids for which to create tracks.
|
||||
* @param {number} [timeout] - A timeout for the createLocalTrack function call.
|
||||
*
|
||||
* @returns {Promise<Object[]>}
|
||||
*/
|
||||
export function createLocalVideoTracks(ids: string[], timeout?: number) {
|
||||
return Promise.all(ids.map(deviceId => createLocalTrack('video', deviceId, timeout)
|
||||
.then((jitsiTrack: any) => {
|
||||
return {
|
||||
jitsiTrack,
|
||||
deviceId
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
return {
|
||||
jitsiTrack: null,
|
||||
deviceId,
|
||||
error: 'deviceSelection.previewUnavailable'
|
||||
};
|
||||
})));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a promise which resolves with a list of objects containing
|
||||
* the audio track and the corresponding audio device information.
|
||||
*
|
||||
* @param {Object[]} devices - A list of microphone devices.
|
||||
* @param {number} [timeout] - A timeout for the createLocalTrack function call.
|
||||
* @returns {Promise<{
|
||||
* deviceId: string,
|
||||
* hasError: boolean,
|
||||
* jitsiTrack: Object,
|
||||
* label: string
|
||||
* }[]>}
|
||||
*/
|
||||
export function createLocalAudioTracks(devices: MediaDeviceInfo[], timeout?: number) {
|
||||
return Promise.all(
|
||||
devices.map(async ({ deviceId, label }) => {
|
||||
let jitsiTrack = null;
|
||||
let hasError = false;
|
||||
|
||||
try {
|
||||
jitsiTrack = await createLocalTrack('audio', deviceId, timeout);
|
||||
} catch (err) {
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
return {
|
||||
deviceId,
|
||||
hasError,
|
||||
jitsiTrack,
|
||||
label
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visibility state of the audio settings.
|
||||
*
|
||||
1
react/features/settings/functions.native.ts
Normal file
1
react/features/settings/functions.native.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './functions.any';
|
||||
64
react/features/settings/functions.web.ts
Normal file
64
react/features/settings/functions.web.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { createLocalTrack } from '../base/lib-jitsi-meet/functions';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
/**
|
||||
* Returns a promise which resolves with a list of objects containing
|
||||
* all the video jitsiTracks and appropriate errors for the given device ids.
|
||||
*
|
||||
* @param {string[]} ids - The list of the camera ids for which to create tracks.
|
||||
* @param {number} [timeout] - A timeout for the createLocalTrack function call.
|
||||
*
|
||||
* @returns {Promise<Object[]>}
|
||||
*/
|
||||
export function createLocalVideoTracks(ids: string[], timeout?: number) {
|
||||
return Promise.all(ids.map(deviceId => createLocalTrack('video', deviceId, timeout)
|
||||
.then((jitsiTrack: any) => {
|
||||
return {
|
||||
jitsiTrack,
|
||||
deviceId
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
return {
|
||||
jitsiTrack: null,
|
||||
deviceId,
|
||||
error: 'deviceSelection.previewUnavailable'
|
||||
};
|
||||
})));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a promise which resolves with a list of objects containing
|
||||
* the audio track and the corresponding audio device information.
|
||||
*
|
||||
* @param {Object[]} devices - A list of microphone devices.
|
||||
* @param {number} [timeout] - A timeout for the createLocalTrack function call.
|
||||
* @returns {Promise<{
|
||||
* deviceId: string,
|
||||
* hasError: boolean,
|
||||
* jitsiTrack: Object,
|
||||
* label: string
|
||||
* }[]>}
|
||||
*/
|
||||
export function createLocalAudioTracks(devices: MediaDeviceInfo[], timeout?: number) {
|
||||
return Promise.all(
|
||||
devices.map(async ({ deviceId, label }) => {
|
||||
let jitsiTrack = null;
|
||||
let hasError = false;
|
||||
|
||||
try {
|
||||
jitsiTrack = await createLocalTrack('audio', deviceId, timeout);
|
||||
} catch (err) {
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
return {
|
||||
deviceId,
|
||||
hasError,
|
||||
jitsiTrack,
|
||||
label
|
||||
};
|
||||
}));
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import debounce from 'lodash/debounce';
|
||||
import { getMultipleVideoSupportFeatureFlag } from '../base/config/functions';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { equals } from '../base/redux/functions';
|
||||
import { ITrack } from '../base/tracks/reducer';
|
||||
import { ITrack } from '../base/tracks/types';
|
||||
import { isFollowMeActive } from '../follow-me/functions';
|
||||
|
||||
import { setRemoteParticipantsWithScreenShare, virtualScreenshareParticipantsUpdated } from './actions.web';
|
||||
|
||||
@@ -37,6 +37,7 @@ curl https://get.acme.sh | sh -s email=$EMAIL
|
||||
NGINX_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx' 2>/dev/null | awk '{print $3}' || true)"
|
||||
NGINX_FULL_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx-full' 2>/dev/null | awk '{print $3}' || true)"
|
||||
NGINX_EXTRAS_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx-extras' 2>/dev/null | awk '{print $3}' || true)"
|
||||
OPENRESTY_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'openresty' 2>/dev/null | awk '{print $3}' || true)"
|
||||
APACHE_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'apache2' 2>/dev/null | awk '{print $3}' || true)"
|
||||
|
||||
RELOAD_CMD=""
|
||||
@@ -44,6 +45,8 @@ if [ "$NGINX_INSTALL_CHECK" = "installed" ] || [ "$NGINX_INSTALL_CHECK" = "unpac
|
||||
|| [ "$NGINX_FULL_INSTALL_CHECK" = "installed" ] || [ "$NGINX_FULL_INSTALL_CHECK" = "unpacked" ] \
|
||||
|| [ "$NGINX_EXTRAS_INSTALL_CHECK" = "installed" ] || [ "$NGINX_EXTRAS_INSTALL_CHECK" = "unpacked" ]; then
|
||||
RELOAD_CMD="systemctl force-reload nginx.service"
|
||||
elif [ "$OPENRESTY_INSTALL_CHECK" = "installed" ] || [ "$OPENRESTY_INSTALL_CHECK" = "unpacked" ] ; then
|
||||
RELOAD_CMD="systemctl force-reload openresty.service"
|
||||
elif [ "$APACHE_INSTALL_CHECK" = "installed" ] || [ "$APACHE_INSTALL_CHECK" = "unpacked" ] ; then
|
||||
RELOAD_CMD="systemctl force-reload apache2.service"
|
||||
else
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"react/features/embed-meeting",
|
||||
"react/features/face-landmarks",
|
||||
"react/features/feedback",
|
||||
"react/features/no-audio-signal",
|
||||
"react/features/noise-suppression",
|
||||
"react/features/screen-share",
|
||||
"react/features/stream-effects/noise-suppression",
|
||||
|
||||
Reference in New Issue
Block a user