feat:提交代码
This commit is contained in:
commit
edc7fb6507
|
@ -0,0 +1,8 @@
|
|||
# webstorm
|
||||
.idea
|
||||
|
||||
# mac
|
||||
.DS_Store
|
||||
|
||||
# nodejs
|
||||
node_modules
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 zhouxhere
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
|
@ -0,0 +1,14 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
|
||||
parserOptions: {
|
||||
parser: "babel-eslint",
|
||||
},
|
||||
rules: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
},
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
# client-vue
|
||||
|
||||
webrtc 客户端 vue 实现 client demo
|
||||
|
||||
问题:navigator.mediaDevices 需要在https下 ( localhost 除外)
|
||||
|
||||
1. 调试时可修改 vue.config.js 中 devServer https,但是后端也需要修改为 https
|
||||
2. chrome 浏览器 设置 chrome://flags,Insecure origins treated as secure,启用填入ip即可
|
||||
|
||||
## Project setup
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
plugins: [
|
||||
[
|
||||
"import",
|
||||
{
|
||||
libraryName: "ant-design-vue",
|
||||
libraryDirectory: "es",
|
||||
style: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "client-vue",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^1.7.7",
|
||||
"babel-plugin-import": "^1.13.3",
|
||||
"core-js": "^3.6.5",
|
||||
"moment": "^2.29.1",
|
||||
"socket.io-client": "^4.1.3",
|
||||
"vue": "^2.6.11",
|
||||
"vue-router": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^5.0.0",
|
||||
"lint-staged": "^9.5.0",
|
||||
"prettier": "^2.2.1",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,vue}": [
|
||||
"vue-cli-service lint",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<video ref="video" controls muted autoplay></video>
|
||||
<p>{{ name }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "VideoCard",
|
||||
props: {
|
||||
srcObject: null,
|
||||
name: null,
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.video.srcObject = this.srcObject;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.card {
|
||||
width: 200px;
|
||||
height: 160px;
|
||||
background: #f3f3f3;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 2px;
|
||||
margin: 6px;
|
||||
|
||||
> video {
|
||||
width: 200px;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
> p {
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,24 @@
|
|||
import Vue from "vue";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Input,
|
||||
List,
|
||||
Result,
|
||||
Row,
|
||||
} from "ant-design-vue";
|
||||
import { Form } from "ant-design-vue";
|
||||
import { Icon } from "ant-design-vue";
|
||||
|
||||
Vue.use(Button);
|
||||
Vue.use(Form);
|
||||
Vue.use(Icon);
|
||||
Vue.use(Input);
|
||||
Vue.use(Row);
|
||||
Vue.use(Col);
|
||||
Vue.use(List);
|
||||
Vue.use(Avatar);
|
||||
Vue.use(Card);
|
||||
Vue.use(Result);
|
|
@ -0,0 +1,12 @@
|
|||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
|
||||
import "@/core/bootstrap";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
|
@ -0,0 +1,25 @@
|
|||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import Home from "../views/Home.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
redirect: "/",
|
||||
},
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: "history",
|
||||
base: process.env.BASE_URL,
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -0,0 +1,745 @@
|
|||
<template>
|
||||
<div class="home">
|
||||
<div class="home-header">
|
||||
<template v-if="status === 'login'">
|
||||
<a-input v-model="loginParams.name" placeholder="name" />
|
||||
<a-input v-model="loginParams.unique" placeholder="unique" />
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!loginParams.name || !loginParams.unique"
|
||||
@click="login"
|
||||
>login</a-button
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="status === 'join'">
|
||||
<a-input v-model="createParam" placeholder="create" />
|
||||
<a-button type="primary" :disabled="!createParam" @click="create"
|
||||
>create</a-button
|
||||
>
|
||||
<a-input v-model="joinParam" placeholder="join" />
|
||||
<a-button type="primary" :disabled="!joinParam" @click="join"
|
||||
>join</a-button
|
||||
>
|
||||
<a-button type="primary" @click="logout">logout</a-button>
|
||||
</template>
|
||||
<template v-else-if="status === 'room' || status === 'chat'">
|
||||
<p>{{ user.id }}</p>
|
||||
<p>{{ user.name }}</p>
|
||||
<p>{{ user.unique }}</p>
|
||||
<p>{{ room.id }}</p>
|
||||
<p>{{ room.name }}</p>
|
||||
<a-button type="primary" @click="leave">leave</a-button>
|
||||
<a-button type="primary" @click="logout">logout</a-button>
|
||||
</template>
|
||||
<template v-else> please login first</template>
|
||||
</div>
|
||||
<a-row
|
||||
class="home-content"
|
||||
:style="
|
||||
status !== 'room' &&
|
||||
status !== 'chat' &&
|
||||
'align-items: center; justify-content: center'
|
||||
"
|
||||
>
|
||||
<a-result
|
||||
v-if="status !== 'room' && status !== 'chat'"
|
||||
title="please login and create or join a room!"
|
||||
>
|
||||
<template #icon>
|
||||
<a-icon type="smile" theme="twoTone" />
|
||||
</template>
|
||||
</a-result>
|
||||
<template v-else>
|
||||
<a-col :span="4" class="home-content-user">
|
||||
<a-list item-layout="horizontal" :data-source="users">
|
||||
<a-list-item slot="renderItem" slot-scope="item">
|
||||
<a-list-item-meta :description="item.unique">
|
||||
<p slot="title">{{ item.name }}</p>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-col>
|
||||
<a-col :span="12" class="home-content-message">
|
||||
<a-list
|
||||
item-layout="horizontal"
|
||||
class="home-content-message-list"
|
||||
:data-source="messages"
|
||||
>
|
||||
<a-list-item
|
||||
slot-scope="msg"
|
||||
:class="[
|
||||
`home-content-message-list-${
|
||||
msg.message.userId !== user.id ? 'left' : 'right'
|
||||
}`,
|
||||
]"
|
||||
slot="renderItem"
|
||||
>
|
||||
<div class="home-content-message-list-user">
|
||||
<p>{{ msg.user ? msg.user.name : "none" }}</p>
|
||||
<span>{{ moment(msg.message.timestamp).format("HH:mm") }}</span>
|
||||
</div>
|
||||
<div class="home-content-message-list-message">
|
||||
{{ msg.message.content }}
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
<div class="home-content-message-input">
|
||||
<a-textarea
|
||||
class="home-content-message-input-textarea"
|
||||
placeholder="发送消息"
|
||||
v-model="message"
|
||||
/>
|
||||
<div class="home-content-message-input-btn">
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!Boolean(message)"
|
||||
@click="send"
|
||||
>发送</a-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8" class="home-content-chat">
|
||||
<div class="home-content-chat-main">
|
||||
<video-card
|
||||
v-if="localStream"
|
||||
:name="user.name"
|
||||
:src-object="localStream"
|
||||
/>
|
||||
<a-result
|
||||
title="leave chat"
|
||||
class="home-content-chat-card"
|
||||
v-if="localStream"
|
||||
>
|
||||
<template #icon>
|
||||
<!-- <a-icon type="smile" theme="twoTone" />-->
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="leaveChat"> leave </a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
<a-result
|
||||
v-if="!localStream"
|
||||
title="join chat"
|
||||
class="home-content-chat-card"
|
||||
>
|
||||
<template #icon>
|
||||
<!-- <a-icon type="smile" theme="twoTone" />-->
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="joinChat"> chat </a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
<video-card
|
||||
v-for="chat in chats.filter((p) => p.stream)"
|
||||
:key="chat.user.id"
|
||||
:name="chat.user.name"
|
||||
:src-object="chat.stream"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { io } from "socket.io-client";
|
||||
import moment from "moment";
|
||||
import VideoCard from "../components/VideoCard";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: { VideoCard },
|
||||
data() {
|
||||
return {
|
||||
status: null,
|
||||
socket: null,
|
||||
clientType: navigator.userAgent.match(
|
||||
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
|
||||
)
|
||||
? "mobile"
|
||||
: "pc",
|
||||
loginParams: { name: null, unique: null },
|
||||
createParam: null,
|
||||
joinParam: null,
|
||||
message: null,
|
||||
users: [],
|
||||
messages: [],
|
||||
chats: [],
|
||||
user: null,
|
||||
client: null,
|
||||
room: null,
|
||||
localStream: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// 页面刷新
|
||||
window.addEventListener("beforeunload", (event) => {
|
||||
event.preventDefault();
|
||||
if (this.socket) {
|
||||
this.socket.emit("close", {
|
||||
userId: this.user ? this.user.id : null,
|
||||
roomId: this.room ? this.room.id : null,
|
||||
clientType: this.clientType,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.socket = io("http://localhost:8000", {});
|
||||
|
||||
this.socket.on("connect", () => {
|
||||
console.log("connect");
|
||||
this.status = "login";
|
||||
});
|
||||
// 登录反馈
|
||||
this.socket.on("login", (res) => {
|
||||
console.log("login", res);
|
||||
if (res.status === "success") {
|
||||
this.user = res.data;
|
||||
this.client = this.user.clients.find((p) => p.type === this.clientType);
|
||||
sessionStorage.setItem("user", JSON.stringify(this.user));
|
||||
this.status = "join";
|
||||
|
||||
if (sessionStorage.getItem("room")) {
|
||||
let _room = JSON.parse(sessionStorage.getItem("room"));
|
||||
this.joinParam = _room.id;
|
||||
this.join();
|
||||
}
|
||||
} else if (res.status === "error") {
|
||||
this.user = null;
|
||||
this.status = "login";
|
||||
}
|
||||
this.loginParams = { name: null, unique: null };
|
||||
});
|
||||
// 登出反馈
|
||||
this.socket.on("logout", (res) => {
|
||||
console.log("logout", res);
|
||||
sessionStorage.clear();
|
||||
this.status = "login";
|
||||
this.user = null;
|
||||
this.client = null;
|
||||
this.room = null;
|
||||
this.users = [];
|
||||
this.messages = [];
|
||||
this.chats = [];
|
||||
});
|
||||
// 新建房间反馈
|
||||
this.socket.on("create", (res) => {
|
||||
console.log("create", res);
|
||||
if (res.status === "success") {
|
||||
this.room = res.data;
|
||||
sessionStorage.setItem("room", JSON.stringify(this.room));
|
||||
this.status = "room";
|
||||
this.messages = this.room.messages.map((item) => {
|
||||
let _user = this.users.find((p) => p.id === item.userId);
|
||||
return {
|
||||
user: _user,
|
||||
message: item,
|
||||
};
|
||||
});
|
||||
this.users = this.room.member;
|
||||
console.log(this.room.chats);
|
||||
this.room.chats.forEach((item) => {
|
||||
if (item.user.id === this.user.id) return;
|
||||
let _chat = this.chats.find((p) => p.user.id === item.user.id);
|
||||
if (_chat && _chat.user && _chat.client) {
|
||||
if (_chat.client.socketId !== item.client.socketId) {
|
||||
// change peerConnection
|
||||
if (_chat.connection) {
|
||||
_chat.connection.close();
|
||||
_chat.connection = null;
|
||||
}
|
||||
_chat.stream = null;
|
||||
}
|
||||
} else {
|
||||
// create peerConnection
|
||||
_chat = {
|
||||
user: item.user,
|
||||
client: item.client,
|
||||
connection: null,
|
||||
options: item.options,
|
||||
stream: null,
|
||||
};
|
||||
this.chats.push(_chat);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.createParam = null;
|
||||
});
|
||||
// 加入房间反馈
|
||||
this.socket.on("join", (res) => {
|
||||
console.log("join", res);
|
||||
if (res.status === "success") {
|
||||
this.joinParam = null;
|
||||
}
|
||||
});
|
||||
// 离开房间反馈
|
||||
this.socket.on("leave", (res) => {
|
||||
console.log("leave", res);
|
||||
if (res.status === "success") {
|
||||
this.status = "join";
|
||||
this.room = null;
|
||||
this.users = [];
|
||||
this.messages = [];
|
||||
this.chats = [];
|
||||
}
|
||||
});
|
||||
// 更新房间数据(users,messages,chats)
|
||||
this.socket.on("room", (res) => {
|
||||
console.log("room", res);
|
||||
if (res.status === "success") {
|
||||
this.room = res.data;
|
||||
|
||||
sessionStorage.setItem("room", JSON.stringify(this.room));
|
||||
this.users = this.room.member;
|
||||
this.messages = this.room.messages.map((item) => {
|
||||
let _user = this.users.find((p) => p.id === item.userId);
|
||||
return {
|
||||
user: _user,
|
||||
message: item,
|
||||
};
|
||||
});
|
||||
|
||||
this.chats.forEach((item) => {
|
||||
if (!this.room.chats.find((q) => q.user.id === item.user.id)) {
|
||||
if (item.connection) {
|
||||
item.connection.close();
|
||||
item.connection = null;
|
||||
item.stream = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.chats = this.chats.filter((p) =>
|
||||
this.room.chats.find((q) => q.user.id === p.user.id)
|
||||
);
|
||||
|
||||
this.room.chats.forEach((item) => {
|
||||
if (item.user.id === this.user.id) return;
|
||||
let _chat = this.chats.find((p) => p.user.id === item.user.id);
|
||||
|
||||
if (!_chat) {
|
||||
_chat = {
|
||||
user: item.user,
|
||||
client: item.client,
|
||||
connection: null,
|
||||
options: item.options,
|
||||
stream: null,
|
||||
};
|
||||
this.chats.push(_chat);
|
||||
} else if (_chat.client.socketId !== item.client.socketId) {
|
||||
// change peerConnection
|
||||
if (_chat.connection) {
|
||||
_chat.connection.close();
|
||||
_chat.connection = null;
|
||||
}
|
||||
_chat.client = item.client;
|
||||
_chat.user = item.user;
|
||||
_chat.stream = null;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.localStream) {
|
||||
this.chats.forEach((item) => {
|
||||
if (this.localStream) {
|
||||
if (!item.connection) {
|
||||
this.register(item);
|
||||
}
|
||||
if (this.status === "chat") {
|
||||
this.connect(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.status = "room";
|
||||
}
|
||||
});
|
||||
// 发送消息反馈
|
||||
this.socket.on("message", (res) => {
|
||||
console.log("message", res);
|
||||
if (res.status === "success") {
|
||||
this.message = null;
|
||||
}
|
||||
});
|
||||
// 加入聊天(media)反馈
|
||||
this.socket.on("join_chat", (res) => {
|
||||
console.log("join_chat", res);
|
||||
this.status = "chat";
|
||||
});
|
||||
// 离开聊天(media)反馈
|
||||
this.socket.on("leave_chat", (res) => {
|
||||
console.log("leave_chat", res);
|
||||
this.status = "room";
|
||||
this.chats = [];
|
||||
});
|
||||
// webrtc
|
||||
this.socket.on("chat", (res) => {
|
||||
console.log("chat", res);
|
||||
if (res.status === "success") {
|
||||
let _chat = this.chats.find((p) => p.client.socketId === res.data.from);
|
||||
if (!_chat) return;
|
||||
if (res.data.sdp) {
|
||||
if (res.data.sdp.type === "offer") {
|
||||
_chat.connection
|
||||
.setRemoteDescription(res.data.sdp)
|
||||
.then(() => {
|
||||
return _chat.connection.createAnswer();
|
||||
})
|
||||
.then((answer) => {
|
||||
return _chat.connection.setLocalDescription(answer);
|
||||
})
|
||||
.then(() => {
|
||||
this.p2p(
|
||||
res.data.from,
|
||||
_chat.connection.localDescription,
|
||||
null
|
||||
);
|
||||
});
|
||||
} else {
|
||||
_chat.connection.setRemoteDescription(res.data.sdp);
|
||||
}
|
||||
}
|
||||
if (
|
||||
res.data.candidate &&
|
||||
_chat.connection.remoteDescription &&
|
||||
_chat.connection.remoteDescription.type
|
||||
) {
|
||||
_chat.connection.addIceCandidate(res.data.candidate);
|
||||
}
|
||||
}
|
||||
});
|
||||
// 断开连接
|
||||
this.socket.on("disconnect", () => {
|
||||
console.log("disconnect");
|
||||
this.user = null;
|
||||
this.client = null;
|
||||
this.room = null;
|
||||
this.users = [];
|
||||
this.messages = [];
|
||||
this.chats = [];
|
||||
this.status = null;
|
||||
this.socket = null;
|
||||
this.localStream = null;
|
||||
});
|
||||
|
||||
if (sessionStorage.getItem("user")) {
|
||||
let _user = JSON.parse(sessionStorage.getItem("user"));
|
||||
this.loginParams = { name: _user.name, unique: _user.unique };
|
||||
this.login();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
login() {
|
||||
this.socket.emit("login", {
|
||||
...this.loginParams,
|
||||
clientType: this.clientType,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 登出
|
||||
*/
|
||||
logout() {
|
||||
this.socket.emit("logout", {
|
||||
userId: this.user.id,
|
||||
roomId: this.room ? this.room.id : null,
|
||||
clientType: this.clientType,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 新建房间
|
||||
*/
|
||||
create() {
|
||||
this.socket.emit("create", {
|
||||
userId: this.user.id,
|
||||
roomName: this.createParam,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 加入房间
|
||||
*/
|
||||
join() {
|
||||
this.socket.emit("join", {
|
||||
userId: this.user.id,
|
||||
roomId: this.joinParam,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 离开房间
|
||||
*/
|
||||
leave() {
|
||||
this.socket.emit("leave", { userId: this.user.id, roomId: this.room.id });
|
||||
},
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
send() {
|
||||
this.socket.emit("message", {
|
||||
userId: this.user.id,
|
||||
roomId: this.room.id,
|
||||
content: this.message,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 加入聊天(media)
|
||||
*/
|
||||
joinChat() {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({
|
||||
audio: true,
|
||||
video: {
|
||||
width: { min: 1024, ideal: 1280, max: 1920 },
|
||||
height: { min: 576, ideal: 720, max: 1080 },
|
||||
},
|
||||
})
|
||||
.then((stream) => {
|
||||
this.localStream = stream;
|
||||
})
|
||||
// .then(() => {
|
||||
// this.$refs.local.srcObject = this.localStream;
|
||||
// })
|
||||
.then(() => {
|
||||
this.socket.emit("join_chat", {
|
||||
userId: this.user.id,
|
||||
roomId: this.room.id,
|
||||
clientType: this.clientType,
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 离开聊天(media)
|
||||
*/
|
||||
leaveChat() {
|
||||
this.localStream.getTracks().forEach(function (track) {
|
||||
if (track.readyState === "live") {
|
||||
track.stop();
|
||||
}
|
||||
});
|
||||
this.localStream = null;
|
||||
this.socket.emit("leave_chat", {
|
||||
userId: this.user.id,
|
||||
roomId: this.room.id,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* webrtc
|
||||
*/
|
||||
p2p(to, sdp, candidate) {
|
||||
this.socket.emit("chat", {
|
||||
from: this.socket.id,
|
||||
to: to,
|
||||
sdp: sdp,
|
||||
candidate: candidate,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 注册 peerConnection
|
||||
*/
|
||||
register(param) {
|
||||
param.connection = new RTCPeerConnection();
|
||||
this.localStream.getTracks().forEach((track) => {
|
||||
param.connection.addTrack(track, this.localStream);
|
||||
});
|
||||
param.connection.ontrack = (event) => {
|
||||
if (event.streams.length > 0) {
|
||||
if (param.stream !== event.streams[0]) {
|
||||
param.stream = event.streams[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
param.connection.onicecandidate = (event) => {
|
||||
if (event.candidate) {
|
||||
this.p2p(param.client.socketId, null, event.candidate);
|
||||
}
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 发起 p2p 连接
|
||||
*/
|
||||
connect(param) {
|
||||
param.connection
|
||||
.createOffer()
|
||||
.then((offer) => {
|
||||
if (param.connection.signalingState !== "stable")
|
||||
return Promise.reject(
|
||||
`connection state is ${param.connection.signalingState}`
|
||||
);
|
||||
return param.connection.setLocalDescription(offer);
|
||||
})
|
||||
.then(() => {
|
||||
this.p2p(
|
||||
param.client.socketId,
|
||||
param.connection.localDescription,
|
||||
null
|
||||
);
|
||||
});
|
||||
},
|
||||
moment,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.home {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
&-header {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
> input {
|
||||
min-width: 120px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
* {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
|
||||
&-user,
|
||||
&-message,
|
||||
&-chat {
|
||||
border: 1px solid #ebedf0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&-user,
|
||||
&-chat {
|
||||
overflow: hidden scroll;
|
||||
}
|
||||
|
||||
&-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-list {
|
||||
flex: 4 1 auto;
|
||||
background: #f3f3f3;
|
||||
border-bottom: 1px solid #ebedf0;
|
||||
overflow: hidden scroll;
|
||||
|
||||
&-left {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&-right {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&-user {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
> p {
|
||||
margin-bottom: 0;
|
||||
border-radius: 2px;
|
||||
background: #fff;
|
||||
padding: 6px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
> span {
|
||||
font-size: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&-message {
|
||||
border: 1px solid #b7eb8f;
|
||||
border-radius: 2px;
|
||||
background: #f6ffed;
|
||||
padding: 6px 12px;
|
||||
min-height: 33px;
|
||||
margin-right: 6px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&-input {
|
||||
flex: 1 1 auto;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> textarea {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&-btn {
|
||||
flex: 0 1 48px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-chat {
|
||||
position: relative;
|
||||
display: block;
|
||||
//width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&-main {
|
||||
width: 100%;
|
||||
//height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&-card {
|
||||
width: 200px;
|
||||
height: 160px;
|
||||
background: #f3f3f3;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 2px;
|
||||
margin: 6px;
|
||||
|
||||
> video {
|
||||
width: 200px;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
> p {
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = {
|
||||
devServer: {
|
||||
https: false,
|
||||
},
|
||||
css: {
|
||||
loaderOptions: {
|
||||
less: {
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
配合实现 webrtc 的 nodejs server demo
|
||||
|
||||
|
||||
|
||||
采用http,若采用https,需要自行修改
|
|
@ -0,0 +1,25 @@
|
|||
import { v4 } from 'uuid'
|
||||
|
||||
export class Client {
|
||||
id: string
|
||||
socketId: string
|
||||
type: string
|
||||
status: string
|
||||
|
||||
constructor(socketId: string, type: string, status: string) {
|
||||
this.id = v4()
|
||||
this.socketId = socketId
|
||||
this.type = type;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
login(socketId: string) {
|
||||
this.socketId = socketId
|
||||
this.status = 'online'
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.socketId = ""
|
||||
this.status = 'offline'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import {v4} from "uuid";
|
||||
|
||||
export class Message {
|
||||
id: string
|
||||
userId: string
|
||||
content: string
|
||||
timestamp: number
|
||||
|
||||
|
||||
constructor(userId: string, content: string) {
|
||||
this.userId = userId;
|
||||
this.content = content;
|
||||
this.id = v4()
|
||||
this.timestamp = new Date().valueOf()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import {User} from "./User";
|
||||
import {v4} from "uuid";
|
||||
import {Message} from "./Message";
|
||||
import {Client} from "./Client";
|
||||
|
||||
export class Room {
|
||||
id: string
|
||||
name: string
|
||||
admin: User
|
||||
member: Array<User>
|
||||
messages: Array<Message>
|
||||
chats: Array<any>
|
||||
|
||||
constructor(name: string, admin: User) {
|
||||
this.id = v4()
|
||||
this.name = name
|
||||
this.admin = admin
|
||||
this.member = []
|
||||
this.messages = []
|
||||
this.chats = []
|
||||
this.join(this.admin)
|
||||
}
|
||||
|
||||
isIn(user: User): boolean {
|
||||
let _index = this.member.findIndex(p => p.id === user.id)
|
||||
return _index >= 0
|
||||
}
|
||||
|
||||
join(user: User) {
|
||||
let _index = this.member.findIndex(p => p.id === user.id)
|
||||
if (_index < 0) {
|
||||
this.member.push(user)
|
||||
}
|
||||
}
|
||||
|
||||
leave(user: User) {
|
||||
let _index = this.member.findIndex(p => p.id === user.id)
|
||||
if (_index >= 0) {
|
||||
this.member.splice(_index, 1)
|
||||
}
|
||||
this.close(user)
|
||||
}
|
||||
|
||||
chown(admin: User, user: User) {
|
||||
if (admin.id !== this.admin.id) throw new Error('no permission to change owner')
|
||||
let _index = this.member.findIndex(p => p.id === user.id)
|
||||
if (_index <= 0) throw new Error('not in the room')
|
||||
this.admin = user
|
||||
}
|
||||
|
||||
open(user: User, client: Client, options: any) {
|
||||
let _index = this.chats.findIndex(p => p.user.id === user.id)
|
||||
if (_index < 0) {
|
||||
this.chats.push({user: user, client: client, options: options})
|
||||
}
|
||||
}
|
||||
|
||||
close(user: User) {
|
||||
let _index = this.chats.findIndex(p => p.user.id === user.id)
|
||||
if (_index >= 0) {
|
||||
this.chats.splice(_index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import { v4 } from 'uuid'
|
||||
import { Client } from './Client'
|
||||
|
||||
export class User {
|
||||
id: string
|
||||
name: string
|
||||
unique: string
|
||||
status: string
|
||||
clients: Array<Client>
|
||||
|
||||
constructor(name: string, unique: string, socketId: string, clientType: string) {
|
||||
this.id = v4()
|
||||
this.name = name
|
||||
this.unique = unique
|
||||
this.status = ''
|
||||
this.clients = []
|
||||
this.login(socketId, clientType)
|
||||
}
|
||||
|
||||
login(socketId: string, clientType: string) {
|
||||
this.status = 'online'
|
||||
let _client = this.clients.find(p => p.type === clientType)
|
||||
if (_client) {
|
||||
_client.login(socketId)
|
||||
} else {
|
||||
_client = new Client(socketId, clientType, 'online')
|
||||
this.clients.push(_client)
|
||||
}
|
||||
}
|
||||
|
||||
logout(clientType: string) {
|
||||
// this.status = 'offline'
|
||||
let _client = this.clients.find(p => p.type === clientType)
|
||||
if (_client) {
|
||||
_client.logout()
|
||||
// setTimeout(() => {
|
||||
// this.clients.splice(this.clients.findIndex(p => p.type === clientType), 1)
|
||||
// }, 1000 * 60 * 30)
|
||||
}
|
||||
this.status = this.clients.some(p => p.status === 'online') ? 'online' : 'offline'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
import express from 'express'
|
||||
import {createServer} from 'http'
|
||||
import {Server, Socket} from 'socket.io'
|
||||
import { Client } from './entity/Client';
|
||||
import {User} from "./entity/User";
|
||||
import {Room} from "./entity/Room";
|
||||
import {Message} from "./entity/Message";
|
||||
|
||||
const app = express()
|
||||
|
||||
const httpServer = createServer(app)
|
||||
const io = new Server(httpServer,{
|
||||
cors: {
|
||||
origin: '*'
|
||||
}
|
||||
})
|
||||
|
||||
const rooms: Array<Room> = []
|
||||
|
||||
function getRoom(id: string): Room | null {
|
||||
return rooms.find(p => p.id === id) || null
|
||||
}
|
||||
|
||||
const users: Array<User> = []
|
||||
|
||||
function checkUser(unique: string, name: string): boolean {
|
||||
return users.some(p => p.unique === unique && p.name != name)
|
||||
}
|
||||
|
||||
function findUser(name: string, unique: string): User | null {
|
||||
return users.find(p => p.name === name && p.unique === unique) || null
|
||||
}
|
||||
|
||||
function getUser(id: string): User | null {
|
||||
return users.find(p => p.id === id) || null
|
||||
}
|
||||
|
||||
function getClient(user: User, clientType: string): Client | null {
|
||||
return user.clients.find(p => p.type === clientType) || null
|
||||
}
|
||||
|
||||
io.on("connection", (socket: Socket) => {
|
||||
console.log('connected', socket.id)
|
||||
|
||||
/**
|
||||
* 登陆操作
|
||||
*/
|
||||
socket.on('login', req => {
|
||||
console.log('login', req)
|
||||
if (checkUser(req.unique, req.name)) return socket.emit('login',{status: 'error', message: 'unique is already existed'})
|
||||
let _user = findUser(req.name, req.unique)
|
||||
if (_user) {
|
||||
let _client = getClient(_user, req.clientType)
|
||||
if (_client) {
|
||||
socket.to(_client.socketId).emit('login', {status: 'error', message: 'login at other place'})
|
||||
_user.logout(req.clientType)
|
||||
}
|
||||
_user.login(socket.id, req.clientType)
|
||||
socket.emit('login', {status: 'success', message: 'login success', data: _user})
|
||||
} else {
|
||||
_user = new User(req.name, req.unique, socket.id, req.clientType)
|
||||
users.push(_user)
|
||||
socket.emit('login', {status: 'success', message: 'login success', data: _user})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 登出操作
|
||||
*/
|
||||
socket.on('logout', req => {
|
||||
console.log('logout', req)
|
||||
let _user = getUser(req.userId)
|
||||
if (_user) {
|
||||
_user.logout(req.clientType)
|
||||
|
||||
let _room = getRoom(req.roomId)
|
||||
if (_room) {
|
||||
_room.leave(_user)
|
||||
socket.leave(_room.id)
|
||||
|
||||
if (_room.member.length === 0) {
|
||||
rooms.splice(rooms.findIndex(p => p.id === req.roomId), 1)
|
||||
} else {
|
||||
io.in(req.roomId).emit('room', {status: 'success', data:_room})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socket.emit('logout', {status: 'success', message: 'logout success'})
|
||||
})
|
||||
|
||||
/**
|
||||
* 新增房间
|
||||
*/
|
||||
socket.on('create', req => {
|
||||
console.log('create', req)
|
||||
let _user = getUser(req.userId)
|
||||
if (!_user) return socket.emit('create', {status: 'failure', message: 'user not existed'})
|
||||
|
||||
let _room = new Room(req.roomName, _user)
|
||||
rooms.push(_room)
|
||||
socket.join(_room.id)
|
||||
socket.emit('create', {status: 'success', message: 'create room success', data: _room})
|
||||
})
|
||||
|
||||
/**
|
||||
* 加入房间
|
||||
*/
|
||||
socket.on('join', req => {
|
||||
console.log('join', req)
|
||||
let _user = getUser(req.userId)
|
||||
if (!_user) return socket.emit('join', {status: 'failure', message: 'user not existed'})
|
||||
let _room = getRoom(req.roomId)
|
||||
if (!_room) return socket.emit('join', {status: 'failure', message: 'room not existed'})
|
||||
|
||||
_room.join(_user)
|
||||
socket.join(_room.id)
|
||||
|
||||
socket.emit('join', {status: 'success', message: 'join success'})
|
||||
io.in(req.roomId).emit('room', {status: 'success', data:_room})
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取房间信息
|
||||
*/
|
||||
socket.on('room', req => {
|
||||
let _room = getRoom(req.roomId)
|
||||
if (!_room) return socket.emit('join', {status: 'failure', message: 'room not existed'})
|
||||
socket.emit('room', {status: 'success',data:_room})
|
||||
})
|
||||
|
||||
/**
|
||||
* 离开房间
|
||||
*/
|
||||
socket.on('leave', req => {
|
||||
console.log('leave', req)
|
||||
let _user = getUser(req.userId)
|
||||
if (!_user) return socket.emit('leave', {status: 'failure', message: 'user not existed'})
|
||||
let _room = getRoom(req.roomId)
|
||||
if (!_room) return socket.emit('leave', {status: 'failure', message: 'room not existed'})
|
||||
|
||||
_room.leave(_user)
|
||||
socket.leave(_room.id)
|
||||
|
||||
if (_room.member.length === 0) {
|
||||
rooms.splice(rooms.findIndex(p => p.id === req.roomId), 1)
|
||||
} else {
|
||||
io.in(req.roomId).emit('room', {status: 'success', data:_room})
|
||||
}
|
||||
|
||||
socket.emit('leave', {status: 'success',message: 'leave success'})
|
||||
})
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
socket.on('message', req => {
|
||||
console.log('message', req)
|
||||
let _room = getRoom(req.roomId)
|
||||
if (!_room) return socket.emit('message', {status: 'failure', message: 'room not existed'})
|
||||
let _message = new Message(req.userId, req.content)
|
||||
_room.messages.push(_message)
|
||||
|
||||
// socket.to(req.roomId).emit('message', {status: 'success', data: _message})
|
||||
socket.emit('message', {status: 'success', message: 'send message success'})
|
||||
io.in(req.roomId).emit('room', {status: 'success', data:_room})
|
||||
})
|
||||
|
||||
/**
|
||||
* 加入聊天
|
||||
*/
|
||||
socket.on('join_chat', req => {
|
||||
console.log('join_chat', req)
|
||||
let _user = getUser(req.userId)
|
||||
if (!_user) return socket.emit('join_chat', {status: 'failure', message: 'user not existed'})
|
||||
let _room = getRoom(req.roomId)
|
||||
if (!_room) return socket.emit('join_chat', {status: 'failure', message: 'room not existed'})
|
||||
let _client = getClient(_user, req.clientType)
|
||||
if (!_client) return socket.emit('join_chat', {status: 'failure', message: 'client not existed'})
|
||||
|
||||
_room.open(_user, _client, {video: req.video, audio: req.audio})
|
||||
|
||||
socket.emit('join_chat', {status: 'success',message: 'join chat success'})
|
||||
|
||||
io.in(req.roomId).emit('room', {status: 'success', data:_room})
|
||||
})
|
||||
|
||||
/**
|
||||
* 离开聊天
|
||||
*/
|
||||
socket.on('leave_chat', req => {
|
||||
console.log('leave_chat', req)
|
||||
let _user = getUser(req.userId)
|
||||
if (!_user) return socket.emit('leave_chat', {status: 'failure', message: 'user not existed'})
|
||||
let _room = getRoom(req.roomId)
|
||||
if (!_room) return socket.emit('leave_chat', {status: 'failure', message: 'room not existed'})
|
||||
|
||||
_room.close(_user)
|
||||
|
||||
socket.emit('leave_chat', {status: 'success',message: 'leave chat success'})
|
||||
io.in(req.roomId).emit('room', {status: 'success', data:_room})
|
||||
})
|
||||
|
||||
/**
|
||||
* webrtc
|
||||
*/
|
||||
socket.on('chat', req => {
|
||||
console.log('chat', req)
|
||||
socket.to(req.to).emit('chat', {status: 'success', data: {from: req.from, to: req.to, sdp: req.sdp, candidate: req.candidate}})
|
||||
})
|
||||
|
||||
/**
|
||||
* 断开
|
||||
*/
|
||||
socket.on('close', req => {
|
||||
console.log('close',req)
|
||||
if (req.userId) {
|
||||
let _user = users.find(p => p.id === req.userId)
|
||||
if (_user) {
|
||||
_user.logout(req.clientType)
|
||||
|
||||
let _room = rooms.find(p => p.id === req.roomId)
|
||||
if (_room) {
|
||||
_room.leave(_user)
|
||||
|
||||
if (_room.member.length === 0) {
|
||||
rooms.splice(rooms.findIndex(p => p.id === req.roomId), 1)
|
||||
} else {
|
||||
io.in(req.roomId).emit('room', {status: 'success', data:_room})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
socket.disconnect()
|
||||
})
|
||||
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('disconnect', socket.id)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
httpServer.listen(8000, () => {
|
||||
console.log('application listening 8000')
|
||||
})
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "server-nodejs",
|
||||
"version": "1.0.0",
|
||||
"description": "webrtc demo server by nodejs",
|
||||
"main": "main.ts",
|
||||
"scripts": {
|
||||
"test": "null",
|
||||
"dev": "nodemon --ext js,ts --exec 'ts-node main.ts'"
|
||||
},
|
||||
"author": "zhouxhere",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/express": "^4.17.13",
|
||||
"express": "^4.17.1",
|
||||
"socket.io": "^4.1.3",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/uuid": "^8.3.1",
|
||||
"nodemon": "^2.0.12",
|
||||
"ts-node": "^10.1.0",
|
||||
"typescript": "^4.3.5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"strict": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue