65.9K
CodeProject 正在变化。 阅读更多。
Home

Wexstream - 使用 Node.js、React 和 Jitsi 的视频会议平台

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (9投票s)

2023 年 1 月 28 日

MIT

14分钟阅读

viewsIcon

45089

downloadIcon

419

使用 Node.js、React 和 Jitsi 的视频会议平台

目录

  1. 引言
  2. 必备组件
  3. 快速概览
  4. 安装
  5. 从源码运行
  6. Using the Code
    1. API
    2. 前端
  7. 服务条款
  8. 历史

引言

Wexstream 是一个开源视频会议平台,采用 Node.jsReactJitsi 构建。

Wexstream 允许您创建一个网络并与您的网络共享私人视频会议,或创建公共视频会议并与网络外的人共享。

Wexstream 让您与所有团队、家人、朋友或同事保持联系。即时视频会议,可免费高效地适应您的规模。

  • 无限用户:用户或会议参与者数量没有人工限制。服务器功率和带宽是唯一的限制因素。
  • 隐私设置、密码和会议锁定将控制权交到您手中。
  • 密码保护的会议室:通过密码控制对会议的访问。
  • 桌面屏幕共享、聊天和许多有用功能。
  • 默认加密。
  • 通过 TLS 加密和端到服务器/传输加密保护的会议。
  • 高质量:通过 Opus 和 VP8 的清晰度和丰富度传输音频和视频。
  • Web 就绪:您的朋友无需下载即可加入对话。Wexstream 也直接在他们的浏览器中运行。只需与他人共享您的会议 URL 即可开始。
  • 移动就绪:适用于所有设备的无障碍、清晰、易用的界面。
  • 用户的个人数据不会被转售或传递给第三方。
  • 用户有权访问、修改、纠正和删除其个人数据。

易于使用

Wexstream 简单、灵活且易于使用,无论您身在何处。

用户可以立即进入在线网络直播,无需下载。

注册后,用户即可享受以下服务

  • 平台成员之间的轻松联网
  • 提供视频会议工具
  • 提供平台成员之间的通信工具

平台的工作方式如下

  1. 用户通过与他人连接来创建一个网络。
  2. 用户广播私人或公开会议。
  3. 广播会议时,用户会获得一个 URL,可以共享该 URL 来邀请他人加入。
  4. 广播会议时,用户的网络会收到通知。

安全

Wexstream 使用 TLS 加密和端到服务器/传输加密保护您的实时和托管内容。此外,额外的隐私设置、密码和会议锁定将控制权交到您手中。

Wexstream 致力于尽一切努力确保用户个人数据的安全和隐私。

用户的个人数据不会被转售或传递给第三方。

用户有权访问、修改、纠正和删除其个人数据。

整洁的内容

明确禁止用户发布任何令人反感、色情、暴力、辱骂、诽谤、威胁或淫秽的内容,进行任何此类活动,串流任何馈送或创建任何帐户,或意图促进或从事非法行为,包括侵犯知识产权、隐私权或专有权,诽谤、诽谤、种族主义、仇外,违反道德和良好习俗,侵犯内容,破坏公共秩序或权利,可能侵犯平台及其更广泛地,其内容违反法律和/或法规(特别是刑事性质)的权利、声誉和形象,包含其密码,或故意包含他人密码、个人数据,或旨在索取此类数据,误导或欺骗,或可能误导或欺骗他人在其身份或与他人或组织的关联性方面,违反其在平台使用条款或任何纳入政策下的任何义务。

必备组件

  • Node.js
  • Express
  • MongoDB
  • React
  • MUI
  • JavaScript
  • Git

快速概览

在本节中,您将看到主要页面的快速概述。

下方是登录页面

用户可以通过 Google、Facebook 或电子邮件进行身份验证,方法是在注册页面创建帐户。

下方是关于页面

下方是服务条款页面

下方是联系页面

用户登录后,将进入主页,可以在其中看到他的时间线。

时间线包含用户网络的视频会议。

下方是网络页面

从网络页面,用户可以搜索用户并发送连接请求或私人消息。

下方是连接页面

连接页面包含用户的网络。

下方是消息页面

从消息页面,用户可以阅读和管理他的消息或发送新消息。

下方是通知页面

从通知页面,用户可以阅读和管理他的通知。

下方是个人资料页面

从个人资料页面,用户可以看到其网络成员的私人和公共视频会议,或网络外用户的公共视频会议。

