├── .github └── workflows │ └── build-release.yml ├── .gitignore ├── LICENSE ├── README.md ├── install-package └── urlProtocol.nsh ├── main.js ├── package.json └── static ├── audio └── some_one_join_room.wav ├── css ├── index.css ├── room.css └── screen.css ├── element-ui ├── element-ui.js ├── fonts │ └── element-icons.woff ├── theme-chalk.css └── vue.js ├── icon ├── camera.png ├── camera_no.png ├── close-hover.png ├── close.png ├── desktop-share.png ├── desktop-share_no.png ├── emoji.png ├── emoji_hover.png ├── exit.png ├── fullscreen.png ├── fullscreen_hover.png ├── fullscreen_no.png ├── layout.png ├── logo-512.png ├── logo.icns ├── logo.ico ├── logo.jpg ├── logo.png ├── logo.psd ├── m3u8.png ├── members.png ├── mic.png ├── mic_no.png ├── minimize.png ├── minimize_hover.png ├── msg.png ├── msg_hover.png ├── network-0.png ├── network-1.png ├── network-2.png ├── network.png ├── record.png ├── screen-1-1.png ├── screen-scale.png ├── show.png ├── speak.png └── speak_no.png ├── imgs ├── background-00.webp ├── background-01.webp ├── background-02.webp ├── background-03.png ├── background-04.png ├── emoji │ ├── emoji_0.png │ ├── emoji_1.png │ ├── emoji_2.png │ ├── emoji_3.png │ ├── emoji_4.png │ ├── emoji_5.png │ ├── emoji_6.png │ └── emoji_7.png ├── room-bkg.svg ├── ui-01.png ├── ui-02.png ├── ui-03.png └── ui-04.gif ├── index.html ├── loginRoom.html ├── scripts ├── adapter-7.0.0.js ├── app-index.js ├── app-videoWindows.js ├── jquery-3.2.1.min.js ├── owt.js ├── rest-sample.js ├── rest.js └── socket.io.js ├── server └── samplertcservice.js └── videoWindows.html /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: OWT-Client-Build 5 | 6 | on: 7 | push: 8 | tags: 9 | - v*.*.* 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [windows-latest, macos-latest, ubuntu-latest] 18 | node-version: [ 14.16.0 ] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: | 27 | npm install 28 | npm run ${{ matrix.os }} 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 31 | GH_TOKEN: ${{ secrets.ACCESS_TOKEN }} 32 | CSC_IDENTITY_AUTO_DISCOVERY: false 33 | 34 | - name: Upload artifact 35 | uses: actions/upload-artifact@v2 36 | with: 37 | name: dist 38 | path: | 39 | dist/OWTClient--* 40 | !dist/OWTClient-*.blockmap 41 | !dist/latest*.yml 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 44 | GH_TOKEN: ${{ secrets.ACCESS_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | dist/* 107 | node_modules/* 108 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 heisir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Release](https://img.shields.io/github/release/heisir2014/owt-client-rtc?style=for-the-badge)](https://github.com/heisir2014/owt-client-rtc/releases/latest) 2 | [![Download](https://img.shields.io/github/downloads/heisir2014/owt-client-rtc/total?style=for-the-badge)](https://github.com/heisir2014/owt-client-rtc/releases/latest) 3 | # OWT Client [直接下载](#下载) 4 | 5 | 服务器程序使用的OWT-Server [Open WebRTC Toolkit](https://github.com/open-webrtc-toolkit/) 6 | 7 | OWT-Client 是基于OWT、Electron开发的视频会议客户端,支持Windows、Linux、MacOS系统。 8 | 9 | ## 特性 10 | 11 | - PC多平台支持(Windows、Linux、MacOS) 12 | - 视频聊天 13 | - 文字聊天 14 | - 多摄像头支持 15 | - 桌面分享 16 | - 本地录制 17 | 18 | ## UI 19 |
20 |
21 | OWT Client 22 |
23 |
24 | 25 | ## 下载 26 | 27 | [Release](https://github.com/HeiSir2014/owt-client-rtc/releases/) 28 | 29 | ## 开发 用源码编译 30 | 31 | 依赖:nodejs、git、electron 32 | 33 | 1. install Node.js [Node.js](https://nodejs.org/) 34 | 35 | 2. install Git [Download Git](https://git-scm.com/) 36 | 37 | 3. shell 38 | ``` 39 | mkdir owt-client 40 | cd owt-client 41 | git clone https://github.com/HeiSir2014/owt-client-rtc.git . 42 | npm install 43 | npm run start 44 | ``` 45 | 46 | ## 交流学习 47 | 48 | 下面的视频是和一个小伙伴介绍本项目的录屏。 49 | 50 | [交流录屏 - B站](https://www.bilibili.com/video/BV1H5411g7A6/) | [交流录屏 - YouTube](https://www.youtube.com/watch?v=fn1tBTqsjyA) 51 | 52 | QQ群:213941700 53 | 54 | -------------------------------------------------------------------------------- /install-package/urlProtocol.nsh: -------------------------------------------------------------------------------- 1 | !macro customInstall 2 | DetailPrint "Register owtclient URI Handler" 3 | DeleteRegKey HKCR "owtclient" 4 | WriteRegStr HKCR "owtclient" "" "URL:owtclient" 5 | WriteRegStr HKCR "owtclient" "URL Protocol" "" 6 | WriteRegStr HKCR "owtclient\shell" "" "" 7 | WriteRegStr HKCR "owtclient\shell\Open" "" "" 8 | WriteRegStr HKCR "owtclient\shell\Open\command" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME} %1" 9 | !macroend -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const winston = require('winston'); 5 | const { app, BrowserWindow, Tray, ipcMain, shell, Menu, dialog,session, webContents,systemPreferences } = require('electron'); 6 | const isDev = require('electron-is-dev'); 7 | const package_self = require('./package.json'); 8 | let mainWindow = null; 9 | let videoWindows = []; 10 | let loginRoomWindow = null; 11 | let logger; 12 | let localConfig; 13 | let _startParam = null; 14 | 15 | 16 | (function(){ 17 | 18 | localConfig = path.join(app.getPath('userData'),'config.json'); 19 | logger = winston.createLogger({ 20 | level: 'debug', 21 | format: winston.format.combine( 22 | winston.format.timestamp({ 23 | format: 'YYYY-MM-DD HH:mm:ss' 24 | }), 25 | winston.format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`+(info.splat!==undefined?`${info.splat}`:" ")) 26 | ), 27 | transports: [ 28 | new winston.transports.Console(), 29 | new winston.transports.File({ filename: path.join(app.getPath('userData'),'logs/error.log'), level: 'error' }), 30 | new winston.transports.File({ filename: path.join(app.getPath('userData'),'logs/all.log') }), 31 | ], 32 | }); 33 | 34 | // 单例应用程序 35 | if (!isDev && !app.requestSingleInstanceLock()) { 36 | app.quit() 37 | return 38 | } 39 | app.on('second-instance', (event, argv, cwd) => { 40 | const [win] = BrowserWindow.getAllWindows(); 41 | logger.info( "second-instance: "+ JSON.stringify(argv)) 42 | if (win) { 43 | if (win.isMinimized()) { 44 | win.restore() 45 | } 46 | win.show() 47 | win.focus() 48 | } 49 | if(argv) 50 | { 51 | console.log(argv); 52 | let param = getStartParam(argv); 53 | param.userId != 'any' && CreateMainWin(param); 54 | } 55 | }); 56 | process.on('uncaughtException',(err, origin) =>{ 57 | logger.error(`uncaughtException: ${err} | ${origin}`) 58 | }); 59 | process.on('unhandledRejection',(reason, promise) =>{ 60 | logger.error(`unhandledRejection: ${promise} | ${reason}`) 61 | }); 62 | 63 | app.on('open-url', function (event, url) { 64 | event.preventDefault(); 65 | logger.info('open-url:' + url); 66 | var u = new URL(url); 67 | if(u.pathname == "/inmeeting") 68 | { 69 | let param = getStartParam(); 70 | param['room'] = u.searchParams.get('room'); 71 | param['userId'] = u.searchParams.get('user'); 72 | if(app.isReady()) 73 | { 74 | CreateMainWin(param); 75 | return; 76 | } 77 | _startParam = param; 78 | } 79 | }); 80 | 81 | app.on('ready', async () => { 82 | 83 | !app.isDefaultProtocolClient('rtcclient') && app.setAsDefaultProtocolClient("rtcclient"); 84 | 85 | logger.info('load success'); 86 | 87 | let param = _startParam ? _startParam : getStartParam(); 88 | if(param.userId == 'any') 89 | { 90 | loginRoomWindow = CreateDefaultWin({width:800,height:520,resizable:false}); 91 | loginRoomWindow.loadFile( path.join(__dirname, 'static/loginRoom.html')); 92 | loginRoomWindow.on('closed', () => { 93 | loginRoomWindow = null; 94 | }); 95 | } 96 | else 97 | { 98 | CreateMainWin(param); 99 | } 100 | }); 101 | })(); 102 | 103 | function MergeObject(a, b) { 104 | let c = JSON.parse(JSON.stringify(a)) 105 | for (const key in b) { 106 | if (Object.hasOwnProperty.call(b, key)) { 107 | c[key] = (typeof b[key] == 'object' && c[key] && typeof c[key] == 'object') ? MergeObject(c[key],b[key]) : b[key] 108 | } 109 | } 110 | return c; 111 | } 112 | 113 | function getStartParam(argv) 114 | { 115 | let param = { 116 | serverURL: Buffer.from('aHR0cHM6Ly95enNsLmJlaWppbmd5dW56aGlzaGFuZy5jb20v','base64').toString(), 117 | room:'8888', 118 | userId:'any', 119 | userNick:'游客' 120 | }; 121 | 122 | let _argv = argv ? argv : process.argv; 123 | 124 | logger.info(`process.argv = ${JSON.stringify(_argv)}`); 125 | 126 | _argv.forEach(arg => { 127 | let _ = null; 128 | if(arg.startsWith("rtcclient://page/inmeeting")) 129 | { 130 | let u = new URL(arg); 131 | param['room'] = u.searchParams.get('room'); 132 | param['userId'] = u.searchParams.get('user'); 133 | return; 134 | } 135 | if((_ = arg.match(/^--(.*)=([^=]*)$/)) && _.length > 2) 136 | { 137 | param[ _[1] ]=_[2]; 138 | } 139 | }); 140 | 141 | logger.info(`param = ${JSON.stringify(param)}`); 142 | 143 | return param; 144 | } 145 | 146 | function CreateMainWin(param) 147 | { 148 | let win = mainWindow; 149 | 150 | os.platform == 'darwin' && systemPreferences.askForMediaAccess('microphone'); 151 | os.platform == 'darwin' && systemPreferences.askForMediaAccess('camera'); 152 | 153 | mainWindow = CreateDefaultWin(); 154 | mainWindow.loadFile( path.join(__dirname, 'static/index.html'),{ query:param }); 155 | mainWindow.on('closed', () => { 156 | mainWindow = null; 157 | BrowserWindow.getAllWindows().forEach(window => { 158 | isDev && window.webContents.closeDevTools() 159 | window.close(); 160 | }) 161 | }); 162 | 163 | mainWindow.once('ready-to-show',()=>{ 164 | mainWindow.focus(); 165 | mainWindow.moveTop(); 166 | //mainWindow.setAspectRatio(16.0/9.0); 167 | }); 168 | 169 | win && win.close(); 170 | 171 | _startParam = null; 172 | } 173 | 174 | function CreateDefaultWin(options) 175 | { 176 | let opt = { 177 | width: 960, 178 | height: 572, 179 | backgroundColor: '#ff2e2c29', 180 | skipTaskbar: false, 181 | transparent: false, frame: false, resizable: true, 182 | webPreferences: { 183 | nodeIntegration: true, 184 | spellcheck: false, 185 | webSecurity:!isDev, 186 | contextIsolation:false 187 | }, 188 | icon: path.join(__dirname, 'static/icon/logo.png'), 189 | alwaysOnTop: false, 190 | hasShadow: false, 191 | }; 192 | options && (opt = MergeObject(opt,options)); 193 | let win = new BrowserWindow(opt); 194 | win.setMenu(null); 195 | isDev && win.webContents.openDevTools(); 196 | win.webContents.on('ipc-message',ipcMessageFun); 197 | win.webContents.on('ipc-message-sync',ipcMessageFun); 198 | win.on('enter-full-screen',fullScreenChanged); 199 | win.on('leave-full-screen',fullScreenChanged); 200 | win.webContents.on('new-window', function(event, url, frameName, disposition, options){ 201 | event.preventDefault(); 202 | shell.openExternal(url); 203 | }); 204 | win.webContents.on('dom-ready',function(e){ 205 | let win = BrowserWindow.fromWebContents(e.sender); 206 | e.sender.send('maximizeChanged', win.isFullScreen()); 207 | e.sender.send('set-version', package_self.version); 208 | }); 209 | return win; 210 | } 211 | 212 | function fullScreenChanged(e){ 213 | e.sender.webContents.send('maximizeChanged', !e.sender.isFullScreen()); 214 | } 215 | 216 | 217 | function ipcMessageFun(e,channel,...theArgs){ 218 | const data = theArgs.length ? theArgs[0] : null; 219 | logger.info( `win webContents Id: ${ e.sender.id } | ${channel} | ${data}`); 220 | let win = BrowserWindow.fromWebContents( webContents.fromId(e.sender.id) ); 221 | if(win == null) 222 | { 223 | logger.error( `winId:${e.sender.id } | win = null`); 224 | return; 225 | } 226 | 227 | if (/-win$/.test(channel)) { 228 | const cmd = channel.replace(/-win$/,''); 229 | isDev && cmd == 'close' && win.webContents.closeDevTools(); 230 | win[cmd].call(win,...theArgs); 231 | return; 232 | } 233 | 234 | if (channel === 'getUser') { 235 | if(fs.existsSync(localConfig)) 236 | { 237 | let con = JSON.parse(fs.readFileSync(localConfig,{encoding:'utf-8',flag:'r'})); 238 | win.webContents.send('getUserRsp',con); 239 | } 240 | return; 241 | } 242 | 243 | if(channel === 'create-video-windows'){ 244 | newWindows = CreateDefaultWin(); 245 | newWindows.loadFile( path.join(__dirname, 'static/videoWindows.html'),{query:{ id:e.sender.id}}); 246 | newWindows.moveTop(); 247 | videoWindows.push( newWindows ); 248 | e.returnValue = { webContentsId: newWindows.webContents.id }; 249 | return; 250 | } 251 | if(channel === 'joinRoom'){ 252 | let param = getStartParam(); 253 | 254 | let con = fs.existsSync(localConfig)? 255 | JSON.parse(fs.readFileSync(localConfig,{encoding:'utf8',flag:'r'})):{}; 256 | let lastConfigStr = JSON.stringify(con); 257 | let mapKeys = ['userId','enableAudio','enableVideo'] 258 | for (const key in data) { 259 | Object.hasOwnProperty.call(data, key) && (param[key] = data[key],mapKeys.indexOf(key) != -1 && (con[key] = data[key])); 260 | } 261 | JSON.stringify(con) != lastConfigStr && (fs.writeFileSync(localConfig,JSON.stringify(con),{encoding:'utf-8'})); 262 | 263 | CreateMainWin(param); 264 | 265 | loginRoomWindow && (loginRoomWindow.close()); 266 | return; 267 | } 268 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "owt-client", 3 | "version": "2.0.1", 4 | "description": "owt-client", 5 | "main": "main.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/HeiSir2014/owt-client-rtc.git" 9 | }, 10 | "keywords": [ 11 | "electron", 12 | "webrtc", 13 | "rtc", 14 | "owt" 15 | ], 16 | "author": "heisir (QQ:1586462)", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/HeiSir2014/owt-client-rtc/issues" 20 | }, 21 | "homepage": "https://github.com/HeiSir2014/owt-client-rtc#readme", 22 | "devDependencies": { 23 | "electron": "^13.1.6", 24 | "electron-builder": "^22.11.7" 25 | }, 26 | "dependencies": { 27 | "electron-is-dev": "^2.0.0", 28 | "winston": "^3.3.3" 29 | }, 30 | "scripts": { 31 | "start": "electron .", 32 | "pack-mac": "electron-builder --mac --publish always", 33 | "pack-win": "electron-builder --win --publish always", 34 | "postinstall": "electron-builder install-app-deps", 35 | "pack-linux": "electron-builder --linux --publish always", 36 | "windows-latest": "npm run pack-win", 37 | "macos-latest": "npm run pack-mac", 38 | "ubuntu-latest": "npm run pack-linux" 39 | }, 40 | "build": { 41 | "appId": "cn.heisir.owtclientrtc", 42 | "artifactName": "OWTClient-${os}_${arch}-v${version}.${ext}", 43 | "electronVersion": "13.1.6", 44 | "copyright": "Copyright © 2021 ${author}", 45 | "compression": "normal", 46 | "directories": { 47 | "output": "dist" 48 | }, 49 | "files": [ 50 | "**/*", 51 | "!dist/*", 52 | "!.github/*", 53 | "!README.md", 54 | "!.gitignore" 55 | ], 56 | "asar": true, 57 | "win": { 58 | "target": [ 59 | { 60 | "target": "nsis" 61 | }, 62 | { 63 | "target": "zip", 64 | "arch": [ 65 | "x64", 66 | "ia32" 67 | ] 68 | } 69 | ], 70 | "icon": "static/icon/logo-512.png" 71 | }, 72 | "dmg": { 73 | "window": { 74 | "width": 540, 75 | "height": 380 76 | }, 77 | "contents": [ 78 | { 79 | "x": 410, 80 | "y": 230, 81 | "type": "link", 82 | "path": "/Applications" 83 | }, 84 | { 85 | "x": 130, 86 | "y": 230, 87 | "type": "file" 88 | } 89 | ] 90 | }, 91 | "mac": { 92 | "hardenedRuntime": true, 93 | "appId": "cn.heisir.owtclientrtc-mac", 94 | "category": "public.app-category.productivity", 95 | "target": [ 96 | "dmg" 97 | ], 98 | "icon": "static/icon/logo.icns", 99 | "minimumSystemVersion": "10.15" 100 | }, 101 | "nsis": { 102 | "perMachine": true, 103 | "oneClick": false, 104 | "allowElevation": true, 105 | "allowToChangeInstallationDirectory": true, 106 | "installerIcon": "static/icon/logo.ico", 107 | "uninstallerIcon": "static/icon/logo.ico", 108 | "installerHeaderIcon": "static/icon/logo.ico", 109 | "createDesktopShortcut": true, 110 | "createStartMenuShortcut": true, 111 | "shortcutName": "OWTClient", 112 | "include": "install-package/urlProtocol.nsh" 113 | }, 114 | "protocols": [ 115 | { 116 | "name": "owtclient", 117 | "schemes": [ 118 | "owtclient" 119 | ] 120 | } 121 | ], 122 | "appImage": { 123 | "category": "public.app-category.productivity" 124 | }, 125 | "linux": { 126 | "target": [ 127 | "AppImage", 128 | "deb" 129 | ], 130 | "maintainer": "heisir ", 131 | "category": "Utility" 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /static/audio/some_one_join_room.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/audio/some_one_join_room.wav -------------------------------------------------------------------------------- /static/css/index.css: -------------------------------------------------------------------------------- 1 | html,body{ 2 | padding: 0; 3 | margin: 0; 4 | box-sizing: border-box; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | html,body *{ 9 | user-select:none; 10 | } 11 | 12 | div,input,span,a,p,br,h1,h2,h3,ul,li,ol{ 13 | padding: 0; 14 | margin: 0; 15 | box-sizing: border-box; 16 | } 17 | 18 | .video-container{ 19 | margin: 0 auto; 20 | width: 100%; 21 | height: calc(100% - 32px); 22 | position: relative; 23 | overflow: hidden; 24 | top: 0; 25 | left: 0; 26 | right: 0; 27 | bottom: 0; 28 | box-sizing: border-box; 29 | } 30 | 31 | .video-container.fullscreen{ 32 | height: 100%; 33 | } 34 | 35 | .video-container video{ 36 | position: relative; 37 | width: 100%; 38 | height: 100%; 39 | object-fit: contain; 40 | box-sizing: border-box; 41 | } 42 | 43 | @keyframes toolsAnimation{ 44 | from {filter: opacity(0);} 45 | to {filter: opacity(1);} 46 | } 47 | 48 | .video-container .tools{ 49 | position: absolute; 50 | width: fit-content; 51 | height: 62px; 52 | bottom: 50px; 53 | left: 0; 54 | right: 0; 55 | margin: 0 auto; 56 | background-color: #404040; 57 | border-radius: 5px; 58 | box-shadow: 0px 0px 3px 0px #4f4f4f8f; 59 | overflow: visible; 60 | padding: 0; 61 | opacity: 0.85; 62 | -webkit-app-region: no-drag; 63 | text-align: left; 64 | } 65 | 66 | .video-container .tools .exit{ 67 | float: right; 68 | } 69 | /* 70 | .video-container .tools:hover{ 71 | animation: toolsAnimation 1.5s; 72 | opacity:1; 73 | } */ 74 | 75 | .video-container .tools:hover >*{ 76 | filter: none; 77 | } 78 | 79 | .video-container .tools .el-dropdown,.video-container .tools .dorpdownLabel,.video-container .tools .el-menu-member{ 80 | width: 100%; 81 | height: 100%; 82 | color:white; 83 | } 84 | 85 | .video-container .tools > *{ 86 | width: 76px; 87 | line-height: 1.0; 88 | height: 100%; 89 | border: none; 90 | text-align: center; 91 | background-color: transparent; 92 | color: white; 93 | box-sizing: content-box; 94 | display: inline-block; 95 | vertical-align: middle; 96 | border-right: solid 1px #a0a0a040; 97 | border-left: solid 1px transparent; 98 | position: relative; 99 | font-size: 0; 100 | margin: 0; 101 | padding: 0; 102 | } 103 | 104 | .video-container .tools > *:not(:first-child){ 105 | margin-left: -4px; 106 | } 107 | 108 | .video-container .tools > *:hover{ 109 | background-color:#00000033; 110 | } 111 | 112 | .video-container .tools >:last-child{ 113 | border-right: 0; 114 | } 115 | 116 | .video-container .tools option{ 117 | text-align: center; 118 | background-color: #404040AA; 119 | height: 32px; 120 | line-height: 1.0; 121 | } 122 | 123 | .video-container .tools .layout{ 124 | line-height: 1.0; 125 | } 126 | .video-container .tools .label{ 127 | width: 28px; 128 | height: 60%; 129 | line-height: 1.0; 130 | object-fit: contain; 131 | margin-top: 5px; 132 | } 133 | .video-container .tools .tip{ 134 | display: none; 135 | } 136 | 137 | .video-container .tools > *:hover .tip{ 138 | display: block; 139 | position: absolute; 140 | top: -40px; 141 | min-width: 150px; 142 | width: fit-content; 143 | height: fit-content; 144 | font-size: 14px; 145 | white-space: nowrap; 146 | text-align: center; 147 | padding: 10px; 148 | background-color: brown; 149 | border-radius: 5px; 150 | left: -30px; 151 | } 152 | 153 | .video-container .tools .record{ 154 | display: none; 155 | } 156 | 157 | .video-container .tools .title{ 158 | position: absolute; 159 | bottom: 8px; 160 | width: 100%; 161 | font-size: 12px; 162 | white-space: nowrap; 163 | text-align: center; 164 | overflow: hidden; 165 | padding: 0 1px; 166 | font-weight: 400; 167 | letter-spacing: 2px; 168 | text-indent:2px; 169 | } 170 | 171 | .video-container .chat{ 172 | position: absolute; 173 | width: fit-content; 174 | height: fit-content; 175 | bottom: 50px; 176 | left: 16px; 177 | background-color:#FAFAFA; 178 | border-radius: 2px; 179 | padding: 0 5px; 180 | -webkit-app-region: no-drag; 181 | } 182 | 183 | .video-container .chat>*{ 184 | width: 32px; 185 | height: 32px; 186 | background-repeat: no-repeat; 187 | background-size: 26px; 188 | background-position: center; 189 | display: inline; 190 | vertical-align: middle; 191 | font-size: 0; 192 | float: left; 193 | } 194 | .video-container .chat .emoji_group span 195 | { 196 | width: 100%; 197 | height: 100%; 198 | display: block; 199 | } 200 | 201 | .video-container .chat .emoji_group span button{ 202 | width: 100%; 203 | height: 100%; 204 | padding: 0; 205 | border: 0; 206 | } 207 | 208 | .video-container .chat .emoji{ 209 | background-image: url(../icon/emoji.png); 210 | 211 | background-repeat: no-repeat; 212 | background-size: 24px; 213 | background-position: center; 214 | 215 | } 216 | .video-container .chat .emoji:hover{ 217 | background-image: url(../icon/emoji_hover.png); 218 | } 219 | .video-container .chat .msg{ 220 | background-image: url(../icon/msg.png); 221 | 222 | } 223 | .video-container .chat .msg:hover{ 224 | background-image: url(../icon/msg_hover.png); 225 | } 226 | 227 | .video-container .chat .msg_input{ 228 | width: 120px; 229 | padding: 2px 5px; 230 | border-left: solid 1px #aaa; 231 | } 232 | 233 | .video-container .chat .msg_input > input{ 234 | width: 100%; 235 | height: 100%; 236 | line-height: 1.0; 237 | border: 0; 238 | padding: 0 5px; 239 | font-size: 12px; 240 | margin: 0; 241 | } 242 | 243 | .video-container .drag{ 244 | position: absolute; 245 | width: 80%; 246 | height: 80%; 247 | left: 10%; 248 | top:10%; 249 | font-size: 0; 250 | margin: 0; 251 | padding: 0; 252 | background: transparent; 253 | -webkit-app-region: drag; 254 | } 255 | 256 | .heisir{ 257 | padding: 0; 258 | margin: 0; 259 | background-color: #252526; 260 | background-size: cover; 261 | background-position: center; 262 | position: relative; 263 | overflow: hidden; 264 | 265 | -webkit-app-region: no-drag; 266 | } 267 | 268 | .heisir .header{ 269 | width: 100%; 270 | height: 32px; 271 | position: relative; 272 | background-color: #333333; 273 | border-bottom: solid 1px #454545; 274 | z-index: 9999; 275 | -webkit-app-region: drag; 276 | box-sizing: border-box; 277 | } 278 | 279 | .heisir .toolbar{ 280 | width: 100%; 281 | height: fit-content; 282 | position: absolute; 283 | z-index: 2; 284 | } 285 | 286 | .heisir .toolbar >:last-child{ 287 | border-bottom-left-radius: 4px; 288 | } 289 | 290 | .heisir .network{ 291 | width: fit-content; 292 | position: relative; 293 | text-align: left; 294 | background-color: #00000088; 295 | height: 24px; 296 | padding: 0 5px; 297 | box-sizing: content-box; 298 | float: right; 299 | } 300 | .heisir .network >*{ 301 | font-size: 10px; 302 | line-height: 24px; 303 | font-style: normal; 304 | display: inline-block; 305 | background-size: contain; 306 | background-repeat: no-repeat; 307 | background-position: center; 308 | height: 100%; 309 | color: white; 310 | vertical-align: middle; 311 | white-space: nowrap; 312 | } 313 | .heisir .network .upload,.heisir .network .download{ 314 | width: fit-content; 315 | min-width: 72px; 316 | text-indent: 1px; 317 | text-align: left; 318 | } 319 | .heisir .network .wireless{ 320 | width: 12px; 321 | background-image: url(../icon/network.png); 322 | margin-right: 5px; 323 | } 324 | 325 | .heisir .fullscreenbar{ 326 | width: 24px; 327 | right: 0; 328 | position: relative; 329 | background-color: #00000088; 330 | height: 24px; 331 | padding: 0; 332 | box-sizing: content-box; 333 | font-size: 0; 334 | outline: 0; 335 | float: right; 336 | } 337 | 338 | .heisir .fullscreenbar .fullscreen{ 339 | width: 100%; 340 | height: 100%; 341 | background-image: url(../icon/fullscreen.png); 342 | background-repeat: no-repeat; 343 | background-size: 18px; 344 | background-position: center; 345 | display: inline-block; 346 | } 347 | .heisir .fullscreenbar .unfullscreen{ 348 | width: 100%; 349 | height: 100%; 350 | background-image: url(../icon/fullscreen_no.png); 351 | background-repeat: no-repeat; 352 | background-size: 18px; 353 | background-position: center; 354 | display: inline-block; 355 | cursor: pointer; 356 | } 357 | 358 | .heisir .fullscreenbar .unfullscreen:hover { 359 | background-color: #00000044; 360 | } 361 | 362 | .heisir .header > *{ 363 | display: inline-block; 364 | } 365 | .heisir .systools { 366 | width: fit-content; 367 | right: 0px; 368 | position: absolute; 369 | height: 100%; 370 | box-sizing: content-box; 371 | overflow: hidden; 372 | -webkit-app-region: no-drag; 373 | } 374 | 375 | .heisir .header .title{ 376 | width: fit-content; 377 | height: 100%; 378 | line-height: 32px; 379 | color: #e5e5e5; 380 | vertical-align: middle; 381 | margin: 0 10px; 382 | font-size: 12px; 383 | font-weight: 400; 384 | letter-spacing: 1px; 385 | text-indent: 1px; 386 | } 387 | 388 | .heisir .systools >*{ 389 | width: 32px; 390 | height: 100%; 391 | display: inline-block; 392 | vertical-align: middle; 393 | color: #e5e5e5; 394 | font-size: 0; 395 | background-size: 14px; 396 | background-repeat: no-repeat; 397 | background-position: center; 398 | } 399 | 400 | .heisir .systools .fullscreen{ 401 | background-image: url(../icon/fullscreen.png); 402 | 403 | background-size: 18px; 404 | } 405 | .heisir .systools .fullscreen:hover{ 406 | background-image: url(../icon/fullscreen_hover.png); 407 | } 408 | .heisir .systools .minimize{ 409 | background-image: url(../icon/minimize.png); 410 | } 411 | 412 | .heisir .systools .minimize:hover{ 413 | background-color: #505050; 414 | background-image: url(../icon/minimize_hover.png); 415 | } 416 | 417 | .heisir .header .systools .close{ 418 | background-image: url(../icon/close.png); 419 | } 420 | 421 | .heisir .header .systools .close:hover{ 422 | background-color: #D71526; 423 | background-image: url(../icon/close-hover.png); 424 | } 425 | 426 | .heisir .header 427 | 428 | .heisir .bitrate-container{ 429 | position: absolute; 430 | top: 10px; 431 | right: 100px; 432 | -webkit-app-region: no-drag; 433 | } 434 | 435 | .heisir .bitrate-container .bitrateSelects,.heisir .bitrate-container .bitrateOption{ 436 | margin: 5px 2px; 437 | border: 0px; 438 | padding: 5px 15px; 439 | font-size: 1em; 440 | background-color: black; 441 | color: white; 442 | -webkit-app-region: no-drag; 443 | } 444 | 445 | .heisir .desktop .transparent 446 | { 447 | width: 100%; 448 | height: 100%; 449 | position: absolute; 450 | left: 0; 451 | top: 0; 452 | background-color: transparent; 453 | border: 0; 454 | } 455 | 456 | .heisir .desktop .screen-dialog{ 457 | display: none; 458 | position: absolute; 459 | width: 700px; 460 | background-color: #d8d8d8ab; 461 | bottom: 72px; 462 | margin: 0 auto; 463 | border-radius: 4px; 464 | padding: 10px; 465 | box-shadow: 0px 0px 10px 1px #666; 466 | text-align: center; 467 | left: -490px; 468 | overflow-x: auto; 469 | z-index: 5; 470 | -webkit-app-region: no-drag; 471 | opacity: 0; 472 | } 473 | 474 | 475 | .heisir .desktop .screen-dialog .screen-poster{ 476 | width: 200px; 477 | margin: 10px; 478 | box-shadow: 0px 0px 10px 1px #333; 479 | } 480 | 481 | .heisir .desktop:hover .screen-dialog{ 482 | opacity: 1; 483 | } 484 | 485 | .heisir .desktop .screen-dialog .screen-poster:hover{ 486 | transform: scale(1.1); 487 | } 488 | 489 | #App{ 490 | width: 100%; 491 | height: 100%; 492 | } 493 | 494 | 495 | .el-header { 496 | -webkit-app-region: drag; 497 | padding:0 !important; 498 | background-color:#009afb; 499 | line-height: 40px; 500 | } 501 | 502 | .el-header .title{ 503 | margin-left: 10px; 504 | font-weight: 600; 505 | font-size: 16px; 506 | color: #f3f3f3; 507 | margin-top: -6px; 508 | } 509 | 510 | .systemTool{ 511 | position: fixed; 512 | right: 0; 513 | top: 0; 514 | margin-top: -6px; 515 | margin-right: 0px; 516 | -webkit-app-region: no-drag; 517 | } 518 | 519 | 520 | .systemTool .mylink{ 521 | color: #f3f3f3; 522 | margin: 0 5px; 523 | text-decoration: none; 524 | font-size: 14px; 525 | } 526 | .systemTool .el-button{ 527 | padding:0 !important; 528 | border:0 !important; 529 | border-radius:0 !important; 530 | color: #f3f3f3 !important; 531 | } 532 | .systemTool .el-button:hover{ 533 | background-color: #D71526; 534 | } 535 | .systemTool .button{ 536 | width: 30px; 537 | height: 30px; 538 | background-color: #00000000; 539 | font-size: large; 540 | } 541 | 542 | .systemTool .el-button+.el-button{ 543 | margin-left: 0 !important; 544 | } 545 | 546 | .heisir .message_tip{ 547 | width: fit-content; 548 | min-width: auto; 549 | border: 0; 550 | background-color:#66666680; 551 | } 552 | 553 | .heisir .message_tip .el-message__content{ 554 | color: #fff; 555 | } 556 | 557 | .heisir .notify_join,.heisir .notify_msg{ 558 | width: fit-content; 559 | min-width: 0; 560 | width: fit-content; 561 | padding: 2px 5px; 562 | border-radius: 2px; 563 | background-color: #444444ee; 564 | border: 0; 565 | } 566 | 567 | .heisir .notify_join.left,.heisir .notify_msg.left{ 568 | left: 16px; 569 | } 570 | 571 | .heisir .notify_join .el-notification__content,.heisir .notify_msg .el-notification__content{ 572 | margin:0; 573 | color: #e6e6e6; 574 | text-indent: 2px; 575 | letter-spacing: 2px; 576 | vertical-align: middle; 577 | } 578 | 579 | .heisir .notify_join .el-notification__group,.heisir .notify_msg .el-notification__group{ 580 | margin: 0; 581 | } 582 | 583 | .heisir .notify_msg .el-notification__content div{ 584 | display: inline-block; 585 | height: 24px; 586 | line-height: 24px; 587 | margin: 0; 588 | padding: 0; 589 | vertical-align: middle; 590 | } 591 | 592 | .heisir .notify_msg .el-notification__content .nick{ 593 | color: #efff00; 594 | } 595 | 596 | .heisir .notify_msg .el-notification__content .content{ 597 | color: #efefef; 598 | vertical-align: middle; 599 | } 600 | 601 | .heisir .notify_msg .el-notification__content img{ 602 | vertical-align: middle; 603 | } 604 | 605 | .heisir .emoji_popover{ 606 | min-width:auto; 607 | padding: 5px; 608 | } 609 | 610 | .heisir .emoji_popover ul{ 611 | text-align: center; 612 | } 613 | 614 | .heisir .emoji_popover li{ 615 | display: inline-block; 616 | cursor: pointer; 617 | font-size: 0; 618 | outline: 0; 619 | inset: 0; 620 | } 621 | 622 | .heisir .emoji_popover li img{ 623 | width: 48px; 624 | height: 48px; 625 | } 626 | 627 | .heisir .emoji_popover li img:hover{ 628 | box-sizing: border-box; 629 | box-shadow: inset 0px 0px 8px 1px #ccc; 630 | border-radius: 15px; 631 | } 632 | 633 | .heisir .screen_select_dialog{ 634 | width: fit-content; 635 | min-width: 40vw; 636 | } 637 | 638 | .heisir .screen_select_dialog .el-dialog__body{ 639 | text-align: center; 640 | } 641 | 642 | .heisir .screen_select li{ 643 | list-style: none; 644 | display: inline; 645 | margin: 0 10px; 646 | } 647 | .heisir .screen_select li img{ 648 | width: 200px; 649 | height: auto; 650 | } 651 | 652 | .heisir .el-dropdown-menu.el-popper{ 653 | background-color: #404040; 654 | border: 0; 655 | opacity: 0.85; 656 | margin: 10px; 657 | color:#ffd400; 658 | 659 | } 660 | .heisir .el-dropdown-menu.el-popper .el-dropdown-menu__item{ 661 | color: white; 662 | } 663 | 664 | .heisir .el-dropdown-menu__item:focus,.el-dropdown-menu__item:not(.is-disabled):hover { 665 | background-color:#303030 !important; 666 | color: #ffd400 !important; 667 | } 668 | 669 | .heisir .el-dropdown-menu__item--divided:before{ 670 | background-color:#404040 !important; 671 | } 672 | 673 | .heisir .el-dropdown-menu__item--divided { 674 | position: relative; 675 | margin-top: 6px; 676 | border-top: 1px solid #888888 677 | } 678 | .heisir .el-popper[x-placement^=top] .popper__arrow,.el-popper[x-placement^=top] .popper__arrow::after{ 679 | border-top-color: #404040 !important; 680 | } -------------------------------------------------------------------------------- /static/css/room.css: -------------------------------------------------------------------------------- 1 | html,body{ 2 | padding: 0; 3 | margin: 0; 4 | box-sizing: border-box; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | html,body *{ 9 | user-select:none; 10 | } 11 | 12 | div,input,span,a,p,br,h1,h2,h3,ul,li,ol{ 13 | padding: 0; 14 | margin: 0; 15 | box-sizing: border-box; 16 | } 17 | 18 | 19 | .heisir{ 20 | padding: 0; 21 | margin: 0; 22 | box-shadow: inset 0px 0px 25px 0px #fff; 23 | position: relative; 24 | overflow: hidden; 25 | background-color: #f0f8ff; 26 | font-weight: 500; 27 | -webkit-app-region: drag; 28 | } 29 | 30 | 31 | .heisir .bk{ 32 | width: 100%; 33 | height: 100%; 34 | position: relative; 35 | } 36 | 37 | .heisir .bk .room{ 38 | position:absolute; 39 | top:calc((100% - 320px) / 2); 40 | height: 320px; 41 | left: 10%; 42 | } 43 | 44 | 45 | .heisir .header{ 46 | width: 100%; 47 | height: 32px; 48 | position: absolute; 49 | z-index: 2; 50 | } 51 | 52 | .heisir .header .systools{ 53 | width: fit-content; 54 | right: 0px; 55 | position: absolute; 56 | text-align: left; 57 | height: 100%; 58 | box-sizing: content-box; 59 | overflow: hidden; 60 | -webkit-app-region: no-drag; 61 | } 62 | 63 | .heisir .header .systools:hover{ 64 | background-color: rgb(177, 0, 0); 65 | } 66 | 67 | .heisir .header .systools .close{ 68 | background-image: url(../icon/close.png); 69 | height: 100%; 70 | width: 32px; 71 | background-size: 14px 14px; 72 | background-repeat: no-repeat; 73 | vertical-align: middle; 74 | margin: 0 auto; 75 | background-position: center; 76 | } 77 | 78 | .heisir .header .systools .close:hover{ 79 | background-image: url(../icon/close-hover.png); 80 | } 81 | 82 | .heisir .content{ 83 | position:absolute; 84 | top: 10%; 85 | right: 5%; 86 | -webkit-app-region: no-drag; 87 | width: 300px; 88 | height: 80%; 89 | background-color: white; 90 | border-radius: 10px; 91 | padding: 10px; 92 | box-shadow: 2px 2px 5px 1px #666; 93 | font-size: 14px; 94 | font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; 95 | } 96 | .heisir .content *{ 97 | font-size: 14px; 98 | font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; 99 | line-height: 1.0; 100 | } 101 | 102 | .heisir .content .line{ 103 | margin: 8px auto; 104 | width: 80%; 105 | display: block; 106 | height: fit-content; 107 | min-height: 14px; 108 | } 109 | 110 | .heisir .content .line > *{ 111 | width: 100%; 112 | height: 100%; 113 | padding: 0 5px; 114 | } 115 | 116 | .heisir .content .line input{ 117 | height: 32px; 118 | margin: 0 auto; 119 | } 120 | 121 | .heisir .content .line input[type=checkbox]{ 122 | height: 24px; 123 | width: fit-content; 124 | vertical-align: middle; 125 | cursor: pointer; 126 | } 127 | 128 | .heisir .content .line input[type=checkbox]:not(:first-child){ 129 | margin-left:50px; 130 | } 131 | 132 | .heisir .content .line .checkLabel{ 133 | height: 24px; 134 | vertical-align: middle; 135 | margin:3px 0; 136 | cursor: pointer; 137 | } 138 | 139 | .heisir .content .line .checkLabel:hover{ 140 | color:#005CC8; 141 | } 142 | 143 | .heisir .content .line input[type=text],.heisir .content .line input[type=number]{ 144 | border-radius: 5px; 145 | border-width: 1px; 146 | border-color: #fff; 147 | box-shadow: 1px 1px 2px 0px #666; 148 | } 149 | 150 | .heisir .content .line input[type=button]{ 151 | height: 42px; 152 | border: 0; 153 | border-radius: 20px; 154 | background-color: #0079F2; 155 | color: white; 156 | cursor: pointer; 157 | box-shadow: 1px 1px 2px 0px #666; 158 | } 159 | 160 | .heisir .content .line input[type=button]:hover{ 161 | font-size: 15px; 162 | font-weight: 450; 163 | } 164 | 165 | .heisir .content .title{ 166 | font-size: 28px; 167 | font-weight: 500; 168 | text-shadow: 1px 1px #888; 169 | text-align: center; 170 | color: #333; 171 | letter-spacing: 8px; 172 | text-indent: 8px; 173 | padding: 40px 0px; 174 | margin: 0 auto; 175 | 176 | -webkit-app-region: drag; 177 | } 178 | 179 | .heisir .content .msg{ 180 | color: tomato; 181 | } 182 | 183 | .heisir .content .copyright{ 184 | text-align: center; 185 | position: absolute; 186 | bottom: 0; 187 | width: 100%; 188 | } 189 | 190 | .heisir .content .copyright a{ 191 | text-decoration: transparent; 192 | color: gray; 193 | font-size: 12px; 194 | } -------------------------------------------------------------------------------- /static/css/screen.css: -------------------------------------------------------------------------------- 1 | html,body{ 2 | padding: 0; 3 | margin: 0; 4 | box-sizing: border-box; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | html,body *{ 9 | user-select:none; 10 | } 11 | 12 | div,input,span,a,p,br,h1,h2,h3,ul,li,ol{ 13 | padding: 0; 14 | margin: 0; 15 | box-sizing: border-box; 16 | } 17 | 18 | .video-container{ 19 | margin: 0 auto; 20 | width: 100%; 21 | height: 100%; 22 | overflow: auto; 23 | } 24 | 25 | .video-container .drag{ 26 | position: absolute; 27 | width: 80%; 28 | height: 80%; 29 | left: 10%; 30 | top:10%; 31 | font-size: 0; 32 | margin: 0; 33 | padding: 0; 34 | background: transparent; 35 | -webkit-app-region: drag; 36 | } 37 | 38 | .video-container video{ 39 | object-fit: none; 40 | box-shadow: inset 0px 0px 25px 0px #fff; 41 | } 42 | 43 | .video-container .tools{ 44 | position: relative; 45 | width: fit-content; 46 | height: 60px; 47 | bottom: 15%; 48 | margin: 0 auto; 49 | background: #404040AA; 50 | border-radius: 10px; 51 | overflow: hidden; 52 | padding: 0 10px; 53 | -webkit-app-region: no-drag; 54 | } 55 | 56 | .video-container .tools > *{ 57 | width: 60px; 58 | line-height: 1.0; 59 | height: 100%; 60 | border: none; 61 | text-align: center; 62 | background-color: transparent; 63 | color: white; 64 | box-sizing: content-box; 65 | display: inline-block; 66 | vertical-align: middle; 67 | } 68 | 69 | .video-container .tools > *:hover{ 70 | transform: scale(1.3); 71 | } 72 | 73 | .video-container .tools option{ 74 | text-align: center; 75 | background-color: #404040AA; 76 | height: 32px; 77 | line-height: 1.0; 78 | } 79 | 80 | .video-container .tools .layout{ 81 | line-height: 1.0; 82 | } 83 | .video-container .tools .label{ 84 | width: 32px; 85 | height: 100%; 86 | line-height: 1.0; 87 | object-fit: contain; 88 | } 89 | 90 | 91 | .video-container .tools .line{ 92 | border-right: solid 1px #a0a0a040; 93 | width: 0px; 94 | height: 100%; 95 | line-height: 1.0; 96 | } 97 | 98 | .heisir{ 99 | padding: 0; 100 | margin: 0; 101 | background-image: url(../imgs/background-04.png); 102 | background-size: cover; 103 | background-position: center; 104 | box-shadow: inset 0px 0px 25px 0px #fff; 105 | position: relative; 106 | overflow: hidden; 107 | 108 | -webkit-app-region: no-drag; 109 | } 110 | 111 | .heisir .header{ 112 | width: 100%; 113 | height: 32px; 114 | position: absolute; 115 | z-index: 2; 116 | -webkit-app-region: drag; 117 | } 118 | 119 | .heisir .header .tools{ 120 | width: fit-content; 121 | right: 32px; 122 | position: absolute; 123 | text-align: left; 124 | background-color: #000000; 125 | height: 100%; 126 | padding: 0 5px; 127 | box-sizing: content-box; 128 | border-bottom-left-radius: 5px; 129 | overflow: hidden; 130 | 131 | -webkit-app-region: no-drag; 132 | } 133 | .heisir .header .tools *{ 134 | font-size: 12px; 135 | line-height: 32px; 136 | font-style: normal; 137 | display: inline-block; 138 | background-size: contain; 139 | background-repeat: no-repeat; 140 | background-position: center; 141 | height: 100%; 142 | color: white; 143 | vertical-align: middle; 144 | white-space: nowrap; 145 | width: 28px; 146 | } 147 | .heisir .header .tools .screen-scale{ 148 | background-image: url(../icon/screen-scale.png); 149 | margin-right: 5px; 150 | } 151 | 152 | .heisir .header .tools .screen-1{ 153 | background-image: url(../icon/screen-1-1.png); 154 | margin-right: 5px; 155 | } 156 | 157 | .heisir .header .systools{ 158 | width: 32px; 159 | right: 0px; 160 | position: absolute; 161 | text-align: left; 162 | background-color: #000000bb; 163 | height: 100%; 164 | box-sizing: content-box; 165 | overflow: hidden; 166 | border-left: solid 1px #a0a0a088; 167 | -webkit-app-region: no-drag; 168 | } 169 | 170 | .heisir .header .systools:hover{ 171 | background-color: rgb(177, 0, 0); 172 | } 173 | 174 | .heisir .header .systools .close{ 175 | background-image: url(../icon/close.png); 176 | height: 100%; 177 | width: 14px; 178 | background-size: contain; 179 | background-repeat: no-repeat; 180 | vertical-align: middle; 181 | margin: 0 auto; 182 | background-position: center; 183 | } 184 | 185 | .heisir .header .systools .close:hover{ 186 | background-image: url(../icon/close-hover.png); 187 | } 188 | 189 | 190 | .heisir .bitrate-container{ 191 | position: absolute; 192 | top: 10px; 193 | right: 100px; 194 | -webkit-app-region: no-drag; 195 | } 196 | 197 | .heisir .bitrate-container .bitrateSelects,.heisir .bitrate-container .bitrateOption{ 198 | margin: 5px 2px; 199 | border: 0px; 200 | padding: 5px 15px; 201 | font-size: 1em; 202 | background-color: black; 203 | color: white; 204 | -webkit-app-region: no-drag; 205 | } 206 | 207 | .heisir .screen-dialog{ 208 | display: none; 209 | position: absolute; 210 | width: 700px; 211 | background-color: #d8d8d8ab; 212 | bottom: calc(15% + 10px); 213 | margin: 0 auto; 214 | border-radius: 4px; 215 | padding: 10px; 216 | box-shadow: 0px 0px 10px 1px #666; 217 | text-align: center; 218 | left: calc(50% - 350px); 219 | overflow-x: auto; 220 | z-index: 5; 221 | -webkit-app-region: no-drag; 222 | } 223 | 224 | 225 | .heisir .screen-dialog .screen-poster{ 226 | width: 200px; 227 | margin: 10px; 228 | box-shadow: 0px 0px 10px 1px #333; 229 | } 230 | 231 | .heisir .screen-dialog .screen-poster:hover{ 232 | transform: scale(1.1); 233 | } -------------------------------------------------------------------------------- /static/element-ui/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/element-ui/fonts/element-icons.woff -------------------------------------------------------------------------------- /static/icon/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/camera.png -------------------------------------------------------------------------------- /static/icon/camera_no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/camera_no.png -------------------------------------------------------------------------------- /static/icon/close-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/close-hover.png -------------------------------------------------------------------------------- /static/icon/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/close.png -------------------------------------------------------------------------------- /static/icon/desktop-share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/desktop-share.png -------------------------------------------------------------------------------- /static/icon/desktop-share_no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/desktop-share_no.png -------------------------------------------------------------------------------- /static/icon/emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/emoji.png -------------------------------------------------------------------------------- /static/icon/emoji_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/emoji_hover.png -------------------------------------------------------------------------------- /static/icon/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/exit.png -------------------------------------------------------------------------------- /static/icon/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/fullscreen.png -------------------------------------------------------------------------------- /static/icon/fullscreen_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/fullscreen_hover.png -------------------------------------------------------------------------------- /static/icon/fullscreen_no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/fullscreen_no.png -------------------------------------------------------------------------------- /static/icon/layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/layout.png -------------------------------------------------------------------------------- /static/icon/logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/logo-512.png -------------------------------------------------------------------------------- /static/icon/logo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/logo.icns -------------------------------------------------------------------------------- /static/icon/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/logo.ico -------------------------------------------------------------------------------- /static/icon/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/logo.jpg -------------------------------------------------------------------------------- /static/icon/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/logo.png -------------------------------------------------------------------------------- /static/icon/logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/logo.psd -------------------------------------------------------------------------------- /static/icon/m3u8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/m3u8.png -------------------------------------------------------------------------------- /static/icon/members.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/members.png -------------------------------------------------------------------------------- /static/icon/mic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/mic.png -------------------------------------------------------------------------------- /static/icon/mic_no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/mic_no.png -------------------------------------------------------------------------------- /static/icon/minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/minimize.png -------------------------------------------------------------------------------- /static/icon/minimize_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/minimize_hover.png -------------------------------------------------------------------------------- /static/icon/msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/msg.png -------------------------------------------------------------------------------- /static/icon/msg_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/msg_hover.png -------------------------------------------------------------------------------- /static/icon/network-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/network-0.png -------------------------------------------------------------------------------- /static/icon/network-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/network-1.png -------------------------------------------------------------------------------- /static/icon/network-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/network-2.png -------------------------------------------------------------------------------- /static/icon/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/network.png -------------------------------------------------------------------------------- /static/icon/record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/record.png -------------------------------------------------------------------------------- /static/icon/screen-1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/screen-1-1.png -------------------------------------------------------------------------------- /static/icon/screen-scale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/screen-scale.png -------------------------------------------------------------------------------- /static/icon/show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/show.png -------------------------------------------------------------------------------- /static/icon/speak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/speak.png -------------------------------------------------------------------------------- /static/icon/speak_no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/icon/speak_no.png -------------------------------------------------------------------------------- /static/imgs/background-00.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/background-00.webp -------------------------------------------------------------------------------- /static/imgs/background-01.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/background-01.webp -------------------------------------------------------------------------------- /static/imgs/background-02.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/background-02.webp -------------------------------------------------------------------------------- /static/imgs/background-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/background-03.png -------------------------------------------------------------------------------- /static/imgs/background-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/background-04.png -------------------------------------------------------------------------------- /static/imgs/emoji/emoji_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/emoji/emoji_0.png -------------------------------------------------------------------------------- /static/imgs/emoji/emoji_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/emoji/emoji_1.png -------------------------------------------------------------------------------- /static/imgs/emoji/emoji_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/emoji/emoji_2.png -------------------------------------------------------------------------------- /static/imgs/emoji/emoji_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/emoji/emoji_3.png -------------------------------------------------------------------------------- /static/imgs/emoji/emoji_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/emoji/emoji_4.png -------------------------------------------------------------------------------- /static/imgs/emoji/emoji_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/emoji/emoji_5.png -------------------------------------------------------------------------------- /static/imgs/emoji/emoji_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/emoji/emoji_6.png -------------------------------------------------------------------------------- /static/imgs/emoji/emoji_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/emoji/emoji_7.png -------------------------------------------------------------------------------- /static/imgs/room-bkg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/imgs/ui-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/ui-01.png -------------------------------------------------------------------------------- /static/imgs/ui-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/ui-02.png -------------------------------------------------------------------------------- /static/imgs/ui-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/ui-03.png -------------------------------------------------------------------------------- /static/imgs/ui-04.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeiSir2014/owt-client-rtc/c8c89cb4cb14808d1a5000eff859bef4c1e18088/static/imgs/ui-04.gif -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 多人会议 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 139 |
140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /static/loginRoom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 多人会议 7 | 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 | 67 | 68 | -------------------------------------------------------------------------------- /static/scripts/app-index.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer, desktopCapturer } = require('electron'); 2 | 3 | Vue.directive('focus', { 4 | update: function (el, { oldValue, value }) { 5 | oldValue != value && value && el.focus(); 6 | } 7 | }); 8 | 9 | const _app = new Vue({ 10 | el: '#App', 11 | data: function () { 12 | return { 13 | version: '', 14 | isMaximized: false, 15 | playerStream: null, 16 | myRoom: '', 17 | statUpload: 0, 18 | statDownload: 0, 19 | playerStream: null, 20 | isSpeakMuted: false, 21 | isMicMuted: false, 22 | isCameraMuted: false, 23 | isCamera2Muted: false, 24 | isDesktopShared: false, 25 | isRecordStarted: false, 26 | usedMicrophone: '', 27 | usedCamera: '', 28 | usedCamera2: '', 29 | usedCamera2DeviceId: '', 30 | layoutIndex: 0, 31 | msg_input_visible: false, 32 | msg_content: '', 33 | emoji_visible: false, 34 | emoji_data: ['emoji_5', 'emoji_6', 'emoji_7', 'emoji_4', 'emoji_0', 'emoji_1', 'emoji_2', 'emoji_3'], 35 | screen_data: [], 36 | screen_select_visible: false, 37 | lastMoveTime: 0, 38 | hideCursorIntervalHandle: null, 39 | tools_visible: true, 40 | tools_hover: false, 41 | participants: [], 42 | remoteStreams: [], 43 | screenSharingUser: '', 44 | localStream:null, 45 | localStreamSecond:null, 46 | selectStreamId:'', 47 | 48 | } 49 | }, 50 | methods: { 51 | init: function (e) { 52 | const that = this; 53 | 54 | ipcRenderer.on('maximizeChanged', that.maximizeChanged.bind(that)); 55 | ipcRenderer.on('set-version', that._setVersion.bind(that)); 56 | window.addEventListener('keyup', that.onkeyup.bind(that)); 57 | window.addEventListener('mousemove', that.onmousemove.bind(that)); 58 | 59 | that.myRoom = getParameterByName('room'); 60 | that.myUserId = getParameterByName('userId'); 61 | that.myUserNick = getParameterByName('userNick'); 62 | that.enableAudio = getParameterByName('enableAudio'); 63 | that.enableVideo = getParameterByName('enableVideo'); 64 | 65 | that.enableAudio = ((!that.enableAudio || that.enableAudio == 'true') ? true : false); 66 | that.enableVideo = ((!that.enableVideo || that.enableVideo == 'true') ? true : false); 67 | 68 | that.isMicMuted = true; 69 | that.isCameraMuted = true; 70 | that.isCamera2Muted = true; 71 | that.isDesktopShared = false; 72 | that.isRecordStarted = false; 73 | 74 | that.checkDevices(); 75 | 76 | createToken(that.myRoom, that.myUserId, 'presenter', function (response) { 77 | var token = response; 78 | if (!token) { 79 | console.error('token is empty'); 80 | return; 81 | } 82 | that.conference = new Owt.Conference.ConferenceClient(); 83 | that.conference.join(token).then(resp => { 84 | that.myId = resp.self.id; 85 | that.myRoomId = resp.id; 86 | 87 | that.participants = resp.participants; 88 | 89 | that.participants.forEach(p => { 90 | p.addEventListener('left', that.participantleft.bind(that, p)); 91 | }); 92 | that.remoteStreams = resp.remoteStreams.filter(r => r.source && r.source.video && r.source.video != 'mixed'); 93 | 94 | that.subscribeMixStream(); 95 | }, function (err) { 96 | console.error('server connection failed:', err); 97 | if (err.message.indexOf('connect_error:') >= 0) { 98 | const div = $('
网络错误
'); 99 | div.appendTo($p); 100 | $p.appendTo($('body')); 101 | } 102 | }); 103 | 104 | that.conference.addEventListener('participantjoined', that.participantjoined.bind(that)); 105 | that.conference.addEventListener('streamadded', that.streamAdded.bind(that)); 106 | that.conference.addEventListener('messagereceived', that.messagereceived.bind(that)); 107 | that.conference.addEventListener('serverdisconnected', that.serverdisconnected.bind(that)); 108 | 109 | that.statInterval && (clearInterval(statInterval), that.statInterval = 0); 110 | that.statInterval = setInterval(that._getStat.bind(that), 1000); 111 | that.onmousemove(); 112 | }); 113 | 114 | }, 115 | _setVersion:function(event , version){ 116 | this.version = version; 117 | }, 118 | onkeyup: function (e) { 119 | e.keyCode == 27 && this.isMaximized && ipcRenderer.send('setFullScreen-win', false); 120 | return e.keyCode != 27; 121 | }, 122 | onmousemove: function (e) { 123 | if (this.hideCursorIntervalHandle == null) { 124 | document.body.style.cursor = "default"; 125 | this.tools_visible = true; 126 | this.hideCursorIntervalHandle = setInterval(this.hideCursorInterval.bind(this), 1000); 127 | } 128 | this.lastMoveTime = Date.now(); 129 | return true; 130 | }, 131 | hideCursorInterval: function () { 132 | if (!this.tools_hover && this.lastMoveTime && Date.now() - (this.isFullViewer ? 5000 : 2000) > this.lastMoveTime) { 133 | 134 | clearInterval(this.hideCursorIntervalHandle), this.hideCursorIntervalHandle = null; 135 | document.body.style.cursor = "none"; 136 | this.tools_visible = false; 137 | } 138 | }, 139 | msg_input_keyup: function (e) { 140 | e.keyCode == 13 && this.msg_content && (this._sendMsg('msg_text', this.msg_content), this.msg_content = ''); 141 | return e.keyCode != 13; 142 | }, 143 | _sendMsg: function (type, content) { 144 | if (!this.conference) return; 145 | let msg = { type: type, content: content }; 146 | this.conference.send(msg); 147 | this.showMessage(this.myUserId, msg); 148 | }, 149 | sendEmojiMsg: function (e) { 150 | this._sendMsg('msg_emoji', e.target.parentElement.getAttribute('data')); 151 | this.emoji_visible = false; 152 | }, 153 | maximizeChanged: function (event, isMaximized) { 154 | this.isMaximized = isMaximized; 155 | if (this.isMaximized) { 156 | this.$message({ 157 | message: '按下 ESC 键可以退出全屏', 158 | center: true, 159 | iconClass: '', 160 | customClass: 'message_tip', 161 | duration: 3000, 162 | offset: (document.body.clientHeight / 2) - 24 163 | }); 164 | } 165 | }, 166 | fullscreen: function (e) { 167 | ipcRenderer.send('setFullScreen-win', true); 168 | }, 169 | unFullscreen: function (e) { 170 | ipcRenderer.send('setFullScreen-win', false); 171 | }, 172 | close: function (e) { 173 | this._exitRoom(); 174 | ipcRenderer.send("close-win"); 175 | }, 176 | minimize: function (e) { 177 | ipcRenderer.send("minimize-win"); 178 | }, 179 | subscribeMixStream:async function(){ 180 | const that = this; 181 | (that.enableAudio || that.enableVideo) && that.publishVideo(); 182 | 183 | var streams = that.conference.info.remoteStreams; 184 | console.log(streams); 185 | for (const stream of streams) { 186 | if ((stream.source.audio === 'mixed' || stream.source.video === 187 | 'mixed') && stream.id.indexOf('-common') != -1) { 188 | that.subscribeStream(stream, (subscribe) => { 189 | console.log('subscribeStream result'); 190 | that.subscriptionGlobal = subscribe; 191 | that.subscriptionGlobal && (that.playerStream = stream.mediaStream, that.mixStreamGlobal = stream); 192 | that.subscriptionGlobal.addEventListener('error',(e)=>{ 193 | console.log("subscriptionGlobal error",e); 194 | 195 | }); 196 | streams.filter((s)=>s.source.video == 'screen-cast').length > 0 && that.switchVideoParam(true); 197 | that.subscriptionGlobal.addEventListener('ended',(e)=>{ 198 | console.log("subscriptionGlobal ended",e); 199 | setTimeout(that.subscribeMixStream.bind(that),5000); 200 | }); 201 | },()=>{ 202 | setTimeout(that.subscribeMixStream.bind(that),5000); 203 | }); 204 | console.log('subscribeStream finish'); 205 | } 206 | that.streamAdded({ stream }); 207 | } 208 | }, 209 | subscribeStream: async function (stream, callback,reject) { 210 | const that = this; 211 | if (!that.conference) return; 212 | try { 213 | let resolution = stream.settings.video[0].resolution; 214 | if(stream.source.video != 'screen-cast') 215 | { 216 | stream.settings.video.forEach(v => { 217 | v.resolution.width == 1280 && v.resolution.height == 720 && (resolution = v.resolution); 218 | }); 219 | 220 | resolution.width != 1280 && stream.extraCapabilities.video.resolutions.forEach(r => { 221 | r.width == 1280 && (resolution = r); 222 | }); 223 | 224 | console.log(resolution,stream.settings.video) 225 | } 226 | await that.conference.subscribe(stream, { 227 | audio: stream.source.audio ? true : false, 228 | video: { 229 | codec: { name: "h264", profile: "CB" }, 230 | resolution: resolution, 231 | } 232 | }).then(callback, (err) => { 233 | console.log('subscribe failed', err); 234 | reject && reject(); 235 | }); 236 | } catch (error) { 237 | console.log('subscribe failed', error); 238 | reject && reject(); 239 | } 240 | return; 241 | }, 242 | convertSource: function (streamSource) { 243 | const dict = { camera: "摄像头", "screen-cast": "桌面" }; 244 | if (streamSource in dict) return dict[streamSource]; 245 | return '视频'; 246 | }, 247 | convertSource2: function (remoteStream) { 248 | return this.getStreamUserId(remoteStream)+"的"+this.convertSource(remoteStream.source.video); 249 | }, 250 | getUserIdFromOrigin: function (origin) { 251 | const p = this.participants.find(p => p.id == origin); 252 | if (!p) return ''; 253 | const userId = this.participants.find(p => p.id == origin).userId; 254 | return userId == this.myId ? userId + '(我)' : userId; 255 | }, 256 | getStreamUserId: function (remoteStream) { 257 | if (!remoteStream) return '' 258 | return this.getUserIdFromOrigin(remoteStream.origin); 259 | }, 260 | participantjoined: function (e) { 261 | if (/robot/.test(e.participant.userId)) return; 262 | console.log('participantjoined', e); 263 | 264 | e.participant.addEventListener('left', this.participantleft.bind(this, e.participant)) 265 | 266 | this.participants = this.conference.info.participants.filter(p => !/robot/.test(p.userId)); 267 | console.log(this.conference.info); 268 | var audio = new Audio('audio/some_one_join_room.wav'); // path to file 269 | audio.play(); 270 | audio = null; 271 | this.showMessage(null, { type: 'msg_text', content: `
成员
${e.participant.userId}
加入频道
` }); 272 | }, 273 | participantleft: function (participant, e) { 274 | console.log('participantleft', e); 275 | 276 | this.participants = this.conference.info.participants.filter(p => !/robot/.test(p.userId)); 277 | var audio = new Audio('audio/some_one_join_room.wav'); // path to file 278 | audio.play(); 279 | audio = null; 280 | this.showMessage(null, { type: 'msg_text', content: `
成员
${participant.userId}
离开频道
` }); 281 | }, 282 | messagereceived: function (e) { 283 | 284 | console.log(e); 285 | if (e.origin == this.myId) return; 286 | if (e.message.type == 'msg_text' || e.message.type == 'msg_emoji') { 287 | this.showMessage(this.getUserIdFromOrigin(e.origin), e.message); 288 | } 289 | }, 290 | showMessage: function (userId, message) { 291 | const duration = 8888; 292 | const offset = 94; 293 | const spacing = 2; 294 | message.type == 'msg_text' && this.$notify({ 295 | customClass: 'notify_msg', 296 | message: `${userId ? ('
' + userId + '
') : ''}
${message.content}
`, 297 | position: 'bottom-left', 298 | duration: duration, 299 | dangerouslyUseHTMLString: true, 300 | showClose: false, 301 | offset: offset, 302 | spacing: spacing, 303 | insertHead: true 304 | }); 305 | message.type == 'msg_emoji' && this.$notify({ 306 | customClass: 'notify_msg emoji', 307 | message: `${userId ? ('
' + userId + '
') : ''} `, 308 | position: 'bottom-left', 309 | duration: duration, 310 | dangerouslyUseHTMLString: true, 311 | showClose: false, 312 | offset: offset, 313 | spacing: spacing, 314 | insertHead: true 315 | }); 316 | message.type == 'msg_html' && this.$notify({ 317 | customClass: 'notify_msg emoji', 318 | message: `${userId ? ('
' + userId + '
') : ''} ${message.content}`, 319 | position: 'bottom-left', 320 | duration: duration, 321 | dangerouslyUseHTMLString: true, 322 | showClose: false, 323 | offset: offset, 324 | spacing: spacing, 325 | insertHead: true 326 | }); 327 | }, 328 | serverdisconnected: function (e) { 329 | console.log('serverdisconnected', e) 330 | this._exitRoom(); 331 | }, 332 | streamAdded: function (e) { 333 | const that = this; 334 | const stream = e.stream; 335 | console.log('A new stream is added ', stream.id); 336 | if (!that.conference) return; 337 | that.remoteStreams = that.conference.info.remoteStreams.filter(r => r.source && r.source.video && r.source.video != 'mixed'); 338 | if (stream.origin !== that.myId && stream.source 339 | && stream.source.video 340 | && stream.source.video == 'screen-cast') { 341 | that.screenSharingUser = that.getStreamUserId(stream); 342 | // that.subscribeStream(stream, (subscription) => { 343 | // that.showScreenStream(stream, subscription); 344 | // }); 345 | that.switchVideoParam(true); 346 | } 347 | stream.addEventListener('ended', that.streamEnded.bind(that, stream)); 348 | }, 349 | streamEnded: function (stream) { 350 | console.log(stream.id + ' is ended.'); 351 | const that = this; 352 | if (!that.conference) return; 353 | stream.source && stream.source.video == 'screen-cast' && (that.screenSharingUser = '',that.subscriptionGlobal && that.subscriptionGlobal.applyOptions({video:{frameRate:24}})) 354 | that.remoteStreams = that.conference.info.remoteStreams.filter(r => r.source && r.source.video && r.source.video != 'mixed'); 355 | }, 356 | switchVideoParam:function(isScreenShared, bitrateMultiplier){ 357 | 358 | const that = this; 359 | const frameRateOrigin = that.mixStreamGlobal ? that.mixStreamGlobal.settings.video[0].frameRate:24; 360 | that.mixStreamGlobal && that.subscriptionGlobal && that.subscriptionGlobal.applyOptions({ 361 | video:{ 362 | frameRate:isScreenShared?6:frameRateOrigin, 363 | bitrateMultiplier:0.9, 364 | resolution:isScreenShared?{width:1920,height:1080}:{width:1280,height:720} 365 | }}) 366 | }, 367 | checkDevices: async function () { 368 | const that = this; 369 | let videoConstraints = new Owt.Base.VideoTrackConstraints(Owt.Base.VideoSourceInfo.CAMERA); 370 | let audioConstraints = [new Owt.Base.AudioTrackConstraints(Owt.Base.AudioSourceInfo.MIC), false]; 371 | let resolutions = [{ width: 1280, height: 720 },{ width: 720, height: 1280 },undefined, false]; 372 | let mediaStream; 373 | for (const audioConstraint of audioConstraints) { 374 | for (const resolution of resolutions) { 375 | try { 376 | if (resolution === false) { 377 | videoConstraints = false; 378 | } 379 | else { 380 | videoConstraints.resolution = resolution; 381 | if (resolution == undefined) videoConstraints = new Owt.Base.VideoTrackConstraints(Owt.Base.VideoSourceInfo.CAMERA); 382 | } 383 | mediaStream = await Owt.Base.MediaStreamFactory.createMediaStream(new Owt.Base.StreamConstraints( 384 | audioConstraint, videoConstraints)); 385 | break; 386 | } catch (error) { 387 | mediaStream = null; 388 | console.error(error); 389 | } 390 | } 391 | if (mediaStream) break; 392 | } 393 | if (!mediaStream) return; 394 | let vT = mediaStream.getVideoTracks(); 395 | let aT = mediaStream.getAudioTracks(); 396 | let videoTrack, audioTrack; 397 | vT && vT.length && (videoTrack = vT[0]) && 398 | (that.usedCamera = videoTrack.label.replace(/ ?\([\w:]{9}\)/, '')) 399 | aT && aT.length && (audioTrack = aT[0]) && 400 | (that.usedMicrophone = audioTrack.label.replace(/ ?\([\w:]{9}\)/, '')) 401 | 402 | try { 403 | let devices = await navigator.mediaDevices.enumerateDevices(); 404 | let vDevices = devices.filter(d => d.kind && d.kind == 'videoinput'); 405 | if (vDevices.length >= 2 && videoTrack) { 406 | vDevices = vDevices.filter(d => d.label != videoTrack.label); 407 | vDevices && vDevices.length && (that.usedCamera2 = vDevices[0].label.replace(/ ?\([\w:]{9}\)/, ''), that.usedCamera2DeviceId = vDevices[0].deviceId) 408 | } 409 | else { 410 | that.usedCamera2 = ''; 411 | that.usedCamera2DeviceId = ''; 412 | } 413 | } catch (error) { 414 | console.error(error); 415 | } 416 | that._destroyMediaStream(mediaStream); 417 | mediaStream = null; 418 | }, 419 | publishVideo: async function () { 420 | const that = this; 421 | let audioConstraints = [new Owt.Base.AudioTrackConstraints(Owt.Base.AudioSourceInfo.MIC), false]; 422 | let facingModes = [{exact:"user"},undefined]; 423 | let resolutions = [{ width: 1280, height: 720 }, { width: 640, height: 360 }, undefined, false]; 424 | screen && screen.orientation && screen.orientation.type && screen.orientation.type.indexOf('landscape') == -1 && 425 | resolutions.unshift({ width: 720, height: 1280 },{ width: 360, height: 640 }); 426 | (!screen || !screen.orientation ) && document.body.clientWidth < document.body.clientHeight && 427 | resolutions.unshift({ width: 720, height: 1280 },{ width: 360, height: 640 }); 428 | let mediaStream; 429 | for (const audioConstraint of audioConstraints) { 430 | 431 | for(const facingMode of facingModes){ 432 | 433 | for (const resolution of resolutions) { 434 | try { 435 | let videoConstraints = new Owt.Base.VideoTrackConstraints(Owt.Base.VideoSourceInfo.CAMERA); 436 | facingMode && (videoConstraints.facingMode = facingMode); 437 | 438 | if (resolution === false) { 439 | videoConstraints = false; 440 | } 441 | else { 442 | videoConstraints.resolution = resolution; 443 | if (resolution == undefined) videoConstraints = new Owt.Base.VideoTrackConstraints(Owt.Base.VideoSourceInfo.CAMERA); 444 | } 445 | mediaStream = await Owt.Base.MediaStreamFactory.createMediaStream(new Owt.Base.StreamConstraints( 446 | audioConstraint, videoConstraints)); 447 | break; 448 | } catch (error) { 449 | mediaStream = null; 450 | console.error(error); 451 | } 452 | } 453 | if (mediaStream) break; 454 | } 455 | if (mediaStream) break; 456 | } 457 | if (!mediaStream) return; 458 | let vT = mediaStream.getVideoTracks(); 459 | let aT = mediaStream.getAudioTracks(); 460 | let videoTrack, audioTrack; 461 | vT && vT.length && (videoTrack = vT[0]) && 462 | (that.usedCamera = videoTrack.label.replace(/ ?\([\w:]{9}\)/, '')) 463 | aT && aT.length && (audioTrack = aT[0]) && 464 | (that.usedMicrophone = audioTrack.label.replace(/ ?\([\w:]{9}\)/, '')) 465 | 466 | let devices = await navigator.mediaDevices.enumerateDevices(); 467 | let vDevices = devices.filter(d => d.kind && d.kind == 'videoinput'); 468 | if (vDevices.length >= 2 && videoTrack) { 469 | vDevices = vDevices.filter(d => d.label != videoTrack.label); 470 | vDevices && vDevices.length && (that.usedCamera2 = vDevices[0].label.replace(/ ?\([\w:]{9}\)/, ''), that.usedCamera2DeviceId = vDevices[0].deviceId) 471 | } 472 | else { 473 | that.usedCamera2 = ''; 474 | that.usedCamera2DeviceId = ''; 475 | } 476 | 477 | audioTrack && (audioTrack.enabled = that.enableAudio); 478 | videoTrack && (videoTrack.enabled = that.enableVideo); 479 | try{ 480 | await videoTrack.applyConstraints({frameRate:{max:15}}) 481 | } 482 | catch(err){ 483 | console.log("applyConstraints") 484 | console.erroe(err) 485 | } 486 | that.isCameraMuted = videoTrack ? !videoTrack.enabled : true; 487 | that.isMicMuted = audioTrack ? !audioTrack.enabled : true; 488 | 489 | that.localStream = new Owt.Base.LocalStream(mediaStream, new Owt.Base.StreamSourceInfo('mic', 'camera')); 490 | try { 491 | that.publicationGlobal = await that.conference.publish(that.localStream, { video: [{ codec: { name: 'h264', profile: 'CB' }, maxBitrate: 1024 }] }); 492 | } catch (error) { 493 | that.publicationGlobal = null; 494 | console.error(error); 495 | that._clearLocalCamera(); 496 | } 497 | if (!that.publicationGlobal) 498 | return; 499 | mixStream(that.myRoomId, that.publicationGlobal.id, ['common']); 500 | that.publicationGlobal.addEventListener('error', that._clearLocalCamera.bind(that)); 501 | that.publicationGlobal.addEventListener('ended', that._clearLocalCamera.bind(that)); 502 | }, 503 | publishVideoSecond: async function () { 504 | const that = this; 505 | let videoConstraints = new Owt.Base.VideoTrackConstraints(Owt.Base.VideoSourceInfo.CAMERA); 506 | let resolutions = [{ width: 1280, height: 720 }, { width: 1280, height: 720 }, { width: 640, height: 360 }, undefined]; 507 | screen && screen.orientation && screen.orientation.type && screen.orientation.type.indexOf('landscape') == -1 && 508 | resolutions.unshift({ width: 720, height: 1280 },{ width: 360, height: 640 }); 509 | 510 | let mediaStream; 511 | videoConstraints.deviceId = that.usedCamera2DeviceId; 512 | for (const resolution of resolutions) { 513 | try { 514 | videoConstraints.resolution = resolution; 515 | if(resolution == undefined) videoConstraints = new Owt.Base.VideoTrackConstraints(Owt.Base.VideoSourceInfo.CAMERA); 516 | 517 | mediaStream = await Owt.Base.MediaStreamFactory.createMediaStream(new Owt.Base.StreamConstraints( 518 | false, videoConstraints)); 519 | break; 520 | } catch (error) { 521 | mediaStream = null; 522 | console.error(error); 523 | } 524 | } 525 | if (!mediaStream) return; 526 | 527 | let vT = mediaStream.getVideoTracks(); 528 | let videoTrack; 529 | vT && vT.length && (videoTrack =vT[0] ) 530 | try{ 531 | await videoTrack.applyConstraints({frameRate:{exact:15,ideal:15}}) 532 | } 533 | catch(err){ 534 | console.log("applyConstraints") 535 | console.erroe(err) 536 | } 537 | 538 | that.localStreamSecond = new Owt.Base.LocalStream(mediaStream, new Owt.Base.StreamSourceInfo('mic', 'camera')); 539 | try { 540 | that.publicationGlobalSecond = await that.conference.publish(that.localStreamSecond, { video: [{ codec: { name: 'h264', profile: 'CB' }, maxBitrate: 1024 }], audio: false }); 541 | that.isCamera2Muted = false; 542 | } catch (error) { 543 | that.publicationGlobalSecond = null; 544 | console.error(error); 545 | that._clearLocalCameraSecond(); 546 | } 547 | if (!that.publicationGlobalSecond) 548 | return; 549 | mixStream(that.myRoomId, that.publicationGlobalSecond.id, ['common']); 550 | that.publicationGlobalSecond.addEventListener('error', that._clearLocalCameraSecond.bind(that)); 551 | that.publicationGlobalSecond.addEventListener('ended', that._clearLocalCameraSecond.bind(that)); 552 | }, 553 | _clearLocalCamera: function () { 554 | const that = this; 555 | that.localStream && that.localStream.mediaStream && that._destroyMediaStream(that.localStream.mediaStream), (that.localStream = null); 556 | 557 | that.isCameraMuted = that.isMicMuted = true; 558 | }, 559 | _clearLocalCameraSecond: function () { 560 | const that = this; 561 | that.localStreamSecond && that.localStreamSecond.mediaStream && that._destroyMediaStream(that.localStreamSecond.mediaStream), (that.localStreamSecond = null); 562 | 563 | that.isCamera2Muted = true; 564 | }, 565 | _startShareScreen: async function (id) { 566 | const that = this; 567 | let mediaStream; 568 | try{ 569 | mediaStream = await navigator.mediaDevices.getUserMedia({ 570 | audio: false, 571 | video: { 572 | mandatory: { 573 | chromeMediaSource: 'screen', 574 | chromeMediaSourceId: id, 575 | maxWidth: 1920 576 | } 577 | } 578 | }); 579 | } 580 | catch(err){ 581 | console.log("applyConstraints") 582 | console.error(err) 583 | return; 584 | } 585 | 586 | let vT = mediaStream.getVideoTracks(); 587 | let videoTrack; 588 | vT && vT.length && (videoTrack =vT[0] ) 589 | try{ 590 | await videoTrack.applyConstraints({frameRate:{max:5}}) 591 | } 592 | catch(err){ 593 | console.log("applyConstraints") 594 | console.erroe(err) 595 | } 596 | 597 | that.screen_select_visible = false; 598 | 599 | let publishOption = { video: [{ codec: { name: 'h264', profile: 'CB' }, maxBitrate: 2500 }] }; 600 | that.ScreenStream = new Owt.Base.LocalStream(mediaStream, new Owt.Base.StreamSourceInfo('screen-cast', 'screen-cast')); 601 | that.conference.publish(that.ScreenStream, publishOption).then(publication => { 602 | that.publicationScreenGlobal = publication; 603 | 604 | that.screenSharingUser = "我"; 605 | that.isDesktopShared = true; 606 | 607 | mixStream(that.myRoomId, publication.id, ['common'],()=>{ 608 | 609 | const remoteStreams = this.conference.info.remoteStreams.filter(r => r.source && r.source.video && r.source.video == 'mixed'); 610 | remoteStreams.forEach(remoteStream => { 611 | activeLayoutStream(that.myRoomId, remoteStream.id, publication.id); 612 | }) 613 | }); 614 | publication.addEventListener('error', that._clearScreenShare.bind(that)); 615 | publication.addEventListener('ended', that._clearScreenShare.bind(that)); 616 | 617 | that.switchVideoParam(true); 618 | // that.showScreenStream(that.ScreenStream); 619 | 620 | }, err => { 621 | that._clearScreenShare(); 622 | }) 623 | }, 624 | startShareScreen: function (e) { 625 | this._startShareScreen(e.target.getAttribute('data')); 626 | }, 627 | showScreenStream: function (stream, subscription) { 628 | if (!stream.mediaStream) return; 629 | const that = this; 630 | const { webContentsId } = ipcRenderer.sendSync('create-video-windows'); 631 | ipcRenderer.on('win-onload', this._onLoadWindow.bind(this, webContentsId, stream, subscription)); 632 | }, 633 | _onLoadWindow: async function (webContentsId, stream, subscription, e) { 634 | if (e.senderId != webContentsId) return; 635 | const pc = new RTCPeerConnection(); 636 | stream.mediaStream.getTracks().forEach(track => pc.addTransceiver(track, { streams: [stream.mediaStream], direction: 'sendonly' })); 637 | stream.addEventListener('ended', this.showStreamEnded.bind(this, pc, webContentsId, subscription)); 638 | pc.onicecandidate = function ({ candidate }) { 639 | candidate && ipcRenderer.sendTo(webContentsId, 'set-peer-param', { candidate: candidate.toJSON() }); 640 | } 641 | pc.onnegotiationneeded = async () => { 642 | await pc.setLocalDescription(); 643 | ipcRenderer.sendTo(webContentsId, 'set-peer-param', { localDescription: pc.localDescription.toJSON() }); 644 | } 645 | ipcRenderer.on('set-peer-param', this._setPeerParam.bind(this, pc, webContentsId, subscription)); 646 | }, 647 | _setPeerParam: async function (peerConnection, webContentsId, subscription, e, { localDescription, candidate, close }) { 648 | if (e.senderId != webContentsId) return; 649 | peerConnection && localDescription && await peerConnection.setRemoteDescription(localDescription); 650 | peerConnection && candidate && await peerConnection.addIceCandidate(candidate); 651 | close && subscription && subscription.stop(); 652 | }, 653 | showStreamEnded: function (pc, webContentsId, subscription) { 654 | try { 655 | console.log("showStreamEnded", pc); 656 | pc && pc.close(); 657 | } catch (error) { 658 | 659 | } 660 | try { 661 | subscription && subscription.stop(); 662 | } catch (error) { 663 | 664 | } 665 | ipcRenderer.sendTo(webContentsId, 'stream_ended'); 666 | }, 667 | _clearScreenShare: function () { 668 | const that = this; 669 | that.ScreenStream && that.ScreenStream.mediaStream && (that.ScreenStream.dispatchEvent({ type: 'ended' }), that._destroyMediaStream(that.ScreenStream.mediaStream)), (that.ScreenStream = null); 670 | try { 671 | that.publicationScreenGlobal && that.publicationScreenGlobal.stop(); 672 | } catch (error) { 673 | console.error(error) 674 | } 675 | finally { 676 | that.publicationScreenGlobal = null; 677 | } 678 | that.screenSharingUser = ''; 679 | that.isDesktopShared = false; 680 | that.parentWebContentsId >= 0 && ipcRenderer.sendTo(that.parentWebContentsId, 'message', { isDesktopShared: false }); 681 | that.switchVideoParam(false); 682 | }, 683 | _getStat: async function () { 684 | const that = this; 685 | let bytesSent = 0; 686 | let bytesReceived = 0; 687 | let stats; 688 | 689 | function statForEach(stat) { 690 | /^RTCIceCandidatePair/.test(stat['id']) && stat['bytesSent'] && (bytesSent = bytesSent + stat['bytesSent']); 691 | /^RTCIceCandidatePair/.test(stat['id']) && stat['bytesReceived'] && (bytesReceived = bytesReceived + stat['bytesReceived']); 692 | } 693 | 694 | that.subscriptionGlobal && (stats = await that.subscriptionGlobal.getStats()); 695 | stats && (stats.forEach(statForEach),stats = null); 696 | that.publicationGlobal && (stats = await that.publicationGlobal.getStats()); 697 | stats && (stats.forEach(statForEach),stats = null); 698 | that.publicationGlobalSecond && (stats = await that.publicationGlobalSecond.getStats()); 699 | stats && (stats.forEach(statForEach),stats = null); 700 | that.publicationScreenGlobal && (stats = await that.publicationScreenGlobal.getStats()); 701 | stats && (stats.forEach(statForEach),stats = null); 702 | 703 | if (that.bytesReceivedGlobal && bytesReceived > that.bytesReceivedGlobal) { 704 | that.statDownload = Math.round((bytesReceived - that.bytesReceivedGlobal) / 1024); 705 | } 706 | if (that.bytesSentGlobal && bytesSent > that.bytesSentGlobal) { 707 | that.statUpload = Math.round((bytesSent - that.bytesSentGlobal) / 1024); 708 | } 709 | that.bytesReceivedGlobal = bytesReceived; 710 | that.bytesSentGlobal = bytesSent; 711 | }, 712 | selectStreamChange: function(){ 713 | if(!this.selectStreamId) return; 714 | this.clickChangeLayout(`activeLayout-${this.selectStreamId}`); 715 | }, 716 | clickChangeLayout: function (e) { 717 | const that = this; 718 | console.log(e); 719 | if (e.indexOf('activeLayout') == 0) 720 | { 721 | const subStream = e.substr('activeLayout-'.length); 722 | const remoteStreams = this.conference.info.remoteStreams.filter(r => r.source && r.source.video && r.source.video == 'mixed'); 723 | remoteStreams.forEach(remoteStream => { 724 | activeLayoutStream(that.myRoomId, remoteStream.id, subStream); 725 | }) 726 | return; 727 | } 728 | if (e.indexOf('switchLayout') != 0) return; 729 | if (!this.subscriptionGlobal) return; 730 | const layouts = [ 731 | [{"region":[{"id":"1","area":{"height":"1","width":"1","top":"0","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1","width":"1","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"214/1080","width":"386/1920","top":"8/10","left":"8/10"},"shape":"rectangle"},{"id":"3","area":{"height":"214/1080","width":"386/1920","top":"6/10","left":"8/10"},"shape":"rectangle"},{"id":"4","area":{"height":"214/1080","width":"386/1920","top":"4/10","left":"8/10"},"shape":"rectangle"},{"id":"5","area":{"height":"214/1080","width":"386/1920","top":"2/10","left":"8/10"},"shape":"rectangle"},{"id":"6","area":{"height":"214/1080","width":"386/1920","top":"0","left":"8/10"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"8/10","width":"8/10","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"214/1080","width":"386/1920","top":"8/10","left":"8/10"},"shape":"rectangle"},{"id":"3","area":{"height":"214/1080","width":"386/1920","top":"6/10","left":"8/10"},"shape":"rectangle"},{"id":"4","area":{"height":"214/1080","width":"386/1920","top":"4/10","left":"8/10"},"shape":"rectangle"},{"id":"5","area":{"height":"214/1080","width":"386/1920","top":"2/10","left":"8/10"},"shape":"rectangle"},{"id":"6","area":{"height":"214/1080","width":"386/1920","top":"0","left":"8/10"},"shape":"rectangle"},{"id":"7","area":{"height":"214/1080","width":"386/1920","top":"8/10","left":"6/10"},"shape":"rectangle"},{"id":"8","area":{"height":"214/1080","width":"386/1920","top":"8/10","left":"4/10"},"shape":"rectangle"},{"id":"9","area":{"height":"214/1080","width":"386/1920","top":"8/10","left":"2/10"},"shape":"rectangle"},{"id":"10","area":{"height":"214/1080","width":"386/1920","top":"8/10","left":"0"},"shape":"rectangle"}]}], 732 | [{ "region": [{ "id": "1", "area": { "height": "1", "width": "1", "top": "0", "left": "0" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "1", "width": "959/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "1", "width": "959/1920", "top": "0", "left": "961/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "1", "width": "959/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "959/1920", "width": "959/1920", "top": "0", "left": "961/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "539/1080", "width": "959/1920", "top": "541/1080", "left": "961/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "539/1080", "width": "959/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "539/1080", "width": "959/1920", "top": "0", "left": "961/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "538/1080", "width": "959/1920", "top": "541/1080", "left": "0" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "539/1080", "width": "959/1920", "top": "541/1080", "left": "961/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "539/1080", "width": "959/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "539/1080", "width": "959/1920", "top": "0", "left": "961/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "538/1080", "width": "639/1920", "top": "541/1080", "left": "0" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "539/1080", "width": "639/1920", "top": "541/1080", "left": "641/1920" }, "shape": "rectangle" }, { "id": "5", "area": { "height": "539/1080", "width": "639/1920", "top": "541/1080", "left": "1281/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "539/1080", "width": "639/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "539/1080", "width": "638/1920", "top": "0", "left": "641/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "539/1080", "width": "639/1920", "top": "0", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "539/1080", "width": "639/1920", "top": "541/1080", "left": "0" }, "shape": "rectangle" }, { "id": "5", "area": { "height": "539/1080", "width": "638/1920", "top": "541/1080", "left": "641/1920" }, "shape": "rectangle" }, { "id": "6", "area": { "height": "539/1080", "width": "639/1920", "top": "541/1080", "left": "1281/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "359/1080", "width": "639/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "359/1080", "width": "638/1920", "top": "0", "left": "641/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "359/1080", "width": "639/1920", "top": "0", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "358/1080", "width": "639/1920", "top": "361/1080", "left": "0" }, "shape": "rectangle" }, { "id": "5", "area": { "height": "358/1080", "width": "638/1920", "top": "361/1080", "left": "641/1920" }, "shape": "rectangle" }, { "id": "6", "area": { "height": "358/1080", "width": "639/1920", "top": "361/1080", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "7", "area": { "height": "359/1080", "width": "639/1920", "top": "721/1080", "left": "0" }, "shape": "rectangle" }, { "id": "8", "area": { "height": "359/1080", "width": "638/1920", "top": "721/1080", "left": "641/1920" }, "shape": "rectangle" }, { "id": "9", "area": { "height": "359/1080", "width": "639/1920", "top": "721/1080", "left": "1281/1920" }, "shape": "rectangle" }] }], 733 | [{"region":[{"id":"1","area":{"height":"1","width":"1","top":"0","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"971/1080","width":"1","top":"109/1080","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"108/1080","width":"190/1920","top":"0","left":"864/1920"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"971/1080","width":"1","top":"109/1080","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"108/1080","width":"190/1920","top":"0","left":"767/1920"},"shape":"rectangle"},{"id":"3","area":{"height":"108/1080","width":"190/1920","top":"0","left":"961/1920"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"971/1080","width":"1","top":"109/1080","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"108/1080","width":"190/1920","top":"0","left":"864/1920"},"shape":"rectangle"},{"id":"3","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1057/1920"},"shape":"rectangle"},{"id":"4","area":{"height":"108/1080","width":"190/1920","top":"0","left":"671/1920"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"971/1080","width":"1","top":"109/1080","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"108/1080","width":"190/1920","top":"0","left":"767/1920"},"shape":"rectangle"},{"id":"3","area":{"height":"108/1080","width":"190/1920","top":"0","left":"961/1920"},"shape":"rectangle"},{"id":"4","area":{"height":"108/1080","width":"190/1920","top":"0","left":"576/1920"},"shape":"rectangle"},{"id":"5","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1154/1920"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"971/1080","width":"1","top":"109/1080","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"108/1080","width":"190/1920","top":"0","left":"864/1920"},"shape":"rectangle"},{"id":"3","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1057/1920"},"shape":"rectangle"},{"id":"4","area":{"height":"108/1080","width":"190/1920","top":"0","left":"671/1920"},"shape":"rectangle"},{"id":"5","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1250/1920"},"shape":"rectangle"},{"id":"6","area":{"height":"108/1080","width":"190/1920","top":"0","left":"478/1920"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"971/1080","width":"1","top":"109/1080","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"108/1080","width":"190/1920","top":"0","left":"767/1920"},"shape":"rectangle"},{"id":"3","area":{"height":"108/1080","width":"190/1920","top":"0","left":"961/1920"},"shape":"rectangle"},{"id":"4","area":{"height":"108/1080","width":"190/1920","top":"0","left":"576/1920"},"shape":"rectangle"},{"id":"5","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1154/1920"},"shape":"rectangle"},{"id":"6","area":{"height":"108/1080","width":"190/1920","top":"0","left":"383/1920"},"shape":"rectangle"},{"id":"7","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1347/1920"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"971/1080","width":"1","top":"109/1080","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"108/1080","width":"190/1920","top":"0","left":"864/1920"},"shape":"rectangle"},{"id":"3","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1057/1920"},"shape":"rectangle"},{"id":"4","area":{"height":"108/1080","width":"190/1920","top":"0","left":"671/1920"},"shape":"rectangle"},{"id":"5","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1250/1920"},"shape":"rectangle"},{"id":"6","area":{"height":"108/1080","width":"190/1920","top":"0","left":"478/1920"},"shape":"rectangle"},{"id":"7","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1443/1920"},"shape":"rectangle"},{"id":"8","area":{"height":"108/1080","width":"190/1920","top":"0","left":"285/1920"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"971/1080","width":"1","top":"109/1080","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"108/1080","width":"190/1920","top":"0","left":"767/1920"},"shape":"rectangle"},{"id":"3","area":{"height":"108/1080","width":"190/1920","top":"0","left":"961/1920"},"shape":"rectangle"},{"id":"4","area":{"height":"108/1080","width":"190/1920","top":"0","left":"576/1920"},"shape":"rectangle"},{"id":"5","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1154/1920"},"shape":"rectangle"},{"id":"6","area":{"height":"108/1080","width":"190/1920","top":"0","left":"383/1920"},"shape":"rectangle"},{"id":"7","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1347/1920"},"shape":"rectangle"},{"id":"8","area":{"height":"108/1080","width":"190/1920","top":"0","left":"190/1920"},"shape":"rectangle"},{"id":"9","area":{"height":"108/1080","width":"190/1920","top":"0","left":"1540/1920"},"shape":"rectangle"}]}], 734 | [{"region":[{"id":"1","area":{"height":"1","width":"1","top":"0","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1","width":"1","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"3","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"4","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"5","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"6","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"7","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"8","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"9","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"10","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"11","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"12","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"13","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"14","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"15","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"16","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"}]}], 735 | ]; 736 | this.layoutIndex = Number.parseInt(e.substr(-1,1)); 737 | 738 | let values = []; 739 | layouts[this.layoutIndex].forEach(__ => { 740 | let regions = __["region"]; 741 | let _ = []; 742 | regions.forEach(region => { 743 | _.push({ region: region }); 744 | }),values.push(_); 745 | }); 746 | setLayoutStream(this.myRoomId, this.mixStreamGlobal.id, values); 747 | }, 748 | clickSpeak: function (e) { 749 | this.isSpeakMuted = !this.isSpeakMuted; 750 | }, 751 | clickMicrophone: function (e) { 752 | let tracks, track; 753 | if (!this.localStream || !this.localStream.mediaStream) { 754 | this.isMicMuted = true; 755 | this.enableAudio = true; 756 | this.enableVideo = false; 757 | this.publishVideo(); 758 | return; 759 | } 760 | tracks = this.localStream.mediaStream.getAudioTracks(); 761 | tracks && tracks.length && (track = tracks[0]); 762 | this.isMicMuted = track ? !this.isMicMuted : true; 763 | track && (track.enabled = !this.isMicMuted); 764 | }, 765 | clickCamera: function (e) { 766 | let tracks, track; 767 | if (!this.localStream || !this.localStream.mediaStream) { 768 | this.isCameraMuted = true; 769 | this.enableAudio = false; 770 | this.enableVideo = true; 771 | this.publishVideo(); 772 | return; 773 | } 774 | tracks = this.localStream.mediaStream.getVideoTracks(); 775 | tracks && tracks.length && (track = tracks[0]); 776 | this.isCameraMuted = track ? !this.isCameraMuted : true; 777 | track && (track.enabled = !this.isCameraMuted); 778 | }, 779 | clickCamera2: function (e) { 780 | if (!this.publicationGlobalSecond || !this.localStreamSecond || !this.localStreamSecond.mediaStream) { 781 | this.isCamera2Muted = true; 782 | this.publishVideoSecond(); 783 | return; 784 | } 785 | this._clearLocalCameraSecond(); 786 | this.publicationGlobalSecond && this.publicationGlobalSecond.stop(), this.publicationGlobalSecond = null; 787 | this.isCamera2Muted = true; 788 | }, 789 | clickDesktop: function (e) { 790 | const that = this; 791 | if (that.publicationScreenGlobal) { 792 | that._clearScreenShare(); 793 | return; 794 | } 795 | if (that.screenSharingUser != '') { 796 | that.$alert(`[${that.screenSharingUser}]`+'已经有人在分享桌面了,您当前不能再进行分享操作', '提示', { confirmButtonText: '确定' }); 797 | return 798 | } 799 | that.screen_data = []; 800 | desktopCapturer.getSources({ types: ['screen'] }).then(sources => sources.forEach(source => that.screen_data.push({ id: source.id, src: source.thumbnail.toDataURL() }))); 801 | that.screen_select_visible = true; 802 | }, 803 | clickRecord: function (e) { 804 | 805 | }, 806 | _exitRoom: function (e) { 807 | const that = this; 808 | try { 809 | that.publicationGlobal && that.publicationGlobal.stop(), that.publicationGlobal = null; 810 | that.publicationGlobalSecond && that.publicationGlobalSecond.stop(), that.publicationGlobalSecond = null; 811 | that.subscriptionGlobal && that.subscriptionGlobal.stop(), that.subscriptionGlobal = null; 812 | that.publicationScreenGlobal && that.publicationScreenGlobal.stop(), that.publicationScreenGlobal = null; 813 | } catch (_) { } 814 | 815 | try { 816 | that.conference && that.conference.leave(), that.conference = null; 817 | } catch (_) { } 818 | 819 | that.mixStreamGlobal && that.mixStreamGlobal.mediaStream && that._destroyMediaStream(that.mixStreamGlobal.mediaStream), (that.mixStreamGlobal = null); 820 | that.ScreenStream && that.ScreenStream.mediaStream && that._destroyMediaStream(that.ScreenStream.mediaStream), (that.ScreenStream = null); 821 | that.localStream && that.localStream.mediaStream && that._destroyMediaStream(that.localStream.mediaStream), (that.localStream = null); 822 | that.conference = that.publicationGlobal = that.subscriptionGlobal = null; 823 | 824 | that.playerStream = null; 825 | that.statInterval && (clearInterval(that.statInterval), that.statInterval = 0); 826 | }, 827 | _destroyMediaStream: function (mediaStream) { 828 | if (!mediaStream) return 829 | try { 830 | mediaStream.getTracks().forEach(t => { t.stop(); mediaStream.removeTrack(t); }), mediaStream = null; 831 | } catch (err) { 832 | console.error(err); 833 | } 834 | finally { 835 | mediaStream = null; 836 | } 837 | } 838 | }, 839 | mounted: function () { 840 | this.init(); 841 | } 842 | }); 843 | 844 | window.onbeforeunload = _app._exitRoom.bind(_app); -------------------------------------------------------------------------------- /static/scripts/app-videoWindows.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { ipcRenderer } = require('electron'); 3 | 4 | const _app = new Vue({ 5 | el: '#App', 6 | data:function(){ 7 | return { 8 | version:'', 9 | isMaximized:false, 10 | playerStream:null, 11 | isSpeakMuted:false, 12 | lastMoveTime: 0, 13 | hideCursorHandle:null, 14 | toolsIsVisible:true, 15 | toolsIsHover:false, 16 | mainWebContentsId: Number.parseInt(getParameterByName('id')) 17 | } 18 | }, 19 | methods:{ 20 | init:function(e){ 21 | const that = this; 22 | 23 | ipcRenderer.on('maximizeChanged',that.maximizeChanged.bind(that)); 24 | ipcRenderer.on('set-version', that._setVersion.bind(that) ); 25 | ipcRenderer.on('stream_ended', that.clickClose.bind(that) ); 26 | window.addEventListener('keyup', that.onkeyup.bind(that)); 27 | window.addEventListener('mousemove', that.onmousemove.bind(that)); 28 | 29 | const pc = new RTCPeerConnection(); 30 | ipcRenderer.on('set-peer-param',that._setPeerParam.bind(that , pc)); 31 | pc.onicecandidate = function( { candidate } ) { 32 | console.log('onicecandidate') 33 | candidate && ipcRenderer.sendTo(that.mainWebContentsId,'set-peer-param',{ candidate:candidate.toJSON() }); 34 | } 35 | 36 | pc.ontrack = function(e) { 37 | console.log(e); 38 | 39 | that.playerStream = null, (that.playerStream = e.streams[0], 40 | that.playerStream.onremovetrack = that._removeTrack.bind(that) ) 41 | } 42 | 43 | pc.onconnectionstatechange = () => { 44 | console.log('onconnectionstatechange'); 45 | (pc.connectionState == 'closed'|| pc.connectionState == 'failed') && (pc.close(),that.clickClose()); 46 | } 47 | 48 | ipcRenderer.sendTo(this.mainWebContentsId , 'win-onload' ); 49 | this.lastMoveTime = Date.now(); 50 | this.hideCursorHandle = setInterval(this.hideCursorInterval.bind(this), 2000); 51 | }, 52 | _setVersion:function(event , version){ 53 | this.version = version; 54 | }, 55 | _setPeerParam:async function(peerConnection, e, { localDescription, candidate }){ 56 | console.log('_setPeerParam', e , localDescription , candidate , peerConnection); 57 | this.mainWebContentsId == -1 && (this.mainWebContentsId = e.senderId); 58 | localDescription && ( await peerConnection.setRemoteDescription(localDescription), 59 | await peerConnection.setLocalDescription(), 60 | ipcRenderer.sendTo(this.mainWebContentsId,'set-peer-param',{ localDescription:peerConnection.localDescription.toJSON() }) ); 61 | 62 | candidate && peerConnection.addIceCandidate(candidate); 63 | }, 64 | _removeTrack:function(e){ 65 | console.log('_removeTrack') 66 | }, 67 | onkeyup:function(e){ 68 | e.keyCode == 27 && this.isMaximized && ipcRenderer.send('setFullScreen-win',false); 69 | return e.keyCode != 27; 70 | }, 71 | onmousemove:function(e){ 72 | if(this.hideCursorHandle == null) 73 | { 74 | this.lastMoveTime = Date.now(); 75 | this.hideCursorHandle = setInterval(this.hideCursorInterval.bind(this), 2000); 76 | 77 | document.body.style.cursor = "default"; 78 | this.toolsIsVisible = true; 79 | } 80 | this.lastMoveTime = Date.now(); 81 | return true; 82 | }, 83 | hideCursorInterval:function(){ 84 | if(!this.toolsIsHover && this.lastMoveTime && Date.now() - 5000 > this.lastMoveTime){ 85 | clearInterval(this.hideCursorHandle),this.hideCursorHandle = null; 86 | 87 | document.body.style.cursor = "none"; 88 | this.toolsIsVisible = false; 89 | } 90 | }, 91 | maximizeChanged:function( event , isMaximized ) { 92 | this.isMaximized = isMaximized; 93 | if(this.isMaximized) 94 | { 95 | this.$message({ 96 | message: '按下 ESC 键可以退出全屏', 97 | center: true, 98 | iconClass: '', 99 | customClass: 'message_tip', 100 | duration: 3000, 101 | offset: (document.body.clientHeight / 2) - 24 102 | }); 103 | } 104 | }, 105 | clickFullScreen:function(e){ 106 | ipcRenderer.send('setFullScreen-win',true); 107 | }, 108 | clickUnFullScreen:function(e){ 109 | ipcRenderer.send('setFullScreen-win',false); 110 | }, 111 | clickClose:function(e){ 112 | this.playerStream = null; 113 | 114 | this.mainWebContentsId >0 && ipcRenderer.sendTo(this.mainWebContentsId,'set-peer-param',{close:true}); 115 | ipcRenderer.send("close-win"); 116 | }, 117 | clickMinimize:function(e){ 118 | ipcRenderer.send("minimize-win"); 119 | }, 120 | _destroyMediaStream:function( mediaStream ){ 121 | if( !mediaStream ) return 122 | try { 123 | mediaStream.getTracks().forEach(t=>{ t.stop(); mediaStream.removeTrack(t);}),mediaStream = null; 124 | } catch (err) { 125 | console.error(err); 126 | } 127 | finally { 128 | mediaStream = null; 129 | } 130 | }, 131 | _unInit:function(e) 132 | { 133 | const that = this; 134 | that.playerStream = null; 135 | } 136 | }, 137 | mounted:function(){ 138 | this.init(); 139 | } 140 | }); 141 | 142 | window.onbeforeunload = _app._unInit.bind(_app); 143 | 144 | function getParameterByName(name) { 145 | name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); 146 | var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 147 | results = regex.exec(location.search); 148 | return results === null ? 149 | "" : 150 | decodeURIComponent(results[1].replace(/\+/g, " ")); 151 | } -------------------------------------------------------------------------------- /static/scripts/rest-sample.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) <2018> Intel Corporation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // REST samples. It sends HTTP requests to sample server, and sample server sends requests to conference server. 6 | // Both this file and sample server are samples. 7 | 'use strict'; 8 | var send = function (method, path, body, onRes, host) { 9 | var req = new XMLHttpRequest() 10 | req.onreadystatechange = function () { 11 | if (req.readyState === 4) { 12 | onRes && onRes(req.responseText); 13 | } 14 | }; 15 | let url = generateUrl(host, path); 16 | req.open(method, url, true); 17 | if (body !== undefined && body !== null) { 18 | req.setRequestHeader('Content-Type', 'application/json; charset=utf-8'); 19 | req.send(JSON.stringify(body)); 20 | } else { 21 | req.send(); 22 | } 23 | }; 24 | function getParameterByName(name) { 25 | name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]'); 26 | var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'), 27 | results = regex.exec(location.search); 28 | return results === null ? '' : decodeURIComponent(results[1].replace( 29 | /\+/g, ' ')); 30 | } 31 | var generateUrl = function(host, path) { 32 | let url; 33 | if (host !== undefined) { 34 | url = host + path; // Use the host user set. 35 | }else { 36 | let s = getParameterByName('serverURL'); 37 | !s && (s = location.href); 38 | let u = new URL(s); 39 | url = u.origin + path; // Get the string before last '/'. 40 | } 41 | return url; 42 | } 43 | 44 | var onResponse = function (result) { 45 | if (result) { 46 | try { 47 | console.info('Result:', JSON.parse(result)); 48 | } catch (e) { 49 | console.info('Result:', result); 50 | } 51 | } else { 52 | console.info('Null'); 53 | } 54 | }; 55 | 56 | var mixStream = function (room, stream, views, callback, host) { 57 | var jsonPatch = []; 58 | views.forEach(view => { 59 | jsonPatch.push({ 60 | op: 'add', 61 | path: '/info/inViews', 62 | value: view 63 | }); 64 | }); 65 | send('PATCH', '/rooms/' + room + '/streams/' + stream, jsonPatch, 66 | callback, host); 67 | }; 68 | var unMixStream = function (room, stream, views, callback, host) { 69 | var jsonPatch = []; 70 | views.forEach(view => { 71 | jsonPatch.push({ 72 | op: 'remove', 73 | path: '/info/inViews', 74 | value: view 75 | }); 76 | }); 77 | send('PATCH', '/rooms/' + room + '/streams/' + stream, jsonPatch, 78 | callback, host); 79 | }; 80 | 81 | var setLayoutStream = function (room, stream, views, host) { 82 | var jsonPatch = []; 83 | views.forEach(view => { 84 | jsonPatch.push({ 85 | op: 'replace', 86 | path: '/info/layout', 87 | value: view 88 | }); 89 | }); 90 | 91 | send('PATCH', '/rooms/' + room + '/streams/' + stream, jsonPatch, 92 | onResponse, host); 93 | }; 94 | 95 | var activeLayoutStream = function (room,mixedStream,subStream, host) { 96 | var jsonPatch = [{ 97 | op: 'replace', 98 | path: '/info/layout/0/stream', 99 | value: subStream 100 | }]; 101 | send('PATCH', '/rooms/' + room + '/streams/' + mixedStream, jsonPatch, 102 | onResponse, host); 103 | }; 104 | 105 | var getRecording = function(room, callback, host) { 106 | send('GET', '/rooms/' + room + '/recordings', null, function(recordingRtn) { 107 | if(callback == null) return; 108 | var result = JSON.parse(recordingRtn); 109 | callback && callback(result); 110 | }, host); 111 | }; 112 | var startRecording = function(room, audioFrom, videoFrom, container, callback, host) { 113 | var options = { 114 | media: { 115 | audio: { 116 | from: audioFrom 117 | }, 118 | video: { 119 | from: videoFrom 120 | } 121 | }, 122 | container: (container ? container : 'auto') 123 | }; 124 | send('POST', '/rooms/' + room + '/recordings/', options, function(recordingRtn) { 125 | if(callback == null) return; 126 | var result = JSON.parse(recordingRtn); 127 | callback && callback(result); 128 | }, host); 129 | }; 130 | 131 | var stopRecording = function(room, id, data ,callback, host) { 132 | send('DELETE', '/rooms/' + room + '/recordings/' + id, data, callback, host); 133 | }; 134 | 135 | var getStreamingOuts = function(room,callback,host){ 136 | send('GET', '/rooms/' + room + '/streaming-outs', null, function(ret) { 137 | if(callback == null) return; 138 | var result = JSON.parse(ret); 139 | callback && callback(result); 140 | }, host); 141 | } 142 | 143 | var startStreamingOut = function (room, streamId, callback, host) { 144 | var options = { 145 | url: 'rtmp://127.0.0.1/live/'+room, 146 | media: { 147 | audio: {from:streamId}, 148 | video: {from:streamId} 149 | } 150 | }; 151 | send('POST', '/rooms/' + room + '/streaming-outs', options, function(ret) { 152 | if(callback == null) return; 153 | var result = JSON.parse(ret); 154 | callback && callback(result); 155 | }, host); 156 | }; 157 | 158 | var stopStreamingOut = function (room, outId, callback, host) { 159 | send('DELETE', '/rooms/' + room + '/streaming-outs/'+ outId, null, function(ret) { 160 | if(callback == null) return; 161 | var result = JSON.parse(ret); 162 | callback && callback(result); 163 | }, host); 164 | }; 165 | 166 | var startStreamingIn = function (room, inUrl, host) { 167 | var options = { 168 | url: inUrl, 169 | media: { 170 | audio: 'auto', 171 | video: true 172 | }, 173 | transport: { 174 | protocol: 'udp', 175 | bufferSize: 2048 176 | } 177 | }; 178 | send('POST', '/rooms/' + room + '/streaming-ins', options, onResponse, host); 179 | }; 180 | 181 | var createToken = function (room, user, role, callback, host) { 182 | var body = { 183 | appKey:"OpenRemote", 184 | room: room, 185 | user: user, 186 | role: role 187 | }; 188 | send('POST', '/tokens/', body, callback, host); 189 | }; -------------------------------------------------------------------------------- /static/server/samplertcservice.js: -------------------------------------------------------------------------------- 1 | /*global require, __dirname, console, process*/ 2 | 'use strict'; 3 | 4 | var express = require('express'), 5 | spdy = require('spdy'), 6 | bodyParser = require('body-parser'), 7 | errorhandler = require('errorhandler'), 8 | morgan = require('morgan'), 9 | fs = require('fs'), 10 | path = require('path'), 11 | log4js = require('log4js'), 12 | https = require('https'), 13 | icsREST = require('./rest'); 14 | 15 | 16 | 17 | log4js.configure({ 18 | appenders: { 19 | console: { 20 | type: 'console' 21 | }, 22 | server: { 23 | type: 'file', 24 | filename: __dirname + '/../../logs/server.log', 25 | "maxLogSize": 4096760, 26 | "numBackups": 5 27 | }, 28 | stream: { 29 | type: 'file', 30 | filename: __dirname + '/../../logs/stream.log', 31 | "maxLogSize": 4096760, 32 | "numBackups": 5 33 | }, 34 | 'just-errors': { 35 | type: 'logLevelFilter', 36 | appender: 'stream', 37 | level: 'error' 38 | } 39 | }, 40 | categories: { 41 | default: { 42 | appenders: ['console', 'server', 'just-errors'], 43 | level: 'debug' 44 | } 45 | } 46 | }); 47 | 48 | const logger = log4js.getLogger(); 49 | 50 | var app = express(); 51 | 52 | // app.configure ya no existe 53 | app.use(errorhandler()); 54 | app.use(morgan('dev')); 55 | app.use(express.static(__dirname + '/public')); 56 | app.use(bodyParser.json()); 57 | app.use(bodyParser.urlencoded({ 58 | extended: true 59 | })); 60 | app.disable('x-powered-by'); 61 | 62 | app.use(function (req, res, next) { 63 | res.header('Access-Control-Allow-Origin', '*'); 64 | res.header('Access-Control-Allow-Methods', 'POST, GET, PUT, PATCH, OPTIONS, DELETE'); 65 | res.header('Access-Control-Allow-Headers', 'origin, content-type'); 66 | res.header('Strict-Transport-Security', 'max-age=1024000; includeSubDomain'); 67 | res.header('X-Content-Type-Options', 'nosniff'); 68 | if (req.method == 'OPTIONS') { 69 | res.send(200); 70 | } else { 71 | next(); 72 | } 73 | }); 74 | 75 | //vod 点播目录 76 | if(!sfs.existsSync(path.join(__dirname ,'/../../vod/'))) 77 | { 78 | fs.mkdirSync(path.join(__dirname ,'/../../vod/')); 79 | } 80 | app.use(express.static(fs.realpathSync(__dirname + '/../../vod/'))); 81 | 82 | icsREST.API.init('_service_ID_', '_service_KEY_', 'http://localhost:3000/', false); 83 | 84 | logger.info('AppService Start...'); 85 | 86 | var sampleRoom; 87 | var pageOption = { 88 | page: 1, 89 | per_page: 9999999 90 | }; 91 | (function initSampleRoom() { 92 | icsREST.API.getRooms(pageOption, function (rooms) { 93 | logger.info(rooms.length + ' rooms in this service.'); 94 | logger.info('AppService Start Success!'); 95 | var tryCreate = function (room, callback) { 96 | var options = {}; 97 | //setDefaultRoomOption(options); 98 | icsREST.API.createRoom(room.name, options, function (response) { 99 | let r = response; 100 | filterRoom(r); 101 | icsREST.API.updateRoom(response._id, r, function (result) { 102 | 103 | }, function (err) { 104 | logger.error('createRoom end' + err); 105 | r.send(err); 106 | }); 107 | callback(r._id); 108 | }, function (status, err) { 109 | logger.error('Error in creating room:' + err + " [Retry]"); 110 | setTimeout(function () { 111 | tryCreate(room, options, callback); 112 | }, 100); 113 | }, room); 114 | }; 115 | 116 | var room; 117 | if (!sampleRoom) { 118 | room = { 119 | name: 'sampleRoom' 120 | }; 121 | tryCreate(room, function (Id) { 122 | sampleRoom = Id; 123 | logger.info('sampleRoom Id:', sampleRoom); 124 | }); 125 | } 126 | }, function (stCode, msg) { 127 | console.log('getRooms failed(', stCode, '):', msg); 128 | }); 129 | 130 | })(); 131 | 132 | 133 | //////////////////////////////////////////////////////////////////////////////////////////// 134 | // legacy interface begin 135 | // ///////////////////////////////////////////////////////////////////////////////////////// 136 | app.get('/getLog', function (req, res) { 137 | res.writeHead(200, { 138 | 'Content-Type': 'text/plain; charset=utf8' 139 | }); 140 | res.end(fs.readFileSync(__dirname + '/../../logs/server.log')); 141 | }); 142 | app.get('/getLogError', function (req, res) { 143 | res.writeHead(200, { 144 | 'Content-Type': 'text/plain; charset=utf8' 145 | }); 146 | res.end(fs.readFileSync(__dirname + '/../../logs/stream.log')); 147 | }); 148 | 149 | app.get('/getUsers/:room', function (req, res) { 150 | var room = req.params.room; 151 | icsREST.API.getParticipants(room, function (users) { 152 | res.send(users); 153 | }, function (err) { 154 | res.send(err); 155 | }); 156 | }); 157 | 158 | app.get('/getRoom/:room', function (req, res) { 159 | 'use strict'; 160 | var room = req.params.room; 161 | icsREST.API.getRoom(room, function (rooms) { 162 | res.send(rooms); 163 | }, function (err) { 164 | res.send(err); 165 | }); 166 | }); 167 | 168 | app.get('/room/:room/user/:user', function (req, res) { 169 | 'use strict'; 170 | var room = req.params.room; 171 | var participant_id = req.params.user; 172 | icsREST.API.getParticipant(room, participant_id, function (user) { 173 | res.send(user); 174 | }, function (err) { 175 | res.send(err); 176 | }); 177 | }); 178 | 179 | app.delete('/room/:room/user/:user', function (req, res) { 180 | 'use strict'; 181 | var room = req.params.room; 182 | var participant_id = req.params.user; 183 | icsREST.API.dropParticipant(room, participant_id, function (result) { 184 | res.send(result); 185 | }, function (err) { 186 | res.send(err); 187 | }); 188 | }) 189 | 190 | app.delete('/room/:room', function (req, res) { 191 | 'use strict'; 192 | var room = req.params.room; 193 | icsREST.API.deleteRoom(room, function (result) { 194 | res.send(result); 195 | }, function (err) { 196 | res.send(err); 197 | }); 198 | }) 199 | //////////////////////////////////////////////////////////////////////////////////////////// 200 | // legacy interface begin 201 | // ///////////////////////////////////////////////////////////////////////////////////////// 202 | 203 | function filterRoom(room) { 204 | if (typeof room != 'object' || room == null) { 205 | return; 206 | } 207 | let r = room; 208 | r.roles = [{ 209 | "subscribe": { 210 | "video": true, 211 | "audio": true 212 | }, 213 | "publish": { 214 | "video": true, 215 | "audio": true 216 | }, 217 | "role": "presenter" 218 | }, { 219 | "subscribe": { 220 | "video": true, 221 | "audio": true 222 | }, 223 | "publish": { 224 | "video": true, 225 | "audio": true 226 | }, 227 | "role": "sip" 228 | }, { 229 | "subscribe": { 230 | "video": true, 231 | "audio": true 232 | }, 233 | "publish": { 234 | "video": true, 235 | "audio": true 236 | }, 237 | "role": "presenter_guest" 238 | }, { 239 | "subscribe": { 240 | "video": true, 241 | "audio": true 242 | }, 243 | "publish": { 244 | "video": false, 245 | "audio": false 246 | }, 247 | "role": "viewer" 248 | }]; 249 | r.mediaOut.video.format = [{ "codec": "h264", "profile": "CB" }]; 250 | r.mediaOut.video.parameters.bitrate = ["x1.2", "x1.0", "x0.8", "x0.6"]; 251 | r.transcoding.video.parameters.bitrate = true; 252 | r.transcoding.video.parameters.framerate = true; 253 | 254 | r.views[0].audio.vad = false; 255 | r.views[0].video.bgColor = { "b": 44, "g": 44, "r": 44 }; 256 | r.views[0].video.format = { "codec": "h264", "profile": "CB" }; 257 | r.views[0].video.motionFactor = 1.0; 258 | r.views[0].video.parameters.resolution.height = 1080; 259 | r.views[0].video.parameters.resolution.width = 1920; 260 | r.views[0].video.layout.templates.custom = [{ "region": [{ "id": "1", "area": { "height": "1", "width": "1", "top": "0", "left": "0" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "1", "width": "1", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "1/4", "width": "1/4", "top": "3/4", "left": "3/4" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "1", "width": "1279/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "539/1080", "width": "639/1920", "top": "0", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "539/1080", "width": "639/1920", "top": "541/1080", "left": "1281/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "1", "width": "1279/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "359/1080", "width": "639/1920", "top": "0", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "358/1080", "width": "639/1920", "top": "361/1080", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "359/1080", "width": "639/1920", "top": "721/1080", "left": "1281/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "1", "width": "1439/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "269/1080", "width": "479/1920", "top": "0", "left": "1441/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "268/1080", "width": "479/1920", "top": "271/1080", "left": "1441/1920" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "268/1080", "width": "479/1920", "top": "541/1080", "left": "1441/1920" }, "shape": "rectangle" }, { "id": "5", "area": { "height": "269/1080", "width": "479/1920", "top": "811/1080", "left": "1441/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "719/1080", "width": "1279/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "359/1080", "width": "639/1920", "top": "0", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "358/1080", "width": "639/1920", "top": "361/1080", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "358/1080", "width": "639/1920", "top": "721/1080", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "5", "area": { "height": "359/1080", "width": "639/1920", "top": "721/1080", "left": "641/1920" }, "shape": "rectangle" }, { "id": "6", "area": { "height": "359/1080", "width": "639/1920", "top": "721/1080", "left": "0" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "809/1080", "width": "1439/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "269/1080", "width": "479/1920", "top": "0", "left": "481/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "268/1080", "width": "479/1920", "top": "281/1080", "left": "1441/1920" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "268/1080", "width": "479/1920", "top": "541/1080", "left": "1441/1920" }, "shape": "rectangle" }, { "id": "5", "area": { "height": "268/1080", "width": "479/1920", "top": "811/1080", "left": "1441/1920" }, "shape": "rectangle" }, { "id": "6", "area": { "height": "269/1080", "width": "479/1920", "top": "811/1080", "left": "961/1920" }, "shape": "rectangle" }, { "id": "7", "area": { "height": "269/1080", "width": "479/1920", "top": "811/1080", "left": "481/1920" }, "shape": "rectangle" }, { "id": "8", "area": { "height": "269/1080", "width": "479/1920", "top": "811/1080", "left": "0" }, "shape": "rectangle" }] }]; 261 | r.views[0].video.layout.templates.base = "lecture"; 262 | //r.views[0].video.layout.templates.inputCount = 9; 263 | r.views[0].video.layout.fitPolicy = "crop"; //crop / letterbox 264 | r.views[0].video.layout.crop = true; 265 | if (r.views.length <= 1) { 266 | var presenters = JSON.parse(JSON.stringify(r.views[0])); 267 | presenters['label'] = 'presenters'; 268 | presenters.video.parameters.resolution.height = 1080; 269 | presenters.video.parameters.resolution.width = 1920; 270 | presenters.video.layout.templates.inputCount = 9; 271 | r.views.push(presenters); 272 | } 273 | if (r.views.length <= 2) { 274 | var presenters = JSON.parse(JSON.stringify(r.views[0])); 275 | presenters['label'] = 'small'; 276 | presenters.video.parameters.resolution.height = 1080; 277 | presenters.video.parameters.resolution.width = 480; // 720 - 320 | 1080 - 480 278 | presenters.video.maxInput = 4; 279 | presenters.video.layout.templates.inputCount = 4; 280 | //不携带边框的布局 281 | //presenters.video.layout.templates.custom = [{"region":[{"id":"1","area":{"height":"1/4","width":"1","top":"0","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/4","width":"1","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/4","width":"1","top":"1/4","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/4","width":"1","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/4","width":"1","top":"1/4","left":"0"},"shape":"rectangle"},{"id":"3","area":{"height":"1/4","width":"1","top":"1/2","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/4","width":"1","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/4","width":"1","top":"1/4","left":"0"},"shape":"rectangle"},{"id":"3","area":{"height":"1/4","width":"1","top":"1/2","left":"0"},"shape":"rectangle"},{"id":"4","area":{"height":"1/4","width":"1","top":"3/4","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/4","width":"1","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/4","width":"1","top":"1/4","left":"0"},"shape":"rectangle"},{"id":"3","area":{"height":"1/4","width":"1","top":"1/2","left":"0"},"shape":"rectangle"},{"id":"4","area":{"height":"1/4","width":"1","top":"3/4","left":"0"},"shape":"rectangle"},{"id":"5","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/4","width":"1","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/4","width":"1","top":"1/4","left":"0"},"shape":"rectangle"},{"id":"3","area":{"height":"1/4","width":"1","top":"1/2","left":"0"},"shape":"rectangle"},{"id":"4","area":{"height":"1/4","width":"1","top":"3/4","left":"0"},"shape":"rectangle"},{"id":"5","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"6","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/4","width":"1","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/4","width":"1","top":"1/4","left":"0"},"shape":"rectangle"},{"id":"3","area":{"height":"1/4","width":"1","top":"1/2","left":"0"},"shape":"rectangle"},{"id":"4","area":{"height":"1/4","width":"1","top":"3/4","left":"0"},"shape":"rectangle"},{"id":"5","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"6","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"7","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/4","width":"1","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/4","width":"1","top":"1/4","left":"0"},"shape":"rectangle"},{"id":"3","area":{"height":"1/4","width":"1","top":"1/2","left":"0"},"shape":"rectangle"},{"id":"4","area":{"height":"1/4","width":"1","top":"3/4","left":"0"},"shape":"rectangle"},{"id":"5","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"6","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"7","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"},{"id":"8","area":{"height":"0","width":"0","top":"0","left":"0"},"shape":"rectangle"}]}]; 282 | //携带边框的布局 283 | presenters.video.layout.templates.custom = [{ "region": [{ "id": "1", "area": { "height": "269/1080", "width": "1", "top": "0", "left": "0" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "269/1080", "width": "1", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "269/1080", "width": "1", "top": "271/1080", "left": "0" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "269/1080", "width": "1", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "269/1080", "width": "1", "top": "271/1080", "left": "0" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "269/1080", "width": "1", "top": "541/1080", "left": "0" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "269/1080", "width": "1", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "268/1080", "width": "1", "top": "271/1080", "left": "0" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "268/1080", "width": "1", "top": "541/1080", "left": "0" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "269/1080", "width": "1", "top": "811/1080", "left": "0" }, "shape": "rectangle" }] }]; 284 | //presenters["audio"] = true; 285 | r.views.push(presenters); 286 | } 287 | if (false && r.views.length <= 3) { 288 | var presenters = JSON.parse(JSON.stringify(r.views[0])); 289 | presenters['label'] = 'rectangle'; 290 | presenters.video.parameters.resolution.height = 720; 291 | presenters.video.parameters.resolution.width = 1280; 292 | presenters.video.layout.templates.inputCount = 9; 293 | presenters.video.maxInput = 9; 294 | //不携带边框的布局 295 | //presenters.video.layout.templates.custom = [{"region":[{"id":"1","area":{"height":"1","width":"1","top":"0","left":"0"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1","width":"1/2","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1","width":"1/2","top":"0","left":"1/2"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1","width":"1/2","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/2","width":"1/2","top":"0","left":"1/2"},"shape":"rectangle"},{"id":"3","area":{"height":"1/2","width":"1/2","top":"1/2","left":"1/2"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/2","width":"1/2","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/2","width":"1/2","top":"0","left":"1/2"},"shape":"rectangle"},{"id":"3","area":{"height":"1/2","width":"1/2","top":"1/2","left":"0"},"shape":"rectangle"},{"id":"4","area":{"height":"1/2","width":"1/2","top":"1/2","left":"1/2"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/2","width":"1/2","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/2","width":"1/2","top":"0","left":"1/2"},"shape":"rectangle"},{"id":"3","area":{"height":"1/2","width":"1/3","top":"1/2","left":"0"},"shape":"rectangle"},{"id":"4","area":{"height":"1/2","width":"1/3","top":"1/2","left":"1/3"},"shape":"rectangle"},{"id":"5","area":{"height":"1/2","width":"1/3","top":"1/2","left":"2/3"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/2","width":"1/3","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/2","width":"1/3","top":"0","left":"1/3"},"shape":"rectangle"},{"id":"3","area":{"height":"1/2","width":"1/3","top":"0","left":"2/3"},"shape":"rectangle"},{"id":"4","area":{"height":"1/2","width":"1/3","top":"1/2","left":"0"},"shape":"rectangle"},{"id":"5","area":{"height":"1/2","width":"1/3","top":"1/2","left":"1/3"},"shape":"rectangle"},{"id":"6","area":{"height":"1/2","width":"1/3","top":"1/2","left":"2/3"},"shape":"rectangle"}]},{"region":[{"id":"1","area":{"height":"1/3","width":"1/3","top":"0","left":"0"},"shape":"rectangle"},{"id":"2","area":{"height":"1/3","width":"1/3","top":"0","left":"1/3"},"shape":"rectangle"},{"id":"3","area":{"height":"1/3","width":"1/3","top":"0","left":"2/3"},"shape":"rectangle"},{"id":"4","area":{"height":"1/3","width":"1/3","top":"1/3","left":"0"},"shape":"rectangle"},{"id":"5","area":{"height":"1/3","width":"1/3","top":"1/3","left":"1/3"},"shape":"rectangle"},{"id":"6","area":{"height":"1/3","width":"1/3","top":"1/3","left":"2/3"},"shape":"rectangle"},{"id":"7","area":{"height":"1/3","width":"1/3","top":"2/3","left":"0"},"shape":"rectangle"},{"id":"8","area":{"height":"1/3","width":"1/3","top":"2/3","left":"1/3"},"shape":"rectangle"},{"id":"9","area":{"height":"1/3","width":"1/3","top":"2/3","left":"2/3"},"shape":"rectangle"}]}]; 296 | //携带边框的布局 297 | presenters.video.layout.templates.custom = [{ "region": [{ "id": "1", "area": { "height": "1", "width": "1", "top": "0", "left": "0" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "1", "width": "959/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "1", "width": "959/1920", "top": "0", "left": "961/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "1", "width": "959/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "959/1920", "width": "959/1920", "top": "0", "left": "961/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "539/1080", "width": "959/1920", "top": "541/1080", "left": "961/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "539/1080", "width": "959/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "539/1080", "width": "959/1920", "top": "0", "left": "961/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "538/1080", "width": "959/1920", "top": "541/1080", "left": "0" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "539/1080", "width": "959/1920", "top": "541/1080", "left": "961/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "539/1080", "width": "959/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "539/1080", "width": "959/1920", "top": "0", "left": "961/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "538/1080", "width": "639/1920", "top": "541/1080", "left": "0" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "539/1080", "width": "639/1920", "top": "541/1080", "left": "641/1920" }, "shape": "rectangle" }, { "id": "5", "area": { "height": "539/1080", "width": "639/1920", "top": "541/1080", "left": "1281/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "539/1080", "width": "639/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "539/1080", "width": "638/1920", "top": "0", "left": "641/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "539/1080", "width": "639/1920", "top": "0", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "539/1080", "width": "639/1920", "top": "541/1080", "left": "0" }, "shape": "rectangle" }, { "id": "5", "area": { "height": "539/1080", "width": "638/1920", "top": "541/1080", "left": "641/1920" }, "shape": "rectangle" }, { "id": "6", "area": { "height": "539/1080", "width": "639/1920", "top": "541/1080", "left": "1281/1920" }, "shape": "rectangle" }] }, { "region": [{ "id": "1", "area": { "height": "359/1080", "width": "639/1920", "top": "0", "left": "0" }, "shape": "rectangle" }, { "id": "2", "area": { "height": "359/1080", "width": "638/1920", "top": "0", "left": "641/1920" }, "shape": "rectangle" }, { "id": "3", "area": { "height": "359/1080", "width": "639/1920", "top": "0", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "4", "area": { "height": "358/1080", "width": "639/1920", "top": "361/1080", "left": "0" }, "shape": "rectangle" }, { "id": "5", "area": { "height": "358/1080", "width": "638/1920", "top": "361/1080", "left": "641/1920" }, "shape": "rectangle" }, { "id": "6", "area": { "height": "358/1080", "width": "639/1920", "top": "361/1080", "left": "1281/1920" }, "shape": "rectangle" }, { "id": "7", "area": { "height": "359/1080", "width": "639/1920", "top": "721/1080", "left": "0" }, "shape": "rectangle" }, { "id": "8", "area": { "height": "359/1080", "width": "638/1920", "top": "721/1080", "left": "641/1920" }, "shape": "rectangle" }, { "id": "9", "area": { "height": "359/1080", "width": "639/1920", "top": "721/1080", "left": "1281/1920" }, "shape": "rectangle" }] }]; 298 | //presenters.audio = null; 299 | 300 | r.views.push(presenters); 301 | } 302 | console.log("viewport length:" + r.views.length); 303 | } 304 | 305 | //////////////////////////////////////////////////////////////////////////////////////////// 306 | // New RESTful interface begin 307 | // ///////////////////////////////////////////////////////////////////////////////////////// 308 | app.post('/rooms', function (req, res) { 309 | 'use strict'; 310 | var name = req.body.name; 311 | var options = req.body.options; 312 | logger.info(req.originalUrl + " " + JSON.stringify(req.body)); 313 | icsREST.API.createRoom(name, options, function (response) { 314 | let r = response; 315 | filterRoom(r); 316 | icsREST.API.updateRoom(response._id, r, function (result) { 317 | res.send(result); 318 | }, function (err) { 319 | logger.error('createRoom end' + err); 320 | res.send(err); 321 | }) 322 | }, function (err) { 323 | logger.error('createRoom end' + err); 324 | res.send(err); 325 | }); 326 | }); 327 | 328 | app.get('/rooms', function (req, res) { 329 | 'use strict'; 330 | var psw = req.query.psw; 331 | psw == 'token' && icsREST.API.getRooms(pageOption, function (rooms) { 332 | res.send(rooms); 333 | }, function (err) { 334 | res.send(err); 335 | }); 336 | psw != 'token' && res.status(404).send(` 337 | 338 | 339 | 340 | Error 341 | 342 | 343 |
Cannot GET /rooms
344 | 345 | `); 346 | }); 347 | 348 | app.get('/rooms/:room', function (req, res) { 349 | 'use strict'; 350 | var room = req.params.room; 351 | icsREST.API.getRoom(room, function (result) { 352 | res.send(result); 353 | }, function (err) { 354 | res.send(err); 355 | }); 356 | }); 357 | 358 | app.put('/rooms/:room', function (req, res) { 359 | 'use strict'; 360 | var room = req.params.room, 361 | config = req.body; 362 | icsREST.API.updateRoom(room, config, function (result) { 363 | res.send(result); 364 | }, function (err) { 365 | res.send(err); 366 | }); 367 | }); 368 | 369 | app.patch('/rooms/:room', function (req, res) { 370 | 'use strict'; 371 | var room = req.params.room, 372 | items = req.body; 373 | icsREST.API.updateRoomPartially(room, items, function (result) { 374 | res.send(result); 375 | }, function (err) { 376 | res.send(err); 377 | }); 378 | }); 379 | 380 | app.delete('/rooms/:room', function (req, res) { 381 | 'use strict'; 382 | var room = req.params.room; 383 | icsREST.API.deleteRoom(room, function (result) { 384 | res.send(result); 385 | }, function (err) { 386 | res.send(err); 387 | }); 388 | }); 389 | 390 | app.get('/rooms/:room/participants', function (req, res) { 391 | 'use strict'; 392 | var room = req.params.room; 393 | icsREST.API.getParticipants(room, function (participants) { 394 | res.send(participants); 395 | }, function (err) { 396 | res.send(err); 397 | }); 398 | }); 399 | 400 | app.get('/rooms/:room/participants/:id', function (req, res) { 401 | 'use strict'; 402 | var room = req.params.room; 403 | var participant_id = req.params.id; 404 | icsREST.API.getParticipant(room, participant_id, function (info) { 405 | res.send(info); 406 | }, function (err) { 407 | res.send(err); 408 | }); 409 | }); 410 | 411 | app.patch('/rooms/:room/participants/:id', function (req, res) { 412 | 'use strict'; 413 | var room = req.params.room; 414 | var participant_id = req.params.id; 415 | var items = req.body; 416 | icsREST.API.updateParticipant(room, participant_id, items, function (result) { 417 | res.send(result); 418 | }, function (err) { 419 | res.send(err); 420 | }); 421 | }); 422 | 423 | app.delete('/rooms/:room/participants/:id', function (req, res) { 424 | 'use strict'; 425 | var room = req.params.room; 426 | var participant_id = req.params.id; 427 | icsREST.API.dropParticipant(room, participant_id, function (result) { 428 | res.send(result); 429 | }, function (err) { 430 | res.send(err); 431 | }); 432 | }); 433 | 434 | app.get('/rooms/:room/streams', function (req, res) { 435 | 'use strict'; 436 | var room = req.params.room; 437 | icsREST.API.getStreams(room, function (streams) { 438 | res.send(streams); 439 | }, function (err) { 440 | res.send(err); 441 | }); 442 | }); 443 | 444 | app.get('/rooms/:room/streams/:stream', function (req, res) { 445 | 'use strict'; 446 | var room = req.params.room, 447 | stream_id = req.params.stream; 448 | icsREST.API.getStream(room, stream_id, function (info) { 449 | res.send(info); 450 | }, function (err) { 451 | res.send(err); 452 | }); 453 | }); 454 | 455 | app.patch('/rooms/:room/streams/:stream', function (req, res) { 456 | 'use strict'; 457 | var room = req.params.room, 458 | stream_id = req.params.stream, 459 | items = req.body; 460 | icsREST.API.updateStream(room, stream_id, items, function (result) { 461 | res.send(result); 462 | }, function (err) { 463 | res.send(err); 464 | }); 465 | }); 466 | 467 | app.delete('/rooms/:room/streams/:stream', function (req, res) { 468 | 'use strict'; 469 | var room = req.params.room, 470 | stream_id = req.params.stream; 471 | icsREST.API.deleteStream(room, stream_id, function (result) { 472 | res.send(result); 473 | }, function (err) { 474 | res.send(err); 475 | }); 476 | }); 477 | 478 | app.post('/rooms/:room/streaming-ins', function (req, res) { 479 | 'use strict'; 480 | var room = req.params.room, 481 | url = req.body.url, 482 | transport = req.body.transport, 483 | media = req.body.media; 484 | 485 | icsREST.API.startStreamingIn(room, url, transport, media, function (result) { 486 | res.send(result); 487 | }, function (err) { 488 | res.send(err); 489 | }); 490 | }); 491 | 492 | app.delete('/rooms/:room/streaming-ins/:id', function (req, res) { 493 | 'use strict'; 494 | var room = req.params.room, 495 | stream_id = req.params.id; 496 | icsREST.API.stopStreamingIn(room, stream_id, function (result) { 497 | res.send(result); 498 | }, function (err) { 499 | res.send(err); 500 | }); 501 | }); 502 | 503 | app.get('/rooms/:room/streaming-outs', function (req, res) { 504 | 'use strict'; 505 | var room = req.params.room; 506 | icsREST.API.getStreamingOuts(room, function (streamingOuts) { 507 | res.send(streamingOuts); 508 | }, function (err) { 509 | res.send(err); 510 | }); 511 | }); 512 | 513 | app.post('/rooms/:room/streaming-outs', function (req, res) { 514 | 'use strict'; 515 | var room = req.params.room, 516 | protocol = req.body.protocol, 517 | url = req.body.url, 518 | parameters = req.body.parameters, 519 | media = req.body.media; 520 | 521 | icsREST.API.startStreamingOut(room, protocol, url, parameters, media, function (info) { 522 | res.send(info); 523 | }, function (err) { 524 | res.send(err); 525 | }); 526 | }); 527 | 528 | app.patch('/rooms/:room/streaming-outs/:id', function (req, res) { 529 | 'use strict'; 530 | var room = req.params.room, 531 | id = req.params.id, 532 | commands = req.body; 533 | icsREST.API.updateStreamingOut(room, id, commands, function (result) { 534 | res.send(result); 535 | }, function (err) { 536 | res.send(err); 537 | }); 538 | }); 539 | 540 | app.delete('/rooms/:room/streaming-outs/:id', function (req, res) { 541 | 'use strict'; 542 | var room = req.params.room, 543 | id = req.params.id; 544 | icsREST.API.stopStreamingOut(room, id, function (result) { 545 | res.send(result); 546 | }, function (err) { 547 | res.send(err); 548 | }); 549 | }); 550 | 551 | app.get('/rooms/:room/recordings', function (req, res) { 552 | 'use strict'; 553 | var room = req.params.room; 554 | icsREST.API.getRecordings(room, function (recordings) { 555 | res.send(recordings); 556 | }, function (err) { 557 | res.send(err); 558 | }); 559 | }); 560 | 561 | app.post('/rooms/:room/recordings', function (req, res) { 562 | 'use strict'; 563 | var room = req.params.room, 564 | container = req.body.container, 565 | media = req.body.media; 566 | logger.info("startRecording " + [room, container, media].join("|")); 567 | icsREST.API.startRecording(room, container, media, function (info) { 568 | res.send(info); 569 | }, function (err) { 570 | logger.error("startRecording error " + [room, container, media, err].join("|")); 571 | res.send(err); 572 | }); 573 | }); 574 | 575 | app.patch('/rooms/:room/recordings/:id', function (req, res) { 576 | 'use strict'; 577 | var room = req.params.room, 578 | id = req.params.id, 579 | commands = req.body; 580 | icsREST.API.updateRecording(room, id, commands, function (result) { 581 | res.send(result); 582 | }, function (err) { 583 | res.send(err); 584 | }); 585 | }); 586 | 587 | const { 588 | exec 589 | } = require('child_process'); 590 | const os = require('os'); 591 | 592 | 593 | function CopyFile(srcFile, destFile, callback) { 594 | var readStream = fs.createReadStream(srcFile); 595 | var writeStream = fs.createWriteStream(destFile); 596 | readStream.pipe(writeStream); 597 | writeStream.on('close', (e) => { 598 | if (callback != null) { 599 | callback(); 600 | } 601 | //console.log("CopyFile " + srcFile + " =>> " + destFile); 602 | }) 603 | 604 | 605 | } 606 | 607 | Date.prototype.Format = function (fmt) { //author: meizz 608 | var o = { 609 | "M+": this.getMonth() + 1, //月份 610 | "d+": this.getDate(), //日 611 | "h+": this.getHours(), //小时 612 | "m+": this.getMinutes(), //分 613 | "s+": this.getSeconds(), //秒 614 | "q+": Math.floor((this.getMonth() + 3) / 3), //季度 615 | "S": this.getMilliseconds() //毫秒 616 | }; 617 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); 618 | for (var k in o) 619 | if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 620 | return fmt; 621 | }; 622 | 623 | app.get('/vod/:recordid/:recordm3u8', function (req, res, next) { 624 | 'use strict'; 625 | let recordid = req.params.recordid; 626 | let recordm3u8 = req.params.recordm3u8; 627 | let vodRootDir = fs.realpathSync(__dirname + '/../../vod'); 628 | let destDescPath = `${vodRootDir}/${recordid}.json`; 629 | if (fs.existsSync(destDescPath)) { 630 | let desc = JSON.parse(fs.readFileSync(destDescPath)); 631 | if (desc.room) { 632 | let id = desc.room + (new Date(desc.time)).Format("yyyyMMdd"); 633 | if (recordm3u8.endsWith('m3u8')) { 634 | res.redirect(`/${id}/index.m3u8`) 635 | return 636 | } 637 | else if (recordm3u8.endsWith('mp4')) { 638 | res.redirect(`/${id}/${id}.mp4`) 639 | } 640 | } 641 | } 642 | res.redirect(`/${recordid}/${recordm3u8}`) 643 | }); 644 | 645 | app.delete('/rooms/:room/recordings/:id', function (req, res) { 646 | 'use strict'; 647 | var room = req.params.room, 648 | id = req.params.id; 649 | logger.info("stopRecording " + room + " id = " + id); 650 | icsREST.API.stopRecording(room, id, function (result) { 651 | res.send(result); 652 | }, function (err) { 653 | logger.error("stopRecording error:" + err); 654 | res.send(err); 655 | }); 656 | }); 657 | 658 | var dict_room_rid = {}; 659 | 660 | function getTokenById(req, res) { 661 | var room = req.body.room || sampleRoom, 662 | user = req.body.user, 663 | role = req.body.role, 664 | appKey = req.body.appKey; 665 | var preference = { 666 | isp: 'isp', 667 | region: 'region' 668 | }; 669 | var room_key = appKey + "_" + room; 670 | if (dict_room_rid[room_key] == null) { 671 | icsREST.API.getRooms({ 672 | page: 1, 673 | per_page: 99999999 674 | }, function (rooms) { 675 | console.log(`all rooms:${rooms.length} room_key:${room_key}`) 676 | for (const idx in rooms) { 677 | let _room = rooms[idx]; 678 | if (_room.name == room_key) { 679 | dict_room_rid[room_key] = _room._id; 680 | icsREST.API.createToken(_room._id, user, role, preference, function (token_) { 681 | res.send(token_); 682 | }, function (err_) { 683 | logger.error(req.originalUrl + " " + [room, user, role].join(' ') + " /tokens error:" + err_); 684 | res.status(401).send(err_); 685 | }); 686 | return; 687 | } 688 | } 689 | console.log(`not found room ${room_key}`) 690 | 691 | icsREST.API.createRoom(room_key, {}, function (response) { 692 | let r = response; 693 | filterRoom(r); 694 | let oldid = response._id; 695 | dict_room_rid[room_key] = oldid; 696 | icsREST.API.updateRoom(oldid, r, function (result) { 697 | icsREST.API.createToken(result._id, user, role, preference, function (token_) { 698 | res.send(token_); 699 | }, function (err_) { 700 | logger.error(req.originalUrl + " " + [room, user, role].join(' ') + " /tokens error:" + err_); 701 | res.status(401).send(err_); 702 | }); 703 | }, function (err) { 704 | logger.error('createRoom end' + err); 705 | res.send(err); 706 | }); 707 | }, function (err) { 708 | logger.error('createRoom end' + err); 709 | res.send(err); 710 | }); 711 | }); 712 | } 713 | else { 714 | icsREST.API.createToken(dict_room_rid[room_key], user, role, preference, function (token_) { 715 | res.send(token_); 716 | }, function (err_) { 717 | logger.error(req.originalUrl + " " + [room, user, role].join(' ') + " /tokens error:" + err_); 718 | res.status(401).send(err_); 719 | }); 720 | } 721 | } 722 | 723 | //Sip call management. 724 | app.get('/rooms/:room/sipcalls', function (req, res) { 725 | 'use strict'; 726 | var room = req.params.room; 727 | icsREST.API.getSipCalls(room, function (sipCalls) { 728 | res.send(sipCalls); 729 | }, function (err) { 730 | res.send(err); 731 | }); 732 | }); 733 | 734 | app.post('/rooms/:room/sipcalls', function (req, res) { 735 | 'use strict'; 736 | var room = req.params.room, 737 | peerUri = req.body.peerURI, 738 | mediaIn = req.body.mediaIn, 739 | mediaOut = req.body.mediaOut; 740 | icsREST.API.makeSipCall(room, peerUri, mediaIn, mediaOut, function (info) { 741 | res.send(info); 742 | }, function (err) { 743 | res.send(err); 744 | }); 745 | }); 746 | 747 | app.patch('/rooms/:room/sipcalls/:id', function (req, res) { 748 | 'use strict'; 749 | var room = req.params.room, 750 | id = req.params.id, 751 | commands = req.body; 752 | icsREST.API.updateSipCall(room, id, commands, function (result) { 753 | res.send(result); 754 | }, function (err) { 755 | res.send(err); 756 | }); 757 | }); 758 | 759 | app.delete('/rooms/:room/sipcalls/:id', function (req, res) { 760 | 'use strict'; 761 | var room = req.params.room, 762 | id = req.params.id; 763 | icsREST.API.endSipCall(room, id, function (result) { 764 | res.send(result); 765 | }, function (err) { 766 | res.send(err); 767 | }); 768 | }); 769 | 770 | app.post('/tokens', function (req, res) { 771 | 'use strict'; 772 | var room = req.body.room || sampleRoom, 773 | user = req.body.user, 774 | role = req.body.role, 775 | appKey = req.body.appKey; 776 | 777 | logger.info(req.originalUrl + " " + [room, user, role].join(' ')); 778 | //Note: The actual *ISP* and *region* information should be retrieved from the *req* object and filled in the following 'preference' data. 779 | var preference = { 780 | isp: 'isp', 781 | region: 'region' 782 | }; 783 | 784 | if (appKey) { 785 | getTokenById(req, res); 786 | return 787 | } 788 | res.status(404).send("appKey not found"); 789 | }); 790 | //////////////////////////////////////////////////////////////////////////////////////////// 791 | // New RESTful interface end 792 | //////////////////////////////////////////////////////////////////////////////////////////// 793 | 794 | spdy.createServer({ 795 | spdy: { 796 | plain: true 797 | } 798 | }, app).listen(3001, (err) => { 799 | if (err) { 800 | logger.error("Failed to setup plain server, " + err); 801 | return process.exit(1); 802 | } 803 | }); 804 | 805 | var cipher = require('./cipher'); 806 | cipher.unlock(cipher.k, 'cert/.woogeen.keystore', function cb(err, obj) { 807 | if (!err) { 808 | spdy.createServer({ 809 | pfx: fs.readFileSync('cert/certificate.pfx'), 810 | passphrase: obj.sample 811 | }, app).listen(3004, (error) => { 812 | if (error) { 813 | console.log('Failed to setup secured server: ', error); 814 | return process.exit(1); 815 | } 816 | }); 817 | } 818 | if (err) { 819 | console.error('Failed to setup secured server:', err); 820 | return process.exit(); 821 | } 822 | }); 823 | -------------------------------------------------------------------------------- /static/videoWindows.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 屏幕分享 8 | 9 | 10 | 11 | 12 | 13 |
14 | 46 |
47 | 48 | 58 | 59 | 60 | 61 | 62 | 63 | --------------------------------------------------------------------------------