├── .gitignore ├── README.md ├── docs ├── assistDev │ ├── assistDev.bundle.js │ └── index.html ├── audio │ ├── audio.bundle.js │ └── index.html ├── audioMixing │ ├── audioMixing.bundle.js │ └── index.html ├── base │ ├── base.bundle.js │ └── index.html ├── cdn │ ├── cdn.bundle.js │ └── index.html ├── content │ └── content.bundle.js ├── docsSharing │ ├── ZegoDocsSDK.md │ ├── ZegoExpressDocs.js │ ├── index.html │ └── lib │ │ └── bootstrap.min.css ├── favicon.ico ├── index.01759751.css ├── index.02f86e4a.css ├── index.03743737.css ├── index.1328ffd4.css ├── index.20849460.css ├── index.2a499927.css ├── index.3e8a8558.css ├── index.439a0432.css ├── index.47339e44.css ├── index.537a4227.css ├── index.574b9725.css ├── index.5755208f.css ├── index.5c25013b.css ├── index.5d5409e8.css ├── index.5df70f5c.css ├── index.72d7a378.css ├── index.7757f426.css ├── index.81032bbc.css ├── index.86ec9855.css ├── index.8f4deb7a.css ├── index.8f55362e.css ├── index.9f9f09e0.css ├── index.9fc13c72.css ├── index.a261e6b7.css ├── index.a8eb07c4.css ├── index.cb8ece3a.css ├── index.d4a6bab9.css ├── index.d87c4062.css ├── index.dbc844d3.css ├── index.e33f6e43.css ├── index.html ├── message │ ├── index.html │ └── message.bundle.js ├── mix │ ├── index.html │ └── mix.bundle.js ├── screen │ ├── index.html │ └── screen.bundle.js ├── third │ ├── index.html │ └── third.bundle.js ├── token │ ├── index.html │ └── token.bundle.js ├── vp8 │ ├── index.html │ └── vp8.bundle.js ├── webrtcCheck │ ├── index.html │ └── webrtcCheck.bundle.js └── whiteboard │ ├── ZegoExpress.js │ ├── ZegoExpressDocs.js │ ├── ZegoWhiteboardSDK.md │ ├── index.html │ └── lib │ ├── bootstrap.min.css │ └── jquery-3.3.1.min.js ├── package-lock.json ├── package.json ├── src ├── assets │ ├── bootstrap.min.css │ ├── bootstrap.min.js │ ├── gitee.png │ ├── github.png │ ├── jZego-rtc.js │ ├── translation │ │ └── translation.js │ └── utils.js ├── assistDev │ ├── index.html │ └── index.js ├── audio │ ├── index.html │ └── index.js ├── audioMixing │ ├── index.html │ └── index.js ├── base │ ├── index.html │ └── index.js ├── cdn │ ├── index.html │ └── index.js ├── common.js ├── content.js ├── docsSharing │ ├── ZegoDocsSDK.md │ ├── ZegoExpressDocs.js │ ├── index.html │ └── lib │ │ └── bootstrap.min.css ├── favicon.ico ├── index.html ├── message │ ├── css │ │ └── chat.css │ ├── font_Icon │ │ ├── demo.css │ │ ├── demo_fontclass.html │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ ├── img │ │ ├── icon01.png │ │ ├── icon02.png │ │ └── icon03.png │ ├── index.html │ └── index.js ├── mix │ ├── index.html │ └── index.js ├── screen │ ├── index.html │ ├── index.js │ └── rangeShare.js ├── third │ ├── index.html │ └── index.js ├── token │ ├── index.html │ └── index.js ├── vp8 │ ├── index.html │ └── index.js ├── webrtcCheck │ ├── index.html │ └── index.js └── whiteboard │ ├── ZegoExpress.js │ ├── ZegoExpressDocs.js │ ├── ZegoWhiteboardSDK.md │ ├── index.html │ └── lib │ ├── bootstrap.min.css │ └── jquery-3.3.1.min.js ├── tools.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /.vscode 5 | /dist 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | testem.log 35 | /typings 36 | 37 | # e2e 38 | /e2e/*.js 39 | /e2e/*.map 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | /docs/assets/jZego-rtc-1.0.5-test.js 45 | /script 46 | /zip 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 即构 webrtc-sdk功能示例demo 2 | 3 | ## 说明 4 | - 该仓库为即构科技webrtc的使用示例demo,希望帮助开发者快速上手webrtc-sdk; 5 | - 每个页面展示sdk一种功能,可根据实际场景自由组合 6 | - 可Github[在线体验](https://zegodev.github.io/zego-express-webrtc-sample/),码云[在线体验2](https://zegodev.gitee.io/zego-express-webrtc-sample) 7 | - [API文档](https://doc-zh.zego.im/zh/306.html) 8 | 9 | 10 | ## 集成条件 11 | - 即构开发者账户([获取appid](https://www.zego.im)) 12 | - webrtc兼容性如下 13 | 14 | - 只支持SSL的Web服务器(https) 15 | > >localhost,127.0.0.1等同于https 16 | 17 | 18 | ## 快速搭建自己demo 19 | - 安装依赖: npm i 20 | - 修改/src/common.ts中代码为自己的配置 21 | > appid,server地址 需要自行修改(请从控制台申请AppID时邮件内容里获取) 22 | > 23 | - 启动: npm run dev 24 | 25 | 26 | ## 注意 27 | - demo里的sdk为实验版本,请不要用这里的sdk作为生产环境 28 | 29 | 30 | ## [常见问题](https://github.com/zegodev/webrtcDemo-js/issues) 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/assistDev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 36 | 推拉流基础功能 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 |
基础推拉流
45 |
46 | 请输入即构控制台中项目对应的 AppID -> Web -> AppID),用以体验示例 demo。 47 | 点击打开ZEGO控制台 48 |
49 | 50 |
51 | 52 | 66 | 67 |
68 | 69 |
70 |
71 |
72 |
73 | 74 |
75 | 77 |
78 |
79 | 88 |
89 |
90 |
91 |
92 |
93 | 94 |
95 | 97 |
98 |
99 |
100 |
101 |
102 | 103 |
104 | 106 |
107 |
108 |
109 |
110 |
111 | 112 |
113 | 115 |
116 |
117 |
118 | 119 |
120 |
121 |
122 |
123 | 124 |
125 | 127 |
128 |
129 |
130 | 131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 |
139 | 140 | 141 |
142 |
143 | 144 |
145 |
146 |
当前音量: 0
147 | 148 | 149 |
150 |
151 | 152 |
153 |
154 |
155 | 156 |
157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /docs/audio/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 24 | 推拉纯音频流 25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 |
推拉纯音频流
33 | 34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 | 43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 |
51 | 53 |
54 |
55 | 56 | 57 |
58 |
59 |
60 | 61 |
62 | 64 |
65 |
66 | 67 | 68 |
69 | 70 |
71 |
72 | 73 | 74 | 75 |
76 |
77 | 78 |
79 |
80 | 81 | 82 |
83 |
84 | 85 |
86 |
87 |
88 |
89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/audioMixing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 28 | 混音 29 | 30 | 31 |
32 | 33 |
34 | 35 |
混音
36 | 37 |
38 | 39 |
40 |
41 |
42 | 44 |
45 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | 55 |
56 | 58 |
59 |
60 | 61 | 62 |
63 |
64 |
65 | 66 |
67 | 69 |
70 |
71 | 72 |
73 | 74 |
75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 |
93 | 94 |
95 | 96 | 音量 97 | 98 | 音量 99 |
100 |
101 |
102 | 103 | 104 |
105 |
106 |
107 |
108 |
109 | 110 |
111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/cdn/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 23 | CDN 24 | 25 | 26 | 27 |
28 |
29 |
CDN
30 | 31 |
32 |
33 |
34 |
35 | 38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 |
46 | 49 |
50 | 51 |
52 |
53 | 54 |
55 |
56 |
57 | 60 |
61 | 69 |
70 |
71 |
72 |
73 | 90 |
91 |
92 |
93 | 94 |
95 | 99 |
100 |
101 |
102 | 103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
113 |
114 | 115 |
116 |
117 | 118 | 119 |
120 |
121 | 122 |
123 |
124 |
125 | 126 |
127 | 128 |
129 | 135 |
136 |
137 |
138 | 139 | 140 | -------------------------------------------------------------------------------- /docs/docsSharing/ZegoDocsSDK.md: -------------------------------------------------------------------------------- 1 | # ZegoDocs Web SDK API 文档 2 | 3 | ## 1 系统接口 4 | 5 | ### 1.1 初始化 6 | 7 | > new ZegoDocs(config) 8 | 9 | > config 对象结构如下: 10 | 11 | | 参数 | 含义 | 类型 | 是否必填 | 12 | | ------ | ------------------------------ | ------ | -------- | 13 | | appID | appID | 数值 | 必填 | 14 | | userID | 用户 ID,最大 128 字节的字符串 | 字符串 | 必填 | 15 | | token | 验证 token | 字符串 | 必填 | 16 | 17 | ### 1.2 设置环境 18 | 19 | > zegoDocs.setTestEnv(env) 20 | 21 | > SDK 默认是正式环境,env 为 true 则切换为测试环境,env 为 false 则切换为正式环境 22 | 23 | ### 1.3 加载文档内容 24 | 25 | > zegoDocs.load(option):Promise 26 | 27 | > option 对象结构如下: 28 | 29 | | 参数 | 含义 | 类型 | 是否必填 | 30 | | ------ | ----------------- | ------ | -------- | 31 | | fileID | 文档转换后唯一 ID | 字符串 | 必填 | 32 | 33 | > LoadResult 对象结构如下: 34 | 35 | | 参数 | 含义 | 类型 | 36 | | --------- | ------------------------------------------------------ | ------ | 37 | | pageCount | 文档总页数 | 数值 | 38 | | fileUrls | 转换完成图片地址列表 | 数组 | 39 | | notes | 备注列表,与图片列表对应,空''表示当前图片对应备注为空 | 数组 | 40 | | fileSize | 源文档大小 | 数值 | 41 | | fileHash | 源文档 hash | 字符串 | 42 | 43 | ### 1.4 上传文档 44 | 45 | > zegoDocs.upload(option):Promise 46 | 47 | > option 对象结构如下: 48 | 49 | | 参数 | 含义 | 类型 | 是否必填 | 50 | | ---------- | ------------------------------------------------------------ | ----------------- | -------- | 51 | | file | File 对象(web)或者文档路径(小程序) | File 对象或字符串 | 必填 | 52 | | renderType | 渲染模式 1:Vector 向量模式 2:IMG 图片模式 3:VectorAndIMG 向量和图片模式 6:DynamicPPTH5 动态PPT模式 | 数值 | 必填 | 53 | | onProgress | 上传进度监听函数 | Function | 选填 | 54 | 55 | > 渲染模式详细解释: 56 | 57 | | 渲染模式 | 描述 | 58 | | --------------------------- | ------------------------------------------------------------ | 59 | | Vector 向量模式 | 适用于文件共享 SDK PC 端、移动端做的文档智能转换, 能够更换地支持文档浏览以及更清晰的缩放。 | 60 | | IMG 图片模式 | 适用于文件共享 SDK Web 端、小程序使用的文档转换,按文件样式每一页都生成图片样式。 | 61 | | VectorAndIMG 向量和图片模式 | 适用于当存在 Web 端、小程序、移动端、PC 端,多端的并存情况,在 web 端、小程序将使用 IMG 图片模式, 而 PC 端、移动端将使用 Vector 向量模式 | 62 | | DynamicPPTH5 动态PPT模式 | 适用于文件共享SDK PC端、移动端、Web端、小程序使用的PPT文件转码格式, 会将PPT文件转成动态PPT的H5文件 | 63 | 64 | > onProgress(data),data 返回如下: 65 | 66 | > 若文档未上传、上传成功未转换,则返回: 67 | 68 | | 参数 | 含义 | 类型 | 69 | | -------- | ----------- | ------ | 70 | | fileHash | 源文档 hash | 字符串 | 71 | | status | 文档状态 | 数值 | 72 | 73 | > 若文档排队转换中、转换中,则返回: 74 | 75 | | 参数 | 含义 | 类型 | 76 | | ------ | --------------- | ------ | 77 | | taskID | 文档转换任务 ID | 字符串 | 78 | | status | 文档状态 | 数值 | 79 | 80 | > 若文档转换成功,则返回: 81 | 82 | | 参数 | 含义 | 类型 | 83 | | ------ | ----------------- | ------ | 84 | | status | 文档转换后状态 | 数值 | 85 | | fileID | 文档转换后唯一 ID | 字符串 | 86 | 87 | > UploadResult 对象结构如下: 88 | 89 | | 参数 | 含义 | 类型 | 90 | | ------ | ----------------- | ------ | 91 | | status | 文档转换后状态 | 数值 | 92 | | fileID | 文档转换后唯一 ID | 字符串 | 93 | 94 | > status 取值如下: 95 | 96 | | 值 | 含义 | 97 | | --- | -------------------- | 98 | | 1 | 文档未上传 | 99 | | 2 | 文档已上传,但未转换 | 100 | | 4 | 文档排队转换中 | 101 | | 8 | 文档转换中 | 102 | | 16 | 文档转换成功 | 103 | 104 | ## 2 常见错误码 105 | 106 | | code | message | 107 | | ------- | -------------------- | 108 | | 30001 | 请求过于频繁 | 109 | | 60002 | 文档不存在 | 110 | | 60011 | token 无效 | 111 | | 60012 | token 已过期 | 112 | | 60013 | 文档转换请求已处理 | 113 | | 60014 | 不存在的任务 ID | 114 | | 60015 | 不支持的文档类型 | 115 | | 2000002 | 参数错误 | 116 | | 2010003 | 不支持渲染模式 | 117 | | 2020001 | 文档已加密 | 118 | | 2020002 | 文档内容过大 | 119 | | 2020003 | Excel 文档标签数过多 | 120 | | 2020004 | 文档转换失败 | 121 | | 2020005 | 转换任务已取消 | 122 | -------------------------------------------------------------------------------- /docs/docsSharing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 文件共享 9 | 10 | 15 | 16 | 17 | 18 |
19 |
20 |
文件共享基础功能
21 |
22 |
23 |
24 |
25 | 26 |
27 | 29 |
30 |
31 |
32 |
33 |
34 | 35 |
36 | 38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 | 47 |
48 |
49 |
50 | 51 |
52 |
53 |
54 |
55 | 56 | 57 |
58 |
59 |
60 |
61 | 62 |
63 |
64 |
65 |
66 |
67 |
68 | 70 |
71 | 77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 | 87 |
88 | 89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 | 101 | 207 | 208 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/docs/favicon.ico -------------------------------------------------------------------------------- /docs/message/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 29 | 房间消息功能 30 | 31 | 32 | 33 | 34 |
35 | 36 |
37 | 38 |
房间消息功能
39 | 40 |
41 | 42 |
43 |
44 |
45 | 47 |
48 | 50 |
51 |
52 | 53 |
54 |
55 |
56 | 58 |
59 | 61 |
62 |
63 | 64 |
65 |
66 |
67 | 68 |
69 | 71 |
72 |
73 | 74 |
75 |
76 |
77 | 78 |
79 | 81 |
82 |
83 |
84 | 85 |
86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 |
94 |
95 | 96 |
97 |
98 | 99 | 100 |
101 |
102 | 103 |
104 |
105 |
106 | 107 | 112 | 113 |
114 | 115 |
116 |
117 | 118 |
119 |
0
120 |
121 | 122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | 133 |
134 |
135 |
136 |
137 |
138 |
139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/mix/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 24 | 混流 25 | 26 | 27 | 28 | 29 |
30 |
31 | 32 |
混流
33 | 34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 | 43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 |
51 | 53 |
54 |
55 | 56 |
57 |
58 |
59 | 60 |
61 | 63 |
64 |
65 |
66 | 67 |
68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 |
78 | 79 |
80 |
81 | 82 | 83 | 84 | 85 |
86 |
87 | 88 |
89 |
90 | 91 |
92 |
93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/third/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 29 | 第三方音视频推流 30 | 31 | 32 |
33 | 34 |
35 | 36 |
第三方音视频推流
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | 54 |
55 | 57 |
58 |
59 | 60 |
61 |
62 |
63 | 64 |
65 | 67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 | 76 |
77 | 81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 | 90 |
91 |
92 |
93 |
94 |
95 | 96 |
97 | 99 |
100 |
101 | 110 |
111 |
112 |
113 | 114 | 115 | 116 | 117 | 118 | 119 |
120 |
121 | 122 |
123 |
124 | 125 | 126 | 127 | 128 |
129 |
130 | 131 |
132 |
133 |
134 | 135 |
136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /docs/token/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 27 | 推拉流基础功能 28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 |
基础推拉流
36 | 37 |
38 | 39 |
40 |
41 |
42 | 44 |
45 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | 55 |
56 | 58 |
59 |
60 | 61 |
62 | 63 |
64 |
65 |
66 |
67 | 68 |
69 | 71 |
72 | 73 |
74 | 75 |
76 |
77 |
78 | 79 |
80 | 82 |
83 |
84 | 85 |
86 |
87 |
88 | 90 |
91 | 94 |
95 | 96 |
97 |
98 | 99 |
100 |
101 | 102 | 103 | 104 | 105 |
106 |
107 | 108 |
109 |
110 |
当前音量: 0
111 | 112 | 113 |
114 |
115 | 116 |
117 |
118 |
119 | 120 |
121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /docs/vp8/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 27 | vp8<-->h264 28 | 29 | 30 | 31 | 32 |
33 | 34 |
35 | 36 |
混流转码(支持vp8,h264互通)
37 | 38 |
39 | 40 |
41 |
42 |
43 | 45 |
46 | 48 |
49 |
50 | 51 |
52 |
53 |
54 | 56 |
57 | 59 |
60 |
61 | 62 |
63 |
64 |
65 | 66 |
67 | 70 |
71 |
72 | 73 |
74 | 75 |
76 |
77 |
78 |
79 | 80 |
81 | 86 |
87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 |
101 |
102 | 103 | 104 | 105 |
106 |
107 | 108 |
109 |
110 | 111 | 112 |
113 |
114 | 115 |
116 |
117 |
118 |
119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /docs/webrtcCheck/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webrtc测试 10 | 66 | 67 | 68 | 69 | 70 |
71 |
72 | 73 |
74 |
75 |

