push im model
This commit is contained in:
parent
0cc80eec5c
commit
0202c68fe0
16
README.md
16
README.md
|
@ -44,19 +44,3 @@ You don’t have to ever use `eject`. The curated feature set is suitable for sm
|
|||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
添加的数据
|
||||
|
||||
1、队伍属性管理 添加队伍
|
||||
2、个人身份管理 添加身份
|
||||
3、组织架构通过部门来组织
|
||||
|
||||
|
||||
首页:
|
||||
1、组织架构 用户数量统计,党员数量统计
|
||||
2、武装力量 根据队伍统计
|
||||
3、年度训练 按照年度统计类别,次数 (任务发布时,可选择多个类别)
|
||||
|
||||
4、物资管理
|
||||
5、档案管理
|
||||
|
||||
6、评优争先
|
|
@ -1,14 +1,14 @@
|
|||
const {override,fixBabelImports,addLessLoader} =require('customize-cra');
|
||||
const { override, fixBabelImports, addLessLoader } = require('customize-cra');
|
||||
module.exports = override(
|
||||
fixBabelImports('import',{
|
||||
libraryName:'antd',
|
||||
libraryDirectory:'es',
|
||||
style:true,
|
||||
fixBabelImports('import', {
|
||||
libraryName: 'antd',
|
||||
libraryDirectory: 'es',
|
||||
style: true,
|
||||
}),
|
||||
addLessLoader({
|
||||
lessOptions:{
|
||||
javascriptEnabled:true ,
|
||||
ModifyVars:{ '@primary-color':'#eee' }
|
||||
lessOptions: {
|
||||
javascriptEnabled: true,
|
||||
ModifyVars: { '@primary-color': '#eee' }
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
|
@ -63,7 +63,6 @@ const LayOut = (props: Store) => {
|
|||
theme="dark"
|
||||
defaultSelectedKeys={["1"]}
|
||||
defaultOpenKeys={["sub1"]}
|
||||
|
||||
style={{ height: "100%", borderRight: 0 }}
|
||||
items={items}
|
||||
onClick={(e) => {
|
||||
|
|
|
@ -4,15 +4,14 @@ import {
|
|||
NotificationOutlined,
|
||||
PaperClipOutlined,
|
||||
FileSyncOutlined,
|
||||
DashboardOutlined
|
||||
DashboardOutlined,
|
||||
} from "@ant-design/icons";
|
||||
|
||||
export const items: ItemType<MenuItemType>[] = [
|
||||
{
|
||||
key: "/",
|
||||
label: `首页看板`,
|
||||
|
||||
icon:<DashboardOutlined />,
|
||||
icon: <DashboardOutlined />,
|
||||
},
|
||||
{
|
||||
key: "/user",
|
||||
|
@ -50,7 +49,7 @@ export const items: ItemType<MenuItemType>[] = [
|
|||
],
|
||||
},
|
||||
{
|
||||
icon:<FileSyncOutlined />,
|
||||
icon: <FileSyncOutlined />,
|
||||
key: "/city",
|
||||
label: `区域管理`,
|
||||
children: [{ label: `区域列表`, key: "/city/list" }],
|
||||
|
@ -58,7 +57,7 @@ export const items: ItemType<MenuItemType>[] = [
|
|||
{
|
||||
key: "/sys",
|
||||
label: `系统管理`,
|
||||
icon:<FileSyncOutlined />,
|
||||
icon: <FileSyncOutlined />,
|
||||
children: [
|
||||
{ label: `部门管理`, key: "/sys/dep" },
|
||||
{ label: `数据字典`, key: "/sys/keywod" },
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
html {
|
||||
html ,#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
body,
|
||||
p,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
|
@ -11,9 +17,7 @@ body {
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
height: 100%;
|
||||
}
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.projectContent {
|
||||
box-sizing: border-box;
|
||||
margin: 10px;
|
||||
|
@ -73,42 +77,6 @@ code {
|
|||
color: #00ef97 !important;
|
||||
border-color: #00ef97 !important;
|
||||
}
|
||||
.owner_model {
|
||||
padding-bottom: 0px !important;
|
||||
.ant-modal-content {
|
||||
background-size: 100% 100%;
|
||||
background-position: center center; /* 可选,确保图片在容器中居中 */
|
||||
background-repeat: no-repeat; /* 确保图片不会重复 */
|
||||
padding-bottom: 0px;
|
||||
.ant-modal-header {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
background: none;
|
||||
border-bottom: 0px solid #f0f0f0;
|
||||
align-items: center;
|
||||
padding-left: 40px;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
left: -30px;
|
||||
.ant-modal-title {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.ant-modal-close {
|
||||
color: #fff;
|
||||
}
|
||||
.ant-modal-body {
|
||||
min-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
.ant-modal-footer {
|
||||
padding: 30px 30px;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
border-top: 0px solid #f0f0f0;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-overflow {
|
||||
display: inline-block;
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
.contentBox{
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
.tableName{
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import { Button, Space, Modal, FormInstance } from "antd";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import BTable from "@/components/b_table";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Store } from "antd/lib/form/interface";
|
||||
import React from "react";
|
||||
import { columns } from "./permission_config";
|
||||
import "./permission.less";
|
||||
|
||||
const Permission = (props: Store) => {
|
||||
const { usrStore } = props;
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
const formRef = React.useRef<FormInstance>(null);
|
||||
const [userId, setId] = useState<Number | null>(null);
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
const addHandler = () => {};
|
||||
return (
|
||||
<div className="contentBox">
|
||||
<Space direction="vertical" size="middle" style={{ display: "flex" }}>
|
||||
<Button type="default" onClick={() => addHandler()}>
|
||||
添加权限
|
||||
</Button>
|
||||
<BTable
|
||||
store={usrStore}
|
||||
scroll={{ x: "max-content" }}
|
||||
columns={columns}
|
||||
dataSource={usrStore.list}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={!userId ? "添加权限" : "编辑权限"}
|
||||
width={800}
|
||||
open={isModalOpen}
|
||||
afterClose={() => formRef.current?.resetFields()}
|
||||
onOk={() => formRef.current?.submit()}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
onCancel={() => {
|
||||
setId(null);
|
||||
setIsModalOpen(false);
|
||||
}}
|
||||
></Modal>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject("usrStore")(observer(Permission));
|
|
@ -1,97 +0,0 @@
|
|||
import { FormType } from "@/components/form/interface";
|
||||
import { UserDataType } from "@/model/userModel";
|
||||
import { ColumnsType } from "antd/lib/table";
|
||||
import { Image } from "antd";
|
||||
import { getBirthDateAndGender } from "@/util/util";
|
||||
export const defaultConfig = (team, per) => [
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "用户名",
|
||||
name: "user_name",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入用户名称!" }],
|
||||
},
|
||||
|
||||
{
|
||||
type: FormType.inputNumber,
|
||||
label: "年龄",
|
||||
name: "age",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入年龄" }],
|
||||
},
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "身份证",
|
||||
name: "id_card",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入身份证" }],
|
||||
},
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "登录账号",
|
||||
name: "account",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入登录账号" }],
|
||||
},
|
||||
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "联系电话",
|
||||
name: "tel",
|
||||
value: "",
|
||||
},
|
||||
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "邮箱",
|
||||
name: "email",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
type: FormType.upload,
|
||||
label: "头像",
|
||||
name: "head_img",
|
||||
value: [],
|
||||
},
|
||||
];
|
||||
|
||||
export const columns: ColumnsType<UserDataType> = [
|
||||
{
|
||||
title: "用户名",
|
||||
dataIndex: "user_name",
|
||||
width: 200,
|
||||
fixed: "left",
|
||||
},
|
||||
{
|
||||
title: "性别",
|
||||
width: 150,
|
||||
render: (render) => (
|
||||
<span>{getBirthDateAndGender(render.id_card)?.gender}</span>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: "头像",
|
||||
dataIndex: "head_img",
|
||||
width: 150,
|
||||
render: (head_img) => {
|
||||
return <Image src={head_img}></Image>;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "登录账号",
|
||||
width: 150,
|
||||
dataIndex: "account",
|
||||
},
|
||||
{
|
||||
title: "身份证",
|
||||
width: 150,
|
||||
dataIndex: "id_card",
|
||||
},
|
||||
{
|
||||
title: "联系电话",
|
||||
width: 150,
|
||||
dataIndex: "tel",
|
||||
},
|
||||
];
|
|
@ -1,11 +1,7 @@
|
|||
import { createHashRouter } from "react-router-dom";
|
||||
import LayOut from "@/components/layout/layout";
|
||||
import Login from "@/pages/login/login";
|
||||
import User from "@/pages/user/user";
|
||||
import Dashbord from "@/pages/dashbord";
|
||||
import { rbac } from "./routers/rbac_router";
|
||||
import { sku } from "./routers/sku_router";
|
||||
import Order from "@/pages/order";
|
||||
|
||||
const routers = createHashRouter([
|
||||
{
|
||||
|
@ -15,25 +11,33 @@ const routers = createHashRouter([
|
|||
{
|
||||
path: "/",
|
||||
index: true,
|
||||
element: <Dashbord />,
|
||||
lazy: async() => ({
|
||||
Component:(await import("@/pages/dashbord")).default,
|
||||
})
|
||||
},
|
||||
{
|
||||
path: "/user/list",
|
||||
index: true,
|
||||
element: <User />,
|
||||
lazy: async() => ({
|
||||
Component:(await import("@/pages/user/user")).default,
|
||||
})
|
||||
},
|
||||
...rbac,
|
||||
...sku,
|
||||
{
|
||||
path: "/order/list",
|
||||
index: true,
|
||||
element: <Order />,
|
||||
lazy: async() => ({
|
||||
Component:(await import("@/pages/order")).default,
|
||||
})
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
element: <Login />,
|
||||
lazy: async() => ({
|
||||
Component:(await import("@/pages/login/login")).default,
|
||||
})
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -1,37 +1,44 @@
|
|||
import Rbac from "@/pages/rbac";
|
||||
import Dep from "@/pages/rbac/dep";
|
||||
import Menu from "@/pages/rbac/menu";
|
||||
import Org from "@/pages/rbac/org";
|
||||
import Role from "@/pages/rbac/role";
|
||||
import Staff from "@/pages/rbac/staff";
|
||||
export const rbac = [
|
||||
{
|
||||
path: "/rbac",
|
||||
element: <Rbac />,
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/rbac")).default,
|
||||
}),
|
||||
children: [
|
||||
{
|
||||
path: "/rbac/menu",
|
||||
index: true,
|
||||
element: <Menu />,
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/rbac/menu")).default,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "/rbac/role",
|
||||
index: true,
|
||||
element: <Role />,
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/rbac/role")).default,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "/rbac/org",
|
||||
index: true,
|
||||
element: <Org />,
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/rbac/org")).default,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "/rbac/dep",
|
||||
index: true,
|
||||
element: <Dep />,
|
||||
}, {
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/rbac/dep")).default,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "/rbac/staff",
|
||||
index: true,
|
||||
element: <Staff />,
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/rbac/staff")).default,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import Menu from "@/pages/rbac/menu";
|
||||
import Sku from "@/pages/sku";
|
||||
import Brand from "@/pages/sku/brand";
|
||||
import Cat from "@/pages/sku/cat";
|
||||
import Spec from "@/pages/sku/spec";
|
||||
export const sku = [
|
||||
{
|
||||
path: "/sku",
|
||||
element: <Sku />,
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/sku")).default,
|
||||
}),
|
||||
children: [
|
||||
{
|
||||
path: "/sku/list",
|
||||
|
@ -16,17 +14,23 @@ export const sku = [
|
|||
{
|
||||
path: "/sku/cat",
|
||||
index: true,
|
||||
element: <Cat />,
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/sku/cat")).default,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "/sku/spec",
|
||||
index: true,
|
||||
element: <Spec />,
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/sku/spec")).default,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "/sku/brand",
|
||||
index: true,
|
||||
element: <Brand />,
|
||||
lazy: async () => ({
|
||||
Component: (await import("@/pages/sku/brand")).default,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h3> Local Video </h3>
|
||||
<video id="localVideo" width="160" height="120" autoplay muted></video>
|
||||
<br />
|
||||
|
||||
<h3> Remote Video </h3>
|
||||
<div id="remoteVideos"></div>
|
||||
<br />
|
||||
|
||||
<h3> Logs </h3>
|
||||
<div id="logs"></div>
|
||||
<button onclick="call()">呼叫</button>
|
||||
|
||||
</body>
|
||||
|
||||
<script>
|
||||
let ws, pc, configuration,
|
||||
url =
|
||||
'https://rw.quwanya.cn/v1/public/webRtcConfig?service=turn&username=flutter-webrtc',
|
||||
formId = "admin",
|
||||
toId = "01J9T8MP541EJ54KY4505C0S4Y";
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
let res = data.data.credential;
|
||||
configuration = {
|
||||
iceServers: [
|
||||
{
|
||||
urls: res.uris[0],
|
||||
"username": res.username,
|
||||
"credential": res.password
|
||||
},
|
||||
]
|
||||
}
|
||||
})
|
||||
const call = () => {
|
||||
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
.then(stream => {
|
||||
pc = new RTCPeerConnection(configuration)
|
||||
pc.ontrack = function (event) {
|
||||
console.log(event)
|
||||
if (event.track.kind === 'audio') {
|
||||
return
|
||||
}
|
||||
let el = document.createElement(event.track.kind)
|
||||
el.srcObject = event.streams[0]
|
||||
el.autoplay = true
|
||||
el.controls = true
|
||||
document.getElementById('remoteVideos').appendChild(el)
|
||||
event.track.onmute = function (event) {
|
||||
el.play()
|
||||
}
|
||||
event.streams[0].onremovetrack = ({ track }) => {
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
document.getElementById('localVideo').srcObject = stream
|
||||
stream.getTracks().forEach(track => pc.addTrack(track, stream))
|
||||
pc.onicecandidate = e => {
|
||||
if (!e.candidate) {
|
||||
return
|
||||
}
|
||||
ws.send(JSON.stringify({
|
||||
type: "candidate", data: {
|
||||
"to": toId,
|
||||
"from": formId,
|
||||
"description": e.candidate,
|
||||
"media": "video",
|
||||
"session_id": toId + "-" + formId,
|
||||
}
|
||||
}))
|
||||
}
|
||||
this.send({
|
||||
type: "call",
|
||||
"data": {
|
||||
"to": toId,
|
||||
"from": formId,
|
||||
"description": "call",
|
||||
"media": "video",
|
||||
"session_id": toId + "-" + formId,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
send = (data) => {
|
||||
ws.send(JSON.stringify(data))
|
||||
}
|
||||
|
||||
// wss://rw.quwanya.cn/ws
|
||||
initSocket = () => {
|
||||
ws = new WebSocket("wss://rw.quwanya.cn/wsadmin?id=" + formId)
|
||||
ws.addEventListener('open', function (event) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "peerRoot", "data": {
|
||||
"name": "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 )",
|
||||
"id": formId,
|
||||
"user_agent": "flutter-webrtc/js"
|
||||
}
|
||||
}))
|
||||
});
|
||||
ws.onclose = function (evt) {
|
||||
console.log(evt);
|
||||
}
|
||||
ws.onmessage = function (evt) {
|
||||
let msg = JSON.parse(evt.data)
|
||||
if (!msg) {
|
||||
return console.log('failed to parse msg')
|
||||
}
|
||||
switch (msg.type) {
|
||||
case 'offer':
|
||||
let offer = msg.content.body.description
|
||||
pc.setRemoteDescription(offer)
|
||||
pc.createAnswer().then(answer => {
|
||||
pc.setLocalDescription(answer)
|
||||
ws.send(JSON.stringify({
|
||||
type: 'answer', data: {
|
||||
'to': msg.content.body.from,
|
||||
'from': formId,
|
||||
'description': { 'sdp': answer.sdp, 'type': answer.type },
|
||||
'session_id': msg.content.body.from + "-" + toId,
|
||||
}
|
||||
}))
|
||||
})
|
||||
return
|
||||
case 'candidate':
|
||||
let candidate = msg.content.body.description
|
||||
console.log("candidate1111", msg.content)
|
||||
if (!candidate) {
|
||||
return console.log('failed to parse candidate')
|
||||
}
|
||||
pc.addIceCandidate(candidate)
|
||||
return
|
||||
case "read":
|
||||
pc?.createOffer().then(function (offer) {
|
||||
pc?.setLocalDescription(offer);
|
||||
ws.send(JSON.stringify({
|
||||
type: "offer", "data": {
|
||||
"to": toId,//"31283192",
|
||||
"from": formId,
|
||||
"description": offer,
|
||||
"media": "video",
|
||||
"session_id": formId + "-" + toId,
|
||||
}
|
||||
}),)
|
||||
}).catch(function (error) {
|
||||
// 错误处理
|
||||
console.error(error);
|
||||
});
|
||||
return
|
||||
case "call": // 接收到call 初始化 然后发送消息 read
|
||||
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
.then(stream => {
|
||||
pc = new RTCPeerConnection(configuration)
|
||||
pc.ontrack = function (event) {
|
||||
if (event.track.kind === 'audio') {
|
||||
return
|
||||
}
|
||||
let el = document.createElement(event.track.kind)
|
||||
el.srcObject = event.streams[0]
|
||||
el.autoplay = true
|
||||
el.controls = true
|
||||
document.getElementById('remoteVideos').appendChild(el)
|
||||
event.track.onmute = function (event) {
|
||||
el.play()
|
||||
}
|
||||
event.streams[0].onremovetrack = ({ track }) => {
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
document.getElementById('localVideo').srcObject = stream
|
||||
stream.getTracks().forEach(track => pc.addTrack(track, stream))
|
||||
pc.onicecandidate = e => {
|
||||
if (!e.candidate) {
|
||||
return
|
||||
}
|
||||
ws.send(JSON.stringify({
|
||||
type: 'candidate',
|
||||
data: {
|
||||
"to": toId,
|
||||
"from": formId,
|
||||
"description": e,
|
||||
"media": "video",
|
||||
"session_id": toId + "-" + formId,
|
||||
}
|
||||
}))
|
||||
}
|
||||
ws.send(JSON.stringify({
|
||||
type: "read", "data": {
|
||||
"to": toId,
|
||||
"from": formId,
|
||||
"description": "read",
|
||||
"media": "video",
|
||||
"session_id": toId + "-" + formId,
|
||||
}
|
||||
}))
|
||||
|
||||
})
|
||||
return
|
||||
case "answer":
|
||||
pc?.setRemoteDescription(msg.content.body.description)
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = function (evt) {
|
||||
console.log("ERROR: " + evt.data)
|
||||
}
|
||||
}
|
||||
initSocket()
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
157
src/ttttt.html
157
src/ttttt.html
|
@ -1,157 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h3> Local Video </h3>
|
||||
<video id="localVideo" width="160" height="120" autoplay muted></video>
|
||||
<br />
|
||||
|
||||
<h3> Remote Video </h3>
|
||||
<div id="remoteVideos"></div>
|
||||
<br />
|
||||
|
||||
<h3> Logs </h3>
|
||||
<div id="logs"></div>
|
||||
<button onclick="call()">呼叫</button>
|
||||
|
||||
</body>
|
||||
|
||||
<script>
|
||||
let ws;
|
||||
let pc;
|
||||
let configuration;
|
||||
var url =
|
||||
'http://127.0.0.1:12214/v1/public/webRtcConfig?service=turn&username=flutter-webrtc';
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
let res = data.data.credential;
|
||||
configuration = {
|
||||
iceServers: [
|
||||
{
|
||||
urls: res.uris[0],
|
||||
"username": res.username,
|
||||
"credential": res.password
|
||||
},
|
||||
]
|
||||
}
|
||||
})
|
||||
const call = () => {
|
||||
console.log(ws);
|
||||
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
.then(stream => {
|
||||
pc = new RTCPeerConnection(configuration)
|
||||
pc.ontrack = function (event) {
|
||||
if (event.track.kind === 'audio') {
|
||||
return
|
||||
}
|
||||
let el = document.createElement(event.track.kind)
|
||||
el.srcObject = event.streams[0]
|
||||
el.autoplay = true
|
||||
el.controls = true
|
||||
document.getElementById('remoteVideos').appendChild(el)
|
||||
event.track.onmute = function (event) {
|
||||
el.play()
|
||||
}
|
||||
event.streams[0].onremovetrack = ({ track }) => {
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
document.getElementById('localVideo').srcObject = stream
|
||||
stream.getTracks().forEach(track => pc.addTrack(track, stream))
|
||||
pc.onicecandidate = e => {
|
||||
if (!e.candidate) {
|
||||
return
|
||||
}
|
||||
ws.send(JSON.stringify({ type: 'candidate', data: e }))
|
||||
}
|
||||
pc?.createOffer().then(function (offer) {
|
||||
pc?.setLocalDescription(offer);
|
||||
ws.send(JSON.stringify({
|
||||
type: "offer", "data": {
|
||||
"to": "31283191",
|
||||
"from": "31283192",
|
||||
"description": offer,
|
||||
"media": "video",
|
||||
"session_id": "31283191-31283192",
|
||||
}
|
||||
}))
|
||||
// socketService.send({ type: 'offer', data: offer })
|
||||
}).catch(function (error) {
|
||||
// 错误处理
|
||||
console.error(error);
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
// wss://rw.quwanya.cn/ws
|
||||
initSocket = () => {
|
||||
ws = new WebSocket("ws://127.0.0.1:12214/wsadmin?id=31283191")
|
||||
ws.addEventListener('open', function (event) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "peerRoot", "data": {
|
||||
"name": "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 )",
|
||||
"id": "31283191",
|
||||
"user_agent": "flutter-webrtc/js"
|
||||
}
|
||||
}))
|
||||
});
|
||||
ws.onclose = function (evt) {
|
||||
console.log(evt);
|
||||
// window.alert("Websocket has closed")
|
||||
}
|
||||
ws.onmessage = function (evt) {
|
||||
let msg = JSON.parse(evt.data)
|
||||
if (!msg) {
|
||||
return console.log('failed to parse msg')
|
||||
}
|
||||
console.log(msg.type);
|
||||
switch (msg.type) {
|
||||
case 'offer':
|
||||
let offer = msg.data.description
|
||||
pc.setRemoteDescription(offer)
|
||||
pc.createAnswer().then(answer => {
|
||||
pc.setLocalDescription(answer)
|
||||
ws.send(JSON.stringify({
|
||||
type: 'answer', data: {
|
||||
'to': msg.data.from,
|
||||
'from': "31283192",
|
||||
'description': { 'sdp': answer.sdp, 'type': answer.type },
|
||||
'session_id': msg.data.from + "-31283192",
|
||||
}
|
||||
}))
|
||||
})
|
||||
return
|
||||
|
||||
case 'candidate':
|
||||
let candidate = msg.data.candidate
|
||||
if (!candidate) {
|
||||
return console.log('failed to parse candidate')
|
||||
}
|
||||
pc.addIceCandidate(candidate)
|
||||
case "answer":
|
||||
pc?.setRemoteDescription(msg.data.description)
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = function (evt) {
|
||||
console.log("ERROR: " + evt.data)
|
||||
}
|
||||
}
|
||||
initSocket()
|
||||
</script>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue