├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── docs └── plantuml │ ├── plan.plantuml │ └── plan.png ├── package.json ├── public ├── config.js ├── css │ ├── img │ │ ├── audio-cover.png │ │ ├── audio-cover_old.png │ │ ├── audio-disable.png │ │ ├── audio-enable.png │ │ ├── audio.png │ │ ├── case-next.png │ │ ├── case-pre.png │ │ ├── cover.png │ │ ├── custom_audio_close.svg │ │ ├── custom_audio_open.svg │ │ ├── custom_video_close.svg │ │ ├── custom_video_open.svg │ │ ├── hangup.png │ │ ├── hangup.svg │ │ ├── icon.png │ │ ├── kick-off-hover.svg │ │ ├── kick-off.svg │ │ ├── loading-img.gif │ │ ├── loading.gif │ │ ├── logi-white.png │ │ ├── logo.png │ │ ├── setting.svg │ │ ├── share-close.png │ │ ├── share-cover.png │ │ ├── share.png │ │ ├── share.svg │ │ ├── sound.gif │ │ ├── triangle.svg │ │ ├── user-list.svg │ │ ├── video-disable.png │ │ ├── video-enable.png │ │ ├── video-enable.svg │ │ ├── video.png │ │ ├── wb.png │ │ ├── wb.svg │ │ └── whiteboard_close.png │ ├── main.css │ ├── main.min.css │ └── main.scss ├── index.html ├── js │ ├── common.js │ ├── im.js │ ├── locale │ │ ├── en.js │ │ └── zh.js │ ├── login.js │ ├── main.js │ ├── utils.js │ └── whiteboard.js ├── lib │ ├── RongIMLib-2.5.9.js │ ├── RongRTC-3.2.7.js │ ├── frameImage.js │ ├── media.js │ ├── protobuf-2.3.9.min.js │ └── screenshare.js ├── mediaresource │ ├── audio.mp3 │ ├── video_demo1.mp4 │ ├── video_demo2.mp4 │ └── video_demo3.mp4 ├── plugin │ ├── screenshare-addon.zip │ └── screenshare-addon │ │ ├── screenshare-addon │ │ ├── background-script.js │ │ ├── content-script.js │ │ ├── icon.png │ │ └── manifest.json │ │ └── 手动安装Chrome扩展说明_201801091600001.doc └── whiteboard │ ├── imgs │ ├── btn_left.png │ ├── btn_right.png │ ├── eraser-pre.svg │ ├── next-scene.png │ ├── opt-icons.svg │ ├── pre-scene.png │ └── word-pre.svg │ ├── index.css │ ├── index.js │ └── whiteboard.html ├── scripts.bak ├── README.md ├── config.default.js ├── config.tpl ├── deploy.conf.js └── deploy.js ├── src ├── App.vue ├── index.ts ├── shims-app.d.ts ├── shims-tsx.d.ts └── shims-vue.d.ts ├── tsconfig.json └── vue.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | src/lib 2 | es5 3 | src/js/mock/user.js 4 | plugin -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": 2015, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "no-console": "warn", 13 | "quotes": [2, "single"], 14 | // "no-alert": "error", 15 | "no-irregular-whitespace": "error", 16 | "eqeqeq": "warn", 17 | "key-spacing": "error", 18 | "no-dupe-keys": "error", 19 | "no-mixed-spaces-and-tabs": "error", 20 | "no-multiple-empty-lines": ["error", { "max": 1 }], 21 | "no-multi-spaces": ["error", { "ignoreEOLComments": true }], 22 | "no-underscore-dangle": "warn", 23 | "no-control-regex": "warn", 24 | "no-use-before-define": "warn", 25 | "no-restricted-globals": "warn", 26 | "indent": ["error", 2], 27 | "max-nested-callbacks": ["error", { "max": 3 }], 28 | "no-underscore-dangle": ["warn", { "allow": ["_on", "_off"] }] 29 | } 30 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .vscode 4 | package-lock.json 5 | es5 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sealrtc-web 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | 31 | ### 配置 AppKey 和 Demo Server 32 | 33 | **1、开发配置** 34 | 配置文件位置: `/public/config.js` 35 | ```js 36 | 开发模式可通过 location.serach 字段配置 appkey 、 Demo Server( rtcs ), 37 | /** 38 | * 解析 location.search 字段 39 | * 【必填】 40 | * 1. appkey 41 | * 2. rtcs: sealrtc-server api 地址 42 | */ 43 | ``` 44 | **2、部署配置** 45 | 配置文件位置: `/public/config.js` 46 | 47 | ```js 48 | 直接修改配置文件中字段 49 | defaultAppkey: AppKey 50 | defaultRTCS: Demo Server 51 | ``` 52 | **3、常见问题** 53 | 54 | >Q1: 是 root 用户,npm install 安装依赖仍出现权限问题 55 | >A1: 使用 npm install --unsafe -perm 56 | 57 | 58 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /docs/plantuml/plan.plantuml: -------------------------------------------------------------------------------- 1 | @startuml plan 2 | 3 | actor User 4 | participant SealRTCClient as C 5 | participant SealRTCServer as S 6 | 7 | User -> C: http[s]://domain/sealrtc[?skip-validate=true] 8 | 9 | == 页面初始化 == 10 | 11 | C --> S: /api/configure?index= 12 | S --> S: 查找配置 13 | S --> C: appkey 14 | 15 | == 登录 == 16 | 17 | alt skip-validate == true 18 | C --> C: 跳过短信验证 19 | else 有参数 20 | C --> C: 普通登录 21 | end 22 | 23 | C --> S: 获取 token 24 | S --> C: TOKEN 25 | 26 | == End == 27 | 28 | @enduml -------------------------------------------------------------------------------- /docs/plantuml/plan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/docs/plantuml/plan.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seal-rtc", 3 | "version": "3.2.7", 4 | "description": "SealRTC Demo", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "dev": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "lint": "vue-cli-service lint --no-fix .", 11 | "lint:fix": "vue-cli-service lint ." 12 | }, 13 | "husky": { 14 | "hooks": { 15 | "pre-commit": "npm run lint", 16 | "commit-msg": "validate-commit-msg" 17 | } 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git@gitlab2.rongcloud.net:public-web/seal-rtc-v2.git" 22 | }, 23 | "keywords": [ 24 | "RTC", 25 | "SealRTC", 26 | "RongCloud" 27 | ], 28 | "author": "RongCloud", 29 | "license": "MIT", 30 | "devDependencies": { 31 | "@babel/core": "7.11.6", 32 | "@typescript-eslint/eslint-plugin": "^2.19.2", 33 | "@typescript-eslint/parser": "^2.19.2", 34 | "@vue/cli-plugin-eslint": "^4.2.2", 35 | "@vue/cli-plugin-router": "^4.2.2", 36 | "@vue/cli-plugin-typescript": "^4.2.2", 37 | "@vue/cli-service": "^4.2.2", 38 | "@vue/eslint-config-standard": "^5.1.1", 39 | "@vue/eslint-config-typescript": "^5.0.1", 40 | "babel-loader": "^8.0.6", 41 | "eslint": "^6.8.0", 42 | "eslint-plugin-import": "^2.20.1", 43 | "eslint-plugin-node": "^11.0.0", 44 | "eslint-plugin-promise": "^4.2.1", 45 | "eslint-plugin-standard": "^4.0.1", 46 | "eslint-plugin-vue": "^6.1.2", 47 | "husky": "^4.2.3", 48 | "lodash": "^4.17.15", 49 | "node-sass": "^4.13.1", 50 | "sass-loader": "^8.0.2", 51 | "typescript": "3.9.7", 52 | "validate-commit-msg": "^2.14.0", 53 | "vue-template-compiler": "^2.6.11" 54 | }, 55 | "dependencies": { 56 | "@vue/composition-api": "^0.6.7", 57 | "element-ui": "^2.13.2", 58 | "vue": "^2.6.11" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/config.js: -------------------------------------------------------------------------------- 1 | (function (dependencies) { 2 | // 便于运维上线生产环境时修改,使 location.search.appkey 失效 3 | const defaultAppkey = ''; 4 | const defaultRTCS = ''; 5 | const defaultMS = '' 6 | /** 7 | * 解析 location.search 字段 8 | * 【必填】 9 | * 1. appkey 10 | * 2. rtcs: sealrtc-server api 地址 11 | * 【选填】 12 | * 1. ms: media-server 地址,值为空时使用导航下发地址 13 | */ 14 | function parseLocation() { 15 | const search = location.search.replace(/^\?/, ''); 16 | if (search.length === 0) { 17 | return {}; 18 | } 19 | const conf = Object.assign({}, ...search.split('&').map((kv) => { 20 | const [key, value] = kv.split(/=/); 21 | return { 22 | [key]: value, 23 | }; 24 | })); 25 | return conf; 26 | } 27 | 28 | const search = parseLocation(); 29 | const appkey = search.appkey || defaultAppkey; 30 | const rtcs = search.rtcs || defaultRTCS; 31 | const ms = search.ms || defaultMS; 32 | 33 | if (!appkey || !rtcs) { 34 | throw 'appkey 与 rtcs 不可为空!' 35 | } 36 | 37 | var win = dependencies.win; 38 | win.RongSeal = win.RongSeal || {}; 39 | 40 | win.RongSeal.Config = { 41 | // 融云应用 AppKey 42 | APPKEY: appkey, 43 | // SealRTC Server 地址 44 | URL: rtcs, 45 | // media server 地址,无值时跟随导航分配 46 | MEDIA_SERVER: ms, 47 | // 屏幕共享插件下载地址,默认即可 48 | DOWNLOAD_SHARE_PLUGIN_URL: 'plugin/screenshare-addon.zip', 49 | }; 50 | })({ 51 | win: window 52 | }); 53 | -------------------------------------------------------------------------------- /public/css/img/audio-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/audio-cover.png -------------------------------------------------------------------------------- /public/css/img/audio-cover_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/audio-cover_old.png -------------------------------------------------------------------------------- /public/css/img/audio-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/audio-disable.png -------------------------------------------------------------------------------- /public/css/img/audio-enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/audio-enable.png -------------------------------------------------------------------------------- /public/css/img/audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/audio.png -------------------------------------------------------------------------------- /public/css/img/case-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/case-next.png -------------------------------------------------------------------------------- /public/css/img/case-pre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/case-pre.png -------------------------------------------------------------------------------- /public/css/img/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/cover.png -------------------------------------------------------------------------------- /public/css/img/custom_audio_close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/css/img/custom_audio_open.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/css/img/custom_video_close.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/css/img/custom_video_open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/css/img/hangup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/hangup.png -------------------------------------------------------------------------------- /public/css/img/hangup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 未命名 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/css/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/icon.png -------------------------------------------------------------------------------- /public/css/img/kick-off-hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 移除人员@2x 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/css/img/kick-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 移除人员@2x 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/css/img/loading-img.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/loading-img.gif -------------------------------------------------------------------------------- /public/css/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/loading.gif -------------------------------------------------------------------------------- /public/css/img/logi-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/logi-white.png -------------------------------------------------------------------------------- /public/css/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/logo.png -------------------------------------------------------------------------------- /public/css/img/setting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 未命名 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/css/img/share-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/share-close.png -------------------------------------------------------------------------------- /public/css/img/share-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/share-cover.png -------------------------------------------------------------------------------- /public/css/img/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/share.png -------------------------------------------------------------------------------- /public/css/img/share.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 未命名 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/css/img/sound.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/sound.gif -------------------------------------------------------------------------------- /public/css/img/triangle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/css/img/user-list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/css/img/video-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/video-disable.png -------------------------------------------------------------------------------- /public/css/img/video-enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/video-enable.png -------------------------------------------------------------------------------- /public/css/img/video-enable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 未命名 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/css/img/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/video.png -------------------------------------------------------------------------------- /public/css/img/wb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/wb.png -------------------------------------------------------------------------------- /public/css/img/wb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 未命名 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/css/img/whiteboard_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/css/img/whiteboard_close.png -------------------------------------------------------------------------------- /public/css/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0; 6 | background-color: black; 7 | color: black; 8 | } 9 | 10 | p, input, button { 11 | outline: none; 12 | } 13 | 14 | ul, li { 15 | margin: 0; 16 | padding: 0; 17 | list-style: none; 18 | } 19 | 20 | input::-webkit-outer-spin-button, 21 | input::-webkit-inner-spin-button { 22 | -webkit-appearance: none; 23 | } 24 | input[type="number"]{ 25 | -moz-appearance: textfield; 26 | } 27 | 28 | .rong-wrap { 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | width: 100%; 33 | height: 100%; 34 | overflow: auto; 35 | } 36 | 37 | .rong-login { 38 | background-image: url("./img/cover.png"); 39 | background-size: cover; 40 | background-color: #4a5267; 41 | min-width: 1000px; 42 | min-height: 760px; 43 | z-index: 10; 44 | } 45 | 46 | .rong-login .rong-login-content { 47 | position: absolute; 48 | top: 50%; 49 | left: 50%; 50 | transform: translate(-50%, -50%); 51 | width: 400px; 52 | text-align: center; 53 | } 54 | 55 | .rong-login .rong-login-logo { 56 | height: 120px; 57 | } 58 | 59 | .rong-login .rong-login-title { 60 | font-size: 32px; 61 | margin-top: 0; 62 | color: white; 63 | } 64 | 65 | .rong-login .rong-login-input { 66 | display: block; 67 | margin: 40px 0; 68 | padding: 10px 20px; 69 | width: 100%; 70 | height: 48px; 71 | border: 1px #DDDDDD solid; 72 | border-radius: 40px; 73 | background-color: white; 74 | text-align: left; 75 | font-size: 15px; 76 | color: #777; 77 | box-sizing: border-box; 78 | } 79 | 80 | .rong-login .rong-login-input::-webkit-input-placeholder { 81 | color: #999; 82 | } 83 | 84 | .rong-login .rong-opt-stream { 85 | margin: 30px 0; 86 | } 87 | 88 | .rong-login .rong-opt-checkbox + .rong-opt-checkbox { 89 | margin: 0 0 0 20px; 90 | } 91 | 92 | .rong-login .rong-opt-checkbox { 93 | display: inline-block; 94 | text-align: left; 95 | position: relative; 96 | margin: 0 20px 0 0; 97 | } 98 | 99 | .rong-login .rong-opt-checkbox input[type="checkbox"] { 100 | display: none; 101 | } 102 | 103 | .rong-login .rong-opt-checkbox input[type="radio"] { 104 | height: 14px; 105 | } 106 | 107 | .rong-login .rong-opt-checkbox input[type="checkbox"]:checked + label::after { 108 | content: '\2714'; 109 | position: absolute; 110 | top: 0; 111 | left: 0; 112 | padding: 1px 0 0 0; 113 | font-size: 18px; 114 | line-height: 15px; 115 | width: 100%; 116 | height: 100%; 117 | } 118 | 119 | .rong-login .rong-opt-checkbox label { 120 | vertical-align: middle; 121 | background-color: white; 122 | border-radius: 5px; 123 | padding: 8px; 124 | display: inline-block; 125 | position: relative; 126 | } 127 | 128 | .rong-login .rong-opt-checkbox span { 129 | margin-left: 5px; 130 | font-size: 14px; 131 | color: white; 132 | } 133 | 134 | .rong-login .rong-btn-start { 135 | width: 100%; 136 | height: 50px; 137 | border-radius: 40px; 138 | border: 1px #28d6f6 solid; 139 | background: #28d6f6; 140 | font-size: 15px; 141 | color: #FFFFFF; 142 | cursor: pointer; 143 | } 144 | 145 | .rong-login .rong-opt-resolution { 146 | position: absolute; 147 | right: 180px; 148 | top: 50px; 149 | width: 20px; 150 | height: 20px; 151 | display: inline-block; 152 | background-image: url(./img/setting.svg); 153 | background-position: 0 0; 154 | background-repeat: no-repeat; 155 | background-size: 20px 20px; 156 | margin-bottom: 3px; 157 | cursor: pointer; 158 | } 159 | 160 | .rong-login .rong-opt-resolution:hover .rong-resolution-wrap { 161 | display: block; 162 | } 163 | 164 | .rong-login .rong-resolution-wrap { 165 | display: none; 166 | position: absolute; 167 | width: 190px; 168 | left: 50%; 169 | transform: translateX(-50%); 170 | padding-top: 35px; 171 | cursor: default; 172 | } 173 | 174 | .rong-login .rong-resolution-wrap .rong-resolution-content { 175 | background-color: white; 176 | padding: 5px 15px 55px 15px; 177 | border-bottom-left-radius: 3px; 178 | border-bottom-right-radius: 3px; 179 | font-size: 15px; 180 | border-top: 4px solid #1fc0f8; 181 | } 182 | 183 | .rong-login .rong-resolution-wrap .rong-resolution-arrow { 184 | width: 0px; 185 | height: 0px; 186 | border-width: 10px; 187 | border-style: solid; 188 | border-color: transparent transparent #1fc0f8 transparent; 189 | position: absolute; 190 | top: 15px; 191 | left: 50%; 192 | transform: translate(-50%); 193 | } 194 | 195 | .rong-login .rong-resolution-wrap .rong-resolution-title { 196 | font-size: 16px; 197 | margin: 5px 0 10px 0; 198 | font-weight: normal; 199 | } 200 | 201 | .rong-login .rong-resolution-wrap ul { 202 | margin-top: 15px; 203 | } 204 | 205 | .rong-login .rong-resolution-wrap ul li { 206 | margin: 6px 0; 207 | } 208 | 209 | .rong-login .rong-resolution-wrap input[type="radio"] { 210 | display: none; 211 | } 212 | 213 | .rong-login .rong-resolution-wrap label { 214 | position: relative; 215 | width: 13px; 216 | height: 13px; 217 | display: inline-block; 218 | border: 1px solid #adb8c0; 219 | border-radius: 50%; 220 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05), inset 15px 10px -12px rgba(255, 255, 255, 0.1), inset 0px 0px 10px rgba(0, 0, 0, 0.1); 221 | vertical-align: middle; 222 | } 223 | 224 | .rong-login .rong-resolution-wrap input[type="radio"]:checked + label:after { 225 | content: ''; 226 | width: 11px; 227 | height: 11px; 228 | border-radius: 50px; 229 | position: absolute; 230 | background: #6CBF1B; 231 | left: 1px; 232 | top: 1px; 233 | font-size: 32px; 234 | } 235 | 236 | .rong-login .rong-resolution-wrap span { 237 | margin: 3px 45px 3px 5px; 238 | vertical-align: middle; 239 | } 240 | 241 | .rong-login .rong-copyright { 242 | position: absolute; 243 | bottom: 0; 244 | color: #fff; 245 | text-align: center; 246 | width: 100%; 247 | font-size: 13px; 248 | } 249 | .rong-login .rong-copyright p:nth-child(1){ 250 | display: inline-block; 251 | } 252 | .rong-login .rong-copyright p:nth-child(2){ 253 | display: inline-block; 254 | margin-left: 50px; 255 | } 256 | 257 | /* .rong-login .rong-login-roomjoin { 258 | display: block; 259 | } */ 260 | .rong-login .rong-login-telverify { 261 | display: none; 262 | } 263 | .rong-login-telverify .rong-login-sms-tip { 264 | color: #fff; 265 | font-size: 15px; 266 | margin-bottom: -20px; 267 | } 268 | .rong-login-telverify .rong-login-telNum { 269 | display: block; 270 | margin: 40px 0; 271 | padding: 10px 20px; 272 | width: 100%; 273 | height: 48px; 274 | border: 1px #DDDDDD solid; 275 | border-radius: 40px; 276 | background-color: white; 277 | text-align: left; 278 | font-size: 15px; 279 | color: #777; 280 | box-sizing: border-box; 281 | } 282 | .rong-login-telverify .rong-login-verifyCode { 283 | display: block; 284 | float:left; 285 | padding: 10px 20px; 286 | width: 60%; 287 | height: 48px; 288 | border: 1px #DDDDDD solid; 289 | border-radius: 40px; 290 | background-color: white; 291 | text-align: left; 292 | font-size: 15px; 293 | color: #777; 294 | box-sizing: border-box; 295 | } 296 | 297 | .rong-login-telverify .rong-login-verifyBtn { 298 | float: right; 299 | width: 33%; 300 | height: 50px; 301 | border-radius: 40px; 302 | border: 1px #475163 solid; 303 | background: #475163; 304 | font-size: 15px; 305 | color: #FFFFFF; 306 | cursor: pointer; 307 | } 308 | .rong-login-telverify .rong-btn-verifyLogin { 309 | float: left; 310 | width: 100%; 311 | height: 50px; 312 | margin: 30px 0; 313 | border-radius: 40px; 314 | border: 1px #28d6f6 solid; 315 | background: #28d6f6; 316 | font-size: 15px; 317 | color: #FFFFFF; 318 | cursor: pointer; 319 | } 320 | .rong-login-telverify .rong-login-verifyTips { 321 | text-align: left; 322 | padding-left: 20px; 323 | color: red; 324 | } 325 | .rong-login-systemTips { 326 | clear: both; 327 | text-align: left; 328 | color: #fff; 329 | font-size: 14px; 330 | } 331 | .rong-rtc { 332 | display: none; 333 | overflow: hidden; 334 | } 335 | 336 | .rong-rtc .rong-rtc-btn { 337 | display: inline-block; 338 | width: 35px; 339 | height: 35px; 340 | border-radius: 50%; 341 | background-position: center; 342 | background-repeat: no-repeat; 343 | background-size: cover; 344 | cursor: pointer; 345 | } 346 | 347 | .rong-rtc .rong-rtc-opt { 348 | position: absolute; 349 | top: 0; 350 | height: 53px; 351 | line-height: 53px; 352 | width: 100%; 353 | background-color: #4E6CAA; 354 | z-index: 10; 355 | color: white; 356 | text-align: left; 357 | font-size: 14px; 358 | box-sizing: border-box; 359 | padding: 0 20px; 360 | text-align: center; 361 | } 362 | 363 | .rong-rtc .rong-rtc-opt .rong-user-title { 364 | float: left; 365 | } 366 | 367 | .rong-rtc .rong-rtc-opt .rong-user-id { 368 | margin-left: 5px; 369 | } 370 | 371 | .rong-rtc .rong-rtc-opt .rong-user-timer { 372 | position: absolute; 373 | left: 300px; 374 | } 375 | 376 | .rong-rtc .rong-rtc-opt .rong-rtc-btn { 377 | float: right; 378 | position: relative; 379 | top: 50%; 380 | transform: translateY(-50%); 381 | margin: 0 13px; 382 | border: none; 383 | } 384 | 385 | .rong-rtc .rong-rtc-opt .rong-opt-hangup { 386 | background-color: #e86262; 387 | background-image: url("./img/hangup.png"); 388 | } 389 | 390 | .rong-rtc .rong-rtc-opt .rong-opt-share { 391 | background-image: url("./img/share.png"); 392 | } 393 | 394 | .rong-rtc .rong-rtc-opt .rong-opt-share-close { 395 | background-image: url("./img/share-close.png"); 396 | background-color: #fff; 397 | background-size: 38px; 398 | cursor: not-allowed; 399 | } 400 | 401 | .rong-rtc .rong-rtc-opt .rong-opt-wb { 402 | background-image: url("./img/wb.png"); 403 | } 404 | .rong-rtc .rong-rtc-opt .rong-share-closeicon { 405 | display: none; 406 | } 407 | 408 | .rong-rtc .rong-rtc-opt .rong-opt-usericon { 409 | background-image: url('./img/user-list.svg'); 410 | } 411 | 412 | .rong-rtc .rong-rtc-opt .rong-opt-videoicon-open { 413 | background-image: url('./img/custom_video_open.svg'); 414 | } 415 | 416 | .rong-rtc .rong-rtc-opt .rong-opt-videoicon-close { 417 | background-image: url('./img/custom_video_close.svg'); 418 | display: none; 419 | } 420 | 421 | .rong-rtc .rong-rtc-opt .rong-opt-audioicon-open { 422 | background-image: url('./img/custom_audio_open.svg'); 423 | } 424 | 425 | .rong-rtc .rong-rtc-opt .rong-opt-audioicon-close { 426 | background-image: url('./img/custom_audio_close.svg'); 427 | display: none; 428 | } 429 | 430 | .rong-rtc .rong-wb-box { 431 | display: none; 432 | position: absolute; 433 | left: 0; 434 | top: 0; 435 | width: 100%; 436 | height: 100%; 437 | z-index: 20; 438 | background-color: #fff; 439 | } 440 | 441 | .rong-rtc .rong-wb-box .rong-wb-close { 442 | width: 24px; 443 | height: 24px; 444 | line-height: 24px; 445 | position: absolute; 446 | top: 10px; 447 | right: 30px; 448 | border-radius: 4px; 449 | z-index: 30; 450 | background-image: url("./img/whiteboard_close.png"); 451 | cursor: pointer; 452 | } 453 | 454 | .rong-rtc .rong-wb-box .rong-whiteboard { 455 | position: absolute; 456 | left: 0; 457 | top: 0; 458 | width: 100%; 459 | height: 100%; 460 | z-index: 20; 461 | background-color: #fff; 462 | } 463 | 464 | .rong-stream-wrap { 465 | position: absolute; 466 | top: 53px; 467 | height: calc(100% - 53px); 468 | width: 100%; 469 | } 470 | 471 | .rong-stream-wrap .rong-stream-list { 472 | max-width: 1368px; 473 | height: 165px; 474 | background-color: transparent; 475 | z-index: 3; 476 | margin: calc(100vh - 165px - 121px) auto 0; 477 | /* margin-top: 205px; */ 478 | white-space: nowrap; 479 | overflow-x: auto; 480 | padding-top: 46px; 481 | } 482 | 483 | .rong-stream-list .rong-case-pre { 484 | display: none; 485 | background-image: url(./img/case-pre.png); 486 | width: 60px; 487 | background-size: 60px 60px; 488 | height: 60px; 489 | z-index: 16; 490 | position: absolute; 491 | left: 36px; 492 | cursor: pointer; 493 | background-color: #d6d5d5ad; 494 | border-radius: 50%; 495 | } 496 | 497 | .rong-stream-list .rong-case-next { 498 | display: none; 499 | background-image: url("./img/case-next.png"); 500 | width: 60px; 501 | background-size: 60px 60px; 502 | height: 60px; 503 | z-index: 16; 504 | position: absolute; 505 | right: 36px; 506 | cursor: pointer; 507 | background-color: #d6d5d5ad; 508 | border-radius: 50%; 509 | } 510 | /* .rong-stream-wrap .rong-stream-list::-webkit-scrollbar { 511 | display:none; 512 | }*/ 513 | 514 | .rong-stream-wrap .rong-stream-box { 515 | /* float: left; */ 516 | width: 120px; 517 | height: 97%; 518 | background-color: black; 519 | margin: -46px 15px; 520 | position: relative; 521 | z-index: 15; 522 | border: 1px solid #b8bdc4; 523 | overflow: hidden; 524 | display: inline-block; 525 | } 526 | 527 | .rong-stream-wrap .rong-stream-box .rong-video { 528 | position: relative; 529 | left: 0; 530 | top: 0; 531 | width: 100%; 532 | height: 100%; 533 | } 534 | 535 | .rong-stream-wrap .rong-stream-box .rong-stream-top { 536 | position: absolute; 537 | top: 0; 538 | width: 100%; 539 | height: 28px; 540 | text-align: right; 541 | z-index: 1; 542 | } 543 | .rong-stream-wrap .rong-stream-box .rong-sound { 544 | display: none; 545 | width: 28px; 546 | background: #00000047; 547 | border-radius: 4px; 548 | } 549 | .rong-stream-wrap .rong-stream-box .rong-sound-show { 550 | display: inline-block; 551 | } 552 | .rong-stream-wrap .rong-stream-box .rong-sound-hide { 553 | display: none; 554 | } 555 | 556 | .rong-stream-wrap .rong-stream-box .rong-user-name { 557 | /* display: none; */ 558 | display: inline-block; 559 | /* position: absolute; */ 560 | right: 5px; 561 | top: 5px; 562 | margin: 0; 563 | font-size: 13px; 564 | padding-right: 3px; 565 | color: white; 566 | overflow: hidden; 567 | white-space: nowrap; 568 | text-overflow: ellipsis; 569 | max-width: 60%; 570 | text-shadow: 0px 2px 4px #000; 571 | z-index: 20; 572 | line-height: 28px; 573 | } 574 | 575 | .rong-stream-wrap .rong-stream-box .rong-rtc-cover { 576 | display: none; 577 | position: absolute; 578 | top: 0; 579 | left: 0; 580 | width: 100%; 581 | height: 100%; 582 | background-color: white; 583 | } 584 | 585 | .rong-stream-wrap .rong-stream-box .rong-cover-content { 586 | position: absolute; 587 | left: 50%; 588 | top: 50%; 589 | transform: translate(-50%, -50%); 590 | text-align: center; 591 | width: 100%; 592 | } 593 | 594 | .rong-stream-wrap .rong-stream-box .rong-cover-content p { 595 | display: none; 596 | width: 100%; 597 | font-size: 17px; 598 | background-color: white; 599 | } 600 | 601 | .rong-stream-wrap .rong-stream-box .rong-audio-cover img { 602 | width: 75px; 603 | } 604 | 605 | .rong-stream-wrap .rong-stream-box .rong-share-cover img { 606 | transform: translateY(15%); 607 | width: 100px; 608 | } 609 | 610 | .rong-stream-wrap .rong-stream-box .rong-stream-opt { 611 | position: absolute; 612 | bottom: 4px; 613 | width: 100%; 614 | z-index: 10; 615 | text-align: left; 616 | padding: 0 6px; 617 | box-sizing: border-box; 618 | } 619 | 620 | .rong-stream-wrap .rong-stream-box .rong-custom-video { 621 | display: none; 622 | position: absolute; 623 | width: 100%; 624 | height: 30px; 625 | z-index: 16; 626 | background: #0e0e0ea8; 627 | bottom: 0px; 628 | text-align: center; 629 | color: #fff; 630 | font-size: 13px; 631 | line-height: 30px; 632 | overflow: hidden; 633 | text-overflow: ellipsis; 634 | } 635 | .rong-stream-wrap .rong-stream-box .rong-opt-btn { 636 | width: 24px; 637 | height: 28px; 638 | display: inline-block; 639 | background-repeat: no-repeat; 640 | background-size: cover; 641 | background-repeat: no-repeat; 642 | cursor: pointer; 643 | } 644 | 645 | .rong-stream-wrap .rong-stream-box .rong-opt-video { 646 | background-image: url("./img/video-enable.png"); 647 | } 648 | 649 | .rong-stream-wrap .rong-stream-box .rong-opt-video-disabled { 650 | display: none; 651 | cursor: not-allowed; 652 | background-image: url("./img/video-disable.png"); 653 | } 654 | 655 | .rong-stream-wrap .rong-stream-box .rong-opt-audio { 656 | background-image: url("./img/audio-enable.png"); 657 | } 658 | .rong-stream-wrap .rong-stream-box .rong-opt-audio-disabled { 659 | display: none; 660 | cursor: not-allowed; 661 | background-image: url("./img/audio-disable.png"); 662 | } 663 | .rong-stream-wrap .rong-stream-box:hover .rong-user-name { 664 | display: inline-block; 665 | } 666 | 667 | .rong-stream-wrap .rong-is-self .rong-video { 668 | transform: rotateY(180deg); 669 | } 670 | 671 | .rong-stream-wrap .rong-is-self .rong-user-name { 672 | display: inline-block; 673 | } 674 | /* .rong-stream-wrap .rong-is-self:hover .rong-user-name { 675 | display: inline-block; 676 | } */ 677 | 678 | .rong-stream-wrap .rong-is-zoom { 679 | position: absolute; 680 | left: 0; 681 | top: 0; 682 | width: 100%; 683 | height: 100%; 684 | border: none; 685 | margin: 0; 686 | z-index: 12; 687 | /* pointer-events: none; */ 688 | } 689 | 690 | .rong-stream-wrap .rong-is-zoom .rong-cover-content p { 691 | display: block; 692 | } 693 | 694 | .rong-stream-wrap .rong-is-zoom .rong-audio-cover img { 695 | width: 160px; 696 | } 697 | 698 | .rong-stream-wrap .rong-is-zoom .rong-share-cover img { 699 | width: 220px; 700 | transform: translateY(25%); 701 | } 702 | 703 | .rong-stream-wrap .rong-is-zoom .rong-stream-opt { 704 | text-align: center; 705 | margin-bottom: 10px; 706 | user-select: none; 707 | } 708 | 709 | .rong-stream-wrap .rong-is-zoom .rong-opt-btn { 710 | border-radius: 50%; 711 | background-color: rgba(0, 0, 0, 0.5); 712 | width: 45px; 713 | height: 45px; 714 | background-position: center; 715 | margin: 0 25px; 716 | } 717 | 718 | .rong-stream-wrap .rong-is-zoom .rong-user-name { 719 | font-size: 18px; 720 | } 721 | 722 | .rong-stream-wrap .rong-video-close .rong-audio-cover, .rong-stream-wrap .rong-video-self-close .rong-audio-cover, .rong-stream-wrap .rong-video-other-close .rong-audio-cover { 723 | display: block; 724 | } 725 | 726 | .rong-stream-wrap .rong-video-self-close .rong-audio-cover .rong-audiocover-title-other { 727 | display: none; 728 | } 729 | 730 | .rong-stream-wrap .rong-video-self-close .rong-audiocover-title { 731 | display: block; 732 | } 733 | 734 | .rong-stream-wrap .rong-video-self-close .rong-opt-video { 735 | background-image: url("./img/video-disable.png"); 736 | } 737 | 738 | .rong-stream-wrap .rong-audio-self-close .rong-opt-audio { 739 | background-image: url("./img/audio-disable.png"); 740 | } 741 | 742 | .rong-stream-wrap .rong-video-other-close .rong-audio-cover .rong-audiocover-title { 743 | display: none; 744 | } 745 | 746 | .rong-stream-wrap .rong-video-other-close .rong-audiocover-title-other { 747 | display: block; 748 | } 749 | 750 | .rong-stream-wrap .rong-video-other-close.rong-video-self-close .rong-audio-cover .rong-audiocover-title { 751 | display: none; 752 | } 753 | 754 | .rong-stream-wrap .rong-video-other-close.rong-video-self-close .rong-audiocover-title-other { 755 | display: block; 756 | } 757 | 758 | .rong-stream-wrap .rong-screenshare-open .rong-share-cover { 759 | z-index: 12; 760 | display: block; 761 | } 762 | 763 | .rong-user-list { 764 | display: none; 765 | position: absolute; 766 | background: #fffefe; 767 | width: 255px; 768 | height: calc(100% - 53px); 769 | z-index: 17; 770 | right: 0; 771 | top: 53px; 772 | box-shadow: 0 0 1px #ccc; 773 | } 774 | .rong-user-list .user-list-main { 775 | overflow-y: scroll; 776 | height: calc(100% - 40px); 777 | font-size: 13px; 778 | } 779 | .rong-user-list .user-list-item { 780 | border-bottom: 1px solid #ebeef5; 781 | clear: both; 782 | padding: 10px 783 | } 784 | .rong-user-list .user-list-item .online-user { 785 | width: 50%; 786 | text-align: left; 787 | /* float: left; */ 788 | display: inline-block; 789 | } 790 | 791 | .rong-user-list .user-list-item .user-join-mode { 792 | display: inline-block; 793 | margin-left: 5px; 794 | padding: 0 5px; 795 | border: 1px solid #15BFCE; 796 | background: #15BFCE; 797 | color: #fff; 798 | line-height: 18px; 799 | } 800 | .rong-user-list .user-list-item .join-video{ 801 | background: #FD7D93; 802 | border: 1px solid #FD7D93; 803 | } 804 | .rong-user-list .user-list-item .join-audio{ 805 | background: #6D81FF; 806 | border: 1px solid #6D81FF; 807 | } 808 | .rong-user-list .user-list-item .user-kick-off { 809 | width: 18px; 810 | height: 18px; 811 | background-image: url(./img/kick-off.svg); 812 | background-size: 100% 100%; 813 | display: inline-block; 814 | float: right; 815 | } 816 | .rong-user-list .user-list-item .user-manage-name { 817 | float: right; 818 | } 819 | .rong-user-list .user-list-item .user-kick-off:hover { 820 | background-image: url(./img/kick-off-hover.svg); 821 | } 822 | .rong-user-list .user-list-main .user-list-item .user-list-close { 823 | text-align: center; 824 | float: right; 825 | height: 23px; 826 | } 827 | .rong-user-list .rong-user-list-title { 828 | border-bottom: 1px solid #ececed; 829 | height: 35px; 830 | line-height: 35px; 831 | background: #fff; 832 | font-size: 13px; 833 | } 834 | .rong-user-list .rong-user-list-title .online-user { 835 | padding-left: 10px; 836 | float: left; 837 | color: #3A91F3; 838 | } 839 | .rong-user-list .rong-user-list-title .online-user span,.rong-user-list .rong-user-list-title .user-list-close span{ 840 | border-bottom: 2px solid #3a91f3; 841 | padding-bottom: 8px; 842 | } 843 | .rong-user-list .rong-user-list-title .user-list-close { 844 | float: right; 845 | cursor: pointer; 846 | padding-right: 10px; 847 | color: #3A91F3; 848 | } 849 | 850 | .rong-user-customvideo ,.rong-user-customaudio { 851 | display: none; 852 | position: absolute; 853 | width: 238px; 854 | /* height: 108px; */ 855 | top: 53px; 856 | right: 105px; 857 | z-index: 15; 858 | text-align: center; 859 | padding: 0 10px; 860 | } 861 | .rong-user-customaudio { 862 | right: 226px; 863 | } 864 | .rong-user-customvideo .customvideo-triangle ,.rong-user-customaudio .customaudio-triangle{ 865 | width: 238px; 866 | height: 9px; 867 | background-image: url("./img/triangle.svg"); 868 | background-repeat: no-repeat; 869 | background-position-x: 112px; 870 | } 871 | .rong-user-customvideo .customvideo-list, .rong-user-customaudio .customaudio-list { 872 | width: 238px; 873 | /* height: 98px; */ 874 | background: #fff; 875 | border-radius: 9.8px; 876 | color: #333; 877 | } 878 | .rong-user-customvideo .customvideo-list li ,.rong-user-customaudio .customaudio-list li { 879 | height: 48px; 880 | line-height: 48px; 881 | cursor: pointer; 882 | } 883 | .rong-user-customvideo .customvideo-list li:nth-child(1) , .rong-user-customvideo .customvideo-list li:nth-child(1) { 884 | border-bottom: .8px solid #eaeaea; 885 | } 886 | .rong-alert-box { 887 | position: absolute; 888 | left: 0; 889 | top: 0; 890 | width: 100%; 891 | height: 100%; 892 | z-index: 200; 893 | } 894 | 895 | .rong-alert-box .rong-alert { 896 | position: absolute; 897 | left: 50%; 898 | top: 50%; 899 | transform: translate(-50%, -50%); 900 | width: 330px; 901 | min-height: 174px; 902 | border: 1px solid #e0e3e1; 903 | border-radius: 5px; 904 | background-color: white; 905 | padding: 10px 20px; 906 | box-shadow: #484848 1px 2px 30px 0px; 907 | } 908 | 909 | .rong-alert-box .rong-alert-title { 910 | font-size: 15px; 911 | } 912 | 913 | .rong-alert-box .rong-alert-content { 914 | text-align: center; 915 | font-size: 16px; 916 | margin: 40px 0; 917 | } 918 | 919 | .rong-alert-box .rong-alert-btn-box { 920 | text-align: right; 921 | } 922 | 923 | .rong-alert-box .rong-alert-btn-box input[type="button"] { 924 | font-size: 12px; 925 | display: inline-block; 926 | min-width: 72px; 927 | height: 30px; 928 | border: 1px solid #a5a5a5; 929 | border-radius: 20px; 930 | margin-left: 8px; 931 | background-color: white; 932 | cursor: pointer; 933 | } 934 | 935 | .rong-alert-box .rong-alert-btn-box input[type="button"] + input[type="button"] { 936 | border: none; 937 | background-color: #4e6caa; 938 | color: white; 939 | } 940 | 941 | .rong-seal-toast { 942 | position: absolute; 943 | color: #fff; 944 | z-index: 12; 945 | width: 100%; 946 | top: 50%; 947 | text-align: center; 948 | } 949 | .rong-seal-toast-text { 950 | background: #1312124a; 951 | padding: 4px 10px; 952 | border-radius: 3px; 953 | } 954 | .rong-seal-tips { 955 | position: absolute; 956 | color: #fff; 957 | z-index: 12; 958 | width: 100%; 959 | top: 10%; 960 | text-align: center; 961 | } 962 | .rong-seal-tips-text { 963 | background: #1312124a; 964 | padding: 4px 10px; 965 | border-radius: 3px; 966 | } 967 | 968 | .rong-loading { 969 | background: #4953654a; 970 | width: 100%; 971 | height: 100%; 972 | position: absolute; 973 | /* left: 38%; */ 974 | top: 0; 975 | z-index: 13; 976 | text-align: center; 977 | /* border-radius: 20px; */ 978 | } 979 | 980 | .rong-loading-img { 981 | background-image: url("./img/loading-img.gif"); 982 | background-size: 47px 10px; 983 | background-repeat: no-repeat; 984 | display: inline-block; 985 | width: 60px; 986 | height: 49px; 987 | margin-top: 425px; 988 | } 989 | .rong-loading-text { 990 | color:#cccccc9e; 991 | } 992 | 993 | .rong-btn-loading { 994 | display: none; 995 | width: 100%; 996 | height: 50px; 997 | border-radius: 40px; 998 | border: 1px #28d6f6 solid; 999 | background: #28d6f6; 1000 | font-size: 15px; 1001 | color: #FFFFFF; 1002 | cursor: pointer; 1003 | position: relative; 1004 | line-height: 50px; 1005 | } 1006 | 1007 | .rong-btn-loading img { 1008 | width: 22px; 1009 | margin: 0 5px 0 -5px; 1010 | position: absolute; 1011 | left: 151px; 1012 | top: 14px; 1013 | } 1014 | 1015 | .rong-videoResolution { 1016 | position: absolute; 1017 | font-size: 12px; 1018 | z-index: 13; 1019 | bottom: 0; 1020 | right: 0; 1021 | color: #eae5e5; 1022 | background: #150101; 1023 | border-radius: 2px; 1024 | display: none; 1025 | } 1026 | 1027 | 1028 | .rong-table-box { 1029 | display: none; 1030 | width: 500px; 1031 | height: auto; 1032 | min-height: 30px; 1033 | position: absolute; 1034 | top: 100px; 1035 | left: calc(50% - 350px); 1036 | color: #cecece; 1037 | z-index: 18; 1038 | } 1039 | 1040 | .rong-table-box .rong-table { 1041 | border: 1px solid #ccc; 1042 | } 1043 | 1044 | .rong-table-box .rong-table tr td { 1045 | border-left: 1px solid #ccc; 1046 | border-top: 1px solid #ccc; 1047 | padding: 5px; 1048 | } 1049 | 1050 | .rong-table-box .rong-table tr td:nth-child(1) { 1051 | border-left: 0px solid #ccc; 1052 | } 1053 | .rong-table-box .rong-table tr td:nth-child(2) { 1054 | border-left: 0px solid #ccc; 1055 | } 1056 | 1057 | .rong-table-box .rong-table thead tr td { 1058 | border-top: 0px solid #ccc; 1059 | } 1060 | .rong-table-box .rong-table tbody tr td span{ 1061 | width: 100px; 1062 | display: inline-block; 1063 | overflow: hidden; 1064 | text-overflow: ellipsis; 1065 | white-space: nowrap; 1066 | } 1067 | /* .rong-table-box .rong-table tbody tr td:nth-of-type(n+1) span { 1068 | width: 100px; 1069 | } */ 1070 | .rong-login .rong-resolution-wrap .rong-setting span{ 1071 | margin:0; 1072 | } 1073 | .rong-setting{ 1074 | margin: 6px 0; 1075 | } 1076 | .rong-login .rong-resolution-wrap .rong-setting input{ 1077 | width: 70px; 1078 | display: inline-block; 1079 | margin-left: 10px; 1080 | } -------------------------------------------------------------------------------- /public/css/main.min.css: -------------------------------------------------------------------------------- 1 | html,body{height:100%;width:100%;margin:0;padding:0;background-color:black;color:black}p,input,button{outline:none}ul,li{margin:0;padding:0;list-style:none}.rong-wrap{position:absolute;top:0;left:0;width:100%;height:100%;overflow:auto}.rong-login{background-image:url("./img/cover.png");background-size:cover;background-color:#4a5267;min-width:1000px;z-index:10}.rong-login .rong-login-content{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:400px;text-align:center}.rong-login .rong-login-logo{height:120px}.rong-login .rong-login-title{font-size:32px;margin-top:0;color:white}.rong-login .rong-login-input{display:block;margin:40px 0;padding:10px 20px;width:100%;height:48px;border:1px #DDDDDD solid;border-radius:40px;background-color:white;text-align:left;font-size:15px;color:#777;box-sizing:border-box}.rong-login .rong-login-input::-webkit-input-placeholder{color:#999}.rong-login .rong-opt-stream{margin:30px 0}.rong-login .rong-opt-checkbox+.rong-opt-checkbox{margin:0 0 0 20px}.rong-login .rong-opt-checkbox{display:inline-block;text-align:left;position:relative;margin:0 20px 0 0}.rong-login .rong-opt-checkbox input[type="checkbox"]{display:none}.rong-login .rong-opt-checkbox input[type="checkbox"]:checked+label::after{content:'\2714';position:absolute;top:0;left:0;padding:1px 0 0 0;font-size:20px;line-height:19px;width:100%;height:100%}.rong-login .rong-opt-checkbox label{vertical-align:middle;background-color:white;border-radius:5px;padding:8px;display:inline-block;position:relative}.rong-login .rong-opt-checkbox span{margin-left:5px;font-size:14px;color:white}.rong-login .rong-btn-start{width:100%;height:50px;border-radius:40px;border:1px #28d6f6 solid;background:#28d6f6;font-size:15px;color:#FFFFFF;cursor:pointer}.rong-login .rong-opt-resolution{position:absolute;right:180px;top:50px;width:20px;height:20px;display:inline-block;background-image:url(./img/setting.svg);background-position:0 0;background-repeat:no-repeat;background-size:20px 20px;margin-bottom:3px}.rong-login .rong-opt-resolution:hover .rong-resolution-wrap{display:block}.rong-login .rong-resolution-wrap{display:none;position:absolute;width:190px;left:50%;transform:translateX(-50%);padding-top:35px;cursor:default}.rong-login .rong-resolution-wrap .rong-resolution-content{background-color:white;padding:5px 15px 55px 15px;border-bottom-left-radius:3px;border-bottom-right-radius:3px;font-size:15px;border-top:4px solid #1fc0f8}.rong-login .rong-resolution-wrap .rong-resolution-arrow{width:0px;height:0px;border-width:10px;border-style:solid;border-color:transparent transparent #1fc0f8 transparent;position:absolute;top:15px;left:50%;transform:translate(-50%)}.rong-login .rong-resolution-wrap .rong-resolution-title{font-size:16px;margin:5px 0 10px 0;font-weight:normal}.rong-login .rong-resolution-wrap ul{margin-top:15px}.rong-login .rong-resolution-wrap ul li{margin:6px 0}.rong-login .rong-resolution-wrap input[type="radio"]{display:none}.rong-login .rong-resolution-wrap label{position:relative;width:13px;height:13px;display:inline-block;border:1px solid #adb8c0;border-radius:50%;box-shadow:0 1px 2px rgba(0,0,0,0.05),inset 0px -15px 10px -12px rgba(0,0,0,0.05),inset 15px 10px -12px rgba(255,255,255,0.1),inset 0px 0px 10px rgba(0,0,0,0.1);vertical-align:middle}.rong-login .rong-resolution-wrap input[type="radio"]:checked+label:after{content:'';width:11px;height:11px;border-radius:50px;position:absolute;background:#6CBF1B;left:1px;top:1px;font-size:32px}.rong-login .rong-resolution-wrap span{margin:3px 45px 3px 5px;vertical-align:middle}.rong-rtc{display:none;overflow:hidden}.rong-rtc .rong-rtc-btn{display:inline-block;width:35px;height:35px;border-radius:50%;background-position:center;background-repeat:no-repeat;background-size:cover;cursor:pointer}.rong-rtc .rong-rtc-opt{position:absolute;top:0;height:53px;line-height:53px;width:100%;background-color:#4E6CAA;z-index:10;color:white;text-align:left;font-size:14px;box-sizing:border-box;padding:0 20px;text-align:center}.rong-rtc .rong-rtc-opt .rong-user-title{float:left}.rong-rtc .rong-rtc-opt .rong-rtc-btn{float:right;position:relative;top:50%;transform:translateY(-50%);margin:0 13px}.rong-rtc .rong-rtc-opt .rong-opt-hangup{background-color:#e86262;background-image:url("./img/hangup.png")}.rong-rtc .rong-rtc-opt .rong-opt-share{background-image:url("./img/share.png")}.rong-rtc .rong-rtc-opt .rong-opt-wb{background-image:url("./img/wb.png")}.rong-rtc .rong-wb-box{display:none;position:absolute;left:0;top:0;width:100%;height:100%;z-index:20;background-color:black}.rong-rtc .rong-wb-box .rong-wb-close{width:24px;height:24px;line-height:24px;position:absolute;top:10px;right:30px;border-radius:4px;z-index:30;background-image:url("./img/whiteboard_close.png");cursor:pointer}.rong-rtc .rong-wb-box .rong-whiteboard{position:absolute;left:0;top:0;width:100%;height:100%;z-index:20;background-color:black}.rong-stream-wrap{position:absolute;top:53px;height:calc(100% - 53px);width:100%}.rong-stream-wrap .rong-stream-list{max-width:100%;height:165px;background-color:transparent;z-index:3;margin-top:calc(100vh - 165px - 70px)}.rong-stream-wrap .rong-stream-box{float:left;width:120px;height:100%;background-color:black;margin:0 15px;position:relative;z-index:15;border:1px solid #b8bdc4;overflow:hidden}.rong-stream-wrap .rong-stream-box .rong-video{position:relative;left:0;top:0;width:100%;height:100%}.rong-stream-wrap .rong-stream-box .rong-user-name{display:none;position:absolute;right:5px;top:5px;margin:0;font-size:13px;padding-right:3px;color:white;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;max-width:60%;text-shadow:0px 2px 4px #000;z-index:20}.rong-stream-wrap .rong-stream-box .rong-rtc-cover{display:none;position:absolute;top:0;left:0;width:100%;height:100%;background-color:white}.rong-stream-wrap .rong-stream-box .rong-cover-content{position:absolute;left:50%;top:50%;transform:translate(-50%, -50%);text-align:center;width:100%}.rong-stream-wrap .rong-stream-box .rong-cover-content p{display:none;width:100%;font-size:17px;background-color:white}.rong-stream-wrap .rong-stream-box .rong-audio-cover img{width:75px}.rong-stream-wrap .rong-stream-box .rong-share-cover img{transform:translateY(15%);width:100px}.rong-stream-wrap .rong-stream-box .rong-stream-opt{position:absolute;bottom:4px;width:100%;z-index:10;text-align:left;padding:0 6px;box-sizing:border-box}.rong-stream-wrap .rong-stream-box .rong-opt-btn{width:24px;height:28px;display:inline-block;background-repeat:no-repeat;background-size:cover;background-repeat:no-repeat;cursor:pointer}.rong-stream-wrap .rong-stream-box .rong-opt-video{background-image:url("./img/video-enable.png")}.rong-stream-wrap .rong-stream-box .rong-opt-audio{background-image:url("./img/audio-enable.png")}.rong-stream-wrap .rong-stream-box:hover .rong-user-name{display:inline-block}.rong-stream-wrap .rong-is-self .rong-video{transform:rotateY(180deg)}.rong-stream-wrap .rong-is-self .rong-user-name{display:inline-block}.rong-stream-wrap .rong-is-zoom{position:absolute;left:0;top:0;width:100%;height:100%;border:none;margin:0;z-index:12}.rong-stream-wrap .rong-is-zoom video{transform:rotateY(180deg)}.rong-stream-wrap .rong-is-zoom .rong-cover-content p{display:block}.rong-stream-wrap .rong-is-zoom .rong-audio-cover img{width:160px}.rong-stream-wrap .rong-is-zoom .rong-share-cover img{width:220px;transform:translateY(25%)}.rong-stream-wrap .rong-is-zoom .rong-stream-opt{text-align:center;margin-bottom:10px}.rong-stream-wrap .rong-is-zoom .rong-opt-btn{border-radius:50%;background-color:rgba(0,0,0,0.5);width:45px;height:45px;background-position:center;margin:0 25px}.rong-stream-wrap .rong-is-zoom .rong-user-name{font-size:18px}.rong-stream-wrap .rong-video-close .rong-audio-cover,.rong-stream-wrap .rong-video-self-close .rong-audio-cover,.rong-stream-wrap .rong-video-other-close .rong-audio-cover{display:block}.rong-stream-wrap .rong-video-self-close .rong-audio-cover .rong-audiocover-title-other{display:none}.rong-stream-wrap .rong-video-self-close .rong-audiocover-title{display:block}.rong-stream-wrap .rong-video-self-close .rong-opt-video{background-image:url("./img/video-disable.png")}.rong-stream-wrap .rong-audio-self-close .rong-opt-audio{background-image:url("./img/audio-disable.png")}.rong-stream-wrap .rong-video-other-close .rong-audio-cover .rong-audiocover-title{display:none}.rong-stream-wrap .rong-video-other-close .rong-audiocover-title-other{display:block}.rong-stream-wrap .rong-video-other-close.rong-video-self-close .rong-audio-cover .rong-audiocover-title{display:none}.rong-stream-wrap .rong-video-other-close.rong-video-self-close .rong-audiocover-title-other{display:block}.rong-stream-wrap .rong-screenshare-open .rong-share-cover{z-index:12;display:block}.rong-alert-box{position:absolute;left:0;top:0;width:100%;height:100%;z-index:200}.rong-alert-box .rong-alert{position:absolute;left:50%;top:50%;transform:translate(-50%, -50%);width:330px;min-height:174px;border:1px solid #e0e3e1;border-radius:5px;background-color:white;padding:10px 20px;box-shadow:#484848 1px 2px 30px 0px}.rong-alert-box .rong-alert-title{font-size:15px}.rong-alert-box .rong-alert-content{text-align:center;font-size:16px;margin:40px 0}.rong-alert-box .rong-alert-btn-box{text-align:right}.rong-alert-box .rong-alert-btn-box input[type="button"]{font-size:12px;display:inline-block;min-width:72px;height:30px;border:1px solid #a5a5a5;border-radius:20px;margin-left:8px;background-color:white;cursor:pointer}.rong-alert-box .rong-alert-btn-box input[type="button"]+input[type="button"]{border:none;background-color:#4e6caa;color:white} 2 | -------------------------------------------------------------------------------- /public/css/main.scss: -------------------------------------------------------------------------------- 1 | $loginBackColor: rgb(74, 82, 103); 2 | $loginBackImg: url('./img/cover.png'); 3 | 4 | html, body { 5 | height: 100%; 6 | width: 100%; 7 | margin: 0; 8 | padding: 0; 9 | background-color: black; 10 | color: black; 11 | } 12 | 13 | p, input, button { 14 | // margin: 5px 0; 15 | outline: none; 16 | } 17 | 18 | ul, li { 19 | margin: 0; 20 | padding: 0; 21 | list-style: none; 22 | } 23 | 24 | .rong-wrap { 25 | position: absolute; 26 | top: 0; 27 | left: 0; 28 | width: 100%; 29 | height: 100%; 30 | overflow: auto; 31 | } 32 | 33 | .rong-login { 34 | background-image: $loginBackImg; 35 | background-size: cover; 36 | background-color: $loginBackColor; 37 | min-width: 1000px; 38 | z-index: 10; 39 | .rong-login-content { 40 | position: absolute; 41 | top: 50%; 42 | left: 50%; 43 | transform: translate(-50%, -50%); 44 | width: 400px; 45 | text-align: center; 46 | } 47 | .rong-login-logo { 48 | height: 120px; 49 | } 50 | .rong-login-title { 51 | font-size: 32px; 52 | margin-top: 0; 53 | color: white; 54 | } 55 | .rong-login-input { 56 | display: block; 57 | margin: 40px 0; 58 | padding: 10px 20px; 59 | width: 100%; 60 | height: 48px; 61 | border: 1px #DDDDDD solid; 62 | border-radius: 40px; 63 | background-color: white; 64 | text-align: left; 65 | font-size: 15px; 66 | color: #777; 67 | box-sizing: border-box; 68 | } 69 | .rong-login-input::-webkit-input-placeholder { 70 | color: #999; 71 | } 72 | .rong-opt-stream { 73 | margin: 30px 0; 74 | } 75 | .rong-opt-checkbox + .rong-opt-checkbox { 76 | margin: 0 0 0 20px; 77 | } 78 | .rong-opt-checkbox { 79 | display: inline-block; 80 | text-align: left; 81 | position: relative; 82 | margin: 0 20px 0 0; 83 | input[type="checkbox"] { 84 | display: none; 85 | } 86 | input[type="checkbox"]:checked + label::after { 87 | content: '\2714'; 88 | position: absolute; 89 | top: 0; 90 | left: 0; 91 | padding: 1px 0 0 0; 92 | font-size: 20px; 93 | line-height: 19px; 94 | width: 100%; 95 | height: 100%; 96 | } 97 | label { 98 | vertical-align: middle; 99 | background-color: white; 100 | border-radius: 5px; 101 | padding: 8px; 102 | display: inline-block; 103 | position: relative; 104 | } 105 | span { 106 | margin-left: 5px; 107 | font-size: 14px; 108 | color: white; 109 | } 110 | } 111 | .rong-btn-start { 112 | width: 100%; 113 | height: 50px; 114 | border-radius: 40px; 115 | border: 1px rgb(40, 214, 246) solid; 116 | background: rgb(40, 214, 246); 117 | font-size: 15px; 118 | color: #FFFFFF; 119 | cursor: pointer; 120 | } 121 | .rong-opt-resolution { 122 | position: absolute; 123 | right: 180px; 124 | top: 50px; 125 | width: 20px; 126 | height: 20px; 127 | display: inline-block; 128 | background-image: url(./img/setting.svg); 129 | background-position: 0 0; 130 | background-repeat: no-repeat; 131 | background-size: 20px 20px; 132 | margin-bottom: 3px; 133 | } 134 | .rong-opt-resolution:hover .rong-resolution-wrap { 135 | display: block; 136 | } 137 | .rong-resolution-wrap { 138 | display: none; 139 | position: absolute; 140 | width: 190px; 141 | left: 50%; 142 | transform: translateX(-50%); 143 | padding-top: 35px; 144 | cursor: default; 145 | .rong-resolution-content { 146 | background-color: white; 147 | padding: 5px 15px 55px 15px; 148 | border-bottom-left-radius: 3px; 149 | border-bottom-right-radius: 3px; 150 | font-size: 15px; 151 | border-top: 4px solid rgb(31, 192, 248); 152 | } 153 | .rong-resolution-arrow { 154 | width: 0px; 155 | height: 0px; 156 | border-width: 10px; 157 | border-style: solid; 158 | border-color: transparent transparent rgb(31, 192, 248) transparent; 159 | position: absolute; 160 | top: 15px; 161 | left: 50%; 162 | transform: translate(-50%); 163 | } 164 | .rong-resolution-title { 165 | font-size: 16px; 166 | margin: 5px 0 10px 0; 167 | font-weight: normal; 168 | } 169 | ul { 170 | margin-top: 15px; 171 | } 172 | ul li { 173 | margin: 6px 0; 174 | } 175 | input[type="radio"] { 176 | display: none; 177 | } 178 | label { 179 | position: relative; 180 | width: 13px; 181 | height: 13px; 182 | display: inline-block; 183 | border: 1px solid #adb8c0; 184 | border-radius: 50%; 185 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), inset 0px -15px 10px -12px 186 | rgba(0, 0, 0, 0.05), inset 15px 10px -12px rgba(255, 255, 255, 0.1), 187 | inset 0px 0px 10px rgba(0, 0, 0, 0.1); 188 | vertical-align: middle; 189 | } 190 | input[type="radio"]:checked + label:after { 191 | content: ''; 192 | width: 11px; 193 | height: 11px; 194 | border-radius: 50px; 195 | position: absolute; 196 | background: #6CBF1B; 197 | left: 1px; 198 | top: 1px; 199 | font-size: 32px; 200 | } 201 | span { 202 | margin: 3px 45px 3px 5px; 203 | vertical-align: middle; 204 | } 205 | } 206 | } 207 | 208 | .rong-rtc { 209 | display: none; 210 | overflow: hidden; 211 | .rong-rtc-btn { 212 | display: inline-block; 213 | width: 35px; 214 | height: 35px; 215 | border-radius: 50%; 216 | background-position: center; 217 | background-repeat: no-repeat; 218 | background-size: cover; 219 | cursor: pointer; 220 | } 221 | .rong-rtc-opt { 222 | position: absolute; 223 | top: 0; 224 | height: 53px; 225 | line-height: 53px; 226 | width: 100%; 227 | background-color: #4E6CAA; 228 | z-index: 10; 229 | color: white; 230 | text-align: left; 231 | font-size: 14px; 232 | box-sizing: border-box; 233 | padding: 0 20px; 234 | text-align: center; 235 | .rong-user-title { 236 | float: left; 237 | } 238 | .rong-rtc-btn { 239 | float: right; 240 | position: relative; 241 | top: 50%; 242 | transform: translateY(-50%); 243 | margin: 0 13px; 244 | } 245 | .rong-opt-hangup { 246 | background-color: rgb(232, 98, 98); 247 | background-image: url('./img/hangup.png'); 248 | } 249 | .rong-opt-share { 250 | background-image: url('./img/share.png'); 251 | } 252 | .rong-opt-wb { 253 | background-image: url('./img/wb.png'); 254 | } 255 | } 256 | .rong-wb-box { 257 | display: none; 258 | position: absolute; 259 | left: 0; 260 | top: 0; 261 | width: 100%; 262 | height: 100%; 263 | z-index: 20; 264 | background-color: black; 265 | .rong-wb-close { 266 | width: 24px; 267 | height: 24px; 268 | line-height: 24px; 269 | position: absolute; 270 | top: 10px; 271 | right: 30px; 272 | border-radius: 4px; 273 | z-index: 30; 274 | background-image: url('./img/whiteboard_close.png'); 275 | cursor: pointer; 276 | } 277 | .rong-whiteboard { 278 | position: absolute; 279 | left: 0; 280 | top: 0; 281 | width: 100%; 282 | height: 100%; 283 | z-index: 20; 284 | background-color: black; 285 | } 286 | } 287 | } 288 | .rong-stream-wrap { 289 | position: absolute; 290 | top: 53px; 291 | height: calc(100% - 53px); 292 | width: 100%; 293 | .rong-stream-list { 294 | max-width: 100%; 295 | height: 165px; 296 | background-color: transparent; 297 | z-index: 3; 298 | margin-top: calc(100vh - 165px - 70px); 299 | } 300 | .rong-stream-box { 301 | float: left; 302 | width: 120px; 303 | height: 100%; 304 | background-color: black; 305 | margin: 0 15px; 306 | position: relative; 307 | z-index: 15; 308 | border: 1px solid rgb(184, 189, 196); 309 | overflow: hidden; 310 | .rong-video { 311 | position: relative; 312 | left: 0; 313 | top: 0; 314 | width: 100%; 315 | height: 100%; 316 | } 317 | .rong-user-name { 318 | display: none; 319 | position: absolute; 320 | right: 5px; 321 | top: 5px; 322 | margin: 0; 323 | font-size: 13px; 324 | padding-right: 3px; 325 | color: white; 326 | overflow: hidden; 327 | white-space: nowrap; 328 | text-overflow: ellipsis; 329 | max-width: 60%; 330 | text-shadow: 0px 2px 4px #000; 331 | z-index: 20; 332 | } 333 | .rong-rtc-cover { 334 | display: none; 335 | position: absolute; 336 | top: 0; 337 | left: 0; 338 | width: 100%; 339 | height: 100%; 340 | background-color: white; 341 | } 342 | .rong-cover-content { 343 | position: absolute; 344 | left: 50%; 345 | top: 50%; 346 | transform: translate(-50%, -50%); 347 | text-align: center; 348 | width: 100%; 349 | p { 350 | display: none; 351 | width: 100%; 352 | font-size: 17px; 353 | background-color: white; 354 | } 355 | } 356 | .rong-audio-cover img { 357 | width: 75px; 358 | } 359 | .rong-share-cover img { 360 | transform: translateY(15%); 361 | width: 100px; 362 | } 363 | .rong-stream-opt { 364 | position: absolute; 365 | bottom: 4px; 366 | width: 100%; 367 | z-index: 10; 368 | text-align: left; 369 | padding: 0 6px; 370 | box-sizing: border-box; 371 | } 372 | .rong-opt-btn { 373 | width: 24px; 374 | height: 28px; 375 | display: inline-block; 376 | background-repeat: no-repeat; 377 | background-size: cover; 378 | background-repeat: no-repeat; 379 | cursor: pointer; 380 | } 381 | .rong-opt-video { 382 | background-image: url('./img/video-enable.png'); 383 | } 384 | .rong-opt-audio { 385 | background-image: url('./img/audio-enable.png'); 386 | } 387 | } 388 | .rong-stream-box:hover .rong-user-name { 389 | display: inline-block; 390 | } 391 | .rong-is-self { 392 | .rong-video { 393 | transform: rotateY(180deg); 394 | } 395 | .rong-user-name { 396 | display: inline-block; 397 | } 398 | } 399 | .rong-is-zoom { 400 | position: absolute; 401 | left: 0; 402 | top: 0; 403 | width: 100%; 404 | height: 100%; 405 | border: none; 406 | margin: 0; 407 | z-index: 12; 408 | video { 409 | // object-fit: cover; 410 | transform: rotateY(180deg); 411 | } 412 | .rong-cover-content { 413 | p { 414 | display: block; 415 | } 416 | } 417 | .rong-audio-cover img { 418 | width: 160px; 419 | } 420 | .rong-share-cover img { 421 | width: 220px; 422 | transform: translateY(25%); 423 | } 424 | .rong-stream-opt { 425 | text-align: center; 426 | margin-bottom: 10px; 427 | } 428 | .rong-opt-btn { 429 | border-radius: 50%; 430 | background-color: rgba(0, 0, 0, 0.5); 431 | width: 45px; 432 | height: 45px; 433 | background-position: center; 434 | margin: 0 25px; 435 | } 436 | .rong-user-name { 437 | font-size: 18px; 438 | } 439 | } 440 | .rong-video-close { 441 | .rong-audio-cover { 442 | display: block; 443 | } 444 | } 445 | .rong-video-self-close { 446 | @extend .rong-video-close; 447 | .rong-audio-cover .rong-audiocover-title-other { 448 | display: none; 449 | } 450 | .rong-audiocover-title { 451 | display: block; 452 | } 453 | .rong-opt-video { 454 | background-image: url('./img/video-disable.png'); 455 | } 456 | } 457 | .rong-audio-self-close { 458 | .rong-opt-audio { 459 | background-image: url('./img/audio-disable.png'); 460 | } 461 | } 462 | .rong-video-other-close { 463 | @extend .rong-video-close; 464 | .rong-audio-cover .rong-audiocover-title { 465 | display: none; 466 | } 467 | .rong-audiocover-title-other { 468 | display: block; 469 | } 470 | } 471 | .rong-video-other-close.rong-video-self-close { 472 | @extend .rong-video-close; 473 | .rong-audio-cover .rong-audiocover-title { 474 | display: none; 475 | } 476 | .rong-audiocover-title-other { 477 | display: block; 478 | } 479 | } 480 | .rong-screenshare-open .rong-share-cover{ 481 | z-index: 12; 482 | display: block; 483 | } 484 | } 485 | 486 | .rong-alert-box { 487 | position: absolute; 488 | left: 0; 489 | top: 0; 490 | width: 100%; 491 | height: 100%; 492 | z-index: 200; 493 | .rong-alert { 494 | position: absolute; 495 | left: 50%; 496 | top: 50%; 497 | transform: translate(-50%, -50%); 498 | width: 330px; 499 | min-height: 174px; 500 | border: 1px solid rgb(224, 227, 225); 501 | border-radius: 5px; 502 | background-color: white; 503 | padding: 10px 20px; 504 | box-shadow: rgb(72, 72, 72) 1px 2px 30px 0px; 505 | } 506 | .rong-alert-title { 507 | font-size: 15px; 508 | } 509 | .rong-alert-content { 510 | text-align: center; 511 | font-size: 16px; 512 | margin: 40px 0; 513 | } 514 | .rong-alert-btn-box { 515 | text-align: right; 516 | input[type="button"] { 517 | font-size: 12px; 518 | display: inline-block; 519 | min-width: 72px; 520 | height: 30px; 521 | border: 1px solid rgb(165, 165, 165); 522 | border-radius: 20px; 523 | margin-left: 8px; 524 | background-color: white; 525 | cursor: pointer; 526 | } 527 | input[type="button"] + input[type="button"] { 528 | border: none; 529 | background-color: rgb(78, 108, 170); 530 | color: white; 531 | } 532 | } 533 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | SealRTC 21 | 22 | 23 | 24 | 25 | 26 |
27 | 66 |
67 |
68 | 72 |
73 |
74 |
75 |

网络探测

76 |
77 |
78 |

与会者

79 |
80 |
81 | 82 | 128 | 129 | 135 | 136 | 164 | 165 | 177 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 |
193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /public/js/common.js: -------------------------------------------------------------------------------- 1 | (function (dependencies) { 2 | var win = dependencies.win, 3 | RongSeal = dependencies.RongSeal, 4 | utils = RongSeal.utils, 5 | Dom = utils.Dom, 6 | addClass = Dom.addClass, 7 | removeClass = Dom.removeClass; 8 | var eventEmitter = new utils.EventEmitter(); 9 | var EventName = { 10 | NETWORK_ERROR: 'network_error' 11 | }; 12 | var RongRTCPageTemp = Dom.getById('RongRTC').innerText; //RTC主页面 13 | var StreamListTemp = Dom.getById('RongStreamList').innerText; // 音视频列表展示模板 14 | var StreamBoxTemp = Dom.getById('RongStreamBox').innerText; // 单个音视频流展示模板 15 | var AlertTemp = Dom.getById('RongAlert').innerText; // 提示弹框展示模板 16 | var TipsTemp = Dom.getById('RongTips').innerText; // 提示弹框展示模板 17 | 18 | var OptClassName = { 19 | isSelf: 'rong-is-self', 20 | IS_ZOOM: 'rong-is-zoom', 21 | CLOSE_VIDEO_BY_SELF: 'rong-video-self-close', 22 | CLOSE_AUDIO_BY_SELF: 'rong-audio-self-close', 23 | CLOSE_VIDEO_BY_OTHER: 'rong-video-other-close', 24 | OPEN_SCREENSHARE: 'rong-screenshare-open', 25 | FLIP_SCREENSHARE: '.rong-is-screenshare', 26 | }; 27 | 28 | /** 29 | * 设置多语言 30 | * @param {object} params 31 | * @param {string} params.dom 只设置该节点下多语言 32 | * @param {string} params.selector 只设置该选择器下多语言, 若已设置 dom, 则在 dom 基础上再选择 selector 生效 33 | */ 34 | function setLocale(params) { 35 | params = params || {}; 36 | var lang = win.document.body.lang, 37 | locale = RongSeal.locale[lang], // locale 数据值 38 | prefix = 'lang'; 39 | 40 | if (!locale) { 41 | return; 42 | } 43 | var selector = params.selector || '', 44 | dom = params.dom || win.document; 45 | 46 | for (var langKey in locale) { 47 | var lowerLangKey = langKey.toLocaleLowerCase(), 48 | attributeTpl = '{prefix}-{key}', 49 | selectorTpl = '{selector} *[{attribute}]'; 50 | var attribute = utils.tplEngine(attributeTpl, { 51 | prefix: prefix, 52 | key: lowerLangKey 53 | }); 54 | var langSelector = utils.tplEngine(selectorTpl, { 55 | attribute: attribute, 56 | selector: selector 57 | }); 58 | var langDomList = dom.querySelectorAll(langSelector); 59 | for (var i = 0; i < langDomList.length; i++) { 60 | var langDom = langDomList[i]; 61 | var localeKey = langDom.getAttribute(attribute); 62 | langDom[langKey] = locale[langKey][localeKey]; 63 | } 64 | } 65 | return dom; 66 | } 67 | 68 | /** 69 | * 获取 rtc token 70 | * @param {object} params 71 | * @param {object} params.tokenUrl 获取 rtc token 的 url 72 | * @param {object} params.userId 用户 id 73 | * @param {object} params.appId 74 | */ 75 | function getRTCToken(params, callback) { 76 | callback = callback || utils.noop; 77 | return new Promise(function (resolve, reject) { 78 | utils.sendForm({ 79 | url: params.tokenUrl, 80 | method: 'POST', 81 | body: { 82 | uid: params.userId, 83 | appid: params.appId 84 | }, 85 | success: function (token) { 86 | callback(token); 87 | resolve(token); 88 | }, 89 | fail: function (error) { 90 | callback(null, error); 91 | reject(error); 92 | } 93 | }); 94 | }); 95 | } 96 | 97 | /** 98 | * 获取 IM token 99 | * @param {String} params.id 用户 id 100 | */ 101 | function getIMToken(params, callback) { 102 | callback = callback || utils.noop; 103 | var url = RongSeal.Config.TOKEN_URL; 104 | return new Promise(function (resolve, reject) { 105 | utils.ajax({ 106 | url: url, 107 | headers: { 108 | 'Content-Type': 'application/json' 109 | }, 110 | method: 'POST', 111 | body: JSON.stringify({ 112 | id: params.id 113 | }), 114 | success: function (result) { 115 | result = JSON.parse(result); 116 | callback(null, result.result); 117 | resolve(result.result); 118 | }, 119 | fail: function (error) { 120 | callback(error); 121 | reject(error); 122 | } 123 | }); 124 | }); 125 | } 126 | 127 | /** 128 | * 格式化分辨率 129 | * @param {string} rate 分辨率 130 | * @return {object} 包含 width、height 131 | */ 132 | var formatResolution = function (rate) { 133 | var index = rate.indexOf('*'); 134 | var width = rate.substring(0, index); 135 | var height = rate.substring(index + 1); 136 | return { 137 | width: Number(width), 138 | height: Number(height) 139 | }; 140 | }; 141 | 142 | /** 143 | * 重置分辨率 144 | * @param {object} rate 包含 width、height 145 | * @return {string} 分辨率 146 | */ 147 | var reFormatResolution = function (rate) { 148 | return rate.width + '*' + rate.height; 149 | }; 150 | 151 | /** 152 | * 根据模板创建 dom 153 | * @param {string} temp 模板 154 | */ 155 | function createLocaleDom(temp) { 156 | var dom = Dom.create(temp); 157 | return setLocale({ dom: dom }); 158 | } 159 | 160 | /** 161 | * 提示弹框 162 | * @param {string} str 提示信息 163 | * @param {object} params.isShowCancel 是否展示取消按钮 164 | * @param {object} params.confirmText 确定按钮文字, 默认为 '确定' 165 | * @param {object} params.confirmCallback 点击确定的回调 166 | * @param {object} params.cancelCallback 点击取消的回调 167 | */ 168 | function sealAlert(str, params) { 169 | params = params || {}; 170 | var cancelDisplay = params.isShowCancel ? 'inline-block' : 'none'; 171 | var innerHTML = utils.tplEngine(AlertTemp, { 172 | content: str, 173 | cancelDisplay: cancelDisplay 174 | }); 175 | var alertBox = createLocaleDom(innerHTML); 176 | var cancelBtnDom = alertBox.querySelector('.rong-alert-cancel'); 177 | var confirmBtnDom = alertBox.querySelector('.rong-alert-confirm'); 178 | if (params.confirmText) { 179 | confirmBtnDom.value = params.confirmText; 180 | } 181 | win.document.body.appendChild(alertBox); 182 | cancelBtnDom = cancelBtnDom || {}; 183 | confirmBtnDom = confirmBtnDom || {}; 184 | cancelBtnDom.onclick = function () { 185 | params.cancelCallback && params.cancelCallback(); 186 | win.document.body.removeChild(alertBox); 187 | }; 188 | confirmBtnDom.onclick = function () { 189 | params.confirmCallback && params.confirmCallback(); 190 | win.document.body.removeChild(alertBox); 191 | }; 192 | } 193 | var sealTipsList = []; 194 | /** 195 | * 提示弹框 196 | * @param {string} str 提示信息 197 | * @param {object} duration 消息停留时间 198 | */ 199 | function sealTips(str, duration) { 200 | sealTipsList.push([str, duration]); 201 | if (sealTipsList.length > 1) { 202 | return; 203 | } 204 | var innerHTML = utils.tplEngine(TipsTemp, { 205 | content: str 206 | }); 207 | var alertBox = createLocaleDom(innerHTML); 208 | win.document.body.appendChild(alertBox); 209 | setTimeout(function () { 210 | sealTipsList.shift(); 211 | win.document.body.removeChild(alertBox); 212 | if (sealTipsList.length > 0) { 213 | var _item = sealTipsList.shift(); 214 | sealTips(_item[0], _item[1]); 215 | } 216 | }, duration) 217 | } 218 | /** * 219 | * toast 提示 220 | * @param {string} str 提示信息 221 | * @param {number} duration 展示时间 222 | */ 223 | var SealToast = function () { }; 224 | SealToast.prototype = { 225 | create: function (str, duration) { 226 | var self = this; 227 | var toastHtml = ''; 228 | var toastText = '' + str + ''; 229 | toastHtml = '
' + toastText + '
'; 230 | if (Dom.get('.rong-sel-toast')) return; //未hide禁止重复点击 231 | document.body.insertAdjacentHTML('beforeend', toastHtml); 232 | if (duration) { 233 | setTimeout(function () { 234 | self.hide(); 235 | }, duration) 236 | } 237 | }, 238 | show: function () { 239 | // var self = this; 240 | Dom.showByClass('rong-seal-toast'); 241 | Dom.get('.rong-seal-toast').style.marginTop = '-' + Math.round(Dom.get('.rong-seal-toast').offsetHeight / 2) + 'px'; 242 | if (Dom.get('.rong-seal-toast')) return; 243 | }, 244 | hide: function () { 245 | // var self = this; 246 | if (Dom.get('.rong-seal-toast')) { 247 | Dom.hideByClass('rong-seal-toast'); 248 | } 249 | }, 250 | destroy: function () { 251 | var parentDom = Dom.get('body'); 252 | var childDom = Dom.getByClass('rong-seal-toast'); 253 | if (childDom) { 254 | parentDom.removeChild(childDom); 255 | } 256 | }, 257 | toast: function (str, duration) { 258 | var self = this; 259 | return self.create(str, duration); 260 | } 261 | } 262 | /** * 263 | * 通话计时器 264 | */ 265 | var SealTimer = (function () { 266 | var hour = 0, 267 | minute = 0, 268 | second = 0; 269 | var countDown, timerDom; 270 | 271 | function stop() { 272 | clearInterval(countDown); 273 | hour = minute = second = 0; 274 | timerDom = Dom.get('.rong-user-timer'); 275 | if (timerDom) { 276 | timerDom.innerHTML = ''; 277 | } 278 | } 279 | 280 | function format(count) { 281 | if (count < 10) { 282 | return '0' + count; 283 | } 284 | return count; 285 | } 286 | 287 | function timer() { 288 | second += 1; 289 | if (second >= 60) { 290 | second = 0; 291 | minute += 1; 292 | } 293 | if (minute >= 60) { 294 | minute = 0; 295 | hour += 1; 296 | } 297 | timerDom = Dom.get('.rong-user-timer'); 298 | if (minute < 60 && hour <= 0) { 299 | timerDom.innerHTML = '通话时长:' + format(minute) + ':' + format(second); 300 | } else { 301 | timerDom.innerHTML = '通话时长:' + format(hour) + ':' + format(minute) + ':' + format(second); 302 | } 303 | 304 | } 305 | 306 | function start() { 307 | countDown = setInterval(timer, 1000); 308 | } 309 | 310 | return function () { 311 | var self = this; 312 | self.stop = stop; 313 | self.start = start; 314 | } 315 | })(); 316 | 317 | /** 318 | * 音视频页面 319 | * @param {string} temp 模板(可选) 320 | */ 321 | var RongRTCPage = (function () { 322 | 323 | function createPage(bodyDom, callback) { 324 | bodyDom.appendChild(this.dom); 325 | callback(); 326 | } 327 | 328 | function destroyPage(bodyDom) { 329 | bodyDom.removeChild(this.dom) 330 | } 331 | 332 | return function (temp) { 333 | temp = temp || RongRTCPageTemp; 334 | var self = this; 335 | self.dom = createLocaleDom(temp); 336 | self.createPage = createPage; 337 | self.destroyPage = destroyPage; 338 | } 339 | })(); 340 | 341 | /** 342 | * 音视频列表 343 | * @param {string} temp 模板(可选) 344 | */ 345 | var StreamList = (function () { 346 | 347 | function hasBox(streamBox) { 348 | var self = this; 349 | var hasStreamBox = false; 350 | var boxList = self.dom.children; 351 | for (var i = 0; i < boxList.length; i++) { 352 | if (boxList[i] === streamBox.dom) { 353 | hasStreamBox = true; 354 | } 355 | } 356 | return hasStreamBox; 357 | } 358 | 359 | function addBox(streamBox) { 360 | var self = this; 361 | if (!self.hasBox(streamBox)) { 362 | self.streamBoxList.push(streamBox); 363 | self.dom.appendChild(streamBox.dom); 364 | } 365 | } 366 | 367 | function insertBox(streamBox, targetEl) { 368 | var self = this; 369 | if (!self.hasBox(streamBox)) { 370 | self.streamBoxList.push(streamBox); 371 | Dom.insertAfter(streamBox.dom, targetEl.dom); 372 | } 373 | } 374 | 375 | function removeBox(streamBox) { 376 | var self = this; 377 | if (self.hasBox(streamBox)) { 378 | var index = self.streamBoxList.indexOf(streamBox); 379 | self.streamBoxList.splice(index, 1); 380 | self.dom.removeChild(streamBox.dom); 381 | } 382 | } 383 | 384 | function clear() { 385 | var self = this; 386 | var streamList = self.streamBoxList; 387 | // streamList.forEach(function (streamBox) { 388 | // console.log('streamBox:',streamBox) 389 | // self.removeBox(streamBox); 390 | // }) 391 | for (var i = 0; i < streamList.length; i++) { 392 | self.removeBox(streamList[i]); 393 | } 394 | } 395 | 396 | return function (temp) { 397 | temp = temp || StreamListTemp; 398 | var self = this; 399 | self.streamBoxList = []; 400 | self.dom = createLocaleDom(temp); 401 | 402 | self.addBox = addBox; 403 | self.insertBox = insertBox; 404 | self.removeBox = removeBox; 405 | self.hasBox = hasBox; 406 | self.clearBox = clear; 407 | 408 | return self; 409 | }; 410 | })(); 411 | 412 | /** 413 | * 单个音视频展示框 414 | * @param {string} id 用户 id 415 | * @param {object} params 其他选项(可扩展) 416 | * @param {boolean} params.isZoom 是否放大 417 | * @param {boolean} params.resizeEvent 是否放大 418 | * @param {string} temp 模板, 可选 419 | */ 420 | /* TODO 去掉该变量的使用, 应使用 streamList 实例进行 box 操作 */ 421 | var StreamBoxList = {}; // streamBox 集合 422 | var StreamBox = (function () { 423 | var setClass = function (dom, className, isOpen) { 424 | isOpen ? addClass(dom, className) : removeClass(dom, className); 425 | }; 426 | 427 | // 清空所有 zoom class 428 | function clearStreamBoxZoom(userId) { 429 | var reset = function (id) { 430 | var streamBox = StreamBoxList[id]; 431 | streamBox.resizeEvent(false, id); 432 | streamBox.isZoom = false; 433 | removeClass(streamBox.dom, OptClassName.IS_ZOOM); 434 | }; 435 | if (userId) { 436 | return reset(userId); 437 | } 438 | for (var id in StreamBoxList) { 439 | reset(id); 440 | } 441 | } 442 | 443 | // 展示流 444 | function showStream(stream) { 445 | var id = this.id; 446 | var videoDom = this.childDom.video; 447 | var customizeValue = utils.tplEngine('{prefix}-{id}', { 448 | prefix: 'Rong', 449 | id: id 450 | }); 451 | if (videoDom && stream) { 452 | videoDom.srcObject = stream; 453 | console.timeEnd('get self media') 454 | videoDom.setAttribute('stream', customizeValue); 455 | } 456 | } 457 | 458 | function zoom(user) { 459 | var self = this; 460 | user = user || {}; 461 | if (self.isZoom) { 462 | // do nothing 463 | } else { 464 | clearStreamBoxZoom(user.id); 465 | self.isZoom = true; 466 | addClass(this.dom, OptClassName.IS_ZOOM) 467 | } 468 | } 469 | function showSoundGif() { 470 | var soundDom = this.dom.childNodes[3].childNodes[1].childNodes[0]; 471 | addClass(soundDom, 'rong-sound-show') 472 | } 473 | function hideSoundGif() { 474 | var soundDom = this.dom.childNodes[3].childNodes[1].childNodes[0]; 475 | removeClass(soundDom, 'rong-sound-show') 476 | } 477 | function closeVideoBySelf() { 478 | setClass(this.dom, OptClassName.CLOSE_VIDEO_BY_SELF, true); 479 | this.isVideoOpenedBySelf = false; 480 | } 481 | function openVideoBySelf() { 482 | setClass(this.dom, OptClassName.CLOSE_VIDEO_BY_SELF, false); 483 | this.isVideoOpenedBySelf = true; 484 | } 485 | function closeAudioBySelf() { 486 | setClass(this.dom, OptClassName.CLOSE_AUDIO_BY_SELF, true); 487 | this.isAudioOpenedBySelf = false; 488 | } 489 | function openAudioBySelf() { 490 | setClass(this.dom, OptClassName.CLOSE_AUDIO_BY_SELF, false); 491 | this.isAudioOpenedBySelf = true; 492 | } 493 | function closeVideoByOther() { 494 | setClass(this.dom, OptClassName.CLOSE_VIDEO_BY_OTHER, true); 495 | this.isVideoOpenedByOther = false; 496 | } 497 | function openVideoByOther() { 498 | setClass(this.dom, OptClassName.CLOSE_VIDEO_BY_OTHER, false); 499 | this.isVideoOpenedByOther = true; 500 | } 501 | function closeAudioByOther() { 502 | this.isAudioOpenedByOther = false; 503 | } 504 | function openAudioByOther() { 505 | this.isAudioOpenedByOther = true; 506 | } 507 | function openScreenShare() { 508 | this.isScreenShareOpened = true; 509 | setClass(this.dom, OptClassName.OPEN_SCREENSHARE, true); 510 | } 511 | function closeScreenShare() { 512 | this.isScreenShareOpened = false; 513 | setClass(this.dom, OptClassName.OPEN_SCREENSHARE, false); 514 | } 515 | function openFlibScreenShare() { 516 | var node = this.dom.querySelector('.rong-video'); 517 | node.style.transform = 'none'; 518 | } 519 | function closeFlibScreenShare() { 520 | var node = this.dom.querySelector('.rong-video'); 521 | node.style.transform = 'rotateY(180deg)'; 522 | } 523 | function disabledVideoBySelf() { 524 | var node = this.dom.querySelector('.rong-opt-video') 525 | var disabledNode = this.dom.querySelector('.rong-opt-video-disabled') 526 | node.style.display = 'none'; 527 | disabledNode.style.display = 'inline-block'; 528 | } 529 | function disabledAudioBySelf() { 530 | var node = this.dom.querySelector('.rong-opt-audio') 531 | var disabledNode = this.dom.querySelector('.rong-opt-audio-disabled') 532 | node.style.display = 'none'; 533 | disabledNode.style.display = 'inline-block'; 534 | } 535 | function setCustomVideoUI(videoTitle) { 536 | var streamTopDom = this.dom.querySelector('.rong-stream-top') 537 | var optDom = this.dom.querySelector('.rong-stream-opt') 538 | var customVideoDom = this.dom.querySelector('.rong-custom-video') 539 | Dom.hide(streamTopDom); 540 | Dom.hide(optDom); 541 | Dom.show(customVideoDom); 542 | customVideoDom.innerText = videoTitle; 543 | } 544 | function hideCustomAudioBox() { 545 | this.dom.style.display = 'none'; 546 | } 547 | function setName(name) { 548 | var node = this.dom.querySelector('.rong-user-name'); 549 | node.innerText = name; 550 | this.userName = name; 551 | } 552 | return function (id, params, temp) { 553 | params = params || {}; 554 | temp = temp || StreamBoxTemp; 555 | 556 | var self = this; 557 | temp = utils.tplEngine(temp, { 558 | name: params.name || id 559 | }); 560 | var dom = createLocaleDom(temp), 561 | videoDom = Dom.getChild(dom, 'rong-video'), 562 | videoBtnDom = dom.querySelector('.rong-opt-video'), 563 | audioBtnDom = dom.querySelector('.rong-opt-audio'); 564 | var customizeValue = utils.tplEngine('{prefix}-{id}', { 565 | prefix: 'Rong', 566 | id: id 567 | }); 568 | dom.setAttribute('user', customizeValue); 569 | dom.onclick = function (e) { 570 | e.stopPropagation(); 571 | if (self.isZoom) { 572 | // do nothing 573 | } else { 574 | self.zoom(); 575 | self.resizeEvent(true, id); 576 | } 577 | }; 578 | 579 | self.id = id; 580 | self.resizeEvent = params.resizeEvent || utils.noop; 581 | self.dom = dom; 582 | self.tag = params.tag; 583 | self.userName = params.name; 584 | self.childDom = { 585 | video: videoDom, 586 | videoBtn: videoBtnDom, 587 | audioBtn: audioBtnDom 588 | }; 589 | self.isVideoOpenedBySelf = true; 590 | self.isAudioOpenedBySelf = true; 591 | self.isVideoOpenedByOther = true; 592 | self.isAudioOpenedByOther = true; 593 | self.isScreenShareOpened = false; 594 | 595 | self.showStream = showStream; 596 | self.zoom = zoom; 597 | self.isZoom = false; 598 | self.openVideoBySelf = openVideoBySelf; 599 | self.closeVideoBySelf = closeVideoBySelf; 600 | self.openAudioBySelf = openAudioBySelf; 601 | self.closeAudioBySelf = closeAudioBySelf; 602 | self.openVideoByOther = openVideoByOther; 603 | self.closeVideoByOther = closeVideoByOther; 604 | self.openAudioByOther = openAudioByOther; 605 | self.closeAudioByOther = closeAudioByOther; 606 | self.openScreenShare = openScreenShare; 607 | self.closeScreenShare = closeScreenShare; 608 | self.openFlibScreenShare = openFlibScreenShare; 609 | self.closeFlibScreenShare = closeFlibScreenShare; 610 | self.disabledVideoBySelf = disabledVideoBySelf; 611 | self.disabledAudioBySelf = disabledAudioBySelf; 612 | self.showSoundGif = showSoundGif; 613 | self.hideSoundGif = hideSoundGif; 614 | self.setCustomVideoUI = setCustomVideoUI; 615 | self.hideCustomAudioBox = hideCustomAudioBox; 616 | self.setName = setName; 617 | 618 | StreamBoxList[id] = self; 619 | 620 | return self; 621 | }; 622 | })(); 623 | StreamBox.get = function (id) { 624 | return StreamBoxList[id]; 625 | }; 626 | 627 | StreamBox.clearQuitUser = function (id) { 628 | delete StreamBoxList[id]; 629 | } 630 | 631 | //返回登录页 632 | function backLoginPage() { 633 | Dom.hideByClass('rong-rtc'); 634 | Dom.showByClass('rong-login'); 635 | Dom.hideByClass('rong-btn-loading'); 636 | Dom.showByClass('rong-btn-start'); 637 | } 638 | 639 | var WhiteBoard = (function () { 640 | return function (domId) { 641 | domId = domId || 'RongWB'; 642 | var self = this; 643 | var dom = Dom.getById(domId); 644 | var closeDom = Dom.getChild(dom, 'rong-wb-close'); 645 | var iframeDom = Dom.getChild(dom, 'rong-whiteboard'); 646 | closeDom.onclick = function (e) { 647 | // e.stopPropagation(); 648 | e.preventDefault(); 649 | self.hide(); 650 | }; 651 | self.dom = dom; 652 | self.closeDom = closeDom; 653 | self.show = function (url) { 654 | iframeDom.src = url; 655 | Dom.show(self.dom); 656 | }; 657 | self.hide = function (callback) { 658 | callback = callback || utils.noop; 659 | Dom.hide(self.dom); 660 | iframeDom.contentWindow.RongWB.leaveWBRoom(); 661 | iframeDom.src = ''; 662 | // iframeDom.close() 663 | callback(); 664 | }; 665 | return self; 666 | }; 667 | })(); 668 | 669 | function userListView(userList) { 670 | var userListDom = Dom.getById('rongRoomUsers'); 671 | var userListNumDom = Dom.getById('userListNum'); 672 | var _local = RongSeal.locale[RongSeal.common.lang].common; 673 | var userJoinMode = { 674 | name: _local.spectators, 675 | cls: '' 676 | }; 677 | var joinData = { 678 | 0: { 679 | name: _local.video, 680 | cls: 'join-video' 681 | }, 682 | 1: { 683 | name: _local.audio, 684 | cls: 'join-audio' 685 | }, 686 | 2: { 687 | name: _local.spectators, 688 | cls: '' 689 | } 690 | } 691 | 692 | userListNumDom.innerHTML = '' + utils.tplEngine(_local.online, { 0: userList.length }) + ''; 693 | userListDom.innerHTML = ''; 694 | for (var i = 0; i < userList.length; i++) { 695 | var masterHtml = ''; 696 | userJoinMode = joinData[userList[i].joinMode] ? joinData[userList[i].joinMode] : userJoinMode; 697 | if (RongSeal.userInfo.master == 1 && userList[i].userId !== RongSeal.userInfo.userId) { 698 | masterHtml = ''; 699 | } else if (userList[i].master == 1) { 700 | masterHtml = '管理员'; 701 | } 702 | var userHtml = '
' + 703 | '
' + userList[i].userName + '
' + 704 | '
' + userJoinMode.name + '
' + masterHtml + '
'; 705 | if (userList[i].master == 1) { 706 | userListDom.innerHTML = userHtml + userListDom.innerHTML; 707 | } else { 708 | userListDom.innerHTML = userListDom.innerHTML + userHtml; 709 | } 710 | } 711 | } 712 | function showUserList() { 713 | Dom.showByClass('rong-user-list'); 714 | } 715 | function hideUserList() { 716 | Dom.hideByClass('rong-user-list'); 717 | } 718 | function showCustomVideoList() { 719 | Dom.showByClass('rong-user-customvideo'); 720 | } 721 | function hideCustomVideoList() { 722 | Dom.hideByClass('rong-user-customvideo'); 723 | } 724 | function showCustomAudioList() { 725 | Dom.showByClass('rong-user-customaudio'); 726 | } 727 | function hideCustomAudioList() { 728 | Dom.hideByClass('rong-user-customaudio'); 729 | } 730 | 731 | function showCustomVideoOpenBtn() { 732 | Dom.showByClass('rong-opt-videoicon-open'); 733 | Dom.hideByClass('rong-opt-videoicon-close'); 734 | } 735 | function hideCustomVideoOpenBtn() { 736 | Dom.hideByClass('rong-opt-videoicon-open'); 737 | } 738 | function showCustomVideoCloseBtn() { 739 | Dom.hideByClass('rong-user-customvideo'); 740 | Dom.hideByClass('rong-opt-videoicon-open'); 741 | Dom.showByClass('rong-opt-videoicon-close'); 742 | } 743 | function switchCustomVideo() { 744 | var isOpen = Dom.getByClass('rong-user-customvideo').style.display === 'block'; 745 | if (isOpen) { 746 | hideCustomVideoList(); 747 | } else { 748 | hideCustomAudioList(); 749 | showCustomVideoList(); 750 | } 751 | } 752 | function switchCustomAudio() { 753 | var isOpen = Dom.getByClass('rong-user-customaudio').style.display === 'block'; 754 | if (isOpen) { 755 | hideCustomAudioList(); 756 | } else { 757 | hideCustomVideoList(); 758 | showCustomAudioList(); 759 | } 760 | } 761 | function showCustomAudioOpenBtn() { 762 | Dom.showByClass('rong-opt-audioicon-open'); 763 | Dom.hideByClass('rong-opt-audioicon-close'); 764 | } 765 | function hideCustomAudioOpenBtn() { 766 | Dom.hideByClass('rong-opt-audioicon-open'); 767 | } 768 | function showCustomAudioCloseBtn() { 769 | Dom.hideByClass('rong-user-customaudio'); 770 | Dom.hideByClass('rong-opt-audioicon-open'); 771 | Dom.showByClass('rong-opt-audioicon-close'); 772 | } 773 | function hideWhiteBoardBtn() { 774 | var whiteBoardBtn = Dom.getByClass('rong-opt-wb') 775 | Dom.hide(whiteBoardBtn); 776 | } 777 | function UiTable(param) { 778 | var params = { 779 | domId: param.domId, 780 | dom: document.getElementById(param.domId), 781 | clm: param.clm, 782 | data: [], 783 | click: param.click || function () { }, 784 | tools: {}, 785 | event: {}, 786 | clmMap: {}, 787 | unique: '' 788 | } 789 | var prefix = { 790 | tr: 'ui_tableuiTr_' 791 | } 792 | params.clmMap = {}; 793 | params.dom.innerHTML = ''; 794 | var clms = params.clm; 795 | for (var i = 0; i < clms.length; i++) { 796 | var clm = clms[i]; 797 | params.clmMap[clm.id] = clm; 798 | if (clm.unique) { 799 | params.unique = clm.id 800 | } 801 | } 802 | 803 | function getHeadHtmlTemple() { 804 | var uiHtml = '' 805 | for (var i = 0; i < params.clm.length; i++) { 806 | var item = params.clm[i]; 807 | uiHtml += '' + item.name + ''; 808 | if (item.type == 'btn') { 809 | params.tools.btns = item; 810 | } 811 | } 812 | uiHtml += ''; 813 | return uiHtml; 814 | } 815 | function getDataHtml(item) { 816 | var uiTr = ''; 817 | for (var i = 0; i < params.clm.length; i++) { 818 | var key = params.clm[i]; 819 | uiTr += ' ' + item[key.id] + ' ' 820 | } 821 | return uiTr; 822 | } 823 | function getBodyHtmlTemple(data) { 824 | var uiHtml = ''; 825 | for (var j = 0; j < data.length; j++) { 826 | var item = data[j]; 827 | var uiTr = ''; 828 | uiTr += getDataHtml(item); 829 | uiTr += ''; 830 | uiHtml += uiTr; 831 | } 832 | uiHtml += ''; 833 | return uiHtml; 834 | } 835 | function addLine(item) { 836 | params.data.push(item); 837 | var uiTr = ''; 838 | uiTr += getDataHtml(item); 839 | uiTr += ''; 840 | params.dom.querySelector('tbody').innerHTML += uiTr; 841 | } 842 | function bindClmEvent() { 843 | var uiTr = params.dom.getElementsByTagName('tbody')[0].getElementsByTagName('tr'); 844 | for (var i = 0; i < uiTr.length; i++) { 845 | uiTr[i].onclick = function (e) { 846 | var index = parseInt(this.getAttribute('data-index')); 847 | var item = params.data[index]; 848 | params.click(e, item); 849 | }; 850 | } 851 | } 852 | function bindEvent() { 853 | bindClmEvent(); 854 | } 855 | this.init = function (data) { 856 | var headHtml = getHeadHtmlTemple(); 857 | var bodyHtml = getBodyHtmlTemple(data); 858 | params.dom.innerHTML = '' + headHtml + bodyHtml + '
'; 859 | params.data = data; 860 | bindEvent(); 861 | } 862 | this.getParams = function () { 863 | return params; 864 | } 865 | this.param = params; 866 | var updateOneLine = function (data) { 867 | var dom = document.getElementById(prefix.tr + data[params.unique]) 868 | if (!dom) { 869 | addLine(data); 870 | return; 871 | } 872 | var childrens = dom.children; 873 | for (var i = 0; i < childrens.length; i++) { 874 | var item = childrens[i]; 875 | var key = item.getAttribute('data-name'); 876 | if (data[key]) { 877 | item.children[0].innerHTML = data[key]; 878 | item.children[0].setAttribute('title',data[key]); 879 | } 880 | } 881 | } 882 | this.updateData = function (data) { 883 | for (var i = 0; i < data.length; i++) { 884 | updateOneLine(data[i]); 885 | } 886 | } 887 | } 888 | 889 | function showUserId(userId) { 890 | var userIdElement = Dom.getById('rongUserId'); 891 | var content = utils.tplEngine('{prefix}{userId}{suffix}', { 892 | prefix: '(', 893 | suffix: ')', 894 | userId: userId 895 | }) 896 | userId && (userIdElement.innerText = content); 897 | } 898 | 899 | var UI = { 900 | RongRTCPage: RongRTCPage, 901 | StreamList: StreamList, 902 | StreamBox: StreamBox, 903 | WhiteBoard: WhiteBoard, 904 | backLoginPage: backLoginPage, 905 | userListView: userListView, 906 | showUserList: showUserList, 907 | hideUserList: hideUserList, 908 | customVideoOpt: { 909 | switchCustomVideo: switchCustomVideo, 910 | showCustomVideoList: showCustomVideoList, 911 | hideCustomVideoList: hideCustomVideoList, 912 | showCustomVideoOpenBtn: showCustomVideoOpenBtn, 913 | showCustomVideoCloseBtn: showCustomVideoCloseBtn, 914 | hideCustomVideoOpenBtn: hideCustomVideoOpenBtn 915 | }, 916 | customAudioOpt: { 917 | switchCustomAudio: switchCustomAudio, 918 | showCustomAudioOpenBtn: showCustomAudioOpenBtn, 919 | hideCustomAudioOpenBtn: hideCustomAudioOpenBtn, 920 | showCustomAudioCloseBtn: showCustomAudioCloseBtn 921 | }, 922 | hideWhiteBoardBtn: hideWhiteBoardBtn, 923 | showUserId: showUserId 924 | }; 925 | 926 | var common = { 927 | sealAlert: sealAlert, 928 | SealTimer: SealTimer, 929 | sealTips: sealTips, 930 | StreamBoxList: StreamBoxList, 931 | SealToast: SealToast, 932 | formatResolution: formatResolution, 933 | reFormatResolution: reFormatResolution, 934 | getRTCToken: getRTCToken, 935 | getIMToken: getIMToken, 936 | UI: UI, 937 | setLocale: setLocale, 938 | lang: win.document.body.lang, 939 | Table: UiTable 940 | }; 941 | win.RongSeal = win.RongSeal || {}; 942 | win.RongSeal.common = common; 943 | win.RongSeal.eventEmitter = eventEmitter; 944 | win.RongSeal.EventName = EventName; 945 | 946 | })({ 947 | win: window, 948 | RongSeal: window.RongSeal 949 | }); -------------------------------------------------------------------------------- /public/js/im.js: -------------------------------------------------------------------------------- 1 | (function (dependencies) { 2 | var win = dependencies.win, 3 | RongIMLib = dependencies.RongIMLib, 4 | RongIMClient = RongIMLib.RongIMClient; 5 | win.RongSeal = win.RongSeal || {}; 6 | 7 | function connect(params, callbacks) { 8 | var appKey = params.appKey, 9 | token = params.token, 10 | navi = params.navi, 11 | api = params.api, 12 | protobuf = params.protobuf; 13 | 14 | var config = {}; 15 | if (navi) { 16 | config.navi = navi; 17 | } 18 | if (api) { 19 | config.api = api; 20 | } 21 | if (protobuf) { 22 | config.protobuf = protobuf; 23 | } 24 | 25 | RongIMClient.init(appKey, null, config); 26 | 27 | RongIMClient.setConnectionStatusListener({ 28 | onChanged: function (status) { 29 | switch (status) { 30 | case RongIMLib.ConnectionStatus['CONNECTED']: 31 | case 0: 32 | break; 33 | case RongIMLib.ConnectionStatus['CONNECTING']: 34 | case 1: 35 | console.log('连接中'); 36 | break; 37 | case RongIMLib.ConnectionStatus['DISCONNECTED']: 38 | case 2: 39 | console.log('当前用户主动断开链接'); 40 | break; 41 | 42 | case RongIMLib.ConnectionStatus['NETWORK_UNAVAILABLE']: 43 | case 3: 44 | console.log('网络不可用'); 45 | callbacks.backLoginPage(); 46 | break; 47 | 48 | case RongIMLib.ConnectionStatus['CONNECTION_CLOSED']: 49 | case 4: 50 | console.log('未知原因,连接关闭'); 51 | break; 52 | 53 | case RongIMLib.ConnectionStatus['KICKED_OFFLINE_BY_OTHER_CLIENT']: 54 | case 6: 55 | console.log('用户账户在其他设备登录,本机会被踢掉线'); 56 | callbacks.kickedByOther() 57 | break; 58 | 59 | case RongIMLib.ConnectionStatus['DOMAIN_INCORRECT']: 60 | case 12: 61 | console.log('当前运行域名错误,请检查安全域名配置'); 62 | break; 63 | } 64 | } 65 | }); 66 | 67 | RongIMClient.setOnReceiveMessageListener({ 68 | // 接收到的消息 69 | onReceived: function (message) { 70 | console.log('receive message', message); 71 | } 72 | }); 73 | 74 | RongIMClient.connect(token, { 75 | onSuccess: function (userId) { 76 | console.log('连接成功', userId); 77 | callbacks.connected && callbacks.connected(userId); 78 | }, 79 | onTokenIncorrect: function () { 80 | callbacks.tokenIncorrect() 81 | console.log('token 无效'); 82 | }, 83 | onError: function (errorCode) { 84 | console.log('connect error', errorCode); 85 | } 86 | }, params.userId); 87 | 88 | } 89 | 90 | function getRTCToken() { 91 | // var instance = RongIMClient.getInstance(); 92 | return new Promise(function (resolve) { 93 | resolve(); 94 | // instance.getAgoraDynamicKey(3, channelId, { 95 | // onSuccess: function (content) { 96 | // resolve(content.dynamicKey); 97 | // }, 98 | // onError: function (error) { 99 | // reject(error); 100 | // } 101 | // }); 102 | }); 103 | } 104 | 105 | function reconnect(callback) { 106 | var config = { 107 | // 默认 false, true 启用自动重连,启用则为必选参数 108 | auto: true, 109 | // 网络嗅探地址 [http(s)://]cdn.ronghub.com/RongIMLib-2.2.6.min.js 可选 110 | url: 'cdn.ronghub.com/RongIMLib-2.2.6.min.js', 111 | // 重试频率 [100, 1000, 3000, 6000, 10000, 18000] 单位为毫秒,可选 112 | rate: [1000] 113 | }; 114 | RongIMClient.reconnect({ 115 | onSuccess: function (userId) { 116 | console.log('Reconnect successfully.' + userId); 117 | callback.success() 118 | }, 119 | onTokenIncorrect: function () { 120 | console.log('token无效'); 121 | }, 122 | onError: function (errorCode) { 123 | console.log(errorCode); 124 | callback.error(); 125 | } 126 | }, config); 127 | } 128 | 129 | function getInstance() { 130 | return RongIMClient.getInstance(); 131 | } 132 | function disconnect() { 133 | RongIMClient.getInstance().disconnect() 134 | } 135 | 136 | win.RongSeal.im = { 137 | connect: connect, 138 | reconnect: reconnect, 139 | getRTCToken: getRTCToken, 140 | instance: getInstance, 141 | disconnect: disconnect 142 | }; 143 | 144 | })({ 145 | win: window, 146 | RongIMLib: window.RongIMLib 147 | }); -------------------------------------------------------------------------------- /public/js/locale/en.js: -------------------------------------------------------------------------------- 1 | (function (win) { 2 | var RongSeal = win.RongSeal = win.RongSeal || {}; 3 | RongSeal = RongSeal || {}; 4 | RongSeal.locale = RongSeal.locale || {}; 5 | RongSeal.locale.en = { 6 | data: { 7 | installPrompt: 'For the first time to use screenshare, please download and install the plugin', 8 | downloadTitle: 'Download', 9 | room: 'Room ID', 10 | user: 'User ID', 11 | self: 'Self', 12 | roomIdEmpty: 'Room Id cannot be empty', 13 | userIdEmpty: 'User ID can not be empty', 14 | roomIdIllegal: 'Room ID can only enter letters and numbers', 15 | networkError: 'Network is disconnected', 16 | userNameEnglishOnly: 'User name can only be entered in English or numbers', 17 | getTokenError: 'Get token failed', 18 | rtcError: 'Init RTC failed', 19 | joinError: 'Join room failed', 20 | leftError: 'Left room failed', 21 | getScreenError: 'Get screenshare failed', 22 | addScreenError: 'Add screenshare failed', 23 | closeScreenError: 'Close screenshare failed', 24 | publishError: 'Publish stream failed', 25 | getLocalStreamError: 'Collection of camera equipment failed, please check:
1. Whether the computer is connected and turned on the camera device;
2. Whether the camera has been authorized to capture the camera;
', 26 | subscriptError: 'Subscription failed', 27 | closeVideoError: 'Failed to close the camera', 28 | openVideoError: 'Failed to open the camera', 29 | closeAudioError: 'Turn off the microphone failed', 30 | openAudioError: 'Turning on the microphone failed', 31 | switchStreamError: 'Switching stream failed', 32 | sealRtcKickOff: 'You have been removed from the room by the administrator, please try to join later', 33 | isManage: 'You are currently an administrator', 34 | newManage: ' become a new administrator', 35 | joinMeeting: ' Join the meeting', 36 | removeUser: 'Are you sure you want to remove user {0} from your room?', 37 | 50065: 'You have been removed from the room!', 38 | 53013: 'You have been removed from the room!', 39 | 40021: 'You are not allowed to join the room!' 40 | }, 41 | placeholder: { 42 | roomId: 'Please enter the RoomId', 43 | userId: 'Please enter the UserName' 44 | }, 45 | textContent: { 46 | closeVideo: 'Turn off the camera', 47 | closeAudio: 'Turn off the microphone', 48 | setting: 'Call settings', 49 | resolution: 'Resolution', 50 | bitrate: 'Bitrate', 51 | frameRate: 'FrameRate', 52 | bitrateMIN: 'bitrateMIN', 53 | bitrateMAX: 'bitrateMAX', 54 | url: 'MediaServer URL', 55 | userLoginId: 'userId', 56 | screenshareBusy: 'Screen Sharing', 57 | videoClosed: 'You have turned off the camera', 58 | otherVideoClosed: 'He/she has turned off the camera', 59 | alertTitle: 'Friendly reminders' 60 | }, 61 | value: { 62 | startRTC: 'Start', 63 | cancel: 'Cancel', 64 | conform: 'OK' 65 | }, 66 | title: { 67 | hangup: 'hangup', 68 | screenshare: 'screenshare', 69 | whiteboard: 'whiteboard' 70 | }, 71 | common: { 72 | spectators: 'Spectators', 73 | video: 'Video', 74 | audio: 'Audio', 75 | online: 'Number of people online ( {0} )' 76 | } 77 | }; 78 | })(window); -------------------------------------------------------------------------------- /public/js/locale/zh.js: -------------------------------------------------------------------------------- 1 | (function (win) { 2 | var RongSeal = win.RongSeal = win.RongSeal || {}; 3 | RongSeal = RongSeal || {}; 4 | RongSeal.locale = RongSeal.locale || {}; 5 | RongSeal.locale.zh = { 6 | data: { 7 | installPrompt: '首次使用屏幕共享, 请下载并安装插件', 8 | downloadTitle: '下载插件', 9 | room: '会议室 ID', 10 | user: '用户 ID', 11 | userName: '用户名', 12 | self: '自己', 13 | roomIdEmpty: '会议室 ID 不能为空', 14 | userNameEmpty: '用户名称不能为空', 15 | userIdEmpty: '用户 ID 不能为空', 16 | roomIdIllegal: '会议室 ID 只能包含大小写字母、阿拉伯数字、+、=、-、_ 且长度不能超过 64 个字符', 17 | userNameIllegal: '用户名不能包含空格', 18 | userNameEnglishOnly: '用户名只能输入英文和数字', 19 | networkError: '网络已断开', 20 | phoneNumberErr: '手机号格式非法', 21 | verifyCodeErr: '请输入手机验证码', 22 | verifyCodeIncorrect: '验证码错误', 23 | verifyCodeExpired: '验证码过期', 24 | tokenExpired: 'token 已失效,请重新获取', 25 | kickedByOtherTips: '此用户已在其他窗口登录,可修改手机号后再尝试登录', 26 | verifyCodeTips: '您还未验证过手机号,请先验证', 27 | publishedCustomVideo: '已发布自定义媒体信息', 28 | supportedBrowsers: '目前支持 Safari 和 Chrome 浏览器,推荐使用 Chrome', 29 | 30 | getTokenError: '获取 token 失败', 31 | rtcError: '初始化 RTC 失败', 32 | joinError: '加入房间失败', 33 | leftError: '离开房间失败', 34 | getScreenError: '获取屏幕共享流失败', 35 | addScreenError: '加入屏幕共享流失败', 36 | closeScreenError: '关闭屏幕共享失败', 37 | getWhiteboardError: '获取白板失败', 38 | publishError: '推送流失败', 39 | getLocalStreamError: '摄像设备采集失败,请检查:
1.电脑是否已连接并开启摄像头设备;
2.是否已给此窗口授权摄像头采集权限;
', 40 | subscriptError: '订阅失败', 41 | closeVideoError: '关闭摄像头失败', 42 | openVideoError: '打开摄像头失败', 43 | closeAudioError: '关闭麦克风失败', 44 | openAudioError: '打开麦克风失败', 45 | switchStreamError: '切换流失败', 46 | sealRtcKickOff: '你已经被管理员移出房间,请稍后再尝试加入', 47 | isManage: '您当前是管理员', 48 | newManage: ' 成为新管理员', 49 | joinMeeting: ' 加入会议', 50 | leaveMeeting: ' 离开会议', 51 | removeUser: '确定从房间中移除用户 {0} 吗?', 52 | 50065: '您已被移出房间!', 53 | 53013: '您已被移出房间!', 54 | 40021: '您被禁止加入该房间!' 55 | }, 56 | placeholder: { 57 | roomId: '请输入会议室 ID', 58 | userId: '请输入用户 ID', 59 | userName: '请输入用户名称', 60 | phoneNumber: '手机号', 61 | verifyCode: '手机验证码' 62 | }, 63 | textContent: { 64 | closeVideo: '音频模式', 65 | closeAudio: '加入时关闭麦克风', 66 | bystanders: '旁听者模式', 67 | setting: '通话设置', 68 | resolution: '分辨率', 69 | bitrate: '初始码率', 70 | frameRate: '帧率设置', 71 | bitrateMIN: '最小码率', 72 | bitrateMAX: '最大码率', 73 | url: 'MediaServer URL', 74 | userLoginId: 'userId', 75 | screenshareBusy: '正在分享屏幕', 76 | videoClosed: '摄像头已关闭', 77 | otherVideoClosed: '对方已关闭摄像头', 78 | alertTitle: '提示', 79 | phoneTips: '请输入手机号验证登录,目前只支持中国区', 80 | btnLoading: '加载中...', 81 | systemTips: '注:SealRTC v3.0.0 及以上版本与老版本不兼容,请使用新版本体验,Web 端推荐使用 Google Chrome 浏览器', 82 | userListClose: '关闭', 83 | copyright: 'Copyright 2020 RongCloud', 84 | copyrightRecord: '京 ICP 备 15042119 号-1', 85 | customVideo1: '视频文件1.mp4', 86 | customVideo2: '视频文件2.mp4', 87 | customVideo3: '视频文件3.mp4', 88 | customAudio1: '音频文件1.mp3' 89 | }, 90 | value: { 91 | startRTC: '开始会议', 92 | cancel: '取消', 93 | conform: '确定', 94 | sendVerifyCode: '发送验证码', 95 | verifyLogin: '验证登录' 96 | }, 97 | title: { 98 | hangup: '挂断', 99 | screenshare: '屏幕共享', 100 | whiteboard: '白板', 101 | userlist: '在线成员', 102 | customvideo: '自定义视频流', 103 | closecustomvideo: '关闭自定义视频流', 104 | customaudio: '自定义音频流', 105 | closecustomaudio: '关闭自定义音频流' 106 | }, 107 | common: { 108 | spectators: '旁听者', 109 | video: '视频', 110 | audio: '音频', 111 | online: '在线人数 ( {0} ) 人' 112 | } 113 | }; 114 | })(window); 115 | -------------------------------------------------------------------------------- /public/js/login.js: -------------------------------------------------------------------------------- 1 | (function (dependencies) { 2 | var RongSeal = dependencies.RongSeal, 3 | win = dependencies.win, 4 | utils = RongSeal.utils, 5 | common = RongSeal.common, 6 | sealAlert = common.sealAlert, 7 | Dom = utils.Dom, 8 | getDomListByName = Dom.getDomListByName, 9 | getSelectedByName = Dom.getSelectedByName, 10 | getDomById = Dom.getById, 11 | Cache = utils.Cache, 12 | Config = RongSeal.Config, 13 | RongRTC = win.RongRTC, 14 | LocalCache = utils.LocalCache; 15 | // var randomUserId; 16 | var rongIMToken; 17 | var rongIMNavi; 18 | 19 | var locale = RongSeal.locale[common.lang], 20 | localeData = locale.data; 21 | 22 | var roomDom = getDomById('roomId'), 23 | startBtnDom = getDomById('start'), 24 | inputDomList = Dom.getList('.rong-login-input'), 25 | roomTelNumDom = getDomById('roomTelNum'), 26 | userNameDom = getDomById('userName'); 27 | var telDom = getDomById('telNumber'), 28 | verifyCodeDom = getDomById('verifyCode'), 29 | verifyCodeBtnDom = getDomById('verifyCodeBtn'), 30 | inputTelVerifyDomList = Dom.getList('.rong-login-verify-input'), 31 | verifyLoginDom = getDomById('verifyLogin'); 32 | 33 | var StorageKeys = { 34 | RoomId: 'rong-sealv2-roomid', 35 | Resolution: 'rong-sealv2-resolution', 36 | IMToken: 'rong-im-token', 37 | IMNavi: 'rong-im-navi', 38 | UserInfoKey: 'rong-user-info', 39 | VideoEnable: 'video-enable', 40 | BystanderEnable: 'bystander-enable', 41 | UserId: 'rong-user-id', 42 | TabIdKey: 'rong-tab-id' 43 | }; 44 | 45 | var verifyLoginClickTimes = 0; 46 | 47 | var platform = 'web'; 48 | 49 | //生成 tabId 并存储 50 | var generateTabId = function() { 51 | var tabId = LocalCache.get(StorageKeys.TabIdKey); 52 | if(!tabId){ 53 | var randomStr = utils.getRandomStr() 54 | LocalCache.set(StorageKeys.TabIdKey, randomStr) 55 | } 56 | } 57 | generateTabId(); 58 | // 处理清除缓存未生成 tabId 59 | var handleNullTabId = function() { 60 | var randomStr = utils.getRandomStr() 61 | LocalCache.set(StorageKeys.TabIdKey, randomStr) 62 | return randomStr; 63 | } 64 | var sealToast = new common.SealToast(); 65 | /** 66 | * 获取手机验证码 67 | * @param {object} params 68 | * @param {object} params.getSmsCodeUrl 获取 手机验证码 的 url 69 | * @param {object} params.tel 获取手机号 70 | * @param {object} params.region 获取 71 | */ 72 | function getSmsCode(params, callback) { 73 | callback = callback || utils.noop; 74 | var url = RongSeal.Config.URL + '/user/send_code'; 75 | return new Promise(function (resolve, reject) { 76 | utils.ajax({ 77 | url: url, 78 | headers: { 79 | 'Content-Type': 'application/json' 80 | }, 81 | method: 'POST', 82 | body: JSON.stringify({ 83 | phone: params.tel, 84 | region: params.region, 85 | }), 86 | success: function (result) { 87 | result = JSON.parse(result); 88 | callback(null, result); 89 | resolve(result); 90 | }, 91 | fail: function (error) { 92 | callback(error); 93 | reject(error); 94 | } 95 | }); 96 | }); 97 | } 98 | /** 99 | * 验证手机验证码 100 | * @param {object} params 101 | * @param {object} params.tokenUrl 验证手机验证码er的 url 102 | * @param {object} params.tel 手机号 103 | * @param {object} params.region 国际区号 104 | * @param {object} params.code 手机验证码 105 | * @param {object} params.key ??? 106 | */ 107 | function verifySmsCode(params, callback) { 108 | callback = callback || utils.noop; 109 | var url = RongSeal.Config.URL + '/user/verify_code'; 110 | return new Promise(function (resolve, reject) { 111 | utils.ajax({ 112 | url: url, 113 | headers: { 114 | 'Content-Type': 'application/json' 115 | }, 116 | method: 'POST', 117 | body: JSON.stringify({ 118 | phone: params.tel, 119 | region: params.region, 120 | code: params.code, 121 | key: params.key, 122 | appkey: Config.APPKEY, 123 | }), 124 | success: function (result) { 125 | result = JSON.parse(result); 126 | callback(null, result); 127 | resolve(result); 128 | }, 129 | fail: function (error) { 130 | callback(error); 131 | reject(error); 132 | } 133 | }); 134 | }); 135 | } 136 | function setRadioCancel() { 137 | var selectID = null; 138 | var radios = document.querySelectorAll('[name=userOption]') 139 | for (var el of radios) { 140 | el.addEventListener('click', function () { 141 | if (selectID == this.id && selectID) { 142 | this.checked = ''; 143 | selectID = null; 144 | } else { 145 | selectID = this.id; 146 | } 147 | }) 148 | } 149 | } 150 | function verifyTelNum(telNum) { 151 | if (telNum.length === 11) { 152 | return true; 153 | } else { 154 | return false; 155 | } 156 | } 157 | var setVerifyCodeBtnEnable = function () { 158 | verifyCodeBtnDom.style.background = '#28d6f6'; 159 | verifyCodeBtnDom.style.border = '#28d6f6'; 160 | verifyCodeBtnDom.onclick = sendSmsCode; 161 | } 162 | var setVErifyCodeBtnDisable = function () { 163 | verifyCodeBtnDom.style.background = '#475163'; 164 | verifyCodeBtnDom.style.border = '#475163'; 165 | verifyCodeBtnDom.onclick = function () { }; 166 | } 167 | var setCountDownTimer = function (countDown) { 168 | if (countDown === 0) { 169 | verifyCodeBtnDom.value = '发送验证码'; 170 | countDown = 60; 171 | setVerifyCodeBtnEnable(); 172 | return; 173 | } else { 174 | countDown--; 175 | setVErifyCodeBtnDisable(); 176 | } 177 | setTimeout(function () { 178 | verifyCodeBtnDom.value = countDown + 's后重新发送'; 179 | setCountDownTimer(countDown) 180 | }, 1000) 181 | } 182 | var verifyPhoneNumber = function (phoneNumber) { 183 | return new Promise(function (resolve, reject) { 184 | var telReg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/; 185 | if (telReg.test(phoneNumber)) { 186 | resolve(); 187 | } else { 188 | reject(); 189 | } 190 | }) 191 | }; 192 | var sendSmsCode = function () { 193 | verifyPhoneNumber(telDom.value).then(function () { 194 | var params = { 195 | tel: telDom.value, 196 | region: 86 197 | } 198 | getSmsCode(params).then(function (data) { 199 | if (data.code !== 200) { 200 | sealAlert(data.message || '服务器错误'); 201 | } 202 | }).catch(function (err) { 203 | console.log('getSMSCodeERR:', err) 204 | }) 205 | var countDown = 60; 206 | setCountDownTimer(countDown); 207 | }).catch(function () { 208 | sealAlert(localeData.phoneNumberErr); 209 | }) 210 | } 211 | var verifyLogin = function () { 212 | if(verifyLoginClickTimes>0){ 213 | return; 214 | } 215 | verifyLoginClickTimes++; 216 | var tel = telDom.value, 217 | tabId = LocalCache.get(StorageKeys.TabIdKey) ? LocalCache.get(StorageKeys.TabIdKey) : handleNullTabId(), 218 | code = verifyCodeDom.value; 219 | var imTokenKey = utils.tplEngine('{IMTokenKey}_{appkey}_{tel}_{tabId}_{platform}', { 220 | IMTokenKey: StorageKeys.IMToken, 221 | appkey: Config.APPKEY, 222 | tel: tel, 223 | tabId: tabId, 224 | platform: platform 225 | }); 226 | var imNaviKey = utils.tplEngine('{IMNaviKey}_{appkey}_{tel}_{tabId}_{platform}', { 227 | IMTokenKey: StorageKeys.IMNavi, 228 | appkey: Config.APPKEY, 229 | tel: tel, 230 | tabId: tabId, 231 | platform: platform 232 | }); 233 | 234 | var loginUserId = utils.tplEngine('{tel}_{tabId}_{platform}', { 235 | tel: tel, 236 | tabId: tabId, 237 | platform: platform 238 | }); 239 | verifyPhoneNumber(tel).then(function () { 240 | if (tel && code) { 241 | //发送验证码 242 | var params = { 243 | tel: tel, //验证码验证手机号 244 | region: '86', 245 | code: code, 246 | key: loginUserId //获取 token 用 id 247 | }; 248 | verifySmsCode(params).then(function (data) { 249 | //验证正确: 250 | if (data.code === 200) { 251 | rongIMToken = data.result.token; 252 | rongIMNavi = data.result.navi; 253 | if (rongIMToken) { 254 | Cache.set(imTokenKey, rongIMToken); 255 | Cache.set(imNaviKey, rongIMNavi); 256 | Cache.remove(StorageKeys.UserId); //移除之前用的 登录 UserId 257 | startRTC(rongIMToken, rongIMNavi); 258 | } else { 259 | //join room 页面 260 | Dom.showByClass('rong-login-roomjoin') 261 | Dom.hideByClass('rong-login-telverify') 262 | } 263 | } else if (data.code === 1000) { 264 | verifyLoginClickTimes = 0; 265 | sealAlert(localeData.verifyCodeIncorrect) 266 | } else if (data.code === 2000) { 267 | verifyLoginClickTimes = 0; 268 | sealAlert(localeData.verifyCodeIncorrect) 269 | } else { 270 | verifyLoginClickTimes = 0; 271 | sealAlert(data.message) 272 | } 273 | }).catch(function (/* err */) { 274 | verifyLoginClickTimes = 0; 275 | }) 276 | } else { 277 | sealAlert(localeData.verifyCodeErr) 278 | verifyLoginClickTimes = 0; 279 | } 280 | }).catch(function () { 281 | verifyLoginClickTimes = 0; 282 | sealAlert(localeData.phoneNumberErr); 283 | }) 284 | } 285 | var bindCodeFn = function () { 286 | verifyLoginDom.onclick = verifyLogin; 287 | telDom.onkeyup = function () { 288 | var telLength = telDom.value.length; 289 | if (telLength === 11) { 290 | //可点击 291 | setVerifyCodeBtnEnable() 292 | } else { 293 | //不可点击 294 | setVErifyCodeBtnDisable(); 295 | } 296 | } 297 | } 298 | var hasIMToken = function () { 299 | var roomTel = roomTelNumDom.value, 300 | tabId = LocalCache.get(StorageKeys.TabIdKey); 301 | var imTokenKey = utils.tplEngine('{IMTokenKey}_{appkey}_{tel}_{tabId}_{platform}', { 302 | IMTokenKey: StorageKeys.IMToken, 303 | appkey: Config.APPKEY, 304 | tel: roomTel, 305 | tabId: tabId, 306 | platform: platform 307 | }); 308 | var imNaviKey = utils.tplEngine('{IMNaviKey}_{appkey}_{tel}_{tabId}_{platform}', { 309 | IMTokenKey: StorageKeys.IMNavi, 310 | appkey: Config.APPKEY, 311 | tel: roomTel, 312 | tabId: tabId, 313 | platform: platform 314 | }); 315 | var IMNavi = Cache.get(imNaviKey); 316 | var IMToken = Cache.get(imTokenKey); 317 | if (IMToken || IMNavi) { 318 | return { token: IMToken, navi: IMNavi }; 319 | } else { 320 | return {}; 321 | } 322 | } 323 | var RTCEnterLogic = function () { 324 | var checkContent = checkRTCValue(); 325 | if (!checkContent.isValid) { 326 | return sealAlert(checkContent.prompt); 327 | } 328 | var userId = roomTelNumDom.value; 329 | var _cache = Cache.get(userId); 330 | if (_cache) { 331 | _cache = JSON.parse(_cache); 332 | var isKick = (((+new Date()) - parseInt(_cache.kickOffTime)) / 1000 - 5 * 60) < 0; 333 | if (_cache.roomId === roomDom.value && isKick) { 334 | sealAlert(localeData.sealRtcKickOff); 335 | return; 336 | } 337 | } 338 | if (!isChromeOrSafari()) { 339 | return sealAlert(localeData.supportedBrowsers); 340 | } 341 | var conf = hasIMToken(); 342 | if (conf) { 343 | startRTC(conf.token, conf.navi); 344 | } else { 345 | // verify tel page 346 | var tips = localeData.verifyCodeTips; 347 | sealAlert(tips, { 348 | confirmCallback: function () { 349 | telDom.value = roomTelNumDom.value; 350 | setVerifyCodeBtnEnable(); 351 | Dom.hideByClass('rong-login-roomjoin') 352 | Dom.showByClass('rong-login-telverify') 353 | } 354 | }) 355 | } 356 | } 357 | var setDefaultRTCInfo = function () { 358 | var roomId = Cache.get(StorageKeys.RoomId); 359 | if (roomId) { 360 | roomDom.value = roomId; 361 | } 362 | var resolution = Cache.get(StorageKeys.Resolution); 363 | if (resolution) { 364 | var list = getDomListByName('resolution'); 365 | for (var i = 0; i < list.length; i++) { 366 | if (list[i].value === resolution) { 367 | list[i].checked = true; 368 | } 369 | } 370 | } 371 | }; 372 | var isChromeOrSafari = function () { 373 | var browser = utils.getBrowser(); 374 | var name = browser.type; 375 | if (name == 'Chrome' || name == 'Safari') { 376 | return true; 377 | } else { 378 | return false; 379 | } 380 | } 381 | var checkRTCValue = function () { 382 | var isRoomIdEmpty = !roomDom.value; 383 | var userNameVal = utils.trim(userNameDom.value); 384 | var isUserNameEmpty = !userNameVal; 385 | var isValid = true; 386 | var prompt = ''; 387 | if (isRoomIdEmpty) { 388 | prompt = localeData.roomIdEmpty; 389 | isValid = false; 390 | } 391 | if (!utils.isNumberAndLetter(roomDom.value)) { 392 | prompt = localeData.roomIdIllegal; 393 | isValid = false; 394 | } 395 | if (isUserNameEmpty) { 396 | prompt = localeData.userNameEmpty; 397 | isValid = false; 398 | } 399 | if (userNameVal.indexOf(' ') !== -1) { 400 | prompt = localeData.userNameIllegal; 401 | isValid = false; 402 | } 403 | // if(!utils.isNumberAndLetter(userNameVal)) { 404 | // prompt = localeData.userNameEnglishOnly; 405 | // isValid = false; 406 | // } 407 | return { 408 | isValid: isValid, 409 | prompt: prompt 410 | }; 411 | }; 412 | function GetRadioValue(radioName) { 413 | var radios = document.getElementsByName(radioName); 414 | for (var i = 0; i < radios.length; i++) { 415 | var radio = radios.item(i); 416 | if (radio.checked) { 417 | return radio.value; 418 | } 419 | } 420 | } 421 | var getRTCOption = function () { 422 | const vueDatas = RongSeal.getDataFromVue(); 423 | var modeOption = GetRadioValue('userOption'); 424 | var videoEnable, bystanderEnable; 425 | 426 | var frameRate = vueDatas.frameRate; 427 | var url = Config.MEDIA_SERVER; 428 | if (modeOption) { 429 | if (modeOption == 'closeVideo') { 430 | videoEnable = false; 431 | bystanderEnable = false; 432 | } 433 | if (modeOption == 'bystander') { 434 | videoEnable = true; 435 | bystanderEnable = true; 436 | } 437 | } else { 438 | videoEnable = true; 439 | bystanderEnable = false; 440 | } 441 | var roomId = roomDom.value, 442 | userId = roomTelNumDom.value, 443 | resolution = common.formatResolution(vueDatas.resolution); 444 | var option ={ 445 | userId: userId, 446 | roomId: roomId, 447 | resolution: resolution, 448 | videoEnable: videoEnable, 449 | audioEnable: true, 450 | bystanderEnable: bystanderEnable, 451 | // bitrate: { 452 | // min: vueDatas.minRate, 453 | // max: vueDatas.maxRate, 454 | // start: vueDatas.defaultRate, 455 | // }, 456 | audioDeviceId: vueDatas.audioDeviceId, 457 | videoDeviceId: vueDatas.videoDeviceId, 458 | }; 459 | if(frameRate){ 460 | option.frameRate = parseInt(frameRate); 461 | } 462 | if(url){ 463 | option.url = url; 464 | } 465 | return option 466 | }; 467 | //用户信息缓存 468 | function setUserInfoObj(params) { 469 | var currentTimestamp = new Date().getTime(); 470 | var userId = utils.tplEngine('{tel}_{userName}_{platform}', { 471 | tel: roomTelNumDom.value, 472 | userName: LocalCache.get(StorageKeys.TabIdKey), 473 | platform: platform 474 | }); 475 | var userInfo = { 476 | userId: userId, 477 | userName: userNameDom.value, 478 | joinMode: params.joinMode, 479 | joinTime: currentTimestamp, 480 | master: params.master 481 | }; 482 | var forKey = roomTelNumDom.value; 483 | var message = { 484 | name: 'SealRTC:SetRoomInfo', 485 | content: { 486 | infoKey: roomTelNumDom.value, 487 | infoValue: userInfo 488 | } 489 | }; 490 | return { 491 | userInfo: userInfo, 492 | forKey: forKey, 493 | message: message 494 | } 495 | } 496 | 497 | var clear = function () { 498 | common.UI.backLoginPage(); 499 | RongSeal.videoTimer.stop(); 500 | RongSeal.userStreams.clearUsers(); 501 | RongSeal.destroyRongRTCPage(); 502 | RongSeal.im.disconnect(); 503 | }; 504 | var isRTCError = false; 505 | var EventName = RongSeal.EventName; 506 | RongSeal.eventEmitter.on(EventName.NETWORK_ERROR, function () { 507 | console.log('isRTCError', isRTCError) 508 | isRTCError = true; 509 | clear(); 510 | }); 511 | 512 | var reconnectionMechanism = function () { 513 | 514 | //30s前网络嗅探并重新连接 515 | var total = 30, count = 0; 516 | 517 | var reconnect = function () { 518 | RongSeal.im.reconnect({ 519 | success: function () { 520 | var isRTCShow = getDomById('RongRTC').style.display === 'block' ? true : false; 521 | if (!isRTCShow) { 522 | getDomById('RongRTC').style.display = 'block'; 523 | } 524 | }, 525 | error: function () { 526 | count++; 527 | console.log('count: ', count); 528 | if (count >= total || isRTCError) { 529 | console.log('back login'); 530 | clear(); 531 | return; 532 | } 533 | reconnect(); 534 | } 535 | }, { 536 | rate: [5000, 2000] 537 | }) 538 | }; 539 | reconnect(); 540 | } 541 | 542 | var connect = function (user) { 543 | // user.navi = Config.NAVI; 544 | user.appKey = Config.APPKEY; 545 | var goVerifyPage = function () { 546 | common.UI.backLoginPage(); 547 | telDom.value = roomTelNumDom.value; 548 | setVerifyCodeBtnEnable(); 549 | //回到手机验证页面 550 | Dom.hideByClass('rong-login-roomjoin') 551 | Dom.showByClass('rong-login-telverify') 552 | }; 553 | var token = user.token; 554 | if (!token) { 555 | return goVerifyPage(); 556 | } 557 | RongSeal.im.connect(user, { 558 | connected: function (IMUserId) { 559 | var option = getRTCOption(); 560 | var resolution = option.resolution; 561 | option.userId = IMUserId; 562 | option.token = user.token; 563 | RongSeal.startRTC(option); 564 | Cache.set(StorageKeys.RoomId, option.roomId); 565 | // Cache.set(StorageKeys.VideoEnable, option.videoEnable); 566 | // Cache.set(StorageKeys.BystanderEnable, option.bystanderEnable); 567 | Cache.set(StorageKeys.Resolution, common.reFormatResolution(resolution)); 568 | Dom.showByClass('rong-login-roomjoin') 569 | Dom.hideByClass('rong-login-telverify') 570 | verifyLoginClickTimes = 0; 571 | }, 572 | backLoginPage: function () { 573 | reconnectionMechanism(); 574 | }, 575 | tokenIncorrect: function () { 576 | console.log('token expired') 577 | var tips = localeData.tokenExpired; 578 | sealAlert(tips, { 579 | confirmCallback: goVerifyPage 580 | }) 581 | }, 582 | kickedByOther: function () { 583 | var tips = localeData.kickedByOtherTips; 584 | sealAlert(tips, { 585 | confirmCallback: function () { 586 | // common.UI.backLoginPage(); 587 | // clear(); 588 | sealToast.destroy(); 589 | // RongSeal.im.disconnect(); 590 | win.location.reload(); 591 | } 592 | }) 593 | } 594 | }); 595 | }; 596 | 597 | var startRTC = function (imToken, imNavi) { 598 | var checkContent = checkRTCValue(); 599 | if (!checkContent.isValid) { 600 | return sealAlert(checkContent.prompt); 601 | } 602 | Dom.hideByClass('rong-btn-start'); 603 | Dom.showByClass('rong-btn-loading'); 604 | // var userId = roomTelNumDom.value; 605 | var userId = utils.tplEngine('{tel}_{userName}_{platform}', { 606 | tel: roomTelNumDom.value, 607 | userName: LocalCache.get(StorageKeys.TabIdKey), 608 | platform: platform 609 | }); 610 | connect({ 611 | userId: userId, 612 | token: imToken, 613 | navi: imNavi, 614 | }) 615 | RongSeal.userInfo = { 616 | userName: userNameDom.value, 617 | userId: userId 618 | } 619 | }; 620 | 621 | var checkRoomTelValue = function () { 622 | // var roomId = roomDom.value; 623 | var telNum = roomTelNumDom.value; 624 | if (verifyTelNum(telNum)) { 625 | startBtnDom.style.background = '#28d6f6'; 626 | startBtnDom.style.border = '#28d6f6'; 627 | startBtnDom.onclick = RTCEnterLogic; 628 | } else { 629 | startBtnDom.style.background = '#475163'; 630 | startBtnDom.style.border = '#475163'; 631 | startBtnDom.onclick = function () { }; 632 | return; 633 | } 634 | } 635 | 636 | var pressInput = function (e) { 637 | var conf = hasIMToken(); 638 | if ((e.keyCode || e.which) == 13) { 639 | startRTC(conf.token, conf.navi); 640 | } 641 | }; 642 | var pressVerifyLogin = function (e) { 643 | if ((e.keyCode || e.which) == 13) { 644 | verifyLogin(); 645 | } 646 | }; 647 | 648 | (function init() { 649 | setRadioCancel(); 650 | setDefaultRTCInfo(); 651 | checkRoomTelValue(); 652 | bindCodeFn(); 653 | // pressVerifyLogin(); 654 | roomTelNumDom.onkeyup = checkRoomTelValue; 655 | // startBtnDom.onclick = startRTC; 656 | utils.forEach(inputDomList, function (dom) { 657 | dom.onkeydown = pressInput; 658 | }); 659 | utils.forEach(inputTelVerifyDomList, function (dom) { 660 | dom.onkeydown = pressVerifyLogin; 661 | }); 662 | common.setLocale(); 663 | })(); 664 | RongSeal.setUserInfoObj = setUserInfoObj; 665 | RongSeal.StorageKeys = StorageKeys; 666 | 667 | })({ 668 | win: window, 669 | RongSeal: window.RongSeal, 670 | RongRTC: window.RongRTC, 671 | globalConfig: window.RongSeal.Config 672 | }); 673 | -------------------------------------------------------------------------------- /public/js/utils.js: -------------------------------------------------------------------------------- 1 | (function (dependencies) { 2 | var win = dependencies.win; 3 | var noop = function () { }; 4 | var utils; 5 | 6 | var isString = function (str) { 7 | return Object.prototype.toString.call(str) === '[object String]'; 8 | }; 9 | 10 | var isObject = function (obj) { 11 | return Object.prototype.toString.call(obj) === '[object Object]'; 12 | }; 13 | 14 | var isArray = function (obj) { 15 | return Object.prototype.toString.call(obj) === '[object Array]'; 16 | }; 17 | 18 | var isNodeList = function (obj) { 19 | return Object.prototype.toString.call(obj) === '[object NodeList]'; 20 | }; 21 | 22 | var forEach = function (obj, callback) { 23 | callback = callback || noop; 24 | var loopObj = function () { 25 | for (var key in obj) { 26 | callback(obj[key], key, obj); 27 | } 28 | }; 29 | var loopArr = function () { 30 | for (var i = 0, len = obj.length; i < len; i++) { 31 | callback(obj[i], i); 32 | } 33 | }; 34 | if (isObject(obj)) { 35 | loopObj(); 36 | } 37 | if (isArray(obj) || isNodeList(obj)) { 38 | loopArr(); 39 | } 40 | }; 41 | 42 | var tplEngine = function (temp, data, regexp) { 43 | var replaceAction = function (object) { 44 | return temp.replace(regexp || (/{([^}]+)}/g), function (match, name) { 45 | if (match.charAt(0) === '\\') return match.slice(1); 46 | return (object[name] !== undefined) ? object[name] : '{' + name + '}'; 47 | }); 48 | }; 49 | if (!(Object.prototype.toString.call(data) === '[object Array]')) data = [data]; 50 | var ret = []; 51 | for (var i = 0, j = data.length; i < j; i++) { 52 | ret.push(replaceAction(data[i])); 53 | } 54 | return ret.join(''); 55 | }; 56 | 57 | var Cache = (function (config) { 58 | config = config || {}; 59 | var prefix = config.prefix || 'rong-sealrtc-v2'; 60 | var genKey = function (key) { 61 | return utils.tplEngine('{prefix}_{key}', { 62 | prefix: prefix, 63 | key: key 64 | }); 65 | }; 66 | var set = function (key, value) { 67 | localStorage.setItem(genKey(key), value); 68 | }; 69 | var get = function (key) { 70 | return localStorage.getItem(genKey(key)); 71 | }; 72 | var remove = function (key) { 73 | localStorage.removeItem(genKey(key)); 74 | }; 75 | return { 76 | set: set, 77 | get: get, 78 | remove: remove 79 | }; 80 | })(); 81 | var SessionCache = (function (config){ 82 | config = config || {}; 83 | var prefix = config.prefix || 'rong-sealrtc-v2'; 84 | var genKey = function (key) { 85 | return utils.tplEngine('{prefix}_{key}', { 86 | prefix: prefix, 87 | key: key 88 | }); 89 | }; 90 | var set = function (key, value) { 91 | sessionStorage.setItem(genKey(key), value); 92 | }; 93 | var get = function (key) { 94 | return sessionStorage.getItem(genKey(key)); 95 | }; 96 | var remove = function (key) { 97 | sessionStorage.removeItem(genKey(key)); 98 | }; 99 | return { 100 | set: set, 101 | get: get, 102 | remove: remove 103 | }; 104 | })(); 105 | var LocalCache = (function (config){ 106 | config = config || {}; 107 | var prefix = config.prefix || 'rong-sealrtc-v2'; 108 | var genKey = function (key) { 109 | return utils.tplEngine('{prefix}_{key}', { 110 | prefix: prefix, 111 | key: key 112 | }); 113 | }; 114 | var set = function (key, value) { 115 | localStorage.setItem(genKey(key), value); 116 | }; 117 | var get = function (key) { 118 | return localStorage.getItem(genKey(key)); 119 | }; 120 | var remove = function (key) { 121 | localStorage.removeItem(genKey(key)); 122 | }; 123 | return { 124 | set: set, 125 | get: get, 126 | remove: remove 127 | }; 128 | })(); 129 | /* 130 | var option = { 131 | url: '', 132 | method: '', 133 | headers: {}, 134 | body: {}, 135 | success: function(){}, 136 | fail: function(){} 137 | }; 138 | */ 139 | var sendForm = function (option) { 140 | var formData = new FormData(); 141 | var data = option.body || {}; 142 | for (var key in data) { 143 | formData.append(key, data[key]); 144 | } 145 | var xhr = new XMLHttpRequest(); 146 | xhr.open(option.method, option.url); 147 | xhr.addEventListener('load', function () { 148 | option.success && option.success(xhr.responseText); 149 | }, false); 150 | xhr.addEventListener('error', function (e) { 151 | option.fail && option.fail(e); 152 | }); 153 | xhr.send(formData); 154 | }; 155 | 156 | var ajax = function (option) { 157 | var getXHR = function () { 158 | var xhr = null; 159 | var hasXDomain = function () { 160 | return (typeof XDomainRequest !== 'undefined'); 161 | }; 162 | var hasXMLRequest = function () { 163 | return (typeof XMLHttpRequest !== 'undefined'); 164 | }; 165 | if (hasXDomain()) { 166 | xhr = new win.XDomainRequest(); 167 | } else if (hasXMLRequest()) { 168 | xhr = new win.XMLHttpRequest(); 169 | } else { 170 | xhr = new win.ActiveXObject('Microsoft.XMLHTTP'); 171 | } 172 | return xhr; 173 | }; 174 | 175 | var xhr = getXHR(); 176 | var method = option.method || 'GET'; 177 | var url = option.url; 178 | var queryStrings = option.queryStrings || {}; 179 | var tpl = '{key}={value}', strings = []; 180 | utils.forEach(queryStrings, function (value, key) { 181 | var str = utils.tplEngine(tpl, { 182 | key: key, 183 | value: value 184 | }); 185 | strings.push(str); 186 | }); 187 | var queryString = strings.join('&'); 188 | var urlTpl = '{url}?{queryString}'; 189 | url = utils.tplEngine(urlTpl, { 190 | url: url, 191 | queryString: queryString 192 | }); 193 | 194 | xhr.open(method, url, true); 195 | 196 | var headers = option.headers || {}; 197 | utils.forEach(headers, function (header, name) { 198 | xhr.setRequestHeader(name, header); 199 | }); 200 | 201 | var success = option.success || utils.noop; 202 | var fail = option.fail || utils.noop; 203 | var isSuccess = function (xhr) { 204 | return /^(200|202|10000)$/.test(xhr.status); 205 | }; 206 | 207 | var onLoad = function () { 208 | var result = xhr.responseText; 209 | if (isSuccess(xhr)) { 210 | success(result); 211 | } else { 212 | fail(result.target.status); 213 | } 214 | }; 215 | if ('onload' in xhr) { 216 | xhr.onload = onLoad; 217 | } 218 | else { 219 | xhr.onreadystatechange = function () { 220 | if (xhr.readyState == 4) { 221 | onLoad(); 222 | } 223 | }; 224 | } 225 | xhr.onerror = function (error) { 226 | fail(error.target.status); 227 | }; 228 | xhr.send(option.body); 229 | }; 230 | 231 | var download = function (url) { 232 | win.open(url); 233 | }; 234 | 235 | var getDom = function (name) { 236 | var selector = null; 237 | try { 238 | selector = win.document.querySelector(name); 239 | } catch (e) { 240 | // console.error(e); 241 | } 242 | return selector; 243 | }; 244 | 245 | var getDomByClass = function (className) { 246 | return getDom('.' + className); 247 | }; 248 | 249 | var getDomList = function (name) { 250 | var selector = null; 251 | try { 252 | selector = win.document.querySelectorAll(name); 253 | } catch (e) { 254 | // console.error(e); 255 | } 256 | return selector; 257 | }; 258 | 259 | var getDomById = function (id) { 260 | return win.document.getElementById(id); 261 | }; 262 | 263 | var showDom = function (dom) { 264 | if (isString(dom)) { 265 | dom = getDom(dom) 266 | } 267 | if (dom) { 268 | dom.style.display = 'block'; 269 | } 270 | }; 271 | 272 | var showDomByClass = function (className) { 273 | showDom('.' + className); 274 | }; 275 | 276 | var hideDom = function (dom) { 277 | if (isString(dom)) { 278 | dom = getDom(dom) 279 | } 280 | if (dom) { 281 | dom.style.display = 'none'; 282 | } 283 | }; 284 | 285 | var hideDomByClass = function (className) { 286 | hideDom('.' + className); 287 | }; 288 | 289 | var getBrotherDom = function (dom, brotherName) { 290 | if (isString(dom)) { 291 | dom = getDom(dom); 292 | } 293 | if (!isString(brotherName)) { 294 | brotherName = brotherName.className; 295 | } 296 | var parent = dom.parentElement; 297 | var brothers = parent.children; 298 | var brother; 299 | for (var i = 0, max = brothers.length; i < max; i++) { 300 | var bro = brothers[i]; 301 | if (bro.className.indexOf(brotherName) !== -1) { 302 | brother = bro; 303 | } 304 | } 305 | return brother; 306 | }; 307 | 308 | var getChildDom = function (dom, childName) { 309 | if (isString(dom)) { 310 | dom = getDom(dom); 311 | } 312 | if (!isString(childName)) { 313 | childName = childName.className; 314 | } 315 | var children = dom.children; 316 | var child; 317 | for (var i = 0, max = children.length; i < max; i++) { 318 | var bro = children[i]; 319 | if (bro.className.indexOf(childName) !== -1) { 320 | child = bro; 321 | } 322 | } 323 | return child; 324 | }; 325 | 326 | var getDomListByName = function (name) { 327 | return win.document.getElementsByName(name); 328 | }; 329 | 330 | var getSelectedDomByName = function (name) { 331 | var list = win.document.getElementsByName(name); 332 | var selectedEl; 333 | for (var i = 0, max = list.length; i < max; i++) { 334 | var el = list[i]; 335 | if (el.checked === true) { 336 | selectedEl = el; 337 | return selectedEl; 338 | } 339 | } 340 | return selectedEl; 341 | }; 342 | 343 | var hasClass = function (dom, className) { 344 | var classList = dom.classList; 345 | var hasClass = false; 346 | for (var i = 0; i < classList.length; i++) { 347 | var name = classList[i]; 348 | if (name === className) { 349 | hasClass = true; 350 | } 351 | } 352 | return hasClass; 353 | }; 354 | 355 | var addClass = function (dom, className) { 356 | if (!hasClass(dom, className)) { 357 | dom.classList.add(className); 358 | } 359 | }; 360 | 361 | var removeClass = function (dom, className) { 362 | if (hasClass(dom, className)) { 363 | dom.classList.remove(className); 364 | } 365 | }; 366 | 367 | var create = function (innerHTML) { 368 | var div = win.document.createElement('div'); 369 | div.innerHTML = innerHTML; 370 | return div.children[0]; 371 | }; 372 | 373 | var isNumberAndLetter = function (val) { 374 | var reg = /^[A-Za-z0-9+=_-]+$/; 375 | return reg.test(val) 376 | }; 377 | 378 | var isLetter = function (val) { 379 | var reg = /^[A-Za-z]+$/; 380 | return reg.test(val) 381 | 382 | }; 383 | 384 | var insertAfter = function(newEl, targetEl) { 385 | var parentEl = targetEl.parentNode; 386 | if(parentEl.lastChild == targetEl){ 387 | parentEl.appendChild(newEl); 388 | }else{ 389 | parentEl.insertBefore(newEl,targetEl.nextSibling); 390 | } 391 | } 392 | 393 | var getBrowser = function() { 394 | var userAgent = win.navigator.userAgent; 395 | var version; 396 | var type; 397 | 398 | /* 记录各浏览器名字和匹配条件 */ 399 | var condition = { 400 | IE: /rv:([\d.]+)\) like Gecko|MSIE ([\d.]+)/, 401 | Edge: /Edge\/([\d.]+)/, 402 | Firefox: /Firefox\/([\d.]+)/, 403 | Opera: /(?:OPERA|OPR).([\d.]+)/, 404 | WeChat: /MicroMessenger/i, 405 | QQBrowser: /QQBrowser\/([\d.]+)/, 406 | Chrome: /Chrome\/([\d.]+)/, 407 | Safari: /Version\/([\d.]+).*Safari/, 408 | iOSChrome: /Mobile\/([\d.]+).*Safari/ 409 | }; 410 | 411 | for (var key in condition) { 412 | if (!condition.hasOwnProperty(key)) continue; 413 | var browserContent = userAgent.match(condition[key]); 414 | if (browserContent) { 415 | type = key; 416 | version = browserContent[1] || browserContent[2]; 417 | break; 418 | } 419 | } 420 | return { 421 | type: type ? type : 'UnKonw', 422 | version: version ? version : 'UnKonw' 423 | }; 424 | } 425 | var isSupportGetDisplayMedia = function() { 426 | var bro = getBrowser(); 427 | var version = parseInt(bro.version); 428 | return version > 71 ? true : false; 429 | } 430 | var Dom = { 431 | create: create, 432 | get: getDom, 433 | getByClass: getDomByClass, 434 | getList: getDomList, 435 | getById: getDomById, 436 | show: showDom, 437 | showByClass: showDomByClass, 438 | hide: hideDom, 439 | hideByClass: hideDomByClass, 440 | getSelectedByName: getSelectedDomByName, 441 | getDomListByName: getDomListByName, 442 | getBrother: getBrotherDom, 443 | getChild: getChildDom, 444 | addClass: addClass, 445 | removeClass: removeClass, 446 | hasClass: hasClass, 447 | insertAfter: insertAfter 448 | }; 449 | //暂时支持 on 单次 450 | function EventEmitter() { 451 | var events = {}; 452 | this.emit = function (name) { 453 | var event = events[name] || function () { }; 454 | event(); 455 | }; 456 | this.on = function (name, event) { 457 | events[name] = event; 458 | }; 459 | } 460 | //去字符串前后空格 461 | function trim(str) { 462 | return str.replace(/(^\s*)|(\s*$)/g, ''); 463 | } 464 | function isChineseChar(str){ 465 | var reg = /[\u4E00-\u9FA5\uF900-\uFA2D]/; 466 | return reg.test(str); 467 | } 468 | function getRandomStr() { 469 | var tplArr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; 470 | var randomArr = [],randomStr = '{a}{b}{c}{d}'; 471 | for(var i=0; i<4; i++){ 472 | var index = Math.floor(Math.random() * tplArr.length) 473 | randomArr.push(tplArr[index]) 474 | } 475 | randomStr = tplEngine('{a}{b}{c}{d}',{ 476 | a: randomArr[0], 477 | b: randomArr[1], 478 | c: randomArr[2], 479 | d: randomArr[3], 480 | }) 481 | return randomStr; 482 | } 483 | utils = { 484 | noop: noop, 485 | forEach: forEach, 486 | tplEngine: tplEngine, 487 | Cache: Cache, 488 | SessionCache: SessionCache, 489 | LocalCache: LocalCache, 490 | sendForm: sendForm, 491 | ajax: ajax, 492 | isObject: isObject, 493 | download: download, 494 | Dom: Dom, 495 | isNumberAndLetter: isNumberAndLetter, 496 | EventEmitter, 497 | trim: trim, 498 | getBrowser: getBrowser, 499 | isChineseChar: isChineseChar, 500 | isLetter: isLetter, 501 | getRandomStr: getRandomStr, 502 | isSupportGetDisplayMedia 503 | }; 504 | win.RongSeal = win.RongSeal || {}; 505 | win.RongSeal.utils = utils; 506 | })({ 507 | win: window 508 | }); -------------------------------------------------------------------------------- /public/js/whiteboard.js: -------------------------------------------------------------------------------- 1 | (function(win){ 2 | var common = win.RongSeal.common; 3 | var sealAlert = common.sealAlert; 4 | var whiteBoardEnum = { 5 | wbKey: 'rongRTCWhite', 6 | msgName: 'SealRTC:WhiteBoardInfo' 7 | } 8 | var rongWhiteBoard,rongStorage; 9 | 10 | function setWBRoomInfo(whiteRoomInfo) { 11 | var info = JSON.stringify(whiteRoomInfo); 12 | var key = whiteBoardEnum.wbKey; 13 | var msg = { 14 | name: whiteBoardEnum.msgName, 15 | content: info 16 | } 17 | rongStorage.set(key,info,msg).then(function(val){ 18 | console.log('set wb info sss', val); 19 | }).catch(function(err) { 20 | console.log('set wb info fff', err) 21 | }); 22 | } 23 | 24 | function createRongWB() { 25 | var iframeWin=document.getElementById('rongWhiteboard').contentWindow; 26 | iframeWin.RongWB.getWhite(true,null,function() { 27 | setWBRoomInfo(iframeWin.RongWB.whiteRoomInfo); 28 | }); 29 | console.log(iframeWin.RongWB); 30 | } 31 | 32 | function joinWBRoom(value,isBystander) { 33 | var key = whiteBoardEnum.wbKey; 34 | var iframeWin=document.getElementById('rongWhiteboard').contentWindow; 35 | var roomInfo = JSON.parse(value[key]); 36 | iframeWin.RongWB.getWhite(false,roomInfo,function(){},isBystander) 37 | } 38 | function destroyWBRoom(){ 39 | rongStorage = win.RongSeal.rongStorage; 40 | //判断是否最后一人 41 | return new Promise(function(resolve, reject) { 42 | rongStorage.get([]).then(function (infos){ 43 | delete infos[whiteBoardEnum.wbKey]; 44 | var rtcPerNum = Object.getOwnPropertyNames(infos); 45 | if (rtcPerNum.length === 1) { 46 | //若是 清房间属性 47 | rongStorage.remove(whiteBoardEnum.wbKey).then(function(val){ 48 | console.log('remove whiteBoard sss',val) 49 | resolve(); 50 | }).catch(function(err){ 51 | console.log('remove whiteBoard fff',err) 52 | resolve(); 53 | }) 54 | } else { 55 | resolve(); 56 | } 57 | }).catch(function(err){ 58 | console.log('get wb room info fail',err) 59 | reject(); 60 | }) 61 | }) 62 | } 63 | function whiteBoardLogic(isBystander) { 64 | rongStorage = win.RongSeal.rongStorage; 65 | var key = whiteBoardEnum.wbKey; 66 | rongWhiteBoard = new common.UI.WhiteBoard(); 67 | var url = './whiteboard/whiteboard.html'; 68 | //获取 69 | if (isBystander) { 70 | rongStorage.get(key).then(function(value){ 71 | if (!value.hasOwnProperty(key)) { 72 | sealAlert('房间内未创建白板'); 73 | }else { 74 | rongWhiteBoard.show(url); 75 | document.getElementById('rongWhiteboard').onload=function(){ 76 | joinWBRoom(value,isBystander); 77 | } 78 | } 79 | }) 80 | return ; 81 | } 82 | rongWhiteBoard.show(url); 83 | document.getElementById('rongWhiteboard').onload=function(){ 84 | rongStorage.get(key).then(function(value){ 85 | if (value.hasOwnProperty(key)) { 86 | //加入房间 87 | joinWBRoom(value) 88 | }else { 89 | //新建 90 | createRongWB(); 91 | } 92 | }).catch(function(err){ 93 | console.log('get wbRoomInfo err',err); 94 | }) 95 | } 96 | } 97 | 98 | win.RongSeal.whiteBoard = { 99 | whiteBoardLogic: whiteBoardLogic, 100 | destroyWBRoom: destroyWBRoom 101 | } 102 | })(window) -------------------------------------------------------------------------------- /public/lib/frameImage.js: -------------------------------------------------------------------------------- 1 | (function (win) { 2 | /** 3 | * 4 | * @param {mediaStream} stream 5 | * @param {object} options 6 | * @param {string} options.type 图片类型 支持 png, jpeg, 默认 png 7 | * @param {number} options.width 图片宽度 8 | * @param {number} options.height 图片高度 9 | * @param {number} options.scale 缩放, 默认为 1, 建议范围: 0.01 - 5 10 | */ 11 | var getBase64Image = function (stream, options) { 12 | options = options || {}; 13 | var type = options.type || 'png', 14 | scale = Number(options.scale || 1); 15 | return new Promise(function (resolve, reject) { 16 | var video = document.createElement('video'); 17 | var canvas = document.createElement('canvas'); 18 | video.srcObject = stream; 19 | video.autoplay = true; 20 | video.onplay = function () { 21 | var width = options.width || video.videoWidth; 22 | var height = options.height || video.videoHeight; 23 | canvas.width = width * scale; 24 | canvas.height = height * scale; 25 | canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height); 26 | var base64 = canvas.toDataURL('image/' + type); 27 | resolve(base64); 28 | }; 29 | video.onerror = reject; 30 | }); 31 | }; 32 | win.getBase64Image = getBase64Image; 33 | })(window); -------------------------------------------------------------------------------- /public/lib/media.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.RongMedia = factory(global)); 5 | })(window, function (win) { 6 | 'use strict'; 7 | var mediaDevices = win.navigator.mediaDevices; 8 | 9 | var isObject = function (obj) { 10 | return Object.prototype.toString.call(obj) === '[object Object]'; 11 | }; 12 | 13 | var noop = function () { }; 14 | 15 | var DeviceKinds = { 16 | audio: { 17 | input: 'audioinput', 18 | output: 'audiooutput' 19 | }, 20 | video: { 21 | input: 'videoinput' 22 | } 23 | }; 24 | 25 | /** 26 | * 结果为: 27 | * { 28 | * audioinput: [ audio, input ], 29 | * audiooutput: [ audio, output ], 30 | * videoinput: [ video, input ] 31 | * } 32 | */ 33 | var kindMapDeviceKeys = (function () { 34 | var keys = {}; 35 | for (var deviceName in DeviceKinds) { 36 | var deviceInfo = DeviceKinds[deviceName]; 37 | if (isObject(deviceInfo)) { 38 | for (var type in deviceInfo) { 39 | var kind = deviceInfo[type]; 40 | keys[kind] = [deviceName, type]; 41 | } 42 | } 43 | } 44 | return keys; 45 | })(); 46 | 47 | function formatDevices(deviceList) { 48 | var formatedDevices = { 49 | audio: { 50 | input: [], 51 | output: [] 52 | }, 53 | video: { 54 | input: [] 55 | } 56 | }; 57 | deviceList.forEach(function (device) { 58 | var kind = device.kind; 59 | var keys = kindMapDeviceKeys[kind]; 60 | formatedDevices[keys[0]][keys[1]].push(device); 61 | }); 62 | return formatedDevices; 63 | } 64 | 65 | var get = function (option) { 66 | var video = option.video, opts; 67 | var audioOnly = option.audioOnly; 68 | if (video === false && audioOnly === true) { 69 | opts = { 70 | audio: true, 71 | video: false 72 | }; 73 | } 74 | else if (video === false) { 75 | opts = { 76 | audio: true, 77 | video: false 78 | }; 79 | } 80 | else { 81 | // option.video.frameRate = 15; 82 | opts = option; 83 | } 84 | 85 | return mediaDevices.getUserMedia(opts).then(function (stream) { 86 | if (video === false) { 87 | stream.getVideoTracks().map(function (track) { 88 | track.enabled = false; 89 | return track; 90 | }); 91 | } 92 | return stream; 93 | }); 94 | }; 95 | 96 | var getDeviceList = function (callback) { 97 | callback = callback || noop; 98 | return new Promise(function (resolve, reject) { 99 | mediaDevices.enumerateDevices().then(function (deviceList) { 100 | var deviceInfos = formatDevices(deviceList); 101 | callback(null, deviceInfos); 102 | resolve(deviceInfos); 103 | }).catch(function (error) { 104 | callback(error); 105 | reject(error); 106 | }); 107 | }); 108 | }; 109 | 110 | var checkDevice = function (callback) { 111 | callback = callback || noop; 112 | var check = function (deviceInfos) { 113 | var devicesState = {}; 114 | for (var deviceName in deviceInfos) { 115 | devicesState[deviceName] = {}; 116 | var types = deviceInfos[deviceName]; 117 | for (var type in types) { 118 | var deviceList = types[type] || []; 119 | devicesState[deviceName][type] = !!deviceList.length; 120 | } 121 | } 122 | return devicesState; 123 | }; 124 | return new Promise(function (resolve, reject) { 125 | getDeviceList().then(function (deviceInfos) { 126 | var devicesState = check(deviceInfos); 127 | callback(null, devicesState); 128 | resolve(devicesState); 129 | }, function (error) { 130 | callback(error); 131 | reject(error); 132 | }); 133 | }); 134 | }; 135 | 136 | return { 137 | get: get, 138 | Device: { 139 | check: checkDevice, 140 | getList: getDeviceList 141 | } 142 | }; 143 | }); -------------------------------------------------------------------------------- /public/lib/screenshare.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.RongScreenShare = factory(global)); 5 | })(window, function (win) { 6 | 'use strict'; 7 | var Keys = { 8 | CHECK: 'rong-check-share-installed', 9 | CHECK_RESPONSE: 'rong-share-installed', 10 | GET: 'rong-share-get', 11 | GET_RESPONSE: 'rong-share-get-response', 12 | CLEAR_BOX: 'rong-share-clear-box' 13 | }; 14 | 15 | var Reason = { 16 | PLUGIN_NOT_INSTALLED: 'Plugin not installed' 17 | }; 18 | 19 | var ShareProfile = { 20 | width: 1280, 21 | height: 720, 22 | frameRate: 15 23 | }; 24 | 25 | var checkTimeout = 1500; 26 | 27 | var get; 28 | 29 | var sendToPlugin = function (key) { 30 | win.postMessage({ 31 | type: key 32 | }); 33 | }; 34 | 35 | var addListener = function (event) { 36 | win.addEventListener('message', event); 37 | }; 38 | 39 | var removeListenr = function (event) { 40 | win.removeEventListener('message', event); 41 | }; 42 | 43 | var clearTimeoutAndListenr = function (timeout, listener) { 44 | listener && removeListenr(listener); 45 | timeout && clearTimeout(timeout); 46 | }; 47 | 48 | var getScreenMediaConfig = function (sourceId) { 49 | var screenMediaConfig = { 50 | video: { 51 | mandatory: { 52 | chromeMediaSource: 'screen', 53 | chromeMediaSourceId: sourceId, 54 | maxWidth: 1280, 55 | maxHeight: 720, 56 | minFrameRate: 15, 57 | maxFrameRate: 15 58 | } 59 | // optional: [{ googTemporalLayeredScreencast: true }] 60 | } 61 | }; 62 | return screenMediaConfig; 63 | }; 64 | 65 | var testSourceId = null; 66 | 67 | var getScreenMedia = function (sourceId) { 68 | return new Promise(function (resolve, reject) { 69 | var mediaConfig = getScreenMediaConfig(sourceId); 70 | console.log(mediaConfig); 71 | win.navigator.mediaDevices.getUserMedia(mediaConfig).then(function (stream) { 72 | resolve(stream); 73 | }, function (err, test) { 74 | reject(err); 75 | }); 76 | }); 77 | }; 78 | window.getScreenMedia = getScreenMedia; 79 | 80 | var check = function (callback) { 81 | return new Promise(function (resolve, reject) { 82 | var timeout, checkCallback; 83 | checkCallback = function (data) { 84 | var data = data.data || {}; 85 | var type = data.type; 86 | if (type === Keys.CHECK_RESPONSE) { 87 | callback && callback(null); 88 | resolve(); 89 | clearTimeoutAndListenr(timeout, checkCallback); 90 | } 91 | }; 92 | timeout = setTimeout(function () { 93 | callback && callback(Reason.PLUGIN_NOT_INSTALLED); 94 | reject(); 95 | clearTimeoutAndListenr(timeout, checkCallback); 96 | }, checkTimeout); 97 | addListener(checkCallback); 98 | sendToPlugin(Keys.CHECK); 99 | }); 100 | }; 101 | 102 | var getScreenByPlugin = function (callback) { 103 | return new Promise(function (resolve, reject) { 104 | var getCallback = function (data) { 105 | var data = data.data || {}; 106 | var type = data.type; 107 | if (type === Keys.GET_RESPONSE) { 108 | var sourceId = data.sourceId; 109 | getScreenMedia(sourceId).then(function (stream) { 110 | resolve(stream); 111 | removeListenr(getCallback); 112 | callback && callback(null, stream); 113 | }, function (err) { 114 | removeListenr(getCallback); 115 | reject(err); 116 | callback && callback(err); 117 | }); 118 | } 119 | }; 120 | check().then(function () { 121 | addListener(getCallback); 122 | sendToPlugin(Keys.GET); 123 | }, function (err) { 124 | reject(Reason.PLUGIN_NOT_INSTALLED); 125 | }); 126 | }); 127 | }; 128 | 129 | var getDisplayMedia = function() { 130 | return win.navigator.mediaDevices.getDisplayMedia(); 131 | } 132 | 133 | var init = function () { 134 | var bro = win.navigator.userAgent.match( /Chrome\/([\d.]+)/); 135 | var version = parseInt(bro[1]); 136 | if(version > 71) { 137 | get = getDisplayMedia; 138 | }else { 139 | get = getScreenByPlugin; 140 | } 141 | } 142 | 143 | var clearChooseBox = function () { 144 | sendToPlugin(Keys.CLEAR_BOX); 145 | }; 146 | 147 | init(); 148 | 149 | return { 150 | check, 151 | get, 152 | clearChooseBox 153 | }; 154 | }); -------------------------------------------------------------------------------- /public/mediaresource/audio.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/mediaresource/audio.mp3 -------------------------------------------------------------------------------- /public/mediaresource/video_demo1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/mediaresource/video_demo1.mp4 -------------------------------------------------------------------------------- /public/mediaresource/video_demo2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/mediaresource/video_demo2.mp4 -------------------------------------------------------------------------------- /public/mediaresource/video_demo3.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/mediaresource/video_demo3.mp4 -------------------------------------------------------------------------------- /public/plugin/screenshare-addon.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/plugin/screenshare-addon.zip -------------------------------------------------------------------------------- /public/plugin/screenshare-addon/screenshare-addon/background-script.js: -------------------------------------------------------------------------------- 1 | var Keys = { 2 | GET: 'rong-share-get', 3 | GET_RESPONSE: 'rong-share-get-response', 4 | CLEAR_BOX: 'rong-share-clear-box' 5 | }; 6 | 7 | var choseIdList = []; 8 | 9 | var sendToContentScript = function (msg) { 10 | var opt = { 11 | active: true, 12 | currentWindow: true 13 | }; 14 | chrome.tabs.query(opt, function (tabs) { 15 | chrome.tabs.sendMessage(tabs[0].id, msg); 16 | }); 17 | }; 18 | 19 | var listenContentMsg = function (callback) { 20 | chrome.runtime.onMessage.addListener(callback); 21 | }; 22 | 23 | var getScreenShare = function (sender) { 24 | var sourceType = ['screen', 'window']; 25 | return new Promise(function (resolve, reject) { 26 | var chooseId = chrome.desktopCapture.chooseDesktopMedia(sourceType, sender.tab, function (sourceId) { 27 | resolve(sourceId); 28 | }); 29 | choseIdList.push(chooseId); 30 | }); 31 | }; 32 | 33 | var clearChooseBox = function () { 34 | choseIdList.forEach(function (choseId) { 35 | chrome.desktopCapture.cancelChooseDesktopMedia(choseId); 36 | }); 37 | choseIdList = []; 38 | }; 39 | 40 | listenContentMsg(function (data, sender) { 41 | var type = data.type; 42 | if (type === Keys.GET) { 43 | getScreenShare(sender).then(function (sourceId) { 44 | var content = { 45 | type: Keys.GET_RESPONSE, 46 | sourceId: sourceId 47 | }; 48 | sendToContentScript(content); 49 | }); 50 | } 51 | if (type === Keys.CLEAR_BOX) { 52 | clearChooseBox(); 53 | } 54 | }); -------------------------------------------------------------------------------- /public/plugin/screenshare-addon/screenshare-addon/content-script.js: -------------------------------------------------------------------------------- 1 | var Keys = { 2 | CHECK: 'rong-check-share-installed', 3 | CHECK_RESPONSE: 'rong-share-installed', 4 | GET: 'rong-share-get', 5 | GET_RESPONSE: 'rong-share-get-response', 6 | CLEAR_BOX: 'rong-share-clear-box' 7 | }; 8 | 9 | var sendToBackground = function (msg) { 10 | chrome.runtime.sendMessage(msg); 11 | }; 12 | 13 | var sendToWindow = function (msg) { 14 | window.postMessage(msg, '*'); 15 | }; 16 | 17 | var listenBackgroundMsg = function (callback) { 18 | chrome.runtime.onMessage.addListener(callback); 19 | }; 20 | 21 | var listenWindowMsg = function (callback) { 22 | window.addEventListener('message', callback); 23 | }; 24 | 25 | listenBackgroundMsg(function (data) { 26 | var type = data.type; 27 | if (type === Keys.GET_RESPONSE) { 28 | sendToWindow(data); 29 | } 30 | }); 31 | 32 | listenWindowMsg(function (event) { 33 | var data = event.data; 34 | var type = data.type; 35 | switch(type) { 36 | case Keys.CHECK: 37 | sendToWindow({ 38 | type: Keys.CHECK_RESPONSE 39 | }); 40 | break; 41 | case Keys.GET: 42 | sendToBackground({ 43 | type: Keys.GET 44 | }); 45 | break; 46 | case Keys.CLEAR_BOX: 47 | sendToBackground({ 48 | type: Keys.CLEAR_BOX 49 | }); 50 | default: 51 | break; 52 | } 53 | }); 54 | 55 | -------------------------------------------------------------------------------- /public/plugin/screenshare-addon/screenshare-addon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/plugin/screenshare-addon/screenshare-addon/icon.png -------------------------------------------------------------------------------- /public/plugin/screenshare-addon/screenshare-addon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "RongRTC", 3 | "author": "RongCloud", 4 | "version" : "1.0", 5 | "manifest_version" : 2, 6 | "minimum_chrome_version": "49", 7 | "description" : "RongRTC ScreenShare", 8 | "background": { 9 | "scripts": ["background-script.js"], 10 | "persistent": false 11 | }, 12 | "content_scripts": [ { 13 | "js": [ "content-script.js" ], 14 | "all_frames": true, 15 | "run_at": "document_start", 16 | "matches": [""] 17 | }], 18 | "externally_connectable": { 19 | "matches": [ 20 | "https://rtc.ronghub.com/*", 21 | "https://web.blinkcloud.cn/*", 22 | "https://t-rtc.ronghub.com/*", 23 | "https://d-rtc.ronghub.com/*", 24 | "https://web.bailingcloud.com/*", 25 | "https://web.blinktalk.site/*" 26 | ] 27 | }, 28 | "icons" : { 29 | "48" : "icon.png" 30 | }, 31 | "permissions": [ 32 | "desktopCapture" 33 | ], 34 | "web_accessible_resources": [ 35 | "icon.png" 36 | ] 37 | } -------------------------------------------------------------------------------- /public/plugin/screenshare-addon/手动安装Chrome扩展说明_201801091600001.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/plugin/screenshare-addon/手动安装Chrome扩展说明_201801091600001.doc -------------------------------------------------------------------------------- /public/whiteboard/imgs/btn_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/whiteboard/imgs/btn_left.png -------------------------------------------------------------------------------- /public/whiteboard/imgs/btn_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/whiteboard/imgs/btn_right.png -------------------------------------------------------------------------------- /public/whiteboard/imgs/eraser-pre.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/whiteboard/imgs/next-scene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/whiteboard/imgs/next-scene.png -------------------------------------------------------------------------------- /public/whiteboard/imgs/pre-scene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/sealrtc-web/ed01a84d0e4aade3596c3b7ddf271bf87ae6110b/public/whiteboard/imgs/pre-scene.png -------------------------------------------------------------------------------- /public/whiteboard/imgs/word-pre.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/whiteboard/index.css: -------------------------------------------------------------------------------- 1 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; } 2 | body, button, input, select, textarea { font:12px/1.5tahoma, arial, \5b8b\4f53; } 3 | h1, h2, h3, h4, h5, h6{ font-size:100%; } 4 | ul, ol { list-style:none; } 5 | a { text-decoration:none; } 6 | a:hover { text-decoration:underline; } 7 | fieldset, img { border:0; } 8 | button, input, select, textarea { font-size:100%; } 9 | table { border-collapse:collapse; border-spacing:0; } 10 | 11 | .rong-whiteboard { 12 | width: 80%; 13 | position: relative; 14 | margin: 1vh auto 0; 15 | box-shadow: 0 0 9px 2px #ccc; 16 | } 17 | .rong-whiteboard .white-tool-box { 18 | position: absolute; 19 | z-index: 10; 20 | height: 60px; 21 | right: 40px; 22 | bottom: 30px; 23 | border-radius: 45px; 24 | background-color: #FCFCFC; 25 | box-shadow: #1e4396 0px 0px 20px -7px; 26 | padding-left: 10px; 27 | padding-right: 10px; 28 | } 29 | .rong-whiteboard .white-tool-box .tool-box-cell { 30 | width: 40px; 31 | height: 42px; 32 | float: left; 33 | margin: 9px 10px; 34 | font-size: 14px; 35 | color: #3D4041; 36 | text-align: center; 37 | cursor: pointer; 38 | } 39 | .rong-whiteboard .tool-box-cell .cell-icon { 40 | height: 20px; 41 | width: 20px; 42 | display: inline-block; 43 | } 44 | .rong-whiteboard .tool-box-cell .pencil { 45 | background: url(./imgs/opt-icons.svg) 53px 1px; 46 | } 47 | .rong-whiteboard .tool-box-cell .pencil-color { 48 | display: none; 49 | width: 110px; 50 | height: 110px; 51 | background: #FCFCFC; 52 | position: absolute; 53 | top: -125px; 54 | left: -10px; 55 | box-shadow: 0 6px 10px rgba(0, 0, 0, 0.2); 56 | border: 1px solid rgba(0, 0, 0, 0.2); 57 | } 58 | #pencilColor::after { 59 | content: ''; 60 | width: 0; 61 | height: 0; 62 | border-left: 13px solid transparent; 63 | border-right: 13px solid transparent; 64 | border-top: 15px solid #FCFCFC; 65 | position: absolute; 66 | left: 41px; 67 | bottom: -14px; 68 | } 69 | .rong-whiteboard .tool-box-cell .pencil-color span { 70 | display: inline-block; 71 | width: 24px; 72 | height: 24px; 73 | margin: 3px; 74 | } 75 | .rong-whiteboard .tool-box-cell .word { 76 | background-size: 19px; 77 | width: 19px; 78 | height: 19px; 79 | margin-top: 1px; 80 | background: url(./imgs/word-pre.svg); 81 | } 82 | .rong-whiteboard .tool-box-cell .eraser { 83 | background: url(./imgs/eraser-pre.svg); 84 | } 85 | .rong-whiteboard .tool-box-cell .clear { 86 | background: url(./imgs/opt-icons.svg) -71px 1px; 87 | } 88 | .rong-whiteboard .tool-box-cell .pagination { 89 | background: url(./imgs/opt-icons.svg) -139px 1px; 90 | } 91 | .rong-whiteboard .tool-box-cell .new { 92 | background: url(./imgs/opt-icons.svg) 90px 1px; 93 | } 94 | .rong-whiteboard .tool-box-cell .delete { 95 | background: url(./imgs/opt-icons.svg) 160px 1px; 96 | } 97 | .rong-whiteboard .pencil-color .color-red{ background: #FF0000} 98 | .rong-whiteboard .pencil-color .color-orange{ background: #FFA500} 99 | .rong-whiteboard .pencil-color .color-yellow{ background: #FFFF00} 100 | .rong-whiteboard .pencil-color .color-blue{ background: #0000FF} 101 | .rong-whiteboard .pencil-color .color-cyan{ background: #00FFFF} 102 | .rong-whiteboard .pencil-color .color-green{ background: #008000} 103 | .rong-whiteboard .pencil-color .color-black{ background: #000} 104 | .rong-whiteboard .pencil-color .color-purple{ background: #800080} 105 | .rong-whiteboard .pencil-color .color-gray{ background: #808080} 106 | 107 | .rong-whiteboard .pre-scene, .rong-whiteboard .next-scene { 108 | position: absolute; 109 | top: 45%; 110 | width: 50px; 111 | height: 50px; 112 | border-radius: 3px; 113 | background: #ccc; 114 | line-height: 46px; 115 | text-align: center; 116 | cursor: pointer; 117 | } 118 | .rong-whiteboard .pre-scene { 119 | left: -7%; 120 | } 121 | .rong-whiteboard .next-scene { 122 | right: -7%; 123 | } 124 | .rong-whiteboard .pre-scene img,.rong-whiteboard .next-scene img { 125 | vertical-align: middle; 126 | } 127 | .rong-whiteboard .close-white { 128 | position: absolute; 129 | top: 0; 130 | right: -15px; 131 | text-align: center; 132 | width: 24px; 133 | height: 24px; 134 | line-height: 24px; 135 | font-size: 14px; 136 | color: #fff; 137 | background: #333; 138 | border-radius: 3px; 139 | cursor: pointer; 140 | } -------------------------------------------------------------------------------- /public/whiteboard/index.js: -------------------------------------------------------------------------------- 1 | (function(win){ 2 | // var WhiteWebSdk; 3 | var whiteWebSdk = new WhiteWebSdk(); 4 | var hereWhiteConfig = { 5 | URL: 'https://cloudcapiv4.herewhite.com/room?token=', 6 | miniToken: 'WHITEcGFydG5lcl9pZD02dFBKT1lzMG52MHFoQzN2Z1BRUXVmN0t0RnVOVGl0bzBhRFAmc2lnPTMyZTRiNTMwNjkyN2RhN2I3NzI4MjMwOTJlZTNmNDJhNWI3MGMyMjU6YWRtaW5JZD0yMTEmcm9sZT1taW5pJmV4cGlyZV90aW1lPTE1ODkzNzY1MjEmYWs9NnRQSk9ZczBudjBxaEMzdmdQUVF1ZjdLdEZ1TlRpdG8wYURQJmNyZWF0ZV90aW1lPTE1NTc4MTk1Njkmbm9uY2U9MTU1NzgxOTU2OTQyNTAw' 7 | } 8 | 9 | var Room,whiteRoomInfo={}; 10 | var url = hereWhiteConfig.URL + hereWhiteConfig.miniToken; 11 | var requestInit = { 12 | method: 'POST', 13 | headers: { 14 | 'content-type': 'application/json', 15 | }, 16 | body: JSON.stringify({ 17 | name: '融云 White Board 房间', 18 | limit: 100, // 房间人数限制 19 | mode: 'persistent' 20 | }), 21 | }; 22 | 23 | var toolsBoxDom = document.getElementById('toolsBox'); 24 | var wordDom = document.getElementById('toolsWord'); 25 | var eraserDom = document.getElementById('toolsEraser'); 26 | var pencilDom = document.getElementById('toolsPencil'); 27 | var clearDom = document.getElementById('toolsClear'); 28 | var newSceneDom = document.getElementById('toolsNewScene'); 29 | var deleteDom = document.getElementById('toolsDelete') 30 | var pencilColorDom = document.getElementById('pencilColor'); 31 | var colorBoxes = pencilColorDom.getElementsByTagName('span'); 32 | var preSceneDom = document.getElementById('preScene'); 33 | var nextSceneDom = document.getElementById('nextScene'); 34 | var whiteEnum = { 35 | wbFolderName: '/rtc', 36 | sceneNamePrefix: 'rongRTCWB' 37 | } 38 | var callbacks = { 39 | onPhaseChanged: function(phase) { 40 | // 白板发生状态改变, 具体状态如下: 41 | // "connecting", 42 | // "connected", 43 | // "reconnecting", 44 | // "disconnecting", 45 | // "disconnected", 46 | console.log(phase); 47 | }, 48 | onRoomStateChanged: function(modifyState) { 49 | if (modifyState.globalState) { 50 | // globalState 改变了 51 | var newGlobalState = modifyState.globalState; 52 | console.log('newGlobalState: ', newGlobalState); 53 | } 54 | if (modifyState.memberState) { 55 | // memberState 改变了 56 | var newMemberState = modifyState.memberState; 57 | console.log('newMemberState: ', newMemberState); 58 | } 59 | if (modifyState.sceneState) { 60 | // sceneState 改变了 61 | var newSceneState = modifyState.sceneState; 62 | console.log('newSceneState: ', newSceneState); 63 | } 64 | if (modifyState.broadcastState) { 65 | // broadcastState 改变了 66 | var broadcastState = modifyState.broadcastState; 67 | console.log('broadcastState: ', broadcastState); 68 | } 69 | }, 70 | onDisconnectWithError: function (error) { 71 | // 出现连接失败后的具体错误 72 | console.log(error); 73 | }, 74 | }; 75 | function leaveWBRoom() { 76 | Room.disconnect(); 77 | } 78 | function getWhite(isNewWhite,roomInfo,callback, isBystander) { 79 | if(isNewWhite){ 80 | fetch(url, requestInit).then(function(response) { 81 | return response.json(); 82 | }).then(function(json) { 83 | whiteRoomInfo.uuid = json.msg.room.uuid; 84 | whiteRoomInfo.roomToken = json.msg.roomToken; 85 | return whiteWebSdk.joinRoom({ 86 | uuid: json.msg.room.uuid, 87 | roomToken: json.msg.roomToken, 88 | },callbacks); 89 | }).then(function(room) { 90 | room.bindHtmlElement(document.getElementById('whiteboard')); 91 | callback(); 92 | var folderName = whiteEnum.wbFolderName; 93 | Room = room; 94 | var sceneName = createSceneName(); 95 | switchNewScene(folderName, sceneName); 96 | Room.setMemberState({ 97 | strokeColor: [255,0,0] 98 | }); 99 | }); 100 | }else { 101 | whiteWebSdk.joinRoom({ 102 | uuid: roomInfo.uuid, 103 | roomToken: roomInfo.roomToken 104 | }).then(function(room){ 105 | room.bindHtmlElement(document.getElementById('whiteboard')); 106 | Room = room; 107 | if(isBystander){ 108 | room.disableOperations = true; 109 | toolsBoxDom.style.display = 'none'; 110 | preSceneDom.style.display = 'none'; 111 | nextSceneDom.style.display = 'none'; 112 | room.setMemberState({ 113 | currentApplianceName: 'selector' 114 | }); 115 | return ; 116 | } 117 | Room.setMemberState({ 118 | strokeColor: [255,0,0] 119 | }); 120 | }); 121 | } 122 | } 123 | pencilDom.onclick = function(e) { 124 | e.preventDefault(); 125 | var isShow = pencilColorDom.style.display; 126 | if(isShow === 'block'){ 127 | pencilColorDom.style.display = 'none'; 128 | return; 129 | } 130 | pencilColorDom.style.display = 'block'; 131 | Room.setMemberState({ 132 | currentApplianceName: 'pencil' 133 | }); 134 | } 135 | wordDom.onclick = function() { 136 | Room.setMemberState({ 137 | currentApplianceName: 'text' 138 | }); 139 | } 140 | eraserDom.onclick = function() { 141 | Room.setMemberState({ 142 | currentApplianceName: 'eraser' 143 | }); 144 | } 145 | clearDom.onclick = function() { 146 | Room.cleanCurrentScene(); 147 | } 148 | newSceneDom.onclick = function() { 149 | var path = whiteEnum.wbFolderName; 150 | var sceneName = createSceneName(); 151 | Room.putScenes(path,[{name: sceneName.toString()}]); 152 | var setCurrentPath = path + '/' + sceneName; 153 | Room.setScenePath(setCurrentPath); 154 | } 155 | deleteDom.onclick = function() { 156 | var scenes = Room.state.sceneState.scenes; 157 | if(scenes.length == 1){ 158 | Room.cleanCurrentScene(); 159 | return; 160 | } 161 | var delPath = Room.state.sceneState.scenePath; 162 | Room.removeScenes(delPath); 163 | } 164 | preSceneDom.onclick = function() { 165 | console.log(Room.state.sceneState.scenePath); 166 | var sceneState = Room.state.sceneState; 167 | var firstSceneName = sceneState.scenes[0].name; 168 | var currentSceneName = sceneState.scenePath.split('/')[2]; 169 | if(firstSceneName == currentSceneName){ 170 | return; 171 | } 172 | sceneState.scenes.forEach(function(item,index) { 173 | if(currentSceneName == item.name){ 174 | var preSceneName = sceneState.scenes[index-1].name; 175 | console.log(index,item,preSceneName); 176 | Room.setScenePath(whiteEnum.wbFolderName+'/'+preSceneName); 177 | } 178 | }); 179 | } 180 | nextSceneDom.onclick = function() { 181 | var sceneState = Room.state.sceneState; 182 | var lastSceneName = sceneState.scenes[sceneState.scenes.length-1].name; 183 | var currentSceneName = sceneState.scenePath.split('/')[2]; 184 | if(lastSceneName == currentSceneName){ 185 | return; 186 | } 187 | sceneState.scenes.forEach(function(item,index) { 188 | if(currentSceneName == item.name){ 189 | var preSceneName = sceneState.scenes[index+1].name; 190 | console.log(index,item,preSceneName); 191 | Room.setScenePath(whiteEnum.wbFolderName+'/'+preSceneName); 192 | } 193 | }); 194 | } 195 | function createSceneName() { 196 | return whiteEnum.sceneNamePrefix + new Date().getTime(); 197 | } 198 | function setPencilColor(rgb) { 199 | Room.setMemberState({ 200 | strokeColor: rgb 201 | }); 202 | } 203 | function setBoxesColor() { 204 | for(var i=0;i 2 | 3 | 4 | 5 | 6 | 7 | whiteboard 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 |

画笔

18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 |

文字

33 |
34 |
35 | 36 |

橡皮

37 |
38 |
39 | 40 |

清空

41 |
42 | 46 |
47 | 48 |

新建

49 |
50 |
51 | 52 |

删除

53 |
54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /scripts.bak/README.md: -------------------------------------------------------------------------------- 1 | ### 脚本命令 2 | 3 | ```js 4 | node deply.js --serverUrl=xxx --appkey=xxx --isDebug=true --navi=xxx 5 | ``` -------------------------------------------------------------------------------- /scripts.bak/config.default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 屏幕共享插件下载地址,默认即可 3 | DOWNLOAD_SHARE_PLUGIN_URL: 'plugin/screenshare-addon.zip', 4 | }; 5 | -------------------------------------------------------------------------------- /scripts.bak/config.tpl: -------------------------------------------------------------------------------- 1 | (function (dependencies) { 2 | var win = dependencies.win; 3 | win.RongSeal = win.RongSeal || {}; 4 | //生产 5 | win.RongSeal.Config = { 6 | // 融云应用 AppKey,可在融云开发者后台获取 7 | APPKEY: {APPKEY}, 8 | // SealRTC Server 地址 9 | URL: {URL}, 10 | // 屏幕共享插件下载地址,默认即可 11 | DOWNLOAD_SHARE_PLUGIN_URL: {DOWNLOAD_SHARE_PLUGIN_URL}, 12 | }; 13 | })({ 14 | win: window 15 | }); -------------------------------------------------------------------------------- /scripts.bak/deploy.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const GenerateConfigFileStep = 'GenerateFile'; 3 | 4 | module.exports = { 5 | GenerateConfigFileStep, 6 | FilePath: { 7 | // 配置文件路径(最终生成配置文件到此路径) 8 | Output: path.join(__dirname, '..', 'src', 'config.js'), 9 | // 默认配置项 10 | DefaultConfig: path.join(__dirname, 'config.default.js'), 11 | // 配置模板 12 | ConfigTpl: path.join(__dirname, 'config.tpl'), 13 | }, 14 | Steps: [ 15 | 'npm install', 16 | GenerateConfigFileStep, 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /scripts.bak/deploy.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const exec = require('child_process').exec; 4 | const DeployConf = require('./deploy.conf'); 5 | 6 | const { FilePath, Steps, GenerateConfigFileStep } = DeployConf; 7 | const DefaultConfig = require(FilePath.DefaultConfig); 8 | 9 | const showLog = (name, out, err, other) => { 10 | console.log('\nLogger:', new Date(), name, out || '', err || '', other || ''); 11 | }; 12 | 13 | const runExec = (execName) => { 14 | return new Promise((resolve, reject) => { 15 | exec(execName, (err, stdout, stderr) => { 16 | const func = err ? reject : resolve; 17 | func({ err, stdout, stderr }); 18 | }); 19 | }); 20 | }; 21 | 22 | const getConfigText = (config, TplPath) => { 23 | const tpl = fs.readFileSync(TplPath, 'utf-8'); 24 | return tpl.replace(/\{(.+?)\}/g, (regStr) => { 25 | const key = regStr.substring(1, regStr.length - 1); 26 | let value = config[key] || ''; 27 | if (Object.prototype.toString.call(value) === '[object String]') { 28 | value = `"${value}"`; 29 | } 30 | return value; 31 | }); 32 | }; 33 | 34 | const generateConfigFile = (config, tplPath, outPath) => { 35 | showLog('Config:\n', config, tplPath, outPath); 36 | return new Promise((resolve, reject) => { 37 | try { 38 | const configText = getConfigText(config, tplPath); 39 | fs.writeFileSync(outPath, configText); 40 | resolve({}); 41 | } catch (e) { 42 | // eslint-disable-next-line prefer-promise-reject-errors 43 | reject({ err: e }); 44 | }; 45 | }); 46 | }; 47 | 48 | const getConfig = () => { 49 | const argv = require('yargs').argv; 50 | const Config = Object.assign(DefaultConfig, argv); 51 | return Config; 52 | }; 53 | 54 | const runStep = (steps, current, callback) => { 55 | current = current || 0; 56 | const total = steps.length; 57 | 58 | const stepName = steps[current]; 59 | const isGenerateFile = stepName === GenerateConfigFileStep; 60 | 61 | current++; 62 | 63 | const func = isGenerateFile ? generateConfigFile : runExec; 64 | const params = [ 65 | isGenerateFile ? getConfig() : stepName, 66 | FilePath.ConfigTpl, 67 | FilePath.Output, 68 | ]; 69 | 70 | showLog('Step Start', stepName); 71 | func(params[0], params[1], params[2]).then(({ err, stdout, stderr }) => { 72 | showLog('Step Success End', stepName, err, stdout, stderr); 73 | const isFinished = current === total; 74 | isFinished ? callback() : runStep(steps, current, callback); 75 | }).catch(({ err, stdout, stderr }) => { 76 | showLog('Step Error End', stepName, err, stdout, stderr); 77 | callback(err); 78 | }); 79 | }; 80 | 81 | showLog('Deploy Start ....'); 82 | 83 | runStep(Steps, 0, (error) => { 84 | if (error) { 85 | showLog('Deploy Error End', error); 86 | } else { 87 | showLog('Deploy Success End'); 88 | } 89 | }); 90 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 133 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import 'element-ui/lib/theme-chalk/index.css'; 2 | 3 | import Vue from 'vue'; 4 | import CompositionApi from '@vue/composition-api'; 5 | import ElementUI from 'element-ui'; 6 | 7 | import App from './App.vue'; 8 | 9 | Vue.use(CompositionApi); 10 | Vue.use(ElementUI); 11 | Vue.config.productionTip = false; 12 | 13 | // 打印代码版本,便于追踪问题 14 | window.console.warn(`Code Version:${COMMIT_ID}`); 15 | // 打印 Demo 版本,便于测试追踪 16 | window.console.warn(`Demo Version:${DEMO_VERSION}`); 17 | 18 | new Vue({ 19 | render: h => h(App), 20 | }).$mount('#vue-app'); 21 | -------------------------------------------------------------------------------- /src/shims-app.d.ts: -------------------------------------------------------------------------------- 1 | import { CombinedVueInstance } from 'vue/types/vue'; 2 | 3 | declare global { 4 | type Root = CombinedVueInstance>; 5 | 6 | const COMMIT_ID: string; 7 | 8 | const DEMO_VERSION: string; 9 | 10 | const RongRTC: { 11 | Resolution: { [key: string]: string } 12 | }; 13 | 14 | const RongSeal: { 15 | getDataFromVue: Function | null; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "strict": true, 6 | // 暂时关闭 strictNullChecks 选项,以避免 vue-router 编译时类型丢失错误 7 | // 等待 vuejs/composition-api 修复 8 | "strictNullChecks": false, /* Enable strict null checks. */ 9 | "jsx": "preserve", 10 | "importHelpers": true, 11 | "moduleResolution": "node", 12 | "experimentalDecorators": true, 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true, 15 | "allowSyntheticDefaultImports": true, 16 | "sourceMap": true, 17 | "baseUrl": ".", 18 | "types": [ 19 | "webpack-env" 20 | ], 21 | "paths": { 22 | "@/*": ["src/*"] 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "dom.iterable", 28 | "scripthost" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | 3 | const { DefinePlugin } = require('webpack'); 4 | 5 | const DEMO_VERSION = require('./package.json').version; 6 | 7 | // 兼容 Jenkins 指定 COMMIT_ID 8 | let COMMIT_ID = JSON.stringify(process.env.VUE_APP_COMMIT_ID); 9 | if (!COMMIT_ID) { 10 | try { 11 | COMMIT_ID = JSON.stringify(execSync('git rev-parse HEAD').toString()); 12 | } catch (err) {} 13 | } 14 | 15 | module.exports = { 16 | outputDir: 'dist', 17 | publicPath: '.', 18 | lintOnSave: true, 19 | productionSourceMap: process.env.NODE_ENV !== 'production', 20 | pages: { 21 | index: { 22 | entry: 'src/index.ts', 23 | template: 'public/index.html', 24 | }, 25 | }, 26 | configureWebpack (config) { 27 | config.plugins.push( 28 | new DefinePlugin({ 29 | COMMIT_ID, 30 | DEMO_VERSION: JSON.stringify(DEMO_VERSION), 31 | }), 32 | ); 33 | }, 34 | }; 35 | --------------------------------------------------------------------------------