webrtc测试

77 | 开始测试 79 |
80 |

此网站用于测试浏览器是否支持webrtc

81 |
82 | 83 |
84 |
85 |

webRTC {{state.webrtcState}}

86 |
87 |
88 |
当前浏览器支持webrtc!!!
89 |
当前浏览器暂不支持webrtc!!!
90 |
91 |
92 | 93 |
94 |
95 |

获取设备 {{state.webrtcState}}

96 |
97 |
98 |
当前浏览器支持获取设备!!!
99 |
当前浏览器不支持获取设备!!!
100 |
101 |
102 | 103 |
104 |
105 |

H264编码 {{state.webrtcState}}

106 |
107 |
108 |
当前浏览器支持H264编码!!!
109 |
当前浏览器不支持H264编码!!!
110 |
111 |
112 | 113 |
114 |
115 |

VP8编码 {{state.webrtcState}}

116 |
117 |
118 |
当前浏览器支持VP8编码!!!
119 |
当前浏览器暂不支持VP8编码!!!
120 |
121 |
122 | 123 |
124 |
125 |

音频输入 {{state.audioinputState}}

126 |
127 |
128 |
129 |
检测到音频输入设备!!!
130 | {{audioinputInfos}} 131 |
132 |
134 |
135 |
136 |
137 |
未检测到音频输入设备!!!
138 | 139 |
140 |
141 | 142 |
143 |
144 |

音频输出 {{state.audiooutputState}}

145 |
146 |
147 | 150 |

151 | {{audiooutputInfos}} 152 |

153 |
154 |
155 | 156 |
157 |
158 |

视频输入 {{state.videoinputState}}

159 |
160 |
161 |
162 |
检测到视频输入设备!!!
163 | {{videoinputInfos}} 164 |
165 |
未检测到视频输入设备!!!
166 |
167 |
168 | 169 |
170 |
171 |

分辨率检测 {{state.resolutionState}}

172 |
173 |
174 |

175 | {{item.width}} X {{item.height}}: {{item.resolutionState}} 176 |

177 |
178 |
179 | 180 |
181 | 182 |
183 |

连通性检测 {{state.connectivityState}}

