[+] 终端
This commit is contained in:
@@ -17,8 +17,8 @@
|
||||
<description>服务:启动服务</description>
|
||||
|
||||
<properties>
|
||||
<system.maven.basedir>${project.parent.basedir}</system.maven.basedir>
|
||||
<system.maven.skip.assembly>false</system.maven.skip.assembly>
|
||||
<taoyao.maven.basedir>${project.parent.basedir}</taoyao.maven.basedir>
|
||||
<taoyao.maven.skip.assembly>false</taoyao.maven.skip.assembly>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -34,18 +34,6 @@
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-meeting</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-webrtc-sfu</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-webrtc-mcu</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.acgist</groupId>
|
||||
<artifactId>taoyao-webrtc-mesh</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.acgist.taoyao.boot.config.MediaProperties;
|
||||
import com.acgist.taoyao.boot.config.WebrtcProperties;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -20,9 +21,17 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@RequestMapping("/config")
|
||||
public class ConfigController {
|
||||
|
||||
@Autowired
|
||||
private MediaProperties mediaProperties;
|
||||
@Autowired
|
||||
private WebrtcProperties webrtcProperties;
|
||||
|
||||
@Operation(summary = "媒体配置", description = "媒体配置")
|
||||
@GetMapping("/media")
|
||||
public MediaProperties media() {
|
||||
return this.mediaProperties;
|
||||
}
|
||||
|
||||
@Operation(summary = "WebRTC配置", description = "WebRTC配置")
|
||||
@GetMapping("/webrtc")
|
||||
public WebrtcProperties webrtc() {
|
||||
|
||||
@@ -53,8 +53,20 @@ taoyao:
|
||||
id:
|
||||
sn: 0
|
||||
max-index: 999999
|
||||
media:
|
||||
audio:
|
||||
format: OPUS
|
||||
samplesize: 16
|
||||
samplerate: 32000
|
||||
video:
|
||||
format: H264
|
||||
bitrate: 1200
|
||||
framerate: 24
|
||||
resolution: 1280*760
|
||||
quality: high|standard|quick
|
||||
webrtc:
|
||||
type: SFU
|
||||
model: SFU
|
||||
framework: JITSI
|
||||
stun:
|
||||
- stun:stun1.l.google.com:19302
|
||||
- stun:stun2.l.google.com:19302
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>终端</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1
taoyao-server/src/main/resources/static/css/font.min.css
vendored
Normal file
1
taoyao-server/src/main/resources/static/css/font.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
36
taoyao-server/src/main/resources/static/css/style.css
Normal file
36
taoyao-server/src/main/resources/static/css/style.css
Normal file
@@ -0,0 +1,36 @@
|
||||
@charset "UTF-8";
|
||||
/**文本选择*/
|
||||
::selection{background:#222;color:#fff;}
|
||||
/**字体大小*/
|
||||
@media screen and (min-width:800px){html{font-size:16px;}}
|
||||
@media screen and (min-width:1200px){html{font-size:18px;}}
|
||||
@media screen and (min-width:1600px){html{font-size:20px;}}
|
||||
/**默认样式*/
|
||||
*{margin:0;padding:0;border:none;outline:none;box-sizing:content-box;}
|
||||
html{background:#EBEBEB;}
|
||||
html,body{font-family:Arial,Consolas,SimSun,"宋体";color:#222;font-weight:normal;}
|
||||
body{width:100%;height:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);font-size:1rem;line-height:1.4em;}
|
||||
a{color:#1155AA;text-decoration:none;}
|
||||
a:link{text-decoration:none;}
|
||||
a:hover{color:#4477EE;text-decoration:none;}
|
||||
a:visited{text-decoration:none;}
|
||||
img{border:0;}
|
||||
ol,ul,li{list-style:none;}
|
||||
input[type=text],textarea{box-shadow:0px 0px 3px 0px rgba(0,0,0,0.1) inset;border:1px solid rgba(0,0,0,0.1)!important;}
|
||||
input[type=text]:focus,textarea:focus,input[type=text]:hover,textarea:hover{border:1px solid #1155AA!important;}
|
||||
input::-webkit-calendar-picker-indicator{color:#1155AA;background:none;}
|
||||
/**容器*/
|
||||
.taoyao{text-align:center;}
|
||||
/**直播*/
|
||||
.taoyao .live > .video{width:100%;height:100%;}
|
||||
.taoyao .live .handler{position:fixed;width:100%;bottom:2rem;font-size:2rem;}
|
||||
/**会议*/
|
||||
.taoyao .handler a{cursor:pointer;}
|
||||
.taoyao > .handler{font-size:2rem;padding:1rem 0;width:100%;}
|
||||
.taoyao .list{width:90vw;margin:auto;}
|
||||
.taoyao .meeting{float:left;overflow:hidden;position:relative;width:calc(25% - 2rem);border:1rem solid #fff;}
|
||||
.taoyao .me,.taoyao .meeting:hover{border-color:#060;}
|
||||
.taoyao .meeting > .video{height:15vw;}
|
||||
.taoyao .meeting > .video video{width:100%;height:100%;}
|
||||
.taoyao .meeting .handler{position:absolute;bottom:0rem;text-align:center;width:100%;background:rgba(0,0,0,0.2);padding:0.2rem 0;}
|
||||
.taoyao .meeting .handler a{color:#fff;}
|
||||
501
taoyao-server/src/main/resources/static/fonts/taoyao.svg
Normal file
501
taoyao-server/src/main/resources/static/fonts/taoyao.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 297 KiB |
BIN
taoyao-server/src/main/resources/static/fonts/taoyao.ttf
Normal file
BIN
taoyao-server/src/main/resources/static/fonts/taoyao.ttf
Normal file
Binary file not shown.
BIN
taoyao-server/src/main/resources/static/fonts/taoyao.woff
Normal file
BIN
taoyao-server/src/main/resources/static/fonts/taoyao.woff
Normal file
Binary file not shown.
@@ -3,8 +3,16 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>桃夭</title>
|
||||
<link rel="stylesheet" type="text/css" href="./css/style.css" />
|
||||
<script type="text/javascript" src="./javascript/taoyao.js"></script>
|
||||
<style type="text/css">
|
||||
a{width:50%;height:100%;position:fixed;text-align:center;line-height:100%;font-size:4rem;display:flex;align-items:center;justify-content:center;}
|
||||
a:last-child{left:50%;}
|
||||
a:hover{color:#fff;background:#060;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a href="./live.html">直播</a>
|
||||
<a href="./meeting.html">会议</a>
|
||||
</body>
|
||||
</html>
|
||||
327
taoyao-server/src/main/resources/static/javascript/taoyao.js
Normal file
327
taoyao-server/src/main/resources/static/javascript/taoyao.js
Normal file
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* 桃夭WebRTC终端示例
|
||||
*/
|
||||
/** 音频配置 */
|
||||
const defaultAudioConfig = {
|
||||
// 音量:0~1
|
||||
volume: 0.5,
|
||||
// 设备
|
||||
// deviceId : '',
|
||||
// 采样率:8000|16000|32000|48000
|
||||
sampleRate: 32000,
|
||||
// 采样数:16
|
||||
sampleSize: 16,
|
||||
// 延迟大小(单位毫秒):500毫秒以内较好
|
||||
latency: 0.3,
|
||||
// 声道数量:1|2
|
||||
channelCount : 1,
|
||||
// 是否开启自动增益:true|false
|
||||
autoGainControl: false,
|
||||
// 是否开启降噪功能:true|false
|
||||
noiseSuppression: true,
|
||||
// 是否开启回音消除:true|false
|
||||
echoCancellation: true,
|
||||
// 消除回音方式:system|browser
|
||||
echoCancellationType: 'system'
|
||||
};
|
||||
/** 视频配置 */
|
||||
const defaultVideoConfig = {
|
||||
// 宽度
|
||||
width: 1280,
|
||||
// 高度
|
||||
height: 720,
|
||||
// 设备
|
||||
// deviceId: '',
|
||||
// 帧率
|
||||
frameRate: 30,
|
||||
// 裁切
|
||||
// resizeMode: '',
|
||||
// 选摄像头:user|left|right|environment
|
||||
facingMode: 'environment'
|
||||
}
|
||||
/** 兼容 */
|
||||
const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
|
||||
/** 桃夭 */
|
||||
function Taoyao(
|
||||
webSocket,
|
||||
iceServer
|
||||
) {
|
||||
this.webSocket = webSocket;
|
||||
this.iceServer = iceServer;
|
||||
this.audioStatus = true;
|
||||
this.videoStatus = true;
|
||||
this.audioStreamId = null;
|
||||
this.videoStreamId = null;
|
||||
this.audioConfig = defaultAudioConfig;
|
||||
this.videoConfig = defaultVideoConfig;
|
||||
/** 初始 */
|
||||
this.init = function() {
|
||||
if(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
|
||||
navigator.mediaDevices.enumerateDevices()
|
||||
.then(list => {
|
||||
let audioDevice = false;
|
||||
let videoDevice = false;
|
||||
list.forEach(v => {
|
||||
console.log('终端媒体设备', v.kind, v.label);
|
||||
if(v.kind === 'audioinput') {
|
||||
audioDevice = true;
|
||||
} else if(v.kind === 'videoinput') {
|
||||
videoDevice = true;
|
||||
}
|
||||
});
|
||||
if(!audioDevice) {
|
||||
console.log('终端没有音频输入设备');
|
||||
this.audioConfig = false;
|
||||
}
|
||||
if(!videoDevice) {
|
||||
console.log('终端没有视频输入设备');
|
||||
this.videoConfig = false;
|
||||
}
|
||||
})
|
||||
.catch(e => console.log('获取终端设备失败', e));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
/** 媒体 */
|
||||
this.buildUserMedia = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log("获取终端媒体:", this.audioConfig, this.videoConfig);
|
||||
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: this.audioConfig,
|
||||
video: this.videoConfig
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
} else if(navigator.getUserMedia) {
|
||||
navigator.getUserMedia({
|
||||
audio: this.audioConfig,
|
||||
video: this.videoConfig
|
||||
}, resolve, reject);
|
||||
} else {
|
||||
reject("获取终端媒体失败");
|
||||
}
|
||||
});
|
||||
};
|
||||
/** 本地 */
|
||||
this.local = async function(localVideoId, stream) {
|
||||
const localVideo = document.getElementById(localVideoId);
|
||||
if ('srcObject' in localVideo) {
|
||||
localVideo.srcObject = stream;
|
||||
} else {
|
||||
localVideo.src = URL.createObjectURL(stream);;
|
||||
}
|
||||
await localVideo.play();
|
||||
};
|
||||
/** 连接 */
|
||||
this.connect = function() {
|
||||
};
|
||||
/** 重连 */
|
||||
/** 定时 */
|
||||
/** 媒体 */
|
||||
/** 视频 */
|
||||
/** 心跳 */
|
||||
}
|
||||
/*
|
||||
var peer;
|
||||
var socket; // WebSocket
|
||||
var supportStream = false; // 是否支持使用数据流
|
||||
var localVideo; // 本地视频
|
||||
var localVideoStream; // 本地视频流
|
||||
var remoteVideo; // 远程视频
|
||||
var remoteVideoStream; // 远程视频流
|
||||
var initiator = false; // 是否已经有人在等待
|
||||
var started = false; // 是否开始
|
||||
var channelReady = false; // 是否打开WebSocket通道
|
||||
// 初始
|
||||
function initialize() {
|
||||
console.log("初始聊天");
|
||||
// 获取视频
|
||||
localVideo = document.getElementById("localVideo");
|
||||
remoteVideo = document.getElementById("remoteVideo");
|
||||
supportStream = "srcObject" in localVideo;
|
||||
// 显示状态
|
||||
if (initiator) {
|
||||
setNotice("开始连接");
|
||||
} else {
|
||||
setNotice("加入聊天:https://www.acgist.com/demo/video/?oid=FFB85D84AC56DAF88B7E22AFFA7533D3");
|
||||
}
|
||||
// 打开WebSocket
|
||||
openChannel();
|
||||
// 创建终端媒体
|
||||
buildUserMedia();
|
||||
}
|
||||
function openChannel() {
|
||||
console.log("打开WebSocket");
|
||||
socket = new WebSocket("wss://www.acgist.com/video.ws/FFB85D84AC56DAF88B7E22AFFA7533D3");
|
||||
socket.onopen = channelOpened;
|
||||
socket.onmessage = channelMessage;
|
||||
socket.onclose = channelClosed;
|
||||
socket.onerror = channelError;
|
||||
}
|
||||
function channelOpened() {
|
||||
console.log("打开WebSocket成功");
|
||||
channelReady = true;
|
||||
}
|
||||
function channelMessage(message) {
|
||||
console.log("收到消息:" + message.data);
|
||||
var msg = JSON.parse(message.data);
|
||||
if (msg.type === "offer") { // 处理Offer消息
|
||||
if (!initiator && !started) {
|
||||
connectPeer();
|
||||
}
|
||||
peer.setRemoteDescription(new RTCSessionDescription(msg));
|
||||
peer.createAnswer().then(buildLocalDescription);
|
||||
} else if (msg.type === "answer" && started) { // 处理Answer消息
|
||||
peer.setRemoteDescription(new RTCSessionDescription(msg));
|
||||
} else if (msg.type === "candidate" && started) {
|
||||
var candidate = new RTCIceCandidate({
|
||||
sdpMLineIndex : msg.label,
|
||||
candidate : msg.candidate
|
||||
});
|
||||
peer.addIceCandidate(candidate);
|
||||
} else if (msg.type === "bye" && started) {
|
||||
onRemoteClose();
|
||||
setNotice("对方已断开!");
|
||||
} else if(msg.type === "nowaiting") {
|
||||
onRemoteClose();
|
||||
setNotice("对方已离开!");
|
||||
}
|
||||
}
|
||||
function channelClosed() {
|
||||
console.log("关闭WebSocket");
|
||||
openChannel(); // 重新打开WebSocket
|
||||
}
|
||||
function channelError(event) {
|
||||
console.log("WebSocket异常:" + event);
|
||||
}
|
||||
function buildUserMedia() {
|
||||
console.log("获取终端媒体");
|
||||
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
"audio" : true,
|
||||
"video" : true
|
||||
})
|
||||
.then(onUserMediaSuccess)
|
||||
.catch(onUserMediaError);
|
||||
} else {
|
||||
navigator.getUserMedia({
|
||||
"audio" : true,
|
||||
"video" : true
|
||||
}, onUserMediaSuccess, onUserMediaError);
|
||||
}
|
||||
}
|
||||
function onUserMediaSuccess(stream) {
|
||||
localVideoStream = stream;
|
||||
if (supportStream) {
|
||||
localVideo.srcObject = localVideoStream;
|
||||
} else {
|
||||
localVideo.src = URL.createObjectURL(localVideoStream);
|
||||
}
|
||||
if (initiator) {
|
||||
connectPeer();
|
||||
}
|
||||
}
|
||||
function onUserMediaError(error) {
|
||||
alert("请打开摄像头!");
|
||||
}
|
||||
function connectPeer() {
|
||||
if (!started && localVideoStream && channelReady) {
|
||||
console.log("开始连接Peer");
|
||||
started = true;
|
||||
buildPeerConnection();
|
||||
peer.addStream(localVideoStream);
|
||||
if (initiator) {
|
||||
peer.createOffer().then(buildLocalDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
function buildPeerConnection() {
|
||||
//var server = {"iceServers" : [{"url" : "stun:stun.l.google.com:19302"}]};
|
||||
var server = {"iceServers" : [{"url" : "stun:stun1.l.google.com:19302"}]};
|
||||
peer = new PeerConnection(server);
|
||||
peer.onicecandidate = peerIceCandidate;
|
||||
peer.onconnecting = peerConnecting;
|
||||
peer.onopen = peerOpened;
|
||||
peer.onaddstream = peerAddStream;
|
||||
peer.onremovestream = peerRemoveStream;
|
||||
}
|
||||
function peerIceCandidate(event) {
|
||||
if (event.candidate) {
|
||||
sendMessage({
|
||||
type : "candidate",
|
||||
id : event.candidate.sdpMid,
|
||||
label : event.candidate.sdpMLineIndex,
|
||||
candidate : event.candidate.candidate
|
||||
});
|
||||
} else {
|
||||
console.log("不支持的candidate");
|
||||
}
|
||||
}
|
||||
function peerConnecting(message) {
|
||||
console.log("Peer连接");
|
||||
}
|
||||
function peerOpened(message) {
|
||||
console.log("Peer打开");
|
||||
}
|
||||
function peerAddStream(event) {
|
||||
console.log("远程视频添加");
|
||||
remoteVideoStream = event.stream;
|
||||
if(supportStream) {
|
||||
remoteVideo.srcObject = remoteVideoStream;
|
||||
} else {
|
||||
remoteVideo.src = URL.createObjectURL(remoteVideoStream);
|
||||
}
|
||||
setNotice("连接成功");
|
||||
waitForRemoteVideo();
|
||||
}
|
||||
function peerRemoveStream(event) {
|
||||
console.log("远程视频移除");
|
||||
}
|
||||
function buildLocalDescription(description) {
|
||||
peer.setLocalDescription(description);
|
||||
sendMessage(description);
|
||||
}
|
||||
function sendMessage(message) {
|
||||
var msgJson = JSON.stringify(message);
|
||||
socket.send(msgJson);
|
||||
console.log("发送信息:" + msgJson);
|
||||
}
|
||||
function setNotice(msg) {
|
||||
document.getElementById("footer").innerHTML = msg;
|
||||
}
|
||||
function onRemoteClose() {
|
||||
started = false;
|
||||
initiator = false;
|
||||
if(supportStream) {
|
||||
remoteVideo.srcObject = null;
|
||||
} else {
|
||||
remoteVideo.src = null;
|
||||
}
|
||||
peer.close();
|
||||
}
|
||||
function waitForRemoteVideo() {
|
||||
if (remoteVideo.currentTime > 0) { // 判断远程视频长度
|
||||
setNotice("连接成功!");
|
||||
} else {
|
||||
setTimeout(waitForRemoteVideo, 100);
|
||||
}
|
||||
}
|
||||
window.onbeforeunload = function() {
|
||||
sendMessage({type : "bye"});
|
||||
if(peer) {
|
||||
peer.close();
|
||||
}
|
||||
socket.close();
|
||||
}
|
||||
if(!WebSocket) {
|
||||
alert("你的浏览器不支持WebSocket!");
|
||||
} else if(!PeerConnection) {
|
||||
alert("你的浏览器不支持RTCPeerConnection!");
|
||||
} else {
|
||||
setTimeout(initialize, 100); // 加载完成调用初始化方法
|
||||
}
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
*/
|
||||
28
taoyao-server/src/main/resources/static/live.html
Normal file
28
taoyao-server/src/main/resources/static/live.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>直播</title>
|
||||
<link rel="stylesheet" type="text/css" href="./css/font.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="./css/style.css" />
|
||||
<script type="text/javascript" src="./javascript/taoyao.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="taoyao" id="taoyao">
|
||||
<div class="live">
|
||||
<div class="video">
|
||||
</div>
|
||||
<div class="handler">
|
||||
<a class="audio icon-volume-medium" title="音频状态"></a>
|
||||
<a class="video icon-play2" title="视频状态"></a>
|
||||
<a class="record icon-radio-checked" title="录制视频"></a>
|
||||
<a class="kick icon-cancel-circle" title="退出直播"></a>
|
||||
<a class="close icon-switch" title="关闭直播"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const live = document.getElementById('taoyao');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
60
taoyao-server/src/main/resources/static/meeting.html
Normal file
60
taoyao-server/src/main/resources/static/meeting.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>会议</title>
|
||||
<link rel="stylesheet" type="text/css" href="./css/font.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="./css/style.css" />
|
||||
<script type="text/javascript" src="./javascript/taoyao.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="taoyao">
|
||||
<div class="handler">
|
||||
<a class="create icon-svg" title="创建房间"></a>
|
||||
<a class="invite icon-address-book" title="邀请房间"></a>
|
||||
<a class="enter icon-enter" title="进入房间"></a>
|
||||
<a class="leave icon-exit" title="离开房间"></a>
|
||||
<a class="close icon-switch" title="关闭房间"></a>
|
||||
</div>
|
||||
<div class="list" id="list">
|
||||
<div class="meeting me">
|
||||
<div class="video">
|
||||
<video id="local"></video>
|
||||
</div>
|
||||
<div class="handler">
|
||||
<a class="audio icon-volume-medium" title="音频状态"></a>
|
||||
<a class="video icon-play2" title="视频状态"></a>
|
||||
<a class="record icon-radio-checked" title="录制视频"></a>
|
||||
<a class="kick icon-cancel-circle" title="踢出房间"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const list = document.getElementById('list');
|
||||
const template = `
|
||||
<div class="video">
|
||||
<video></video>
|
||||
</div>
|
||||
<div class="handler">
|
||||
<a class="audio icon-volume-medium" title="音频状态"></a>
|
||||
<a class="video icon-play2" title="视频状态"></a>
|
||||
<a class="record icon-radio-checked" title="录制视频"></a>
|
||||
<a class="kick icon-cancel-circle" title="踢出房间"></a>
|
||||
</div>
|
||||
`;
|
||||
for(let i = 0; i < 10; i++) {
|
||||
const child = document.createElement('div');
|
||||
child.className = 'meeting';
|
||||
child.innerHTML = template;
|
||||
list.appendChild(child);
|
||||
}
|
||||
const taoyao = new Taoyao();
|
||||
taoyao
|
||||
.init()
|
||||
.buildUserMedia()
|
||||
.then(stream => taoyao.local('local', stream))
|
||||
.catch((e) => alert('获取终端媒体失败:' + e));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,10 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>房间</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user