[*] js
This commit is contained in:
@@ -26,5 +26,21 @@ public class Meeting {
|
|||||||
*/
|
*/
|
||||||
@Schema(title = "终端会话标识列表", description = "终端会话标识列表")
|
@Schema(title = "终端会话标识列表", description = "终端会话标识列表")
|
||||||
private List<String> sns;
|
private List<String> sns;
|
||||||
|
/**
|
||||||
|
* 创建终端标识
|
||||||
|
*/
|
||||||
|
@Schema(title = "创建终端标识", description = "创建终端标识")
|
||||||
|
private String creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增终端会话标识
|
||||||
|
*
|
||||||
|
* @param sn 终端会话标识
|
||||||
|
*/
|
||||||
|
public void addSn(String sn) {
|
||||||
|
synchronized (this.sns) {
|
||||||
|
this.sns.add(sn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ package com.acgist.taoyao.meeting;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.boot.service.IdService;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,6 +19,9 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Service
|
@Service
|
||||||
public class MeetingManager {
|
public class MeetingManager {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IdService idService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会议列表
|
* 会议列表
|
||||||
*/
|
*/
|
||||||
@@ -50,4 +56,22 @@ public class MeetingManager {
|
|||||||
return meeting == null ? List.of() : meeting.getSns();
|
return meeting == null ? List.of() : meeting.getSns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建会议
|
||||||
|
*
|
||||||
|
* @param sn 创建会议终端标识
|
||||||
|
*
|
||||||
|
* @return 会议信息
|
||||||
|
*/
|
||||||
|
public Meeting create(String sn) {
|
||||||
|
final Meeting meeting = new Meeting();
|
||||||
|
meeting.setId(this.idService.buildIdToString());
|
||||||
|
meeting.setSns(new CopyOnWriteArrayList<>());
|
||||||
|
meeting.setCreator(sn);
|
||||||
|
meeting.addSn(sn);
|
||||||
|
this.meetings.add(meeting);
|
||||||
|
log.info("创建会议:{}", meeting.getId());
|
||||||
|
return meeting;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,32 +6,34 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
|
import com.acgist.taoyao.meeting.Meeting;
|
||||||
import com.acgist.taoyao.meeting.MeetingManager;
|
import com.acgist.taoyao.meeting.MeetingManager;
|
||||||
import com.acgist.taoyao.signal.client.ClientSession;
|
import com.acgist.taoyao.signal.client.ClientSession;
|
||||||
|
import com.acgist.taoyao.signal.client.ClientSessionManager;
|
||||||
import com.acgist.taoyao.signal.event.meeting.MeetingCreateEvent;
|
import com.acgist.taoyao.signal.event.meeting.MeetingCreateEvent;
|
||||||
import com.acgist.taoyao.signal.listener.ApplicationListenerAdapter;
|
import com.acgist.taoyao.signal.listener.ApplicationListenerAdapter;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建会议监听
|
* 创建会议监听
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
@Component
|
@Component
|
||||||
public class MeetingCreateListener extends ApplicationListenerAdapter<MeetingCreateEvent> {
|
public class MeetingCreateListener extends ApplicationListenerAdapter<MeetingCreateEvent> {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MeetingManager meetingManager;
|
private MeetingManager meetingManager;
|
||||||
|
@Autowired
|
||||||
|
private ClientSessionManager clientSessionManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(MeetingCreateEvent event) {
|
public void onApplicationEvent(MeetingCreateEvent event) {
|
||||||
// this.meetingManager.create();
|
|
||||||
final ClientSession session = event.getSession();
|
final ClientSession session = event.getSession();
|
||||||
|
final Meeting meeting = this.meetingManager.create(session.sn());
|
||||||
final Message message = event.getMessage();
|
final Message message = event.getMessage();
|
||||||
message.setBody(Map.of("id", "1234"));
|
message.setBody(Map.of("id", meeting.getId()));
|
||||||
session.push(message);
|
// 广播:不改ID触发创建终端事件回调
|
||||||
|
this.clientSessionManager.broadcast(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,6 @@ input::-webkit-calendar-picker-indicator{color:#1155AA;background:none;}
|
|||||||
.taoyao .meeting > .video video{width:100%;height:100%;}
|
.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{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;}
|
.taoyao .meeting .handler a{color:#fff;}
|
||||||
.taoyao .meeting .handler a.record:hover{color:#060!important;}
|
.taoyao .meeting .handler a:hover{color:#060!important;}
|
||||||
|
.taoyao .meeting .handler a.expel:hover{color:#C00!important;}
|
||||||
.taoyao .meeting .handler a.record.active{color:#C00;}
|
.taoyao .meeting .handler a.record.active{color:#C00;}
|
||||||
.taoyao .meeting .handler a.kick:hover{color:#C00;}
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
/** 桃夭WebRTC终端应用功能 */
|
|
||||||
// 样式操作
|
|
||||||
function classSwitch(e, className) {
|
|
||||||
if(e.className.indexOf(className) >= 0) {
|
|
||||||
e.className = e.className.replace(className, '').replace(/(^\s)|(\s$)/g, "");
|
|
||||||
} else {
|
|
||||||
e.className = e.className + ' ' + className;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -277,6 +277,70 @@ const signalChannel = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/** 终端 */
|
||||||
|
function TaoyaoClient(
|
||||||
|
sn
|
||||||
|
) {
|
||||||
|
/** 终端标识 */
|
||||||
|
this.sn = sn;
|
||||||
|
/** 视频对象 */
|
||||||
|
this.video = null;
|
||||||
|
/** 媒体状态 */
|
||||||
|
this.audioStatus = true;
|
||||||
|
this.videoStatus = true;
|
||||||
|
this.recordStatus = false;
|
||||||
|
/** 媒体信息 */
|
||||||
|
this.audioStreamId = null;
|
||||||
|
this.videoStreamId = null;
|
||||||
|
/** 播放视频 */
|
||||||
|
this.play = async function() {
|
||||||
|
await this.video.play();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
/** 重新加载 */
|
||||||
|
this.load = function() {
|
||||||
|
this.video.load();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/** 暂停视频 */
|
||||||
|
this.pause = function() {
|
||||||
|
this.video.pause();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
/** 关闭视频 */
|
||||||
|
this.close = function() {
|
||||||
|
this.video.close();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
/** 设置视频对象 */
|
||||||
|
this.buildVideo = async function(videoId, stream) {
|
||||||
|
if(!this.video) {
|
||||||
|
this.video = document.getElementById(videoId);
|
||||||
|
}
|
||||||
|
await this.buildStream(stream);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
/** 设置媒体流 */
|
||||||
|
this.buildStream = async function(stream) {
|
||||||
|
if(stream) {
|
||||||
|
if ('srcObject' in this.video) {
|
||||||
|
this.video.srcObject = stream;
|
||||||
|
} else {
|
||||||
|
this.video.src = URL.createObjectURL(stream);;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.play();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
/** 设置音频流 */
|
||||||
|
this.buildAudioStream = function() {
|
||||||
|
|
||||||
|
};
|
||||||
|
/** 设置视频流 */
|
||||||
|
this.buildVideoStream = function() {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
/** 桃夭 */
|
/** 桃夭 */
|
||||||
function Taoyao(
|
function Taoyao(
|
||||||
webSocket,
|
webSocket,
|
||||||
@@ -288,26 +352,20 @@ function Taoyao(
|
|||||||
this.webSocket = webSocket;
|
this.webSocket = webSocket;
|
||||||
/** IceServer地址 */
|
/** IceServer地址 */
|
||||||
this.iceServer = iceServer;
|
this.iceServer = iceServer;
|
||||||
/** 媒体状态 */
|
|
||||||
this.audioStatus = true;
|
|
||||||
this.videoStatus = true;
|
|
||||||
/** 设备状态 */
|
/** 设备状态 */
|
||||||
this.audioEnabled = true;
|
this.audioEnabled = true;
|
||||||
this.videoEnabled = true;
|
this.videoEnabled = true;
|
||||||
/** 媒体信息 */
|
|
||||||
this.audioStreamId = null;
|
|
||||||
this.videoStreamId = null;
|
|
||||||
/** 媒体配置 */
|
/** 媒体配置 */
|
||||||
this.audioConfig = audioConfig || defaultAudioConfig;
|
this.audioConfig = audioConfig || defaultAudioConfig;
|
||||||
this.videoConfig = videoConfig || defaultVideoConfig;
|
this.videoConfig = videoConfig || defaultVideoConfig;
|
||||||
/** 本地视频 */
|
|
||||||
this.localVideo = null;
|
|
||||||
/** 终端媒体 */
|
|
||||||
this.clientMedia = {};
|
|
||||||
/** 信令通道 */
|
|
||||||
this.signalChannel = null;
|
|
||||||
/** 发送信令 */
|
/** 发送信令 */
|
||||||
this.push = null;
|
this.push = null;
|
||||||
|
/** 本地终端 */
|
||||||
|
this.localClient = null;
|
||||||
|
/** 远程终端 */
|
||||||
|
this.remoteClient = [];
|
||||||
|
/** 信令通道 */
|
||||||
|
this.signalChannel = null;
|
||||||
/** 检查设备 */
|
/** 检查设备 */
|
||||||
this.checkDevice = function() {
|
this.checkDevice = function() {
|
||||||
let self = this;
|
let self = this;
|
||||||
@@ -394,15 +452,10 @@ function Taoyao(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
/** 设置本地媒体 */
|
/** 设置本地终端 */
|
||||||
this.localMedia = async function(localVideoId, stream) {
|
this.buildLocalClient = async function(localVideoId, stream) {
|
||||||
this.localVideo = document.getElementById(localVideoId);
|
this.localClient = new TaoyaoClient(signalConfig.sn);
|
||||||
if ('srcObject' in this.localVideo) {
|
await this.localClient.buildVideo(localVideoId, stream);
|
||||||
this.localVideo.srcObject = stream;
|
|
||||||
} else {
|
|
||||||
this.localVideo.src = URL.createObjectURL(stream);;
|
|
||||||
}
|
|
||||||
await this.localVideo.play();
|
|
||||||
};
|
};
|
||||||
/** 关闭:关闭媒体 */
|
/** 关闭:关闭媒体 */
|
||||||
this.close = function() {
|
this.close = function() {
|
||||||
|
|||||||
@@ -5,91 +5,128 @@
|
|||||||
<title>会议</title>
|
<title>会议</title>
|
||||||
<link rel="stylesheet" type="text/css" href="./css/font.min.css" />
|
<link rel="stylesheet" type="text/css" href="./css/font.min.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="./css/style.css" />
|
<link rel="stylesheet" type="text/css" href="./css/style.css" />
|
||||||
<script type="text/javascript" src="./javascript/app.js"></script>
|
<script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script>
|
||||||
<script type="text/javascript" src="./javascript/taoyao.js"></script>
|
<script type="text/javascript" src="./javascript/taoyao.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="taoyao">
|
<div class="taoyao" id="app">
|
||||||
<div class="handler">
|
<div class="handler">
|
||||||
<a class="create icon-svg" title="创建房间" onclick="create(this)"></a>
|
<a class="create icon-svg" title="创建房间" @click="create"></a>
|
||||||
<a class="invite icon-address-book" title="邀请房间" onclick="invite"></a>
|
<a class="invite icon-address-book" title="邀请房间" @click="invite"></a>
|
||||||
<a class="enter icon-enter" title="进入房间" onclick="enter"></a>
|
<a class="enter icon-enter" title="进入房间" @click="enter"></a>
|
||||||
<a class="leave icon-exit" title="离开房间" onclick="leave"></a>
|
<a class="leave icon-exit" title="离开房间" @click="leave"></a>
|
||||||
<a class="close icon-switch" title="关闭房间" onclick="close"></a>
|
<a class="close icon-switch" title="关闭房间" @click="close"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="list" id="list">
|
<div class="list">
|
||||||
<div class="meeting me">
|
<div class="meeting me">
|
||||||
<div class="video">
|
<div class="video">
|
||||||
<video id="local"></video>
|
<video id="local"></video>
|
||||||
</div>
|
</div>
|
||||||
<div class="handler">
|
<div class="handler">
|
||||||
<a class="audio icon-volume-medium" title="音频状态" onclick="audio"></a>
|
<a class="audio icon-volume-medium" title="音频状态" @click="audioSelf"></a>
|
||||||
<a class="video icon-play2" title="视频状态" onclick="video"></a>
|
<a class="video icon-play2" title="视频状态" @click="videoSelf"></a>
|
||||||
<a class="record icon-radio-checked" title="录制视频" onclick="record(this)"></a>
|
<a class="record icon-radio-checked" title="录制视频" @click="recordSelf"></a>
|
||||||
<a class="kick icon-cancel-circle" title="踢出房间" onclick="kick"></a>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="meeting" v-for="client in this.clients" :key="client.sn">
|
||||||
|
<div class="video">
|
||||||
|
<video v-bind:id="client.sn"></video>
|
||||||
|
</div>
|
||||||
|
<div class="handler">
|
||||||
|
<a class="audio" title="音频状态" v-bind:class="client.audio?'icon-volume-medium':'icon-volume-mute2'" @click="audio(client.sn)"></a>
|
||||||
|
<a class="video" title="视频状态" v-bind:class="client.video?'icon-play2':'icon-stop'" @click="video(client.sn)"></a>
|
||||||
|
<a class="record icon-radio-checked" title="录制视频" v-bind:class="client.record?'active':''" @click="record(client.sn)"></a>
|
||||||
|
<a class="expel icon-cancel-circle" title="踢出房间" @click="expel(client.sn)"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const list = document.getElementById('list');
|
const vue = new Vue({
|
||||||
const template = `
|
el: "#app",
|
||||||
<div class="video">
|
data: {
|
||||||
<video></video>
|
clients: [
|
||||||
</div>
|
{sn:"1", audio: true, video: true, record: false},
|
||||||
<div class="handler">
|
{sn:"2", audio: true, video: true, record: false},
|
||||||
<a class="audio icon-volume-medium" title="音频状态"></a>
|
{sn:"3", audio: true, video: true, record: false}
|
||||||
<a class="video icon-play2" title="视频状态"></a>
|
],
|
||||||
<a class="record icon-radio-checked" title="录制视频"></a>
|
taoyao: null,
|
||||||
<a class="kick icon-cancel-circle" title="踢出房间"></a>
|
meetingId: null
|
||||||
</div>
|
},
|
||||||
`;
|
mounted() {
|
||||||
for(let i = 0; i < 10; i++) {
|
if(signalConfig.sn) {
|
||||||
const child = document.createElement('div');
|
// TODO:修改sn
|
||||||
child.className = 'meeting';
|
}
|
||||||
child.innerHTML = template;
|
this.taoyao = new Taoyao("wss://localhost:8888/websocket.signal");
|
||||||
list.appendChild(child);
|
// 检查设备
|
||||||
}
|
this.taoyao
|
||||||
const taoyao = new Taoyao("wss://localhost:8888/websocket.signal");
|
.checkDevice()
|
||||||
// 检查设备
|
.buildChannel(this.callback)
|
||||||
taoyao
|
//.buildLocalMedia()
|
||||||
.checkDevice()
|
//.then(stream => this.taoyao.buildLocalClient('local', stream))
|
||||||
.buildChannel(callback)
|
//.catch((e) => console.error('打开终端媒体失败', e));
|
||||||
//.buildLocalMedia()
|
},
|
||||||
//.then(stream => taoyao.localMedia('local', stream))
|
beforeDestroy() {
|
||||||
//.catch((e) => alert('获取终端媒体失败:' + e));
|
},
|
||||||
// 信令回调
|
methods: {
|
||||||
function callback(data) {
|
// 信令回调:返回true表示已经处理
|
||||||
switch(data.header.pid) {
|
callback: function(data) {
|
||||||
case signalProtocol.client.heartbeat:
|
switch(data.header.pid) {
|
||||||
// 心跳
|
case signalProtocol.client.heartbeat:
|
||||||
return true;
|
// 心跳
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
// 创建会议
|
||||||
|
create: function(event) {
|
||||||
|
let self = this;
|
||||||
|
this.taoyao.createMeeting(data => {
|
||||||
|
self.meetingId = data.body.id;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 返回终端
|
||||||
|
client: function(sn) {
|
||||||
|
return this.clients.filter(v => v.sn === sn)[0];
|
||||||
|
},
|
||||||
|
// 会议邀请
|
||||||
|
invite: function(sn) {
|
||||||
|
},
|
||||||
|
// 进入会议
|
||||||
|
enter: function(sn) {
|
||||||
|
},
|
||||||
|
// 离开会议
|
||||||
|
leave: function(sn) {
|
||||||
|
},
|
||||||
|
// 关闭会议
|
||||||
|
close: function(sn) {
|
||||||
|
},
|
||||||
|
// 控制音频
|
||||||
|
audio: function(sn) {
|
||||||
|
this.client(sn).audio = !this.client(sn).audio;
|
||||||
|
},
|
||||||
|
// 控制视频
|
||||||
|
video: function(sn) {
|
||||||
|
this.client(sn).video = !this.client(sn).video;
|
||||||
|
},
|
||||||
|
// 录制视频
|
||||||
|
record: function(sn) {
|
||||||
|
this.client(sn).record = !this.client(sn).record;
|
||||||
|
},
|
||||||
|
// 踢出会议
|
||||||
|
expel: function(sn) {
|
||||||
|
},
|
||||||
|
// 控制音频
|
||||||
|
audioSelf: function(sn) {
|
||||||
|
},
|
||||||
|
// 控制视频
|
||||||
|
videoSelf: function(sn) {
|
||||||
|
},
|
||||||
|
// 录制视频
|
||||||
|
recordSelf: function(sn) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
});
|
||||||
}
|
|
||||||
// 创建房间
|
|
||||||
function create() {
|
|
||||||
taoyao.createMeeting(data => {
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 进入房间
|
|
||||||
function enter() {
|
|
||||||
}
|
|
||||||
// 声音控制
|
|
||||||
function audio() {
|
|
||||||
}
|
|
||||||
// 视频控制
|
|
||||||
function video() {
|
|
||||||
}
|
|
||||||
// 录制视频
|
|
||||||
function record(e) {
|
|
||||||
taoyao.push(signalProtocol.buildProtocol(signalConfig.sn, signalProtocol.client.heartbeat), () => {
|
|
||||||
classSwitch(e, 'active');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 踢出会议
|
|
||||||
function kick() {
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -184,6 +184,12 @@
|
|||||||
|
|
||||||
### 创建会议信令(4000)
|
### 创建会议信令(4000)
|
||||||
|
|
||||||
|
终端->服务端
|
||||||
|
|
||||||
|
```
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
|
||||||
### 关闭会议信令(4001)
|
### 关闭会议信令(4001)
|
||||||
|
|
||||||
释放资源、广播广播
|
释放资源、广播广播
|
||||||
@@ -243,3 +249,14 @@ MCU/SFU模式有效
|
|||||||
### 开启录像(5006)
|
### 开启录像(5006)
|
||||||
|
|
||||||
### 停止录像(5007)
|
### 停止录像(5007)
|
||||||
|
|
||||||
|
### 配置媒体(5008)
|
||||||
|
|
||||||
|
配置订阅媒体:码率、帧率、分辨率等等
|
||||||
|
|
||||||
|
|
||||||
|
### IceCandidate
|
||||||
|
|
||||||
|
### Offer
|
||||||
|
|
||||||
|
### Answer
|
||||||
@@ -3,6 +3,8 @@ package com.acgist.taoyao.signal.media;
|
|||||||
/**
|
/**
|
||||||
* 终端媒体操作
|
* 终端媒体操作
|
||||||
*
|
*
|
||||||
|
* TODO:注意暂停心跳防止端口关闭
|
||||||
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
public interface ClientMediaHandler {
|
public interface ClientMediaHandler {
|
||||||
|
|||||||
Reference in New Issue
Block a user