下方是设置页面

从设置页面,用户可以管理他的设置或删除他的帐户。

下方是重置密码页面

从该页面,通过电子邮件注册的用户可以更改密码。

下方是创建新视频会议的对话框

用户输入必填的标题、可选的描述以及一个指示视频会议是私有还是公开的标志。如果视频会议是私有的,则仅对其网络可用。否则,如果视频会议是公开的,则对所有人可用。

下方是会议页面

用户将获得一个唯一的视频会议 URL,如果会议是私有的,则可以与他的网络共享;如果会议是公开的,则可以与所有人共享。用户可以开始/停止摄像头、与参会者共享屏幕、使用密码保护会议、举手/放手、查看参会者、向参会者发送私人消息等。

安装

您可以在此处找到安装说明。

从源码运行

以下是从源代码运行 Wexstream 的说明。

必备组件

说明

  1. 克隆 Wexstream 仓库
    git clone https://github.com/aelassas/wexstream.git 
  2. 添加 api/.env 文件
    NODE_ENV = development
    WS_PORT = 4003
    WS_HTTPS = false
    WS_PRIVATE_KEY = /etc/jitsi/meet/192.168.100.223.key
    WS_CERTIFICATE = /etc/jitsi/meet/192.168.100.223.crt
    WS_APP_HOST = localhost
    WS_DB_HOST = 127.0.0.1
    WS_DB_PORT = 27017
    WS_DB_SSL = false
    WS_DB_SSL_KEY = /etc/jitsi/meet/192.168.100.223.key
    WS_DB_SSL_CERT = /etc/jitsi/meet/192.168.100.223.crt
    WS_DB_SSL_CA = /etc/jitsi/meet/192.168.100.223.ca.pem
    WS_DB_DEBUG = false
    WS_DB_APP_NAME = wexstream
    WS_DB_AUTH_SOURCE = admin
    WS_DB_USERNAME = admin
    WS_DB_PASSWORD = PASSWORD
    WS_DB_NAME = wexstream
    WS_JWT_SECRET = JWT_SECRET
    WS_JWT_SUB = 192.168.100.223
    WS_JWT_EXPIRE_AT = 86400
    WS_TOKEN_EXPIRE_AT = 86400
    WS_SMTP_HOST = host
    WS_SMTP_PORT = 587
    WS_SMTP_USER = USER
    WS_SMTP_PASS = PASSWORD
    WS_SMTP_FROM = no-reply@wexstream.com
    WS_ADMIN_EMAIL = admin@wexstream.com
    WS_DEFAULT_LANGUAGE = en
    WS_CDN = /var/www/cdn/wexstream

    您必须配置以下选项

    WS_DB_PASSWORD
    WS_JWT_SECRET 
    WS_JWT_SUB 
    WS_SMTP_HOST 
    WS_SMTP_PORT 
    WS_SMTP_USER 
    WS_SMTP_PASS 
    WS_SMTP_FROM
    WS_ADMIN_EMAIL 

    WS_JWT_SECRET 必须与 Jitsi 中使用的 JWT 密钥相同。

    WS_JWT_SUB 必须是安装了 Jitsi 的服务器的 FQDN 或 IP 地址。

  3. 添加 frontend/.env 文件
    REACT_APP_NODE_ENV = development
    REACT_APP_WS_DEFAULT_LANGUAGE = en
    REACT_APP_WS_DATE_FORMAT = llll
    REACT_APP_WS_PAGE_SIZE = 30
    REACT_APP_WS_JITSI_HOST = 192.168.100.223:444
    REACT_APP_WS_JITSI_API = https://192.168.100.223:444/external_api.js
    REACT_APP_WS_API_HOST = https://:4003
    REACT_APP_WS_CDN = https:///cdn/wexstream
    REACT_APP_WS_GOOGLE_CLIENT_ID = GOOGLE_CLIENT_ID
    REACT_APP_WS_FACEBOOK_APP_ID = FACEBOOK_APP_ID

    REACT_APP_WS_GOOGLE_CLIENT_ID 用于 Google 身份验证。

    REACT_APP_WS_FACEBOOK_APP_ID 用于 Facebook 身份验证。

    您必须配置以下选项

    REACT_APP_WS_JITSI_HOST 
    REACT_APP_WS_JITSI_API
    REACT_APP_WS_GOOGLE_CLIENT_ID 
    REACT_APP_WS_FACEBOOK_APP_ID 
  4. 安装 nodemon
    npm i -g nodemon
  5. 运行 api
    cd ./api
    npm install
    npm run dev
  6. 运行 frontend
    cd ./frontend
    npm install
    npm start
  7. 配置 https:///cdn
    • 在 Windows 上,安装 IIS 并创建 C:\inetpub\wwwroot\cdn\wexstream 文件夹。
    • 在 Linux 上,安装 NGINX 并通过修改 /etc/nginx/sites-available/default 来添加 cdn 文件夹,如下所示
    server {
        listen 80 default_server;
        server_name _;
        
        ...
    
        location /cdn {
          alias /var/www/cdn;
        }
    }

    创建 /var/www/cdn/wexstream 文件夹并添加必要的权限

    mkdir /var/www/cdn/wexstream
    sudo chown -R $USER:$USER /var/www/cdn/wexstream

Using the Code

本节描述了 Wexstream 的软件架构,包括数据库、API 和前端。

API

Wexstream API 公开了前端所需的所有 Wexstream 函数。API 遵循 MVC 设计模式。JWT 用于身份验证。有些函数需要身份验证,例如与会议和连接管理相关的函数,而其他函数则不需要身份验证,例如注册。

  • ./api/models/ 文件夹包含 MongoDB 模型。
  • ./api/routes/ 文件夹包含 Express 路由。
  • ./api/controllers/ 文件夹包含控制器。
  • ./api/middlewares/ 文件夹包含中间件。
  • ./api/server.js 是建立数据库连接并加载路由的主服务器。
  • ./api/app.js 是 Wexstream API 的主入口点。

app.js

app.jsWexstream API 的主入口点

import app from './server.js'
import fs from 'fs'
import https from 'https'

const PORT = parseInt(process.env.WS_PORT) || 4000
const HTTPS = process.env.WS_HTTPS.toLocaleLowerCase() === 'true'
const PRIVATE_KEY = process.env.WS_PRIVATE_KEY
const CERTIFICATE = process.env.WS_CERTIFICATE

if (HTTPS) {
    https.globalAgent.maxSockets = Infinity
    const privateKey = fs.readFileSync(PRIVATE_KEY, 'utf8')
    const certificate = fs.readFileSync(CERTIFICATE, 'utf8')
    const credentials = { key: privateKey, cert: certificate }
    const httpsServer = https.createServer(credentials, app)

    httpsServer.listen(PORT, () => {
        console.log('HTTPS server is running on Port:', PORT)
    })
} else {
    app.listen(PORT, () => {
        console.log('HTTPS server is running on Port:', PORT)
    })
}

app.js 中,我们检索 HTTPS 设置变量,该变量指示是否启用了 https。如果启用了 https,我们就使用提供的私钥和证书创建一个 https 服务器并开始监听。否则,将创建一个 http 服务器并开始监听。app 是建立数据库连接并加载路由的主服务器。

server.js

server.js 位于主服务器中

import express from 'express'
import cors from 'cors'
import mongoose from 'mongoose'
import compression from 'compression'
import helmet from 'helmet'
import nocache from 'nocache'
import strings from './config/app.config.js'
import userRoutes from './routes/userRoutes.js'
import connectionRoutes from './routes/connectionRoutes.js'
import notificationRoutes from './routes/notificationRoutes.js'
import messageRoutes from './routes/messageRoutes.js'
import conferenceRoutes from './routes/conferenceRoutes.js'
import timelineRoutes from './routes/timelineRoutes.js'

const DB_HOST = process.env.WS_DB_HOST
const DB_PORT = process.env.WS_DB_PORT
const DB_SSL = process.env.WS_DB_SSL.toLowerCase() === 'true'
const DB_SSL_KEY = process.env.WS_DB_SSL_KEY
const DB_SSL_CERT = process.env.WS_DB_SSL_CERT
const DB_SSL_CA = process.env.WS_DB_SSL_CA
const DB_DEBUG = process.env.WS_DB_DEBUG.toLowerCase() === 'true'
const DB_AUTH_SOURCE = process.env.WS_DB_AUTH_SOURCE
const DB_USERNAME = process.env.WS_DB_USERNAME
const DB_PASSWORD = process.env.WS_DB_PASSWORD
const DB_APP_NAME = process.env.WS_DB_APP_NAME
const DB_NAME = process.env.WS_DB_NAME
const DB_URI = `mongodb://${encodeURIComponent(DB_USERNAME)}:${encodeURIComponent
                        (DB_PASSWORD)}@${DB_HOST}:${DB_PORT}/${DB_NAME}?
                         authSource=${DB_AUTH_SOURCE}&appName=${DB_APP_NAME}`

let options = {}
if (DB_SSL) {
    options = {
        ssl: true,
        sslValidate: true,
        sslKey: DB_SSL_KEY,
        sslCert: DB_SSL_CERT,
        sslCA: [DB_SSL_CA]
    }
}

mongoose.set('debug', DB_DEBUG)
mongoose.Promise = global.Promise
mongoose.connect(DB_URI, options)
    .then(
        () => { console.log('Database is connected') },
        err => { console.error('Cannot connect to the database:', err) }
    )

const app = express()
app.use(helmet.contentSecurityPolicy())
app.use(helmet.dnsPrefetchControl())
app.use(helmet.crossOriginEmbedderPolicy())
app.use(helmet.frameguard())
app.use(helmet.hidePoweredBy())
app.use(helmet.hsts())
app.use(helmet.ieNoOpen())
app.use(helmet.noSniff())
app.use(helmet.permittedCrossDomainPolicies())
app.use(helmet.referrerPolicy())
app.use(helmet.xssFilter())
app.use(helmet.originAgentCluster())
app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' }))
app.use(helmet.crossOriginOpenerPolicy())
app.use(nocache())
app.use(compression({ threshold: 0 }))
app.use(express.urlencoded({ limit: '50mb', extended: true }))
app.use(express.json({ limit: '50mb' }))
app.use(cors())
app.use('/', userRoutes)
app.use('/', connectionRoutes)
app.use('/', notificationRoutes)
app.use('/', messageRoutes)
app.use('/', conferenceRoutes)
app.use('/', timelineRoutes)

strings.setLanguage(process.env.WS_DEFAULT_LANGUAGE)

export default app

首先,我们构建 MongoDB 连接字符串,然后建立与 Wexstream MongoDB 数据库的连接。然后我们创建一个 Express app 并加载中间件。最后,我们加载 Express 路由并导出 app

路由

Wexstream API 中有六个路由。每个路由都有自己的控制器,遵循 MVC 设计模式和 SOLID 原则。以下是主要路由

  • userRoutes:提供与用户相关的 REST 函数
  • connectionRoutes:提供与用户连接相关的 REST 函数
  • conferenceRoutes:提供与会议相关的 REST 函数
  • timelineRoutes:提供与时间线相关的 REST 函数
  • messageRoutes:提供与消息相关的 REST 函数
  • notificationRoutes:提供与通知相关的 REST 函数

我们不打算一一解释每个路由。我们将以 conferenceRoutes 为例,看看它是如何实现的。

import express from 'express'
import routeNames from '../config/conferenceRoutes.config.js'
import authJwt from '../middlewares/authJwt.js'
import * as conferenceController from '../controllers/conferenceController.js'

const routes = express.Router()

routes.route(routeNames.create).post(authJwt.verifyToken, conferenceController.create)
routes.route(routeNames.update).post(authJwt.verifyToken, conferenceController.update)
routes.route(routeNames.addMember).post(authJwt.verifyToken, 
                                        conferenceController.addMember)
routes.route(routeNames.removeMember).post(authJwt.verifyToken, 
                                           conferenceController.removeMember)
routes.route(routeNames.delete).delete(authJwt.verifyToken, 
                                       conferenceController.deleteConference)
routes.route(routeNames.getConference).get(authJwt.verifyToken, 
                                           conferenceController.getConference)
routes.route(routeNames.getConferences).get(authJwt.verifyToken, 
                                            conferenceController.getConferences)
routes.route(routeNames.getMembers).get(authJwt.verifyToken, 
                                        conferenceController.getMembers)
routes.route(routeNames.close).post(authJwt.verifyToken, conferenceController.close)

export default routes

首先,我们创建一个 Express Router。然后,我们使用其名称、方法、中间件和控制器来创建路由。

routeNames 包含 conferenceRoutes 路由名称

export default {
    create: '/api/create-conference',
    update: '/api/update-conference/:conferenceId',
    addMember: '/api/add-member/:conferenceId/:userId',
    removeMember: '/api/remove-member/:conferenceId/:userId',
    delete: '/api/delete-conference/:conferenceId',
    getConference: '/api/get-conference/:conferenceId',
    getConferences: '/api/get-conferences/:userId/:isPrivate/:page/:pageSize',
    getMembers:  '/api/get-members/:conferenceId',
    close:  '/api/close-conference/:userId/:conferenceId'
}