184 |
185 | 186 |
187 |
连通性检测成功!!!
188 |
连通性检测失败!!!
189 |
190 |
191 | 192 |
193 | 194 |
195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /docs/whiteboard/ZegoWhiteboardSDK.md: -------------------------------------------------------------------------------- 1 | ## ZegoWhiteboard Web SDK 2 | 3 | ### 1、接口定义 4 | 5 | #### 1.1.1 获取SDK版本号 6 | 7 | ```typescript 8 | whiteboardManager.getVersion(): string 9 | ``` 10 | 11 | #### 1.1.2 创建白板 12 | 13 | ```typescript 14 | whiteboardManager.createView(options: { 15 | roomID: string, 16 | name: string, 17 | aspectWidth: number, 18 | aspectHeight: number, 19 | pageCount: number, 20 | fileInfo?: { 21 | fileID: string, 22 | fileName: string, 23 | fileType: number, 24 | authKey: string 25 | } 26 | }): Promise 27 | ``` 28 | 29 | | 字段 | 含义 | 是否必填 | 30 | | ----------------------------------------- | ------- | ----------------------------- | 31 | | roomID | roomId | 是 | 32 | | name | 白板名称 | 是 | 33 | | aspectWidth | 等比宽 | 是 | 34 | | aspectHeight | 等比高 | 是 | 35 | | pageCount | 白板页数 | 是 | 36 | | fileInfo | 关联文件信息 | 否 | 37 | 38 | fileInfo参数解释: 39 | 40 | | 字段 | 含义 | 是否必填 | 41 | | ----------------------------------------- | ------- | ----------------------------- | 42 | | fileID | 文件转换完成唯一ID | 是 | 43 | | fileName | 文件名称 | 是 | 44 | | fileType | 文件类型 | 是 | 45 | | authKey | 预留字段 | 否 | 46 | 47 | #### 1.1.3 销毁白板 48 | 49 | ```typescript 50 | whiteboardManager.destroyView(whiteboardView: WhiteboardView): Promise 51 | ``` 52 | 53 | | 字段 | 含义 | 是否必填 | 54 | | ----------------------------------------- | ------- | ----------------------------- | 55 | | whiteboardView | 白板view实例 | 是 | 56 | 57 | #### 1.1.4 添加白板到视图(渲染) 58 | 59 | ```typescript 60 | whiteboardManager.attachView(whiteboardView: WhiteboardView, parent: string): Promise 61 | ``` 62 | 63 | | 字段 | 含义 | 是否必填 | 64 | | ----------------------------------------- | ------- | ----------------------------- | 65 | | whiteboardView | 白板view实例 | 是 | 66 | | parent | 需要挂载的父容器id | 是 | 67 | 68 | #### 1.1.5 获取白板列表 69 | 70 | ```typescript 71 | whiteboardManager.getViewList(): Promise 72 | ``` 73 | 74 | #### 1.1.6 清空白板view 75 | 76 | ```typescript 77 | whiteboardView.clear(): boolean 78 | ``` 79 | 80 | #### 1.1.7 获取该白板view关联的文件信息 81 | 82 | ```typescript 83 | whiteboardView.getFileInfo(): {fileID: string, fileName: string, authKey: string, fileType: number} | undefined 84 | ``` 85 | 86 | #### 1.1.8 设置白板允许涂鸦权限 87 | 88 | ```typescript 89 | whiteboardView.enable(enable: boolean): boolean 90 | ``` 91 | 92 | #### 1.1.9 获取白板允许涂鸦权限 93 | 94 | ```typescript 95 | whiteboardView.IsEnable(): boolean 96 | ``` 97 | 98 | #### 1.1.10 设置白板背景色(color支持16进制、rgba) 99 | 100 | ```typescript 101 | whiteboardView.setBackgroundColor(color: string): boolean 102 | ``` 103 | 104 | #### 1.1.11 获取白板页的背景色 105 | 106 | ```typescript 107 | whiteboardView.getBackgroundColor(): string 108 | ``` 109 | 110 | #### 1.1.12 获取白板页宽高比 111 | 112 | ```typescript 113 | whiteboardView.getAspectRatio(): string 114 | ``` 115 | 116 | #### 1.1.13 设置要使用的白板工具 117 | 118 | ```typescript 119 | whiteboardView.setToolType(type: ViewTool): boolean 120 | ``` 121 | 122 | | 字段 | 含义 | 是否必填 | 123 | | ----------------------------------------- | ------- | ----------------------------- | 124 | | type | 工具类型1:path 涂鸦,2:text 文本框,4:line 线段,8:rect 矩形,16:ellipse 椭圆,32: selector 选择 | 是 | 125 | 126 | #### 1.1.14 获取正在使用的白板工具 127 | 128 | ```typescript 129 | whiteboardView.getToolType(): ViewTool 130 | ``` 131 | 132 | #### 1.1.15 设置画笔颜色 133 | 134 | ```typescript 135 | whiteboardView.setBrushColor(color: string): boolean 136 | ``` 137 | 138 | #### 1.1.16 获取画笔颜色 139 | 140 | ```typescript 141 | whiteboardView.getBrushColor(): string 142 | ``` 143 | 144 | #### 1.1.17 设置画笔粗细 145 | 146 | ```typescript 147 | whiteboardView.setBrushSize(thin: number): boolean 148 | ``` 149 | 150 | | 字段 | 含义 | 是否必填 | 151 | | ----------------------------------------- | ------- | ----------------------------- | 152 | | thin | 画笔粗细,取值1~100 | 是 | 153 | 154 | #### 1.1.18 获取画笔粗细 155 | 156 | ```typescript 157 | whiteboardView.getBrushSize(): number 158 | ``` 159 | 160 | #### 1.1.19 设置文本大小 161 | 162 | ```typescript 163 | whiteboardView.setTextSize(thin: number): boolean 164 | ``` 165 | 166 | | 字段 | 含义 | 是否必填 | 167 | | ----------------------------------------- | ------- | ----------------------------- | 168 | | thin | 文本大小,取值12~100 | 是 | 169 | 170 | #### 1.1.20 获取文本大小 171 | 172 | ```typescript 173 | whiteboardView.getTextSize(): number 174 | ``` 175 | 176 | #### 1.1.21 滚动白板页 177 | 178 | ```typescript 179 | whiteboardView.scroll(horizontalPercent: number, verticalPercent: number): boolean 180 | ``` 181 | 182 | | 字段 | 含义 | 是否必填 | 183 | | ----------------------------------------- | ------- | ----------------------------- | 184 | | horizontalPercent | 水平滚动百分比 | 否 | 185 | | verticalPercent | 垂直滚动百分比 | 否 | 186 | 187 | #### 1.1.22 获取滚动百分比 188 | 189 | ```typescript 190 | whiteboardView.getCurrentScrollPercent(): { horizontalPercent: number; verticalPercent: number } 191 | ``` 192 | 193 | #### 1.1.23 撤销 194 | 195 | ```typescript 196 | whiteboardView.undo(): void 197 | ``` 198 | 199 | #### 1.1.24 重做 200 | 201 | ```typescript 202 | whiteboardView.redo(): void 203 | ``` 204 | 205 | #### 1.1.25 获取白板ID 206 | 207 | ```typescript 208 | whiteboardView.getID(): string 209 | ``` 210 | 211 | #### 1.1.26 获取关联roomID 212 | 213 | ```typescript 214 | whiteboardView.getRoomID(): string 215 | ``` 216 | 217 | #### 1.1.27 获取白板页数 218 | 219 | ```typescript 220 | whiteboardView.getPageCount(): number 221 | ``` 222 | 223 | #### 1.1.28 获取白板当前页码,从1开始 224 | 225 | ```typescript 226 | whiteboardView.getPage(): number 227 | ``` 228 | 229 | ### 2、回调 230 | 231 | #### 2.1.1 错误回调 232 | 233 | ```typescript 234 | whiteboardManager.on('error', (errorData: ErrorData) => {}) 235 | ``` 236 | 237 | | 字段 | 含义 | 238 | | ----------------------------------------- | ------- | 239 | | code | 错误码 | 240 | | msg | 错误描述 | 241 | 242 | #### 2.1.2 创建view 243 | 244 | ```typescript 245 | whiteboardManager.on('viewAdd', (whiteboardView: WhiteboardView) => {}) 246 | ``` 247 | 248 | | 字段 | 含义 | 249 | | ----------------------------------------- | ------- | 250 | | whiteboardView | 白板view实例 | 251 | 252 | #### 2.1.3 销毁view 253 | 254 | ```typescript 255 | whiteboardManager.on('viewRemoved', (whiteboardID: string) => {}) 256 | ``` 257 | 258 | | 字段 | 含义 | 259 | | ----------------------------------------- | ------- | 260 | | whiteboardID | 白板ID | 261 | 262 | #### 2.1.4 滚动view 263 | 264 | ```typescript 265 | whiteboardManager.on('viewScroll', (whiteboardID: string, horizontalPercent: number, verticalPercent: number, page: number) => {}) 266 | ``` 267 | 268 | | 字段 | 含义 | 269 | | ----------------------------------------- | ------- | 270 | | whiteboardID | 白板ID | 271 | | horizontalPercent | 水平滚动百分比 | 272 | | verticalPercent | 垂直滚动百分比 | 273 | | page | 翻页模式时当前页码 | 274 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webrtcdemo", 3 | "version": "1.0.0", 4 | "description": "demo", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "dev": "webpack-dev-server" 9 | }, 10 | "author": "choui", 11 | "license": "ISC", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com:zegodev/zego-express-webrtc-sample.git" 15 | }, 16 | "dependencies": { 17 | "@types/ali-oss": "^6.0.4", 18 | "@types/jquery": "^3.3.33", 19 | "ali-oss": "^6.3.1", 20 | "blueimp-md5": "^2.10.0", 21 | "compressing": "^1.5.0", 22 | "eslint": "^6.5.1", 23 | "flv.js": "^1.5.0", 24 | "http-server": "^0.11.1", 25 | "i18next": "^19.4.2", 26 | "jquery": "^3.4.1", 27 | "jquery-i18next": "^1.2.1", 28 | "md5": "^2.2.1", 29 | "vconsole": "^3.3.4" 30 | }, 31 | "devDependencies": { 32 | "@types/bootstrap": "^4.3.1", 33 | "@types/md5": "^2.2.0", 34 | "@typescript-eslint/eslint-plugin": "^2.3.0", 35 | "@typescript-eslint/parser": "^2.3.0", 36 | "bootstrap": "^3.4.1", 37 | "copy-webpack-plugin": "^6.2.1", 38 | "css-loader": "^3.2.0", 39 | "eslint-config-prettier": "^6.3.0", 40 | "eslint-plugin-prettier": "^3.1.1", 41 | "expose-loader": "^0.7.5", 42 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 43 | "file-loader": "^4.2.0", 44 | "html-webpack-plugin": "^3.2.0", 45 | "internal-ip": "^5.0.0", 46 | "mini-css-extract-plugin": "^0.8.0", 47 | "path": "^0.12.7", 48 | "prettier": "^1.18.2", 49 | "style-loader": "^1.0.0", 50 | "ts-loader": "^6.2.0", 51 | "typescript": "^3.6.4", 52 | "url": "^0.11.0", 53 | "url-loader": "^2.2.0", 54 | "webpack": "^4.41.2", 55 | "webpack-cli": "^3.3.9", 56 | "webpack-dev-server": "^3.8.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/assets/gitee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/src/assets/gitee.png -------------------------------------------------------------------------------- /src/assets/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/src/assets/github.png -------------------------------------------------------------------------------- /src/assets/translation/translation.js: -------------------------------------------------------------------------------- 1 | const en = { 2 | title: 'WebRTC Sample Demo', 3 | base: 'Basic Function', 4 | advanced: 'Advanced', 5 | auxiliary: 'Auxiliary tools', 6 | complete: 'Completed Demo', 7 | api: 'Api documentation', 8 | issues: 'issues', 9 | source: 'source', 10 | jump: 'go', 11 | basicPushPull: 'publish and play', 12 | basicPushPullContent: 'publish and play stream', 13 | pureAudio: 'pure audio', 14 | pureAudioContent: 'publish and play audio only', 15 | thirdVideo: 'Third-party audio and video streaming', 16 | thirdVideoContent: 'Supports pushing and pulling third-party audio and video', 17 | message: 'Real time message', 18 | messageContent: 'real time message', 19 | tokenRole: 'token role', 20 | tokenRoleContent: 'token role', 21 | cdn: 'CDN', 22 | cdnContent: 23 | 'Pull the stream through the CDN, this function needs to apply to the configuration to use before it can also be used to transfer to other CDN', 24 | clickPlay: 'click to play', 25 | addCdn: 'Increase retweet cdn', 26 | removeCdn: 'Delete retweet cdn', 27 | cdnPlay: 'play CDN stream', 28 | pushThird: 'Push third-party videos', 29 | mixStream: 'Mix Stream', 30 | stopMixStream: 'stop mix stream', 31 | mixStreamContent: 'mix stream and play the stream from CDN', 32 | mixAudio: 'mix audio', 33 | mixAudioContent: 34 | 'The mixing function refers to playing background music during the live broadcast, and can also be heard by the pull stream end', 35 | stopMixAudio: 'stop mix audio', 36 | realTimeMix: 'real-time mixing', 37 | stopRealTimeMix: 'stop real-time mixing', 38 | screenSharing: 'Screen Sharing', 39 | stopScreenSharing: 'stop screen sharing', 40 | screenSharingContent: 'screen sharing', 41 | transcode: 'Transcoding', 42 | transcodeContent: 'H264 and VP8 transcoding', 43 | docsSharing: 'File sharing', 44 | docsSharingContent: 'File sharing', 45 | whiteboard: 'whiteboard', 46 | whiteboardContent: 'whiteboard', 47 | detect: 'webRTC detection', 48 | detectContent: 'test whether the page supports webrtc, detect the supported resolution and connectivity', 49 | token: 'token detection', 50 | tokenContent: 'Check if the token is the same as the one generated in the server', 51 | assistDev: 'assist development', 52 | assistDevContent: 'It is used for auxiliary debugging in the development stage of accessing SDK', 53 | audioDevices: 'microphone devices', 54 | videoDevices: 'camera devices', 55 | playMode: 'play mode', 56 | auto: 'auto', 57 | all: 'video and audio', 58 | onlyVideo: 'only video', 59 | onlyAudio: 'only audio', 60 | roomid: 'room ID', 61 | enterRoomPush: 'enterRoom(publish)', 62 | enterRoomPlay: 'enterRoom(noPublish)', 63 | enableCamera: 'enable / disable camera', 64 | enableMicrophone: 'enable / disable microphone', 65 | soundText: 'sound: ', 66 | unenableSound: 'stop soundwave', 67 | resumeSound: 'resume soundwave', 68 | shareScreen: 'screen sharing', 69 | leaveRoom: 'leave room', 70 | userlist: 'staff', 71 | barrage: 'barrage', 72 | customMessage: 'custom message', 73 | videoCodeType: 'debugging example', 74 | assistDevPageTitle: 'Debugging Example', 75 | enterRoom: 'enter room', 76 | startPublish: 'strat publish', 77 | stopPublish: 'stop publish', 78 | tokenDesc: 'Please enter the AppID corresponding to the project in the instant console (Environment Configuration -> Web -> AppID) to experience the sample demo.', 79 | toConsole: 'To ZEGO console' 80 | 81 | }; 82 | 83 | const zh = { 84 | title: '视频互动及通讯功能Demo', 85 | base: '基础功能', 86 | advanced: '进阶功能', 87 | auxiliary: '辅助工具及组件', 88 | complete: '完整 Demo', 89 | api: 'Api 文档', 90 | issues: '常见问题', 91 | source: '源码', 92 | jump: '前往', 93 | basicPushPull: '基础推拉流', 94 | basicPushPullContent: '推流,拉流', 95 | pureAudio: '纯音频', 96 | pureAudioContent: '推拉纯音频流', 97 | thirdVideo: '第三方音视频推流', 98 | thirdVideoContent: '支持推拉第三方的音视频', 99 | message: '实时消息', 100 | messageContent: '实时消息功能', 101 | tokenRole: 'token 鉴权', 102 | tokenRoleContent: 'token 鉴权', 103 | cdn: 'CDN', 104 | cdnContent: '通过CDN拉取流,该功能需向即构申请配置方可使用,也支持转推到其他CDN', 105 | clickPlay: '点击播放', 106 | addCdn: '增加转推cdn', 107 | removeCdn: '删除转推cdn', 108 | cdnPlay: '播放cdn', 109 | pushThird: '推第三方视频', 110 | mixStream: '混流', 111 | stopMixStream: '停止混流', 112 | mixStreamContent: '混流,转推到cdn,页面播放cdn地址流', 113 | mixAudio: '混音', 114 | mixAudioContent: '混音功能指在直播过程中播放背景音乐,并且能够让拉流端也听到', 115 | stopMixAudio: '停止混音', 116 | realTimeMix: '实时混音', 117 | stopRealTimeMix: '停止实时混音', 118 | screenSharing: '推流进阶', 119 | stopScreenSharing: '停止捕捉屏幕', 120 | screenSharingContent: '屏幕共享', 121 | transcode: '混流转码(微信浏览器-safari11互通)', 122 | transcodeContent: '微信浏览器不支持H264,safari11只支持h264,互通需要转码(测试中。。)', 123 | docsSharing: '文件共享', 124 | docsSharingContent: '文件共享', 125 | whiteboard: '互动白板', 126 | whiteboardContent: '互动白板', 127 | detect: 'webrtc检测', 128 | detectContent: '用于测试页面是否支持webrtc,检测所支持分辨率及连通性检测', 129 | token: 'token检测', 130 | tokenContent: '检测token是否与后台生成的相同', 131 | assistDev: '调试示例', 132 | assistDevContent: '用于接入SDK的开发阶段进行辅助调试', 133 | audioDevices: '音频设备', 134 | videoDevices: '视频设备', 135 | playMode: '拉流模式', 136 | auto: '自动', 137 | all: '音视频', 138 | onlyVideo: '只拉视频', 139 | onlyAudio: '只拉音频', 140 | roomid: '房间号', 141 | enterRoomPush: '进入房间(推流)', 142 | enterRoomPlay: '进入房间(不推流)', 143 | enableCamera: '启用/关闭摄像头', 144 | enableMicrophone: '启用/关闭麦克风', 145 | soundText: '当前音量:', 146 | unenableSound: '停止获取音浪', 147 | resumeSound: '恢复获取音浪', 148 | shareScreen: '共享屏幕', 149 | leaveRoom: '退出房间', 150 | userlist: '房间人员', 151 | barrage: '弹幕消息', 152 | customMessage: '发送自定义消息', 153 | videoCodeType: '视频编码', 154 | assistDevPageTitle: '调试示例', 155 | enterRoom: '进入房间', 156 | startPublish: '开始推流', 157 | stopPublish: '停止推流', 158 | tokenDesc: '请输入即构控制台中项目对应的 AppID 和 Token(环境配置 -> Web -> AppID),用以体验示例 demo。', 159 | toConsole: '点击打开 ZEGO 控制台' 160 | }; 161 | 162 | export { en, zh }; 163 | -------------------------------------------------------------------------------- /src/assets/utils.js: -------------------------------------------------------------------------------- 1 | export function addCssByLink(url) { 2 | let doc = document; 3 | let link = doc.createElement('link'); 4 | link.setAttribute('rel', 'stylesheet'); 5 | link.setAttribute('type', 'text/css'); 6 | link.setAttribute('href', url); 7 | 8 | let heads = doc.getElementsByTagName('head'); 9 | if (heads.length) heads[0].appendChild(link); 10 | else doc.documentElement.appendChild(link); 11 | } 12 | 13 | export function loadJs(url, callback) { 14 | let script = document.createElement('script'); 15 | script.type = 'text/javascript'; 16 | if (typeof callback != 'undefined') { 17 | if (script.readyState) { 18 | script.onreadystatechange = function() { 19 | if (script.readyState == 'loaded' || script.readyState == 'complete') { 20 | script.onreadystatechange = null; 21 | callback(); 22 | } 23 | }; 24 | } else { 25 | script.onload = function() { 26 | callback(); 27 | }; 28 | } 29 | } 30 | script.src = url; 31 | document.body.appendChild(script); 32 | } 33 | 34 | export function getBrowser() { 35 | let ua = window.navigator.userAgent; 36 | let isWechat = ua.toLowerCase().match(/MicroMessenger/i) == "micromessenger"; 37 | let isIE = window['ActiveXObject'] != undefined && ua.indexOf('MSIE') != -1; 38 | let isFirefox = ua.indexOf('Firefox') != -1; 39 | let isOpera = window['opr'] != undefined; 40 | let isChrome = ua.indexOf('Chrome') && window['chrome']; 41 | let isSafari = ua.indexOf('Safari') != -1 && ua.indexOf('Version') != -1; 42 | if (isWechat) { 43 | return 'Wechat'; 44 | } else if (isIE) { 45 | return 'IE'; 46 | } else if (isFirefox) { 47 | return 'Firefox'; 48 | } else if (isOpera) { 49 | return 'Opera'; 50 | } else if (isChrome) { 51 | return 'Chrome'; 52 | } else if (isSafari) { 53 | return 'Safari'; 54 | } else { 55 | return 'Unkown'; 56 | } 57 | } 58 | 59 | export function IsPC() { 60 | return ![ 'Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod' ].some( 61 | (item) => navigator.userAgent.indexOf(item) > 0 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/assistDev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 36 | 推拉流基础功能 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 |
基础推拉流
45 |
46 | 请输入即构控制台中项目对应的 AppID -> Web -> AppID),用以体验示例 demo。 47 | 点击打开ZEGO控制台 48 |
49 | 50 |
51 | 52 | 66 | 67 |
68 | 69 |
70 |
71 |
72 |
73 | 74 |
75 | 77 |
78 |
79 | 88 |
89 |
90 |
91 |
92 |
93 | 94 |
95 | 97 |
98 |
99 |
100 |
101 |
102 | 103 |
104 | 106 |
107 |
108 |
109 |
110 |
111 | 112 |
113 | 115 |
116 |
117 |
118 | 119 |
120 |
121 |
122 |
123 | 124 |
125 | 127 |
128 |
129 |
130 | 131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 |
139 | 140 | 141 |
142 |
143 | 144 |
145 |
146 |
当前音量: 0
147 | 148 | 149 |
150 |
151 | 152 |
153 |
154 |
155 | 156 |
157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /src/audio/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 24 | 推拉纯音频流 25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 |
推拉纯音频流
33 | 34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 | 43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 |
51 | 53 |
54 |
55 | 56 | 57 |
58 |
59 |
60 | 61 |
62 | 64 |
65 |
66 | 67 | 68 |
69 | 70 |
71 |
72 | 73 | 74 | 75 |
76 |
77 | 78 |
79 |
80 | 81 | 82 |
83 |
84 | 85 |
86 |
87 |
88 |
89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/audio/index.js: -------------------------------------------------------------------------------- 1 | import { checkAnRun, enterRoom, publish, zg, useLocalStreamList } from '../common'; 2 | 3 | $(async () => { 4 | await checkAnRun(); 5 | zg.off('roomStreamUpdate'); 6 | zg.on('roomStreamUpdate', async (roomID, updateType, streamList, extendedData) => { 7 | console.log('roomStreamUpdate roomID ', roomID, streamList, extendedData); 8 | if (updateType == 'ADD') { 9 | for (let i = 0; i < streamList.length; i++) { 10 | console.info(streamList[i].streamID + ' was added'); 11 | let remoteStream; 12 | 13 | try { 14 | remoteStream = await zg.startPlayingStream(streamList[i].streamID, { 15 | video: false, 16 | }); 17 | useLocalStreamList.push(streamList[i]); 18 | 19 | $('.remoteVideo').append($(``)); 20 | const audio = $('.remoteVideo audio:last')[0]; 21 | console.warn('audio', audio, remoteStream); 22 | audio.srcObject = remoteStream; 23 | audio.muted = false; 24 | } catch (error) { 25 | console.error(error); 26 | continue; 27 | } 28 | 29 | } 30 | } else if (updateType == 'DELETE') { 31 | for (let k = 0; k < useLocalStreamList.length; k++) { 32 | for (let j = 0; j < streamList.length; j++) { 33 | if (useLocalStreamList[k].streamID === streamList[j].streamID) { 34 | try { 35 | zg.stopPlayingStream(useLocalStreamList[k].streamID); 36 | } catch (error) { 37 | console.error(error); 38 | } 39 | 40 | console.info(useLocalStreamList[k].streamID + 'was devared'); 41 | 42 | 43 | $('.remoteVideo audio:eq(' + k + ')').remove(); 44 | useLocalStreamList.splice(k--, 1); 45 | 46 | break; 47 | } 48 | } 49 | } 50 | } 51 | }); 52 | 53 | $('#createRoom').unbind('click'); 54 | $('#createRoom').click(async () => { 55 | let loginSuc = false; 56 | try { 57 | loginSuc = await enterRoom(); 58 | loginSuc && (await publish({ camera: { video: false } })); 59 | } catch (error) { 60 | console.error(error); 61 | } 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/audioMixing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 28 | 混音 29 | 30 | 31 |
32 | 33 |
34 | 35 |
混音
36 | 37 |
38 | 39 |
40 |
41 |
42 | 44 |
45 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | 55 |
56 | 58 |
59 |
60 | 61 | 62 |
63 |
64 |
65 | 66 |
67 | 69 |
70 |
71 | 72 |
73 | 74 |
75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 |
93 | 94 |
95 | 96 | 音量 97 | 98 | 音量 99 |
100 |
101 |
102 | 103 | 104 |
105 |
106 |
107 |
108 |
109 | 110 |
111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/audioMixing/index.js: -------------------------------------------------------------------------------- 1 | import { checkAnRun, zg, publishStreamId, logout } from '../common'; 2 | 3 | $(async () => { 4 | let isMixingAudio = false; 5 | let isMixingBuffer = false; 6 | const audioEffectList = [ 7 | { 8 | effectId: '1', 9 | path: 'https://zego-public.oss-cn-shanghai.aliyuncs.com/sdk-doc/assets/station.mp3', 10 | }, 11 | ]; 12 | await checkAnRun(); 13 | $('#MixAudio').click(() => { 14 | const result = zg.startMixingAudio(publishStreamId, [ 15 | $('#extenerVideo1')[0] , 16 | $('#extenerVideo2')[0] , 17 | ]); 18 | console.warn('混音', result); 19 | }); 20 | 21 | $('#stopMixAudio').click(() => { 22 | zg.stopMixingAudio(publishStreamId); 23 | }); 24 | 25 | $('#volume1').on('input', () => { 26 | // @ts-ignore 27 | zg.setMixingAudioVolume(publishStreamId, parseInt($('#volume1').val()), $( 28 | '#extenerVideo1', 29 | )[0] ); 30 | }); 31 | 32 | $('#volume2').on('input', () => { 33 | // @ts-ignore 34 | zg.setMixingAudioVolume(publishStreamId, parseInt($('#volume2').val()), $( 35 | '#extenerVideo2', 36 | )[0] ); 37 | }); 38 | 39 | $('#mixingBuffer').click(function() { 40 | const xhr = new XMLHttpRequest(); 41 | 42 | xhr.open('GET', 'https://storage.zego.im/demo/tonight.m4a', true); 43 | xhr.responseType = 'arraybuffer'; 44 | xhr.onload = () => { 45 | if (xhr.status == 200 || xhr.status == 304) { 46 | const buffer = xhr.response; 47 | zg.zegoWebRTC.mixingBuffer(publishStreamId, '1', buffer, (err) => { 48 | if (err) { 49 | console.error(err); 50 | } else { 51 | isMixingBuffer = true; 52 | console.warn('real time effect success'); 53 | } 54 | }); 55 | } 56 | }; 57 | 58 | xhr.send(); 59 | }); 60 | 61 | $('#stopMixingBuffer').click(function() { 62 | zg.zegoWebRTC.stopMixingBuffer(publishStreamId, '1'); 63 | }); 64 | 65 | $('#leaveMixRoom').click(function() { 66 | isMixingAudio && zg.stopMixingAudio(publishStreamId); 67 | isMixingBuffer && zg.zegoWebRTC.stopMixingBuffer(publishStreamId, '1'); 68 | isMixingAudio = false; 69 | isMixingBuffer = false; 70 | logout(); 71 | }); 72 | 73 | $('#preloadEffect').click(() => { 74 | audioEffectList.forEach(effect => { 75 | zg.zegoWebRTC.preloadEffect(effect.effectId, effect.path, () => { 76 | console.warn('preload success'); 77 | 78 | $('#playEffect')[0].disabled = false; 79 | 80 | $('#unloadEffect')[0].disabled = false; 81 | }); 82 | }); 83 | }); 84 | 85 | $('#playEffect').click(() => { 86 | zg.zegoWebRTC.playEffect( 87 | { 88 | streamID: publishStreamId, 89 | effectID: '1', 90 | }, 91 | () => { 92 | isMixingAudio = true; 93 | 94 | $('#pauseEffect')[0].disabled = false; 95 | 96 | $('#resumeEffect')[0].disabled = false; 97 | 98 | $('#stopEffect')[0].disabled = false; 99 | console.warn('start play'); 100 | }, 101 | () => { 102 | isMixingAudio = false; 103 | 104 | $('#pauseEffect')[0].disabled = true; 105 | 106 | $('#resumeEffect')[0].disabled = true; 107 | 108 | $('#stopEffect')[0].disabled = true; 109 | console.warn('play end'); 110 | }, 111 | ); 112 | }); 113 | 114 | $('#pauseEffect').click(() => { 115 | zg.zegoWebRTC.pauseEffect(publishStreamId); 116 | }); 117 | 118 | $('#resumeEffect').click(() => { 119 | zg.zegoWebRTC.resumeEffect(publishStreamId); 120 | }); 121 | 122 | $('#stopEffect').click(() => { 123 | zg.zegoWebRTC.stopEffect(publishStreamId); 124 | 125 | $('#pauseEffect')[0].disabled = true; 126 | 127 | $('#resumeEffect')[0].disabled = true; 128 | 129 | $('#stopEffect')[0].disabled = true; 130 | }); 131 | 132 | $('#unloadEffect').click(() => { 133 | let num = 0; 134 | audioEffectList.forEach(effect => { 135 | zg.zegoWebRTC.unloadEffect(effect.effectId) && num++; 136 | }); 137 | 138 | if (num === audioEffectList.length) { 139 | console.warn('all unload success'); 140 | 141 | $('#playEffect')[0].disabled = true; 142 | 143 | $('#unloadEffect')[0].disabled = true; 144 | } 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /src/cdn/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 23 | CDN 24 | 25 | 26 | 27 |
28 |
29 |
CDN
30 | 31 |
32 |
33 |
34 |
35 | 38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 |
46 | 49 |
50 | 51 |
52 |
53 | 54 |
55 |
56 |
57 | 60 |
61 | 69 |
70 |
71 |
72 |
73 | 90 |
91 |
92 |
93 | 94 |
95 | 99 |
100 |
101 |
102 | 103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
113 |
114 | 115 |
116 |
117 | 118 | 119 |
120 |
121 | 122 |
123 |
124 |
125 | 126 |
127 | 128 |
129 | 135 |
136 |
137 |
138 | 139 | 140 | -------------------------------------------------------------------------------- /src/content.js: -------------------------------------------------------------------------------- 1 | import './assets/bootstrap.min'; 2 | import './assets/bootstrap.min.css'; 3 | import $ from 'jquery'; 4 | import i18n from 'i18next'; 5 | import { en, zh } from './assets/translation/translation'; 6 | 7 | const resources = { 8 | en: { 9 | translation: en, 10 | }, 11 | zh: { 12 | translation: zh, 13 | }, 14 | }; 15 | 16 | i18n.init({ 17 | resources, 18 | }).then(t => { 19 | for (const key in en) { 20 | $(`.${key}`).html(i18n.t(key)); 21 | } 22 | }); 23 | 24 | location.search.substr(1).indexOf('lang=en') > -1 ? i18n.changeLanguage('en') : i18n.changeLanguage('zh'); 25 | 26 | export function getCgi(appId, serverUrl, cgi) { 27 | // 测试用代码,开发者请忽略 28 | // Test code, developers please ignore 29 | let appID = appId; 30 | let server = serverUrl; 31 | let cgiToken = cgi; 32 | let userID = ""; 33 | let l3 = false; 34 | let isPeer = false; 35 | if (location.search) { 36 | const arrConfig = location.search.substr(1).split('&'); 37 | 38 | arrConfig.forEach(function(item) { 39 | const key = item.split('=')[0], 40 | value = item.split('=')[1]; 41 | 42 | if (key == 'appid') { 43 | appID = Number(value); 44 | } 45 | 46 | if (key == 'server') { 47 | const _server = decodeURIComponent(value); 48 | console.warn('server', _server); 49 | const _serArr = _server.split('|'); 50 | if (_serArr.length > 1) { 51 | server = _serArr; 52 | } else { 53 | server = _server; 54 | } 55 | console.warn('server', server); 56 | } 57 | 58 | if (key == 'cgi_token') { 59 | cgiToken = decodeURIComponent(value); 60 | } 61 | 62 | if (key == 'user_id') { 63 | userID = value; 64 | } 65 | 66 | if (key == 'l3') { 67 | l3 = decodeURIComponent(value) == 'true' ? true : false; 68 | } 69 | 70 | if (key == 'isPeer') { 71 | isPeer = decodeURIComponent(value) == 'false' ? false : true; 72 | } 73 | }); 74 | } 75 | return { appID, server, cgiToken, userID, l3, isPeer }; 76 | // 测试用代码 end 77 | // Test code end 78 | } 79 | -------------------------------------------------------------------------------- /src/docsSharing/ZegoDocsSDK.md: -------------------------------------------------------------------------------- 1 | # ZegoDocs Web SDK API 文档 2 | 3 | ## 1 系统接口 4 | 5 | ### 1.1 初始化 6 | 7 | > new ZegoDocs(config) 8 | 9 | > config 对象结构如下: 10 | 11 | | 参数 | 含义 | 类型 | 是否必填 | 12 | | ------ | ------------------------------ | ------ | -------- | 13 | | appID | appID | 数值 | 必填 | 14 | | userID | 用户 ID,最大 128 字节的字符串 | 字符串 | 必填 | 15 | | token | 验证 token | 字符串 | 必填 | 16 | 17 | ### 1.2 设置环境 18 | 19 | > zegoDocs.setTestEnv(env) 20 | 21 | > SDK 默认是正式环境,env 为 true 则切换为测试环境,env 为 false 则切换为正式环境 22 | 23 | ### 1.3 加载文档内容 24 | 25 | > zegoDocs.load(option):Promise 26 | 27 | > option 对象结构如下: 28 | 29 | | 参数 | 含义 | 类型 | 是否必填 | 30 | | ------ | ----------------- | ------ | -------- | 31 | | fileID | 文档转换后唯一 ID | 字符串 | 必填 | 32 | 33 | > LoadResult 对象结构如下: 34 | 35 | | 参数 | 含义 | 类型 | 36 | | --------- | ------------------------------------------------------ | ------ | 37 | | pageCount | 文档总页数 | 数值 | 38 | | fileUrls | 转换完成图片地址列表 | 数组 | 39 | | notes | 备注列表,与图片列表对应,空''表示当前图片对应备注为空 | 数组 | 40 | | fileSize | 源文档大小 | 数值 | 41 | | fileHash | 源文档 hash | 字符串 | 42 | 43 | ### 1.4 上传文档 44 | 45 | > zegoDocs.upload(option):Promise 46 | 47 | > option 对象结构如下: 48 | 49 | | 参数 | 含义 | 类型 | 是否必填 | 50 | | ---------- | ------------------------------------------------------------ | ----------------- | -------- | 51 | | file | File 对象(web)或者文档路径(小程序) | File 对象或字符串 | 必填 | 52 | | renderType | 渲染模式 1:Vector 向量模式 2:IMG 图片模式 3:VectorAndIMG 向量和图片模式 6:DynamicPPTH5 动态PPT模式 | 数值 | 必填 | 53 | | onProgress | 上传进度监听函数 | Function | 选填 | 54 | 55 | > 渲染模式详细解释: 56 | 57 | | 渲染模式 | 描述 | 58 | | --------------------------- | ------------------------------------------------------------ | 59 | | Vector 向量模式 | 适用于文件共享 SDK PC 端、移动端做的文档智能转换, 能够更换地支持文档浏览以及更清晰的缩放。 | 60 | | IMG 图片模式 | 适用于文件共享 SDK Web 端、小程序使用的文档转换,按文件样式每一页都生成图片样式。 | 61 | | VectorAndIMG 向量和图片模式 | 适用于当存在 Web 端、小程序、移动端、PC 端,多端的并存情况,在 web 端、小程序将使用 IMG 图片模式, 而 PC 端、移动端将使用 Vector 向量模式 | 62 | | DynamicPPTH5 动态PPT模式 | 适用于文件共享SDK PC端、移动端、Web端、小程序使用的PPT文件转码格式, 会将PPT文件转成动态PPT的H5文件 | 63 | 64 | > onProgress(data),data 返回如下: 65 | 66 | > 若文档未上传、上传成功未转换,则返回: 67 | 68 | | 参数 | 含义 | 类型 | 69 | | -------- | ----------- | ------ | 70 | | fileHash | 源文档 hash | 字符串 | 71 | | status | 文档状态 | 数值 | 72 | 73 | > 若文档排队转换中、转换中,则返回: 74 | 75 | | 参数 | 含义 | 类型 | 76 | | ------ | --------------- | ------ | 77 | | taskID | 文档转换任务 ID | 字符串 | 78 | | status | 文档状态 | 数值 | 79 | 80 | > 若文档转换成功,则返回: 81 | 82 | | 参数 | 含义 | 类型 | 83 | | ------ | ----------------- | ------ | 84 | | status | 文档转换后状态 | 数值 | 85 | | fileID | 文档转换后唯一 ID | 字符串 | 86 | 87 | > UploadResult 对象结构如下: 88 | 89 | | 参数 | 含义 | 类型 | 90 | | ------ | ----------------- | ------ | 91 | | status | 文档转换后状态 | 数值 | 92 | | fileID | 文档转换后唯一 ID | 字符串 | 93 | 94 | > status 取值如下: 95 | 96 | | 值 | 含义 | 97 | | --- | -------------------- | 98 | | 1 | 文档未上传 | 99 | | 2 | 文档已上传,但未转换 | 100 | | 4 | 文档排队转换中 | 101 | | 8 | 文档转换中 | 102 | | 16 | 文档转换成功 | 103 | 104 | ## 2 常见错误码 105 | 106 | | code | message | 107 | | ------- | -------------------- | 108 | | 30001 | 请求过于频繁 | 109 | | 60002 | 文档不存在 | 110 | | 60011 | token 无效 | 111 | | 60012 | token 已过期 | 112 | | 60013 | 文档转换请求已处理 | 113 | | 60014 | 不存在的任务 ID | 114 | | 60015 | 不支持的文档类型 | 115 | | 2000002 | 参数错误 | 116 | | 2010003 | 不支持渲染模式 | 117 | | 2020001 | 文档已加密 | 118 | | 2020002 | 文档内容过大 | 119 | | 2020003 | Excel 文档标签数过多 | 120 | | 2020004 | 文档转换失败 | 121 | | 2020005 | 转换任务已取消 | 122 | -------------------------------------------------------------------------------- /src/docsSharing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 文件共享 9 | 10 | 15 | 16 | 17 | 18 |
19 |
20 |
文件共享基础功能
21 |
22 |
23 |
24 |
25 | 26 |
27 | 29 |
30 |
31 |
32 |
33 |
34 | 35 |
36 | 38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 | 47 |
48 |
49 |
50 | 51 |
52 |
53 |
54 |
55 | 56 | 57 |
58 |
59 |
60 |
61 | 62 |
63 |
64 |
65 |
66 |
67 |
68 | 70 |
71 | 77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 | 87 |
88 | 89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 | 101 | 207 | 208 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/src/favicon.ico -------------------------------------------------------------------------------- /src/message/font_Icon/demo.css: -------------------------------------------------------------------------------- 1 | *{margin: 0;padding: 0;list-style: none;} 2 | /* 3 | KISSY CSS Reset 4 | 理念:1. reset 的目的不是清除浏览器的默认样式,这仅是部分工作。清除和重置是紧密不可分的。 5 | 2. reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。 6 | 3. reset 期望提供一套普适通用的基础样式。但没有银弹,推荐根据具体需求,裁剪和修改后再使用。 7 | 特色:1. 适应中文;2. 基于最新主流浏览器。 8 | 维护:玉伯, 正淳 9 | */ 10 | 11 | /** 清除内外边距 **/ 12 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */ 13 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */ 14 | pre, /* text formatting elements 文本格式元素 */ 15 | form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */ 16 | th, td /* table elements 表格元素 */ { 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | /** 设置默认字体 **/ 22 | body, 23 | button, input, select, textarea /* for ie */ { 24 | font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif; 25 | } 26 | h1, h2, h3, h4, h5, h6 { font-size: 100%; } 27 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 28 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */ 29 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 30 | 31 | /** 重置列表元素 **/ 32 | ul, ol { list-style: none; } 33 | 34 | /** 重置文本格式元素 **/ 35 | a { text-decoration: none; } 36 | a:hover { text-decoration: underline; } 37 | 38 | 39 | /** 重置表单元素 **/ 40 | legend { color: #000; } /* for ie6 */ 41 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */ 42 | button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */ 43 | /* 注:optgroup 无法扶正 */ 44 | 45 | /** 重置表格元素 **/ 46 | table { border-collapse: collapse; border-spacing: 0; } 47 | 48 | /* 清除浮动 */ 49 | .ks-clear:after, .clear:after { 50 | content: '\20'; 51 | display: block; 52 | height: 0; 53 | clear: both; 54 | } 55 | .ks-clear, .clear { 56 | *zoom: 1; 57 | } 58 | 59 | .main { 60 | padding: 30px 100px; 61 | width: 960px; 62 | margin: 0 auto; 63 | } 64 | .main h1{font-size:36px; color:#333; text-align:left;margin-bottom:30px; border-bottom: 1px solid #eee;} 65 | 66 | .helps{margin-top:40px;} 67 | .helps pre{ 68 | padding:20px; 69 | margin:10px 0; 70 | border:solid 1px #e7e1cd; 71 | background-color: #fffdef; 72 | overflow: auto; 73 | } 74 | 75 | .icon_lists{ 76 | width: 100% !important; 77 | 78 | } 79 | 80 | .icon_lists li{ 81 | float:left; 82 | width: 100px; 83 | height:180px; 84 | text-align: center; 85 | list-style: none !important; 86 | } 87 | .icon_lists .icon{ 88 | font-size: 42px; 89 | line-height: 100px; 90 | margin: 10px 0; 91 | color:#333; 92 | -webkit-transition: font-size 0.25s ease-out 0s; 93 | -moz-transition: font-size 0.25s ease-out 0s; 94 | transition: font-size 0.25s ease-out 0s; 95 | 96 | } 97 | .icon_lists .icon:hover{ 98 | font-size: 100px; 99 | } 100 | 101 | 102 | 103 | .markdown { 104 | color: #666; 105 | font-size: 14px; 106 | line-height: 1.8; 107 | } 108 | 109 | .highlight { 110 | line-height: 1.5; 111 | } 112 | 113 | .markdown img { 114 | vertical-align: middle; 115 | max-width: 100%; 116 | } 117 | 118 | .markdown h1 { 119 | color: #404040; 120 | font-weight: 500; 121 | line-height: 40px; 122 | margin-bottom: 24px; 123 | } 124 | 125 | .markdown h2, 126 | .markdown h3, 127 | .markdown h4, 128 | .markdown h5, 129 | .markdown h6 { 130 | color: #404040; 131 | margin: 1.6em 0 0.6em 0; 132 | font-weight: 500; 133 | clear: both; 134 | } 135 | 136 | .markdown h1 { 137 | font-size: 28px; 138 | } 139 | 140 | .markdown h2 { 141 | font-size: 22px; 142 | } 143 | 144 | .markdown h3 { 145 | font-size: 16px; 146 | } 147 | 148 | .markdown h4 { 149 | font-size: 14px; 150 | } 151 | 152 | .markdown h5 { 153 | font-size: 12px; 154 | } 155 | 156 | .markdown h6 { 157 | font-size: 12px; 158 | } 159 | 160 | .markdown hr { 161 | height: 1px; 162 | border: 0; 163 | background: #e9e9e9; 164 | margin: 16px 0; 165 | clear: both; 166 | } 167 | 168 | .markdown p, 169 | .markdown pre { 170 | margin: 1em 0; 171 | } 172 | 173 | .markdown > p, 174 | .markdown > blockquote, 175 | .markdown > .highlight, 176 | .markdown > ol, 177 | .markdown > ul { 178 | width: 80%; 179 | } 180 | 181 | .markdown ul > li { 182 | list-style: circle; 183 | } 184 | 185 | .markdown > ul li, 186 | .markdown blockquote ul > li { 187 | margin-left: 20px; 188 | padding-left: 4px; 189 | } 190 | 191 | .markdown > ul li p, 192 | .markdown > ol li p { 193 | margin: 0.6em 0; 194 | } 195 | 196 | .markdown ol > li { 197 | list-style: decimal; 198 | } 199 | 200 | .markdown > ol li, 201 | .markdown blockquote ol > li { 202 | margin-left: 20px; 203 | padding-left: 4px; 204 | } 205 | 206 | .markdown code { 207 | margin: 0 3px; 208 | padding: 0 5px; 209 | background: #eee; 210 | border-radius: 3px; 211 | } 212 | 213 | .markdown pre { 214 | border-radius: 6px; 215 | background: #f7f7f7; 216 | padding: 20px; 217 | } 218 | 219 | .markdown pre code { 220 | border: none; 221 | background: #f7f7f7; 222 | margin: 0; 223 | } 224 | 225 | .markdown strong, 226 | .markdown b { 227 | font-weight: 600; 228 | } 229 | 230 | .markdown > table { 231 | border-collapse: collapse; 232 | border-spacing: 0px; 233 | empty-cells: show; 234 | border: 1px solid #e9e9e9; 235 | width: 95%; 236 | margin-bottom: 24px; 237 | } 238 | 239 | .markdown > table th { 240 | white-space: nowrap; 241 | color: #333; 242 | font-weight: 600; 243 | 244 | } 245 | 246 | .markdown > table th, 247 | .markdown > table td { 248 | border: 1px solid #e9e9e9; 249 | padding: 8px 16px; 250 | text-align: left; 251 | } 252 | 253 | .markdown > table th { 254 | background: #F7F7F7; 255 | } 256 | 257 | .markdown blockquote { 258 | font-size: 90%; 259 | color: #999; 260 | border-left: 4px solid #e9e9e9; 261 | padding-left: 0.8em; 262 | margin: 1em 0; 263 | font-style: italic; 264 | } 265 | 266 | .markdown blockquote p { 267 | margin: 0; 268 | } 269 | 270 | .markdown .anchor { 271 | opacity: 0; 272 | transition: opacity 0.3s ease; 273 | margin-left: 8px; 274 | } 275 | 276 | .markdown .waiting { 277 | color: #ccc; 278 | } 279 | 280 | .markdown h1:hover .anchor, 281 | .markdown h2:hover .anchor, 282 | .markdown h3:hover .anchor, 283 | .markdown h4:hover .anchor, 284 | .markdown h5:hover .anchor, 285 | .markdown h6:hover .anchor { 286 | opacity: 1; 287 | display: inline-block; 288 | } 289 | 290 | .markdown > br, 291 | .markdown > p > br { 292 | clear: both; 293 | } 294 | 295 | 296 | .hljs { 297 | display: block; 298 | background: white; 299 | padding: 0.5em; 300 | color: #333333; 301 | overflow-x: auto; 302 | } 303 | 304 | .hljs-comment, 305 | .hljs-meta { 306 | color: #969896; 307 | } 308 | 309 | .hljs-string, 310 | .hljs-variable, 311 | .hljs-template-variable, 312 | .hljs-strong, 313 | .hljs-emphasis, 314 | .hljs-quote { 315 | color: #df5000; 316 | } 317 | 318 | .hljs-keyword, 319 | .hljs-selector-tag, 320 | .hljs-type { 321 | color: #a71d5d; 322 | } 323 | 324 | .hljs-literal, 325 | .hljs-symbol, 326 | .hljs-bullet, 327 | .hljs-attribute { 328 | color: #0086b3; 329 | } 330 | 331 | .hljs-section, 332 | .hljs-name { 333 | color: #63a35c; 334 | } 335 | 336 | .hljs-tag { 337 | color: #333333; 338 | } 339 | 340 | .hljs-title, 341 | .hljs-attr, 342 | .hljs-selector-id, 343 | .hljs-selector-class, 344 | .hljs-selector-attr, 345 | .hljs-selector-pseudo { 346 | color: #795da3; 347 | } 348 | 349 | .hljs-addition { 350 | color: #55a532; 351 | background-color: #eaffea; 352 | } 353 | 354 | .hljs-deletion { 355 | color: #bd2c00; 356 | background-color: #ffecec; 357 | } 358 | 359 | .hljs-link { 360 | text-decoration: underline; 361 | } 362 | 363 | pre{ 364 | background: #fff; 365 | } 366 | 367 | 368 | 369 | 370 | 371 | -------------------------------------------------------------------------------- /src/message/font_Icon/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/src/message/font_Icon/iconfont.eot -------------------------------------------------------------------------------- /src/message/font_Icon/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/src/message/font_Icon/iconfont.ttf -------------------------------------------------------------------------------- /src/message/font_Icon/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/src/message/font_Icon/iconfont.woff -------------------------------------------------------------------------------- /src/message/img/icon01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/src/message/img/icon01.png -------------------------------------------------------------------------------- /src/message/img/icon02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/src/message/img/icon02.png -------------------------------------------------------------------------------- /src/message/img/icon03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zegodev/zego-express-webrtc-sample/42f4e15e1010173289aa68edd3d6592da6684df9/src/message/img/icon03.png -------------------------------------------------------------------------------- /src/message/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 29 | 房间消息功能 30 | 31 | 32 | 33 | 34 |
35 | 36 |
37 | 38 |
房间消息功能
39 | 40 |
41 | 42 |
43 |
44 |
45 | 47 |
48 | 50 |
51 |
52 | 53 |
54 |
55 |
56 | 58 |
59 | 61 |
62 |
63 | 64 |
65 |
66 |
67 | 68 |
69 | 71 |
72 |
73 | 74 |
75 |
76 |
77 | 78 |
79 | 81 |
82 |
83 |
84 | 85 |
86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 |
94 |
95 | 96 |
97 |
98 | 99 | 100 |
101 |
102 | 103 |
104 |
105 |
106 | 107 | 112 | 113 |
114 | 115 |
116 |
117 | 118 |
119 |
0
120 |
121 | 122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | 133 |
134 |
135 |
136 |
137 |
138 |
139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /src/message/index.js: -------------------------------------------------------------------------------- 1 | import '../common'; 2 | import 'popper.js'; 3 | import './css/chat.css'; 4 | import './font_Icon/iconfont.css'; 5 | import { checkAnRun, zg, userID, logout, loginRoom } from '../common'; 6 | 7 | 8 | let msgCount = 0; 9 | let localUserList= []; 10 | $(async () => { 11 | await checkAnRun(); 12 | 13 | zg.on( 14 | 'IMRecvBroadcastMessage', 15 | (_roomID, chatData) => { 16 | console.log('IMRecvBroadcastMessage roomID ', _roomID); 17 | const chatBox = ` 18 |
19 |
${new Date().toLocaleString()}
20 |
21 |
头像
22 |
${chatData[0].message}
23 |
24 |
25 | `; 26 | 27 | $('.chatBox-content-demo').append(chatBox); 28 | //发送后清空输入框 29 | $('.div-textarea').html(''); 30 | //聊天框默认最底部 31 | $('#chatBox-content-demo').scrollTop($('#chatBox-content-demo')[0].scrollHeight); 32 | 33 | msgCount++; 34 | $('.chat-message-num').text(msgCount); 35 | $('.chatBox').show(); 36 | $('.chatBox-kuang').show(); 37 | }, 38 | ); 39 | 40 | zg.on( 41 | 'IMRecvBarrageMessage', 42 | (_roomID, chatData) => { 43 | console.log('IMRecvBarrageMessage roomID ', _roomID, chatData); 44 | $('#toastBody').text(`IMRecvBarrageMessage from ${chatData[0].fromUser.userID} message: ${chatData[0].message}`); 45 | $('#toast')[0].className = "toast fade show" 46 | }, 47 | ); 48 | zg.on('IMRecvCustomCommand', (_roomID, fromUser, command) => { 49 | console.log('IMRecvCustomCommand roomID ', _roomID, ' ', fromUser.userID, ' send ', command); 50 | $('#toastBody').text( 'IMRecvCustomCommand from' + ' ' + fromUser.userID + ' send ' + command); 51 | $('#toast')[0].className = "toast fade show" 52 | }); 53 | zg.off('roomUserUpdate'); 54 | zg.on('roomUserUpdate', (roomID, updateType, userList) => { 55 | console.warn( 56 | `roomUserUpdate: room ${roomID}, user ${updateType === 'ADD' ? 'added' : 'left'} `, 57 | JSON.stringify(userList), 58 | ); 59 | if (updateType === 'ADD') { 60 | localUserList.push(...userList); 61 | } else if (updateType === 'DELETE') { 62 | userList.forEach(user => { 63 | localUserList = localUserList.filter(item => item.userID !== user.userID); 64 | }); 65 | } 66 | let userListHtml = ''; 67 | localUserList.forEach(user => { 68 | user.userID !== userID && (userListHtml += ``); 69 | }); 70 | $('#memberList').html(userListHtml); 71 | }); 72 | zg.on('roomExtraInfoUpdate', (roomID, extraInfoList) => { 73 | console.warn(`roomExtraInfo: room ${roomID} `, extraInfoList); 74 | $('#toastBody').text(`${extraInfoList[0].key} ${extraInfoList[0].value}`) 75 | $('#toast')[0].className = "toast fade show" 76 | }); 77 | $('.chatBox').hide(); 78 | 79 | //打开/关闭聊天框 80 | $('.chatBtn').click(function() { 81 | $('.chatBox').toggle(); 82 | $('.chatBox-kuang').toggle(); 83 | 84 | //聊天框默认最底部 85 | $('#chatBox-content-demo').scrollTop($('#chatBox-content-demo')[0].scrollHeight); 86 | }); 87 | 88 | // 发送信息 89 | $('.div-textarea').bind('keydown', e => { 90 | e.keyCode == 13 && $('#chat-fasong').click() && e.preventDefault(); 91 | }); 92 | 93 | $('#chat-fasong').click(async () => { 94 | if (!loginRoom) { 95 | alert('no login rooom'); 96 | return; 97 | } 98 | const textContent = $('.div-textarea') 99 | .html() 100 | .replace(/[\n\r]/g, '
'); 101 | if (textContent) { 102 | const roomId= $('#roomId').val() ; 103 | if (!roomId) { 104 | alert('roomId is empty'); 105 | return false; 106 | } 107 | const result = await zg.sendBroadcastMessage(roomId, textContent); 108 | console.log('', result); 109 | if (result.errorCode === 0) { 110 | console.warn('send Message success'); 111 | } else { 112 | console.error('send Message fail ', result.errorCode); 113 | } 114 | 115 | $('.chatBox-content-demo').append(` 116 |
117 |
118 | ${new Date().toLocaleString()} 119 |
120 |
121 |
${textContent}
122 |
123 | 头像 124 |
125 |
126 |
127 | `); 128 | //发送后清空输入框 129 | $('.div-textarea').html(''); 130 | //聊天框默认最底部 131 | $('#chatBox-content-demo').scrollTop($('#chatBox-content-demo')[0].scrollHeight); 132 | } 133 | }); 134 | 135 | $('#sendCustomrMsg').click(async () => { 136 | if (!loginRoom) { 137 | alert('no login rooom'); 138 | return; 139 | } 140 | const roomId= $('#roomId').val() ; 141 | const result = await zg.sendCustomCommand(roomId, 'test', [$('#memberList').val() ]); 142 | if (result.errorCode === 0) { 143 | console.warn('sendCustomCommand suc'); 144 | } else { 145 | console.error('sendCustomCommand err', result.errorCode); 146 | } 147 | }); 148 | 149 | $('#BarrageMessage').click(async () => { 150 | if (!loginRoom) { 151 | alert('no login rooom'); 152 | return; 153 | } 154 | const roomId= $('#roomId').val() ; 155 | if (!roomId) { 156 | alert('roomId is empty'); 157 | return false; 158 | } 159 | const result = await zg.sendBarrageMessage(roomId, 'BarrageMessage test'); 160 | console.log('', result); 161 | if (result.errorCode === 0) { 162 | console.warn('send BarrageMessage success'); 163 | } else { 164 | console.error('send BarrageMessage fail ', result.errorCode); 165 | } 166 | }); 167 | 168 | $('#ReliableMessage').click(async () => { 169 | const roomId= $('#roomId').val() ; 170 | const result = await zg.setRoomExtraInfo(roomId, '2', 'ReliableMessage test'); 171 | if (result.errorCode === 0) { 172 | console.warn('setRoomExtraInfo suc'); 173 | } else { 174 | console.error('setRoomExtraInfo err', result.errorCode); 175 | } 176 | }); 177 | $('#leaveRoom').unbind('click'); 178 | $('#leaveRoom').click(function() { 179 | localUserList = []; 180 | $('#toast')[0].className = "toast fade hide" 181 | logout(); 182 | }); 183 | }); 184 | -------------------------------------------------------------------------------- /src/mix/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 24 | 混流 25 | 26 | 27 | 28 | 29 |
30 |
31 | 32 |
混流
33 | 34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 | 43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 |
51 | 53 |
54 |
55 | 56 |
57 |
58 |
59 | 60 |
61 | 63 |
64 |
65 |
66 | 67 |
68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 |
78 | 79 |
80 |
81 | 82 | 83 | 84 | 85 |
86 |
87 | 88 |
89 |
90 | 91 |
92 |
93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/mix/index.js: -------------------------------------------------------------------------------- 1 | import '../common'; 2 | import { checkAnRun, logout, publishStreamId, useLocalStreamList, zg } from '../common'; 3 | import { getBrowser } from '../assets/utils'; 4 | import flvjs from 'flv.js'; 5 | 6 | $(async () => { 7 | await checkAnRun(); 8 | $(''); 9 | const taskID = 'task-' + new Date().getTime(); 10 | const mixStreamID = 'mixwebrtc-' + new Date().getTime(); 11 | const mixVideo = $('#mixVideo')[0]; 12 | let hlsUrl; 13 | let flvPlayer = null; 14 | 15 | let taskID2; 16 | let mixStreamID2; 17 | const mixVideo2 = $('#mixVideo2')[0]; 18 | let hlsUrl2; 19 | let flvPlayer2 = null; 20 | $('#mixStream').click(async () => { 21 | try { 22 | const streamList = [ 23 | { 24 | streamID: publishStreamId, 25 | layout: { 26 | top: 0, 27 | left: 0, 28 | bottom: 240, 29 | right: 320, 30 | }, 31 | }, 32 | ]; 33 | if (useLocalStreamList.length !== 0) { 34 | streamList.push({ 35 | streamID: useLocalStreamList[0].streamID, 36 | layout: { 37 | top: 240, 38 | left: 0, 39 | bottom: 480, 40 | right: 320, 41 | }, 42 | }); 43 | } 44 | 45 | const res = await zg.startMixerTask({ 46 | taskID, 47 | inputList: streamList, 48 | outputList: [ 49 | mixStreamID, 50 | // { 51 | // target: mixStreamID, 52 | // // target: 'rtmp://test.aliyun.zego.im/livestream/zegodemo', 53 | // }, 54 | ], 55 | outputConfig: { 56 | outputBitrate: 300, 57 | outputFPS: 15, 58 | outputWidth: 320, 59 | outputHeight: 480, 60 | }, 61 | }); 62 | if (res.errorCode == 0) { 63 | $('#stopMixStream').removeAttr('disabled'); 64 | const result = JSON.parse(res.extendedData).mixerOutputList; 65 | if ( 66 | navigator.userAgent.indexOf('iPhone') !== -1 && 67 | getBrowser() == 'Safari' && 68 | result && 69 | result[0].hlsURL 70 | ) { 71 | hlsUrl = result[0].hlsURL.replace('http', 'https'); 72 | mixVideo.src = hlsUrl; 73 | } else if (result && result[0].flvURL) { 74 | const flvUrl = result[0].flvURL.replace('http', 'https'); 75 | console.log('mixStreamId: ' + mixStreamID); 76 | console.log('mixStreamUrl:' + flvUrl); 77 | alert('混流开始。。。'); 78 | if (flvjs.isSupported()) { 79 | flvPlayer = flvjs.createPlayer({ 80 | type: 'flv', 81 | url: flvUrl, 82 | }); 83 | flvPlayer.attachMediaElement(mixVideo); 84 | flvPlayer.load(); 85 | } 86 | } 87 | mixVideo.muted = false; 88 | } 89 | 90 | $('#mixVideo').css('display', ''); 91 | } catch (err) { 92 | alert('混流失败。。。'); 93 | console.error('err: ', err); 94 | } 95 | }); 96 | 97 | $('#mixStreamOnlyAudio').click(async () => { 98 | try { 99 | const streamList = [ 100 | { 101 | streamID: publishStreamId, 102 | contentType: 'AUDIO', 103 | }, 104 | ]; 105 | if (useLocalStreamList.length !== 0) { 106 | streamList.push({ 107 | streamID: useLocalStreamList[0].streamID, 108 | contentType: 'AUDIO', 109 | }); 110 | } 111 | taskID2 = 'task-' + new Date().getTime(); 112 | mixStreamID2 = 'mixwebrtc-' + new Date().getTime(); 113 | const res = await zg.startMixerTask({ 114 | taskID: taskID2, 115 | inputList: streamList, 116 | outputList: [ 117 | mixStreamID2, 118 | // { 119 | // target: mixStreamID, 120 | // // target: 'rtmp://test.aliyun.zego.im/livestream/zegodemo', 121 | // }, 122 | ], 123 | // outputConfig: { 124 | // outputBitrate: 1, 125 | // outputFPS: 1, 126 | // outputWidth: 10, 127 | // outputHeight: 10, 128 | // }, 129 | }); 130 | if (res.errorCode == 0) { 131 | $('#stopMixStream2').removeAttr('disabled'); 132 | const result = JSON.parse(res.extendedData).mixerOutputList; 133 | if ( 134 | navigator.userAgent.indexOf('iPhone') !== -1 && 135 | getBrowser() == 'Safari' && 136 | result && 137 | result[0].hlsURL 138 | ) { 139 | hlsUrl2 = result[0].hlsURL.replace('http', 'https'); 140 | mixVideo2.src = hlsUrl2; 141 | } else if (result && result[0].flvURL) { 142 | const flvUrl = result[0].flvURL.replace('http', 'https'); 143 | console.log('mixStreamId: ' + mixStreamID); 144 | console.log('mixStreamUrl:' + flvUrl); 145 | alert('混流开始。。。'); 146 | if (flvjs.isSupported()) { 147 | flvPlayer2 = flvjs.createPlayer({ 148 | type: 'flv', 149 | url: flvUrl, 150 | hasVideo: false 151 | }); 152 | flvPlayer2.attachMediaElement(mixVideo2); 153 | flvPlayer2.load(); 154 | } 155 | } 156 | mixVideo2.muted = false; 157 | } 158 | 159 | $('#mixVideo2').css('display', ''); 160 | } catch (err) { 161 | alert('混流失败。。。'); 162 | console.error('err: ', err); 163 | } 164 | }); 165 | $('#stopMixStream').click(async () => { 166 | try { 167 | await zg.stopMixerTask(taskID); 168 | alert('停止混流成功。。。'); 169 | if (flvPlayer) { 170 | flvPlayer.destroy(); 171 | flvPlayer = null; 172 | } 173 | console.log('stopMixStream success: '); 174 | $('#stopMixStream').attr('disabled', 'disabled'); 175 | $('#mixVideo').css('display', 'none'); 176 | } catch (err) { 177 | alert('停止混流失败。。。'); 178 | console.log('stopMixStream err: ', err); 179 | } 180 | }); 181 | $('#stopMixStream2').click(async () => { 182 | try { 183 | await zg.stopMixerTask(taskID2); 184 | alert('停止混流成功。。。'); 185 | if (flvPlayer2) { 186 | flvPlayer2.destroy(); 187 | flvPlayer2 = null; 188 | } 189 | console.log('stopMixStream success: '); 190 | $('#stopMixStream2').attr('disabled', 'disabled'); 191 | $('#mixVideo2').css('display', 'none'); 192 | } catch (err) { 193 | alert('停止混流失败。。。'); 194 | console.log('stopMixStream err: ', err); 195 | } 196 | }); 197 | 198 | $('#leaveRoom').unbind('click'); 199 | $('#leaveRoom').click(function () { 200 | if (flvPlayer) { 201 | flvPlayer.destroy(); 202 | flvPlayer = null; 203 | } 204 | mixVideo.src = ''; 205 | $('#mixVideo').css('display', 'none'); 206 | if (flvPlayer2) { 207 | flvPlayer2.destroy(); 208 | flvPlayer2 = null; 209 | } 210 | mixVideo2.src = ''; 211 | $('#mixVideo2').css('display', 'none'); 212 | 213 | logout(); 214 | }); 215 | }); 216 | -------------------------------------------------------------------------------- /src/screen/rangeShare.js: -------------------------------------------------------------------------------- 1 | export default function (canvas, mediaStream, video) { // 不传 video 就是使用 ImageCapture模式 2 | const canvasMedidaStream = canvas.captureStream(25); 3 | console.log('get canvas capture end'); 4 | let timer = null, imageCapture = null; 5 | const ctx = canvas.getContext('2d'); 6 | if (!video) { 7 | imageCapture = new ImageCapture(mediaStream.getVideoTracks()[0]); 8 | console.log('new ImageCapture success'); 9 | } 10 | 11 | if (mediaStream.getAudioTracks().length) { 12 | let micro = mediaStream.getAudioTracks()[0]; 13 | canvasMedidaStream.addTrack(micro); 14 | console.log('addTrack audio'); 15 | } 16 | 17 | /** 18 | * 把video的画在cavans上 19 | * @param {*} ctx canvas的绘图上下文 context 20 | * @param {*} video 要绘制的到 canvas 上的 video Dom 对象 21 | * @param {*} canvas canvas 对象 22 | * @param {*} videoX 原视频的矩形(裁剪)选择框的左上角 X 轴坐标。 23 | * @param {*} videoY 原视频的矩形(裁剪)选择框的左上角 Y 轴坐标。 24 | * @param {*} videoWidth 要裁剪的视频的区域的宽度 25 | * @param {*} videoHeight 要裁剪的视频的区域的高度 26 | * @param {*} videoWidthInCanvas 视频在canvas上的渲染宽度 27 | * @param {*} videoHeightInCanvas 视频在canvas上的渲染高度 28 | * @param {*} canvasX 视频的左上角在目标canvas上 X 轴坐标。 29 | * @param {*} canvasY 视频的左上角在目标canvas上 Y 轴坐标 30 | */ 31 | function videoDrawInCanvas( 32 | ctx, 33 | source, 34 | canvas, 35 | videoX, 36 | videoY, 37 | videoWidth, 38 | videoHeight, 39 | videoWidthInCanvas = videoWidth, 40 | videoHeightInCanvas = videoHeight, 41 | canvasX = 0, 42 | canvasY = 0 43 | ) { 44 | ctx.drawImage( 45 | source, 46 | videoX, 47 | videoY, 48 | videoWidth, 49 | videoHeight, 50 | canvasX, 51 | canvasY, 52 | videoWidthInCanvas, 53 | videoHeightInCanvas 54 | ); 55 | timer = setTimeout(async () => { 56 | if (video) { 57 | videoDrawInCanvas( ctx, source, canvas, videoX, videoY, videoWidth, videoHeight); 58 | } else { 59 | imageCapture.grabFrame().then((ImageBitmap) => { 60 | videoDrawInCanvas( ctx, ImageBitmap, canvas, videoX, videoY, videoWidth, videoHeight); 61 | }).catch(function(error) { 62 | console.log('grabFrame() error: ', error); 63 | }); 64 | } 65 | }, 60); 66 | }; 67 | 68 | return { 69 | canvasMedidaStream, 70 | changeRange(sx, sy, sWidth, sHeight) { 71 | clearTimeout(timer); 72 | canvas.width = sWidth; 73 | canvas.height = sHeight; 74 | console.log('change range start'); 75 | if (video) { 76 | videoDrawInCanvas(ctx, video, canvas, sx, sy, sWidth, sHeight); 77 | console.log('change range end'); 78 | } else { 79 | imageCapture.grabFrame().then((ImageBitmap) => { 80 | videoDrawInCanvas(ctx, ImageBitmap, canvas, sx, sy, sWidth, sHeight); 81 | console.log('change range end'); 82 | }).catch(function(error) { 83 | console.log('grabFrame() error: ', error); 84 | }); 85 | } 86 | } 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/third/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 29 | 第三方音视频推流 30 | 31 | 32 |
33 | 34 |
35 | 36 |
第三方音视频推流
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | 54 |
55 | 57 |
58 |
59 | 60 |
61 |
62 |
63 | 64 |
65 | 67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 | 76 |
77 | 81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 | 90 |
91 |
92 |
93 |
94 |
95 | 96 |
97 | 99 |
100 |
101 | 110 |
111 |
112 |
113 | 114 | 115 | 116 | 117 | 118 | 119 |
120 |
121 | 122 |
123 |
124 | 125 | 126 | 127 | 128 |
129 |
130 | 131 |
132 |
133 |
134 | 135 |
136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /src/third/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { checkAnRun, enterRoom, push, previewVideo, isPreviewed } from '../common'; 3 | 4 | $(async () => { 5 | await checkAnRun(); 6 | $('#changeUrl').click(async () => { 7 | if ($('#videoUrl').val()) { 8 | $('#externerVideo')[0].src = $('#videoUrl').val(); 9 | $('#externerVideo')[0].play(); 10 | } 11 | 12 | }) 13 | $('#externalCaptureV').click(async () => { 14 | let loginSuc = false; 15 | const channelCount = parseInt($('#channelCount').val()); 16 | 17 | let media = await changeStream($('#externerVideo')[0]); 18 | 19 | const constraints = { 20 | custom: { 21 | source: media, 22 | channelCount: channelCount, 23 | 24 | } 25 | } 26 | $('#audioBitrate').val() && (constraints.audioBitrate = parseInt($('#audioBitrate').val())); 27 | 28 | try { 29 | // $('#externerVideo')[0].play(); 30 | loginSuc = await enterRoom(); 31 | 32 | if (loginSuc) { 33 | doPreviewPublish({ 34 | custom: { 35 | source: media, 36 | channelCount: channelCount, 37 | }, 38 | ...constraints 39 | }, new Date().getTime() + ""); 40 | 41 | } 42 | } catch (error) { 43 | console.error(error); 44 | } 45 | }); 46 | $('#externalCaptureA').click(async () => { 47 | let loginSuc = false; 48 | const channelCount = parseInt($('#channelCount').val()); 49 | 50 | const constraints = { 51 | source: $('#externerAudio')[0], 52 | channelCount: channelCount, 53 | } 54 | 55 | $('#audioBitrate').val() && (constraints.audioBitrate = parseInt($('#audioBitrate').val())); 56 | 57 | try { 58 | loginSuc = await enterRoom(); 59 | if (loginSuc) { 60 | push({ 61 | custom: constraints 62 | }); 63 | } 64 | } catch (error) { 65 | console.error(error); 66 | } 67 | }); 68 | 69 | $('#inputFile').change(function () { 70 | const video = this.files[0]; 71 | const url = URL.createObjectURL(video); 72 | $('#externerVideo')[0].src = url; 73 | }) 74 | function doPreviewPublish(config, streamID) { 75 | zg.createStream(config).then(stream => { 76 | zg.startPublishingStream(streamID ? streamID : idName, stream, { videoCodec: $('#videoCodeType').val(), extraInfo: JSON.stringify({ role: 2 }) }) 77 | 78 | previewVideo.srcObject = stream; 79 | }).catch(err => { 80 | console.error(err) 81 | }) 82 | } 83 | //判断浏览器版本 必须是谷歌88 版本 84 | function getChromeVersion() { 85 | var arr = navigator.userAgent.split(' '); 86 | var chromeVersion = ''; 87 | for (var i = 0; i < arr.length; i++) { 88 | if (/chrome/i.test(arr[i])) 89 | chromeVersion = arr[i] 90 | } 91 | if (chromeVersion) { 92 | return Number(chromeVersion.split('/')[1].split('.')[0]); 93 | } else { 94 | return false; 95 | } 96 | } 97 | 98 | function changeStream(source) { 99 | 100 | var version = getChromeVersion(); 101 | if (version != 88) { 102 | return source 103 | } 104 | return zg.createStream({ 105 | custom: { 106 | source: source 107 | } 108 | }).then(stream => { 109 | let video = document.createElement("video"); 110 | let canvas = document.createElement("canvas"); 111 | video.setAttribute("style", "display:none"); 112 | canvas.setAttribute("style", "display:none"); 113 | video.setAttribute("muted", ""); 114 | video.muted = !0; 115 | video.setAttribute("autoplay", ""); 116 | video.autoplay = !0; 117 | video.setAttribute("playsinline", ""); 118 | document.body.append(video); 119 | document.body.append(canvas); 120 | video.srcObject = stream; 121 | video.oncanplay = function () { 122 | canvas.width = video.videoWidth; 123 | canvas.height = video.videoHeight; 124 | video.play(); 125 | draw(); 126 | } 127 | 128 | let media = canvas.captureStream(25); 129 | let track = media.getVideoTracks()[0]; 130 | let ctx = canvas.getContext("2d"); 131 | let draw = function () { 132 | ctx.drawImage(video, 0, 0, canvas.width, canvas.height); 133 | // window.requestAnimationFrame(draw) 134 | track.requestFrame && track.requestFrame(); 135 | video.srcObject = stream; 136 | 137 | } 138 | let q = track.stop 139 | track.stop = () => { 140 | q.call(track); 141 | draw(); 142 | video.remove(); 143 | canvas.width = 0; 144 | canvas.remove(); 145 | video = canvas = null; 146 | } 147 | if (stream instanceof MediaStream && stream.getAudioTracks().length) { 148 | let micro = stream.getAudioTracks()[0]; 149 | media.addTrack(micro) 150 | } 151 | return media 152 | }).catch(err => { 153 | console.error(err) 154 | }) 155 | } 156 | 157 | }); 158 | -------------------------------------------------------------------------------- /src/token/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 27 | 推拉流基础功能 28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 |
基础推拉流
36 | 37 |
38 | 39 |
40 |
41 |
42 | 44 |
45 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | 55 |
56 | 58 |
59 |
60 | 61 |
62 | 63 |
64 |
65 |
66 |
67 | 68 |
69 | 71 |
72 | 73 |
74 | 75 |
76 |
77 |
78 | 79 |
80 | 82 |
83 |
84 | 85 |
86 |
87 |
88 | 90 |
91 | 94 |
95 | 96 |
97 |
98 | 99 |
100 |
101 | 102 | 103 | 104 | 105 |
106 |
107 | 108 |
109 |
110 |
当前音量: 0
111 | 112 | 113 |
114 |
115 | 116 |
117 |
118 |
119 | 120 |
121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/vp8/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 27 | vp8<-->h264 28 | 29 | 30 | 31 | 32 |
33 | 34 |
35 | 36 |
混流转码(支持vp8,h264互通)
37 | 38 |
39 | 40 |
41 |
42 |
43 | 45 |
46 | 48 |
49 |
50 | 51 |
52 |
53 |
54 | 56 |
57 | 59 |
60 |
61 | 62 |
63 |
64 |
65 | 66 |
67 | 70 |
71 |
72 | 73 |
74 | 75 |
76 |
77 |
78 |
79 | 80 |
81 | 86 |
87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 |
101 |
102 | 103 | 104 | 105 |
106 |
107 | 108 |
109 |
110 | 111 | 112 |
113 |
114 | 115 |
116 |
117 |
118 |
119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/webrtcCheck/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | webrtc测试 10 | 66 | 67 | 68 | 69 | 70 |
71 |
72 | 73 |
74 |
75 |

webrtc测试

77 | 开始测试 79 |
80 |

此网站用于测试浏览器是否支持webrtc

81 |
82 | 83 |
84 |
85 |

webRTC {{state.webrtcState}}

86 |
87 |
88 |
当前浏览器支持webrtc!!!
89 |
当前浏览器暂不支持webrtc!!!
90 |
91 |
92 | 93 |
94 |
95 |

获取设备 {{state.webrtcState}}

96 |
97 |
98 |
当前浏览器支持获取设备!!!
99 |
当前浏览器不支持获取设备!!!
100 |
101 |
102 | 103 |
104 |
105 |

H264编码 {{state.webrtcState}}

106 |
107 |
108 |
当前浏览器支持H264编码!!!
109 |
当前浏览器不支持H264编码!!!
110 |
111 |
112 | 113 |
114 |
115 |

VP8编码 {{state.webrtcState}}

116 |
117 |
118 |
当前浏览器支持VP8编码!!!
119 |
当前浏览器暂不支持VP8编码!!!
120 |
121 |
122 | 123 |
124 |
125 |

音频输入 {{state.audioinputState}}

126 |
127 |
128 |
129 |
检测到音频输入设备!!!
130 | {{audioinputInfos}} 131 |
132 |
134 |
135 |
136 |
137 |
未检测到音频输入设备!!!
138 | 139 |
140 |
141 | 142 |
143 |
144 |

音频输出 {{state.audiooutputState}}

145 |
146 |
147 | 150 |

151 | {{audiooutputInfos}} 152 |

153 |
154 |
155 | 156 |
157 |
158 |

视频输入 {{state.videoinputState}}

159 |
160 |
161 |
162 |
检测到视频输入设备!!!
163 | {{videoinputInfos}} 164 |
165 |
未检测到视频输入设备!!!
166 |
167 |
168 | 169 |
170 |
171 |

分辨率检测 {{state.resolutionState}}

172 |
173 |
174 |

175 | {{item.width}} X {{item.height}}: {{item.resolutionState}} 176 |

177 |
178 |
179 | 180 |
181 | 182 |
183 |

连通性检测 {{state.connectivityState}}

184 |
185 | 186 |
187 |
连通性检测成功!!!
188 |
连通性检测失败!!!
189 |
190 |
191 | 192 |
193 | 194 |
195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /src/webrtcCheck/index.js: -------------------------------------------------------------------------------- 1 | import { zg, enterRoom, publishStreamId, logout } from '../common'; 2 | 3 | new Vue({ 4 | el: '#app', 5 | data: { 6 | state: { 7 | webrtcState: '', 8 | audioinputState: '', 9 | audiooutputState: '', 10 | videoinputState: '', 11 | resolutionState: '', 12 | H264State: '', 13 | connectivityState: '' 14 | }, 15 | webRTC: false, 16 | capture: false, 17 | H264State: false, 18 | VP8State: false, 19 | isAudioInput: false, 20 | isVideoInput: false, 21 | isH264: false, 22 | isSound: false, 23 | isConnectivity: false, 24 | audioinputInfos: '', 25 | videoinputInfos: '', 26 | audiooutputInfos: '', 27 | sounder: '', 28 | localStream: null, 29 | resolutionList: [ 30 | { 31 | width: 180, 32 | height: 320, 33 | resolutionState: '' 34 | }, 35 | { 36 | width: 240, 37 | height: 320, 38 | resolutionState: '' 39 | }, 40 | { 41 | width: 264, 42 | height: 480, 43 | resolutionState: '' 44 | }, 45 | { 46 | width: 360, 47 | height: 640, 48 | resolutionState: '' 49 | }, 50 | { 51 | width: 480, 52 | height: 640, 53 | resolutionState: '' 54 | }, 55 | { 56 | width: 540, 57 | height: 960, 58 | resolutionState: '' 59 | }, 60 | { 61 | width: 720, 62 | height: 1280, 63 | resolutionState: '' 64 | }, 65 | { 66 | width: 1080, 67 | height: 1920, 68 | resolutionState: '' 69 | } 70 | ] 71 | }, 72 | methods: { 73 | startTest() { 74 | this.state.webrtcState = '正在检测...'; 75 | zg.checkSystemRequirements() 76 | .then((result) => { 77 | this.webRTC = result.webRTC; 78 | this.capture = result.camera && result.microphone; 79 | this.H264State = result.videoCodec.H264; 80 | this.VP8State = result.videoCodec.VP8; 81 | }) 82 | .catch((err) => console.log(err)); 83 | setTimeout(() => { 84 | this.state.webrtcState = '检测完成'; 85 | this.state.audioinputState = '正在检测...'; 86 | this.state.videoinputState = '正在检测...'; 87 | setTimeout(() => { 88 | this.checkDeviceSupport(); 89 | this.checkMicrophoneSound(); 90 | this.state.audioinputState = '检测完成'; 91 | this.state.videoinputState = '检测完成'; 92 | this.state.resolutionState = '正在检测...'; 93 | setTimeout(() => { 94 | this.checkResolution(); 95 | this.state.resolutionState = '检测完成'; 96 | this.state.H264State = '正在检测...'; 97 | setTimeout(() => { 98 | this.checkConnectivity(); 99 | }, 2500); 100 | }, 2500); 101 | }, 2500); 102 | }, 2500); 103 | }, 104 | 105 | checkDeviceSupport() { 106 | let that = this; 107 | let cameras = []; 108 | let microphones = []; 109 | let speakers = []; 110 | 111 | zg.enumDevices().then((devicesinfo) => { 112 | if (devicesinfo.cameras.length != 0) { 113 | devicesinfo.cameras.forEach((item) => { 114 | cameras.push(item.deviceName); 115 | }); 116 | that.isVideoInput = true; 117 | that.videoinputInfos = cameras.join(' '); 118 | } 119 | 120 | if (devicesinfo.microphones.length != 0) { 121 | devicesinfo.microphones.forEach((item) => { 122 | microphones.push(item.deviceName); 123 | }); 124 | that.isAudioInput = true; 125 | that.audioinputInfos = microphones.join(' '); 126 | } 127 | 128 | if (devicesinfo.speakers.length != 0) { 129 | devicesinfo.speakers.forEach((item) => { 130 | speakers.push(item.deviceName); 131 | }); 132 | that.audiooutputInfos = speakers.join(' '); 133 | } 134 | }); 135 | }, 136 | 137 | checkMicrophoneSound() { 138 | window.AudioContext = 139 | window.AudioContext || window.webkitAudioContext || mozAudioContext; 140 | navigator.getUserMedia = 141 | navigator.getUserMedia || 142 | navigator.webkitGetUserMedia || 143 | navigator.mozGetUserMedia; 144 | if (!window.AudioContext || !navigator.getUserMedia) return; 145 | let context = new AudioContext(); 146 | let script = context.createScriptProcessor(2048, 1, 1); 147 | 148 | navigator.getUserMedia( 149 | { 150 | audio: true 151 | }, 152 | (stream) => { 153 | let audioinput = context.createMediaStreamSource(stream); 154 | audioinput.connect(script); 155 | script.connect(context.destination); 156 | this.isSound = true; 157 | } 158 | ); 159 | 160 | script.onaudioprocess = (event) => { 161 | let input = event.inputBuffer.getChannelData(0); 162 | let instant = 0.0; 163 | let sum = 0.0; 164 | for (let i = 0; i < input.length; ++i) { 165 | sum += input[i] * input[i]; 166 | } 167 | instant = Math.sqrt(sum / input.length); 168 | this.sounder = instant * 100; 169 | }; 170 | }, 171 | 172 | async resolutionDetection(camera) { 173 | try { 174 | this.localStream = await zg.createStream({ camera }) 175 | if(!this.localStream) return false 176 | const settings = this.localStream.getVideoTracks()[0].getSettings() 177 | 178 | return camera.width === settings.width && camera.height === settings.height 179 | } catch(err) { 180 | return false 181 | } 182 | }, 183 | 184 | async checkResolution() { 185 | 186 | navigator.getUserMedia = 187 | navigator.getUserMedia || 188 | navigator.webkitGetUserMedia || 189 | navigator.mozGetUserMedia; 190 | if (!navigator.getUserMedia) return; 191 | 192 | const resolutionList = [...this.resolutionList] 193 | const excessConfig = { 194 | videoQuality: 4, 195 | frameRate: 15, 196 | bitRate: 800 197 | } 198 | let i = 0 199 | while(i < resolutionList.length) { 200 | this.localStream && zg.destroyStream(this.localStream) 201 | const flag = await this.resolutionDetection({...resolutionList[i], ...excessConfig}) 202 | if(!flag) { 203 | this.resolutionList[i++]['resolutionState'] = '不支持' 204 | } else { 205 | this.resolutionList[i++]['resolutionState'] = '支持' 206 | } 207 | } 208 | }, 209 | 210 | async checkConnectivity() { 211 | this.localStream && zg.destroyStream(this.localStream) 212 | const result = await enterRoom() 213 | if(!result) 214 | return console.log('loginRoom fail'); 215 | 216 | console.log('loginRoom success'); 217 | 218 | try { 219 | this.localStream = await zg.createStream() 220 | console.log('createStream success'); 221 | } catch(err) { 222 | console.log('createStream fail reason', err); 223 | return 224 | } 225 | 226 | try { 227 | this.isConnectivity = await zg.startPublishingStream(publishStreamId, this.localStream) 228 | this.state.connectivityState = '检测完成' 229 | } catch(err) { 230 | console.log('PublishingStream fail reason', err); 231 | } 232 | 233 | zg.on('publisherStateUpdate', result => { 234 | zg.destroyStream(this.localStream) 235 | logout() 236 | }) 237 | }, 238 | 239 | isAndroidWexin() { 240 | let ua = navigator.userAgent.toLowerCase(); 241 | return ( 242 | ua.match(/MicroMessenger/i) == 'micromessenger' && ua.match(/android/i) 243 | ); 244 | } 245 | } 246 | }); 247 | -------------------------------------------------------------------------------- /src/whiteboard/ZegoWhiteboardSDK.md: -------------------------------------------------------------------------------- 1 | ## ZegoWhiteboard Web SDK 2 | 3 | ### 1、接口定义 4 | 5 | #### 1.1.1 获取SDK版本号 6 | 7 | ```typescript 8 | whiteboardManager.getVersion(): string 9 | ``` 10 | 11 | #### 1.1.2 创建白板 12 | 13 | ```typescript 14 | whiteboardManager.createView(options: { 15 | roomID: string, 16 | name: string, 17 | aspectWidth: number, 18 | aspectHeight: number, 19 | pageCount: number, 20 | fileInfo?: { 21 | fileID: string, 22 | fileName: string, 23 | fileType: number, 24 | authKey: string 25 | } 26 | }): Promise 27 | ``` 28 | 29 | | 字段 | 含义 | 是否必填 | 30 | | ----------------------------------------- | ------- | ----------------------------- | 31 | | roomID | roomId | 是 | 32 | | name | 白板名称 | 是 | 33 | | aspectWidth | 等比宽 | 是 | 34 | | aspectHeight | 等比高 | 是 | 35 | | pageCount | 白板页数 | 是 | 36 | | fileInfo | 关联文件信息 | 否 | 37 | 38 | fileInfo参数解释: 39 | 40 | | 字段 | 含义 | 是否必填 | 41 | | ----------------------------------------- | ------- | ----------------------------- | 42 | | fileID | 文件转换完成唯一ID | 是 | 43 | | fileName | 文件名称 | 是 | 44 | | fileType | 文件类型 | 是 | 45 | | authKey | 预留字段 | 否 | 46 | 47 | #### 1.1.3 销毁白板 48 | 49 | ```typescript 50 | whiteboardManager.destroyView(whiteboardView: WhiteboardView): Promise 51 | ``` 52 | 53 | | 字段 | 含义 | 是否必填 | 54 | | ----------------------------------------- | ------- | ----------------------------- | 55 | | whiteboardView | 白板view实例 | 是 | 56 | 57 | #### 1.1.4 添加白板到视图(渲染) 58 | 59 | ```typescript 60 | whiteboardManager.attachView(whiteboardView: WhiteboardView, parent: string): Promise 61 | ``` 62 | 63 | | 字段 | 含义 | 是否必填 | 64 | | ----------------------------------------- | ------- | ----------------------------- | 65 | | whiteboardView | 白板view实例 | 是 | 66 | | parent | 需要挂载的父容器id | 是 | 67 | 68 | #### 1.1.5 获取白板列表 69 | 70 | ```typescript 71 | whiteboardManager.getViewList(): Promise 72 | ``` 73 | 74 | #### 1.1.6 清空白板view 75 | 76 | ```typescript 77 | whiteboardView.clear(): boolean 78 | ``` 79 | 80 | #### 1.1.7 获取该白板view关联的文件信息 81 | 82 | ```typescript 83 | whiteboardView.getFileInfo(): {fileID: string, fileName: string, authKey: string, fileType: number} | undefined 84 | ``` 85 | 86 | #### 1.1.8 设置白板允许涂鸦权限 87 | 88 | ```typescript 89 | whiteboardView.enable(enable: boolean): boolean 90 | ``` 91 | 92 | #### 1.1.9 获取白板允许涂鸦权限 93 | 94 | ```typescript 95 | whiteboardView.IsEnable(): boolean 96 | ``` 97 | 98 | #### 1.1.10 设置白板背景色(color支持16进制、rgba) 99 | 100 | ```typescript 101 | whiteboardView.setBackgroundColor(color: string): boolean 102 | ``` 103 | 104 | #### 1.1.11 获取白板页的背景色 105 | 106 | ```typescript 107 | whiteboardView.getBackgroundColor(): string 108 | ``` 109 | 110 | #### 1.1.12 获取白板页宽高比 111 | 112 | ```typescript 113 | whiteboardView.getAspectRatio(): string 114 | ``` 115 | 116 | #### 1.1.13 设置要使用的白板工具 117 | 118 | ```typescript 119 | whiteboardView.setToolType(type: ViewTool): boolean 120 | ``` 121 | 122 | | 字段 | 含义 | 是否必填 | 123 | | ----------------------------------------- | ------- | ----------------------------- | 124 | | type | 工具类型1:path 涂鸦,2:text 文本框,4:line 线段,8:rect 矩形,16:ellipse 椭圆,32: selector 选择 | 是 | 125 | 126 | #### 1.1.14 获取正在使用的白板工具 127 | 128 | ```typescript 129 | whiteboardView.getToolType(): ViewTool 130 | ``` 131 | 132 | #### 1.1.15 设置画笔颜色 133 | 134 | ```typescript 135 | whiteboardView.setBrushColor(color: string): boolean 136 | ``` 137 | 138 | #### 1.1.16 获取画笔颜色 139 | 140 | ```typescript 141 | whiteboardView.getBrushColor(): string 142 | ``` 143 | 144 | #### 1.1.17 设置画笔粗细 145 | 146 | ```typescript 147 | whiteboardView.setBrushSize(thin: number): boolean 148 | ``` 149 | 150 | | 字段 | 含义 | 是否必填 | 151 | | ----------------------------------------- | ------- | ----------------------------- | 152 | | thin | 画笔粗细,取值1~100 | 是 | 153 | 154 | #### 1.1.18 获取画笔粗细 155 | 156 | ```typescript 157 | whiteboardView.getBrushSize(): number 158 | ``` 159 | 160 | #### 1.1.19 设置文本大小 161 | 162 | ```typescript 163 | whiteboardView.setTextSize(thin: number): boolean 164 | ``` 165 | 166 | | 字段 | 含义 | 是否必填 | 167 | | ----------------------------------------- | ------- | ----------------------------- | 168 | | thin | 文本大小,取值12~100 | 是 | 169 | 170 | #### 1.1.20 获取文本大小 171 | 172 | ```typescript 173 | whiteboardView.getTextSize(): number 174 | ``` 175 | 176 | #### 1.1.21 滚动白板页 177 | 178 | ```typescript 179 | whiteboardView.scroll(horizontalPercent: number, verticalPercent: number): boolean 180 | ``` 181 | 182 | | 字段 | 含义 | 是否必填 | 183 | | ----------------------------------------- | ------- | ----------------------------- | 184 | | horizontalPercent | 水平滚动百分比 | 否 | 185 | | verticalPercent | 垂直滚动百分比 | 否 | 186 | 187 | #### 1.1.22 获取滚动百分比 188 | 189 | ```typescript 190 | whiteboardView.getCurrentScrollPercent(): { horizontalPercent: number; verticalPercent: number } 191 | ``` 192 | 193 | #### 1.1.23 撤销 194 | 195 | ```typescript 196 | whiteboardView.undo(): void 197 | ``` 198 | 199 | #### 1.1.24 重做 200 | 201 | ```typescript 202 | whiteboardView.redo(): void 203 | ``` 204 | 205 | #### 1.1.25 获取白板ID 206 | 207 | ```typescript 208 | whiteboardView.getID(): string 209 | ``` 210 | 211 | #### 1.1.26 获取关联roomID 212 | 213 | ```typescript 214 | whiteboardView.getRoomID(): string 215 | ``` 216 | 217 | #### 1.1.27 获取白板页数 218 | 219 | ```typescript 220 | whiteboardView.getPageCount(): number 221 | ``` 222 | 223 | #### 1.1.28 获取白板当前页码,从1开始 224 | 225 | ```typescript 226 | whiteboardView.getPage(): number 227 | ``` 228 | 229 | ### 2、回调 230 | 231 | #### 2.1.1 错误回调 232 | 233 | ```typescript 234 | whiteboardManager.on('error', (errorData: ErrorData) => {}) 235 | ``` 236 | 237 | | 字段 | 含义 | 238 | | ----------------------------------------- | ------- | 239 | | code | 错误码 | 240 | | msg | 错误描述 | 241 | 242 | #### 2.1.2 创建view 243 | 244 | ```typescript 245 | whiteboardManager.on('viewAdd', (whiteboardView: WhiteboardView) => {}) 246 | ``` 247 | 248 | | 字段 | 含义 | 249 | | ----------------------------------------- | ------- | 250 | | whiteboardView | 白板view实例 | 251 | 252 | #### 2.1.3 销毁view 253 | 254 | ```typescript 255 | whiteboardManager.on('viewRemoved', (whiteboardID: string) => {}) 256 | ``` 257 | 258 | | 字段 | 含义 | 259 | | ----------------------------------------- | ------- | 260 | | whiteboardID | 白板ID | 261 | 262 | #### 2.1.4 滚动view 263 | 264 | ```typescript 265 | whiteboardManager.on('viewScroll', (whiteboardID: string, horizontalPercent: number, verticalPercent: number, page: number) => {}) 266 | ``` 267 | 268 | | 字段 | 含义 | 269 | | ----------------------------------------- | ------- | 270 | | whiteboardID | 白板ID | 271 | | horizontalPercent | 水平滚动百分比 | 272 | | verticalPercent | 垂直滚动百分比 | 273 | | page | 翻页模式时当前页码 | 274 | -------------------------------------------------------------------------------- /tools.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const fs = require('fs'); 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const path = require('path'); 5 | 6 | module.exports = function filterFileList (rootName, result, targetDirNames, unReadDirNames) { 7 | //读取文件夹---->下一层目录list 8 | let paths = fs.readdirSync(rootName); 9 | 10 | //过滤指定文件夹以及子目录 11 | if (targetDirNames) 12 | paths = paths.filter(_path => 13 | targetDirNames.some(tdpath => path.resolve(rootName, _path).indexOf(tdpath) > -1), 14 | ); 15 | 16 | //去除指定文件夹以及子目录 17 | if (unReadDirNames && unReadDirNames.includes(rootName)) return; 18 | 19 | //逐个读取文件夹下一层目录,并在读取到文件夹时递归,直到读取到文件为止 20 | for (let i = 0; i < paths.length; i++) { 21 | const _path = paths[i]; 22 | const _stats = fs.lstatSync(path.resolve(rootName, _path)); 23 | //console.log(path.resolve(rootName, _path), _stats.isDirectory(), _stats.size == 0); 24 | if (_stats.isDirectory()) { 25 | const subPaths = filterFileList(path.resolve(rootName, _path), result, targetDirNames, unReadDirNames); 26 | //console.log('sub_paths', sub_paths); 27 | } else if (_stats.size == 0) { 28 | result.push(path.resolve(rootName, _path)); 29 | } else if (_stats.isFile) { 30 | //console.log('file_path', path.resolve(rootName, _path)); 31 | result.push(path.resolve(rootName, _path)); 32 | } 33 | } 34 | //console.log(result); 35 | return result; 36 | }; 37 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const filterFileList = require('./tools'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const webpack = require('webpack'); 5 | const miniCssExtractPlugin = require('mini-css-extract-plugin'); 6 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 7 | const internalIp = require('internal-ip'); 8 | const tempList = []; 9 | const entry = {}; 10 | const htmlPlugins = []; 11 | filterFileList('./src', tempList); 12 | 13 | const targetList = tempList.filter(path => { 14 | if (path.includes('docsSharing') || path.includes('whiteboard')) { 15 | return false; 16 | } else { 17 | return true; 18 | } 19 | }); 20 | 21 | 22 | entry['content'] = targetList.find(item => item.endsWith('content.js')); 23 | targetList 24 | .filter(item => item.endsWith('index.js')) 25 | .forEach(p => { 26 | const regResult = /.+src[\/|\\](.+)[\/|\\]index.js$/.exec(p); 27 | if (regResult && regResult[1]) { 28 | entry[regResult[1]] = regResult[0]; 29 | } 30 | }); 31 | 32 | targetList 33 | .filter(item => item.endsWith('index.html')) 34 | .forEach(tepmlate => { 35 | const regResult = /.+src[\/|\\](.+)[\/|\\]index.html$/.exec(tepmlate); 36 | if (regResult && regResult[1]) { 37 | htmlPlugins.push( 38 | new HtmlWebpackPlugin({ 39 | template: tepmlate, 40 | chunks: [regResult[1]], 41 | filename: regResult[1] + '/index.html', 42 | }), 43 | ); 44 | } else if (tepmlate.includes('src/index.html')) { 45 | htmlPlugins.push( 46 | new HtmlWebpackPlugin({ 47 | template: './src/index.html', 48 | filename: 'index.html', 49 | chunks: ['content'], 50 | favicon: './src/favicon.ico', 51 | }), 52 | ); 53 | } 54 | }); 55 | 56 | module.exports = { 57 | entry, 58 | mode: 'development', 59 | output: { 60 | filename: '[name]/[name].bundle.js', 61 | path: path.resolve(__dirname, 'docs'), 62 | libraryTarget: "umd", 63 | umdNamedDefine: true, 64 | globalObject: "typeof self !== 'undefined' ? self : this" 65 | }, 66 | resolve: { 67 | extensions: ['.tsx', '.ts', '.js'], 68 | }, 69 | module: { 70 | rules: [ 71 | { 72 | test: /\.css$/, 73 | use: [ 74 | miniCssExtractPlugin.loader, 75 | { 76 | loader: 'css-loader', 77 | options: { 78 | url: false, 79 | }, 80 | }, 81 | ], 82 | }, 83 | { 84 | test: /\.(gif|png|jpe?g|svg)$/i, 85 | use: [ 86 | { 87 | loader: 'url-loader', 88 | options: { 89 | limit: 8192, 90 | name: '[name].[ext]', 91 | fallback: 'file-loader', //超过了限制大小调用回调函数 92 | outputPath: 'public/images', //图片存储的地址 93 | }, 94 | }, 95 | ], 96 | }, 97 | { 98 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 99 | use: [ 100 | { 101 | loader: 'url-loader', 102 | options: { 103 | limit: 10000, // 小于10000 / 1024 kb的字体会被url-loader压缩成base64格式 104 | name: 'static/font/[name].[hash:7].[ext]', // 字体名字,7位哈希值,扩展名 105 | }, 106 | }, 107 | ], 108 | }, 109 | { 110 | test: /\.tsx?$/, 111 | use: 'ts-loader', 112 | exclude: /node_modules/, 113 | }, 114 | { 115 | test: require.resolve('jquery'), 116 | loader: 'expose-loader?$!expose-loader?jQuery', 117 | }, 118 | ], 119 | }, 120 | plugins: [ 121 | ...htmlPlugins, 122 | 123 | new miniCssExtractPlugin({ 124 | filename: 'index.[contenthash:8].css', 125 | }), 126 | new CopyWebpackPlugin({ 127 | patterns: [ 128 | { from: './src/docsSharing', to: './docsSharing' }, 129 | { from: './src/whiteboard', to: './whiteboard' }, 130 | ], 131 | }), 132 | new webpack.ProvidePlugin({ 133 | $: 'jquery', 134 | jQuery: 'jquery', 135 | 'window.$': 'jquery', 136 | 'window.jQuery': 'jquery', 137 | }), 138 | ], 139 | devServer: { 140 | contentBase: './docs', 141 | port: 9092, 142 | host: internalIp.v4.sync(), 143 | https: true, 144 | open: true, 145 | }, 146 | }; 147 | --------------------------------------------------------------------------------