conferenceController 包含与会议相关的主要业务逻辑。我们不打算查看控制器的所有源代码,因为它相当大,但我们将以 creategetConferences 控制器函数为例。

以下是 Conference 模型

import mongoose from 'mongoose'

const Schema = mongoose.Schema

const conferenceSchema = new Schema({
    title: {
        type: String,
        required: [true, "can't be blank"],
        index: true
    },
    description: {
        type: String,
        index: true
    },
    isPrivate: {
        type: Boolean,
        default: true
    },
    isLive: {
        type: Boolean,
        default: false
    },
    speaker: {
        type: Schema.Types.ObjectId,
        required: true,
        ref: 'User'
    },
    members: [{
        type: Schema.Types.ObjectId,
        ref: 'User'
    }],
    broadcastedAt: {
        type: Date
    },
    finishedAt: {
        type: Date
    },
}, {
    timestamps: true,
    strict: true,
    collection: 'Conference'
})

const conferenceModel = mongoose.model('Conference', conferenceSchema)

conferenceModel.on('index', (err) => {
    if (err) {
        console.error('Conference index error: %s', err)
    } else {
        console.info('Conference indexing complete')
    }
})

export default conferenceModel

会议由标题、可选描述、isPrivateisLive 标志、演讲者、成员、广播和结束日期/时间组成。

以下是 create 控制器函数

export const create = (req, res) => {
    const conference = new Conference(req.body)
    conference.save()
        .then(conf => {
            res.status(200).json(conf)
        })
        .catch(err => {
            console.error(strings.DB_ERROR, err)
            res.status(400).send(strings.DB_ERROR + err)
        })
}

在此函数中,我们检索请求体并创建会议。

以下是 getConferences 控制器函数

export const getConferences = (req, res) => {
    const page = parseInt(req.params.page)
    const pageSize = parseInt(req.params.pageSize)
    let query = { speaker: req.params.userId }

    if (req.params.isPrivate === 'false') {
        query.isPrivate = false
    }

    Conference.find(query, null, { skip: ((page - 1) * pageSize), limit: pageSize })
        .sort({ createdAt: -1 })
        .then(confs => {
            res.json(confs)
        })
        .catch(err => {
            console.error(strings.DB_ERROR, err)
            res.status(400).send(strings.DB_ERROR + err)
        })
}

在此控制器函数中,我们根据 pagepageSizeuserIdisPrivate 标志检索会议。

前端

frontend 是一个使用 Node.js、React 和 MUI 构建的 Web 应用程序。

  • ./frontend/assets/ 文件夹包含 CSS 文件。
  • ./frontend/pages/ 文件夹包含 React 页面。
  • ./frontend/components/ 文件夹包含 React 组件。
  • ./frontend/services/ 包含 Wexstream API 客户端服务。
  • ./frontend/App.js 是包含路由的主 React App。
  • ./frontend/index.js 是前端的主入口点。

App.js 是主 React App

import React, { lazy, Suspense } from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'

const SignIn = lazy(() => import('./pages/SignIn'))
const SignUp = lazy(() => import('./pages/SignUp'))
const Home = lazy(() => import('./pages/Home'))
const Conference = lazy(() => import('./pages/Conference'))
const Profile = lazy(() => import('./pages/Profile'))
const Search = lazy(() => import('./pages/Search'))
const Notifications = lazy(() => import('./pages/Notifications'))
const Connections = lazy(() => import('./pages/Connections'))
const ToS = lazy(() => import('./pages/ToS'))
const About = lazy(() => import('./pages/About'))
const Messages = lazy(() => import('./pages/Messages'))
const Settings = lazy(() => import('./pages/Settings'))
const ResetPassword = lazy(() => import('./pages/ResetPassword'))
const Contact = lazy(() => import('./pages/Contact'))
const NoMatch = lazy(() => import('./pages/NoMatch'))

const App = () => (
    <Router>
        <div className="App">
            <Suspense fallback={<></>}>
                <Routes>
                    <Route exact path="/sign-in" element={<SignIn />} />
                    <Route exact path="/sign-up" element={<SignUp />} />
                    <Route exact path="/" element={<Home />} />
                    <Route exact path="/home" element={<Home />} />
                    <Route exact path="/conference" element={<Conference />} />
                    <Route exact path="/profile" element={<Profile />} />
                    <Route exact path="/search" element={<Search />} />
                    <Route exact path="/notifications" element={<Notifications />} />
                    <Route exact path="/connections" element={<Connections />} />
                    <Route exact path="/messages" element={<Messages />} />
                    <Route exact path="/settings" element={<Settings />} />
                    <Route exact path="/reset-password" element={<ResetPassword />} />
                    <Route exact path="/tos" element={<ToS />} />
                    <Route exact path="/about" element={<About />} />
                    <Route exact path="/contact" element={<Contact />} />

                    <Route path="*" element={<NoMatch />} />
                </Routes>
            </Suspense>
        </div>
    </Router>
)

export default App

我们使用 React 懒加载来加载每个路由。

以下是 Conference 页面中启动会议的主函数

import { JITSI_API, JITSI_HOST, isMobile } from '../config/env'

const startConf = () => {
    setConference(conference)

    const script = document.createElement('script')
    script.src = JITSI_API
    script.id = 'external-api'
    script.setAttribute('defer', 'defer')
    document.body.appendChild(script)

    // External API is loaded
    const externalApi = document.getElementById('external-api')

    externalApi.addEventListener('load', () => {
        if (window.JitsiMeetExternalAPI) {
            if (!conference.isLive && (conference.speaker._id === user._id)) {
                updateConf(conference, 
                { isLive: true, broadcastedAt: Date.now() }, () => {
                    setConferenceUrl(window.location.href)
                    setStartConf(true)
                })
            } else {
                setConferenceUrl(window.location.href)
                setStartConf(true)
            }
        } else {
            setLoading(false)
            setExternalApiError(true)
        }
    })

在此函数中,我们加载 Jitsi 外部 API。

Jitsi 是一系列开源项目,赋能用户部署具有最先进视频质量和功能的视频会议平台。

然后,我们通过以下函数开始会议

import React, { useCallback, useEffect, useState } from 'react'
import { JITSI_API, JITSI_HOST, isMobile } from '../config/env'

let domain = JITSI_HOST
let api = {}

const startConference = useCallback(() => {
    localStorage.removeItem('jitsiLocalStorage')
    localStorage.setItem('jitsiLocalStorage', 
                          JSON.stringify({ language: user.language }))

    const options = {
        roomName: conference._id,
        width: '100%',
        height: '100%',
        configOverwrite: {
            prejoinPageEnabled: false,
            useHostPageLocalStorage: true,
            disableDeepLinking: true,
            disabledNotifications: ['dialog.thankYou']
        },
        interfaceConfigOverwrite: {
            // overwrite interface properties
        },
        parentNode: document.querySelector('#conf'),
        userInfo: {
            displayName: user.fullName
        },
        lang: user.language,
        jwt: user.accessToken
    }
    api = new window.JitsiMeetExternalAPI(domain, options)

    api.addEventListeners({
        readyToClose: handleClose,
        videoConferenceJoined: handleVideoConferenceJoined,
        videoConferenceLeft: handleVideoConferenceLeft,
        participantLeft: handleParticipantLeft,
        participantJoined: handleParticipantJoined,
        participantRoleChanged: handleParticipantRoleChanged,
        participantKickedOut: handleParticipantKickedOut,
        audioMuteStatusChanged: handleMuteStatus,
        videoMuteStatusChanged: handleVideoStatus
    })

    window.onbeforeunload = (e) => {
        localStorage.removeItem('jitsiLocalStorage')
        setExit(true)
    }

    if (conference.isLive && conference.speaker._id === user._id) {
        createTimelineEntries(user._id, conference._id, true)
    }
}, [user, conference])

在上面的函数中,我们使用选项和事件监听器创建了一个新的 Jitsi API,启动了会议,并创建了时间线条目。

我们不会涵盖前端的每个页面,但如果您愿意,可以打开源代码查看。

服务条款

前导码

Wexstream 让您与所有团队、家人、朋友或同事保持联系。即时视频会议,可免费高效地适应您的规模。

Wexstream 使用最新、高性能的技术开发,能够处理大量会议和参会者。

  • 无限用户:用户或会议参与者数量没有人工限制。服务器功率和带宽是唯一的限制因素。
  • 隐私设置、密码和会议锁定将控制权交到您手中。
  • 密码保护的会议室:通过密码控制对会议的访问。
  • 桌面屏幕共享、聊天和许多有用功能。
  • 默认加密。
  • 通过 TLS 加密和端到服务器/传输加密保护的会议。
  • 高质量:通过 Opus 和 VP8 的清晰度和丰富度传输音频和视频。
  • Web 就绪:您的朋友无需下载即可加入对话。Wexstream 也直接在他们的浏览器中运行。只需与他人共享您的会议 URL 即可开始。
  • 移动就绪:适用于所有设备的无障碍、清晰、易用的界面。
  • 用户的个人数据不会被转售或传递给第三方。
  • 用户有权访问、修改、纠正和删除其个人数据。

订阅

要访问服务,用户必须通过免费注册来创建帐户。

用户必须提供真实准确的信息,并承诺在任何更改发生时立即更新。

平台访问受用户注册时选择的用户名和密码保护。用户对其用户名和密码的任何使用承担全部责任,并且是其保密性的唯一保证人,也是其帐户的任何使用的保证人。

如果用户提供虚假、不准确、过时或不完整的数据,Wexstream 将有权暂停或关闭其帐户,并拒绝其将来访问全部或部分服务。

服务

注册后,用户即可享受以下服务

  • 平台成员之间的轻松联网
  • 提供视频会议工具
  • 提供平台成员之间的通信工具

平台的工作方式如下

  1. 用户通过与他人连接来创建一个网络。
  2. 用户广播私人或公开会议。
  3. 广播会议时,用户会获得一个 URL,可以共享该 URL 来邀请他人加入。
  4. 广播会议时,用户的网络会收到通知。

服务访问

平台和服务的访问仅保留给注册用户。用户可以在线广播学习视频、网络研讨会、讲座或直播,或参加在线学习课程、网络研讨会、会议或直播,并可以访问允许访问平台的 IT 和电信资源。

该平台每周 7 天、每天 24 小时对所有用户开放。Wexstream 保留未经通知或赔偿的权利,暂时或永久关闭平台或对一项或多项服务的访问,以进行任何更新、修改或更改运营方法、服务器和可访问时间,此列表并非详尽无遗。Wexstream 保留对其认为为了平台及其服务正常运行而必要或有用的平台和服务进行任何修改和改进的权利。

诉讼

Wexstream 对广播者和用户之间的协作不负责,但 Wexstream 可能会协助解决任何纠纷。

承诺

用户承诺公平使用平台和服务,并明确避免规避服务和平台。因此,禁止任何用户从平台提取内容以进行类似或并发活动。

职责

用户对其因注册时提供的不准确、不完整和/或误导性信息或未更新其信息而可能遭受的直接或间接损害负责,并独自承担后果。

用户对其选择广播的所有内容负责。

明确禁止用户发布任何令人反感、色情、暴力、辱骂、诽谤、威胁或淫秽的内容,进行任何此类活动,串流任何馈送或创建任何帐户,或意图促进或从事非法行为,包括侵犯知识产权、隐私权或专有权,诽谤、诽谤、种族主义、仇外,违反道德和良好习俗,侵犯内容,破坏公共秩序或权利,可能侵犯平台及其更广泛地,其内容违反法律和/或法规(特别是刑事性质)的权利、声誉和形象,包含其密码,或故意包含他人密码、个人数据,或旨在索取此类数据,误导或欺骗,或可能误导或欺骗他人在其身份或与他人或组织的关联性方面,违反其在平台使用条款或任何纳入政策下的任何义务。

隐私

Wexstream 使用 TLS 加密和端到服务器/传输加密保护您的实时和托管内容。此外,额外的隐私设置、密码和会议锁定将控制权交到您手中。

Wexstream 致力于尽一切努力确保用户个人数据的安全和隐私。

用户的个人数据不会被转售或传递给第三方。

用户有权访问、修改、纠正和删除其个人数据。

期限、终止和制裁

本合同自用户接受平台使用条款之日起无限期生效。

如果用户不遵守这些使用条款和/或违反现行法律法规,Wexstream 有权自动暂停或关闭用户帐户,并在将来拒绝其访问全部或部分服务,而不会损害 Wexstream 有权要求赔偿的任何损害。

服务条款修改

Wexstream 保留修改全部或部分使用条款的权利。

Wexstream 将在这些通用条款的修改发布到平台后立即告知用户。

适用法律和管辖法院

与本服务条款的形成、达成、解释和/或执行相关的任何争议均由法院专属管辖。

历史

© . All rights reserved.