webrtc
This commit is contained in:
parent
87963fa8e1
commit
3d3f2fd89e
|
@ -6,7 +6,7 @@ import { useEffect } from "react";
|
|||
import SocketService from "./util/socket";
|
||||
const socketService = SocketService.getInstance();
|
||||
const onMessage = (data: any) => {
|
||||
console.log("message", data);
|
||||
// console.log("message", data);
|
||||
};
|
||||
socketService.on("message", onMessage);
|
||||
const onConnected = () => {
|
||||
|
|
|
@ -12,6 +12,7 @@ interface UploadFileProps {
|
|||
interface UploadFileEx extends UploadFile {
|
||||
systemImageId?: number;
|
||||
bannerName?: string;
|
||||
file_name?: string;
|
||||
redictUrl?: string;
|
||||
id?: number;
|
||||
}
|
||||
|
@ -48,6 +49,7 @@ const AliUpload = (props: UploadFileProps) => {
|
|||
const handleChange: UploadProps["onChange"] = ({ fileList: newFileList }) => {
|
||||
newFileList.forEach((i)=>{
|
||||
i.url = `${Config.uploadUrl}uploads/`+i.name
|
||||
i.fileName = i.name
|
||||
})
|
||||
setFileList(newFileList);
|
||||
props.onChnage(newFileList)
|
||||
|
@ -66,7 +68,7 @@ const AliUpload = (props: UploadFileProps) => {
|
|||
fileList={files}
|
||||
action={`${Config.uploadUrl}v1/public/fts/upload`}
|
||||
onPreview={handlePreview}
|
||||
maxCount={1}
|
||||
maxCount={4}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{files.length >= 4 ? null : uploadButton}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Pagination, PaginationProps, Table } from "antd";
|
||||
import { useState } from "react";
|
||||
const BTable = (props: any) => {
|
||||
const { store, dataSource } = props;
|
||||
const { store, dataSource ,selectCallback} = props;
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
|
||||
setSelectedRowKeys(newSelectedRowKeys);
|
||||
selectCallback(newSelectedRowKeys);
|
||||
};
|
||||
|
||||
const rowSelection = {
|
||||
|
|
|
@ -87,6 +87,13 @@ const LayOut = (props: Store) => {
|
|||
{ label: "训练任务", key: "/admin/training" },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "/admin/sys",
|
||||
label: `系统管理`,
|
||||
children: [
|
||||
{ label: "系统设置", key: "/admin/sys/setting" },
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="layout">
|
||||
|
|
|
@ -37,7 +37,6 @@ export default function MapFrom(props:any) {
|
|||
// 开启坐标选择功能
|
||||
mouseTool.on("draw", function (event) {
|
||||
var lnglat = event.obj.getBounds().getCenter(); // 获取图形的中心点坐标
|
||||
console.log("选择的坐标是:", lnglat.lng, lnglat.lat);
|
||||
markers = mouseTool.overlays.marker
|
||||
props.onChange({lng:lnglat.lng,lat:lnglat.lat})
|
||||
var mk = []
|
||||
|
|
|
@ -20,6 +20,7 @@ export interface UserDataType {
|
|||
startTime: any;
|
||||
endTime: any;
|
||||
status: any;
|
||||
identity: string;
|
||||
}
|
||||
|
||||
export interface TagDataType {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import SimpleForm from "@/components/form/simple_form";
|
||||
import { TrainingConfig } from "@/pages/training/traning_config";
|
||||
import { traningConfig } from "@/pages/training/traning_config";
|
||||
import { Button, Form, FormInstance, Modal, Select } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
|
@ -15,7 +15,7 @@ const Dispath = (props: Store) => {
|
|||
const openDispatch = () => {
|
||||
setIsModalOpen(true);
|
||||
setProjectConfig([
|
||||
...TrainingConfig,
|
||||
...traningConfig,
|
||||
{
|
||||
type: FormType.cehckboxGroup,
|
||||
label: "参与人员选择",
|
||||
|
|
|
@ -18,10 +18,10 @@ const Ec = (props: Store) => {
|
|||
const openDispatch = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
const callphone=(record:any)=>{
|
||||
webRTC.init()
|
||||
webRTC.calls()
|
||||
}
|
||||
const callphone = (record: any) => {
|
||||
webRTC.init();
|
||||
webRTC.calls();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<span onClick={openDispatch}> 应急连线</span>
|
||||
|
@ -49,7 +49,13 @@ const Ec = (props: Store) => {
|
|||
<div>姓名:{item.account} : 未在线</div>
|
||||
<p></p>
|
||||
<div>
|
||||
点击呼叫:<PhoneTwoTone style={{ fontSize: "20px" }} onClick={(item)=>{callphone(item)}} />
|
||||
点击呼叫:
|
||||
<PhoneTwoTone
|
||||
style={{ fontSize: "20px" }}
|
||||
onClick={(item) => {
|
||||
callphone(item);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -57,10 +63,22 @@ const Ec = (props: Store) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="ec_right">
|
||||
<video id="rtcVideo"></video>
|
||||
<p className="ec_right_end" onClick={()=>{
|
||||
webRTC.close()
|
||||
}}>结束通话</p>
|
||||
<div style={{display:"flex"}}>
|
||||
<video id="rtcVideo" style={{ width: "300px", height: "300px" }}></video>
|
||||
<video
|
||||
id="remoteVideo"
|
||||
style={{ width: "300px", height: "300px" }}
|
||||
></video>
|
||||
</div>
|
||||
|
||||
<p
|
||||
className="ec_right_end"
|
||||
onClick={() => {
|
||||
webRTC.close();
|
||||
}}
|
||||
>
|
||||
结束通话
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import AliUpload from "@/components/ali_upload";
|
||||
import { Button, UploadFile } from "antd";
|
||||
import { Store } from "antd/es/form/interface";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useEffect, useState } from "react";
|
||||
interface UploadFilewx extends UploadFile {
|
||||
file_name: string;
|
||||
file_url: string;
|
||||
}
|
||||
|
||||
const Banner = (props: Store) => {
|
||||
const { sysStore } = props;
|
||||
const [files, setFileList] = useState<UploadFilewx[]>([]);
|
||||
const uploadChange = (v) => {
|
||||
v.forEach((i) => {
|
||||
i.file_name = i.fileName;
|
||||
i.file_url = i.url
|
||||
});
|
||||
setFileList(v);
|
||||
};
|
||||
useEffect(() => {
|
||||
sysStore.getlist().then(() => {
|
||||
let data = sysStore.list
|
||||
data.forEach((e)=>{
|
||||
e.fileNmae = e.file_name
|
||||
e.name = e.file_name
|
||||
e.url = e.file_url
|
||||
})
|
||||
setFileList(data);
|
||||
});
|
||||
}, [sysStore]);
|
||||
const save = () => {
|
||||
sysStore.add({ list: files });
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Button type="dashed" onClick={save}>
|
||||
保存
|
||||
</Button>
|
||||
<AliUpload imgList={files} onChnage={uploadChange} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject("sysStore")(observer(Banner));
|
|
@ -0,0 +1,20 @@
|
|||
import { Tabs, TabsProps } from "antd";
|
||||
import Banner from "./banner";
|
||||
|
||||
const SystemPage = () => {
|
||||
const items: TabsProps["items"] = [
|
||||
{
|
||||
key: "1",
|
||||
label: "banner管理",
|
||||
children: <Banner />,
|
||||
},
|
||||
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Tabs defaultActiveKey="1" items={items} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemPage;
|
|
@ -7,14 +7,17 @@ import { UserDataType } from "@/model/userModel";
|
|||
import { Store } from "antd/lib/form/interface";
|
||||
import SimpleForm from "@/components/form/simple_form";
|
||||
import React from "react";
|
||||
import { FormType } from "@/components/form/interface";
|
||||
import baseHttp from "@/service/base";
|
||||
import dayjs from "dayjs";
|
||||
import { traningConfig } from "./traning_config";
|
||||
import { FormType } from "@/components/form/interface";
|
||||
import TraningUser from "./traningUser";
|
||||
const { Option } = Select;
|
||||
|
||||
const Trainings = (props: Store) => {
|
||||
const { trainingStore, trainingCatStore } = props;
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
const [isModalOpenUser, setIsModalOpenUser] = useState<boolean>(false);
|
||||
const [projectConfig, setProjectConfig] = useState<any>([]);
|
||||
const formRef = React.useRef<FormInstance>(null);
|
||||
const [record, setRecord] = useState<any>(null);
|
||||
|
@ -34,6 +37,16 @@ const Trainings = (props: Store) => {
|
|||
dataIndex: "id",
|
||||
render: (any, record) => (
|
||||
<Space wrap>
|
||||
<Button
|
||||
type="dashed"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
trainingStore.id = record?.identity;
|
||||
setIsModalOpenUser(true);
|
||||
}}
|
||||
>
|
||||
训练人员管理
|
||||
</Button>
|
||||
<Button
|
||||
type="dashed"
|
||||
size="small"
|
||||
|
@ -66,12 +79,25 @@ const Trainings = (props: Store) => {
|
|||
start_time: dayjs(record.start_time),
|
||||
end_time: dayjs(record.end_time),
|
||||
};
|
||||
setProjectConfig(defaultConfig);
|
||||
setProjectConfig([
|
||||
...traningConfig,
|
||||
{
|
||||
type: FormType.cehckboxGroup,
|
||||
label: "参与人员选择",
|
||||
name: "user_id",
|
||||
value: [],
|
||||
checkboxData: userList,
|
||||
rules: [{ required: true, message: "请选择参与人员!" }],
|
||||
},
|
||||
]);
|
||||
setIsModalOpen(true);
|
||||
formRef.current?.setFieldsValue(data);
|
||||
setRecord(data);
|
||||
setId(record.id);
|
||||
};
|
||||
useEffect(() => {
|
||||
trainingStore.getlist();
|
||||
}, [trainingStore]);
|
||||
useEffect(() => {
|
||||
trainingCatStore.getlist().then(() => {
|
||||
setStash(trainingCatStore.list);
|
||||
|
@ -99,59 +125,11 @@ const Trainings = (props: Store) => {
|
|||
}
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
trainingStore.getlist();
|
||||
}, [trainingStore]);
|
||||
const defaultConfig = [
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "任务标题",
|
||||
name: "title",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入任务标题!" }],
|
||||
},
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "任务描述",
|
||||
name: "desc",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入任务描述!" }],
|
||||
},
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "任务地点",
|
||||
name: "address",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入任务地点!" }],
|
||||
},
|
||||
{
|
||||
type: FormType.date,
|
||||
label: "任务开始时间",
|
||||
name: "start_time",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入任务开始时间!" }],
|
||||
},
|
||||
{
|
||||
type: FormType.date,
|
||||
label: "任务结束时间",
|
||||
name: "end_time",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入任务结束时间!" }],
|
||||
},
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "任务积分设置",
|
||||
name: "score",
|
||||
value: "",
|
||||
rules: [{ required: true, message: "请输入任务积分设置!" }],
|
||||
},
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "训练次数",
|
||||
name: "count",
|
||||
value: 0,
|
||||
rules: [{ required: true, message: "请输入训练次数!" }],
|
||||
},
|
||||
|
||||
// 任务发布handler
|
||||
const fabu = () => {
|
||||
setProjectConfig([
|
||||
...traningConfig,
|
||||
{
|
||||
type: FormType.cehckboxGroup,
|
||||
label: "参与人员选择",
|
||||
|
@ -160,25 +138,18 @@ const Trainings = (props: Store) => {
|
|||
checkboxData: userList,
|
||||
rules: [{ required: true, message: "请选择参与人员!" }],
|
||||
},
|
||||
];
|
||||
]);
|
||||
setId(null);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
const onFinishFailed = () => {};
|
||||
// 用户选择回调
|
||||
return (
|
||||
<div className="contentBox">
|
||||
<Space direction="vertical" size="middle" style={{ display: "flex" }}>
|
||||
<Space direction="horizontal" size={"middle"}>
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => {
|
||||
setProjectConfig(defaultConfig);
|
||||
setId(null);
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
>
|
||||
任务发布
|
||||
</Button>
|
||||
<Button onClick={() => fabu()}>任务发布</Button>
|
||||
</Space>
|
||||
|
||||
<BTable
|
||||
store={trainingStore}
|
||||
columns={columns}
|
||||
|
@ -228,6 +199,18 @@ const Trainings = (props: Store) => {
|
|||
</>
|
||||
</SimpleForm>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="训练人员"
|
||||
width={1200}
|
||||
open={isModalOpenUser}
|
||||
footer={null}
|
||||
onCancel={() => {
|
||||
setIsModalOpenUser(false);
|
||||
}}
|
||||
>
|
||||
<TraningUser/>
|
||||
</Modal>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import BTable from "@/components/b_table";
|
||||
import { UserDataType } from "@/model/userModel";
|
||||
import { Button, InputNumber, message, Modal, Space } from "antd";
|
||||
import { Store } from "antd/es/form/interface";
|
||||
import { ColumnsType } from "antd/lib/table";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const TraningUser = (props: Store) => {
|
||||
const { trainingStore } = props;
|
||||
const [keys, setKeys] = useState<Array<string>>([]);
|
||||
const [score, setScore] = useState<number | null>(0);
|
||||
const [isModalOpenUser, setIsModalOpenUser] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
trainingStore.getUserListByTraning();
|
||||
}, [trainingStore]);
|
||||
|
||||
const okhandler = () => {
|
||||
let arr: any[] = [];
|
||||
for (let index = 0; index < keys.length; index++) {
|
||||
arr.push({
|
||||
score: score,
|
||||
user_identity: keys[index],
|
||||
source: 2,
|
||||
source_identity: trainingStore.id,
|
||||
});
|
||||
}
|
||||
let res = trainingStore.setUserScore(arr);
|
||||
if (res){
|
||||
message.success("积分设置成功")
|
||||
setIsModalOpenUser(false);
|
||||
}
|
||||
};
|
||||
const _columns: ColumnsType<UserDataType> = [
|
||||
{ title: "民兵名称", dataIndex: "user_name" },
|
||||
{ title: "民兵描述", dataIndex: "remark" },
|
||||
{ title: "民兵账号", dataIndex: "account" },
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "id",
|
||||
render: (any, record) => (
|
||||
<Space wrap>
|
||||
<Button
|
||||
type="dashed"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setKeys([record.identity]);
|
||||
setIsModalOpenUser(true);
|
||||
}}
|
||||
>
|
||||
设置积分
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<p>训练人员</p>
|
||||
<Button
|
||||
disabled={keys.length === 0}
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setIsModalOpenUser(true);
|
||||
}}
|
||||
>
|
||||
批量设置
|
||||
</Button>
|
||||
</div>
|
||||
<Modal
|
||||
title="积分设置"
|
||||
width={300}
|
||||
open={isModalOpenUser}
|
||||
onOk={() => okhandler()}
|
||||
afterClose={() => setKeys([])}
|
||||
onCancel={() => {
|
||||
setIsModalOpenUser(false);
|
||||
}}
|
||||
>
|
||||
<InputNumber
|
||||
style={{ width: "200px" }}
|
||||
defaultValue={0}
|
||||
value={score}
|
||||
onChange={(e) => {
|
||||
setScore(e);
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
<BTable
|
||||
store={trainingStore}
|
||||
columns={_columns}
|
||||
dataSource={trainingStore.userList}
|
||||
selectCallback={(e) => {
|
||||
setKeys(e);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject("trainingStore")(observer(TraningUser));
|
|
@ -1,6 +1,5 @@
|
|||
import { FormType } from "@/components/form/interface";
|
||||
|
||||
export const TrainingConfig = [
|
||||
export const traningConfig = [
|
||||
{
|
||||
type: FormType.input,
|
||||
label: "任务标题",
|
||||
|
@ -51,4 +50,4 @@ export const TrainingConfig = [
|
|||
rules: [{ required: true, message: "请输入训练次数!" }],
|
||||
},
|
||||
|
||||
]
|
||||
];
|
|
@ -19,6 +19,7 @@ import Training from "@/pages/training";
|
|||
import OrgChart from "@/pages/OrgChart";
|
||||
import PoverPage from "@/pages/poverPage";
|
||||
import PoverDetail from "@/pages/poverDetail";
|
||||
import SystemPage from "@/pages/system";
|
||||
export const homeRouter = [
|
||||
{
|
||||
path: "/",
|
||||
|
@ -130,7 +131,17 @@ export const homeRouter = [
|
|||
},
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: "/admin/sys",
|
||||
element: <WhseMgmtRoute />,
|
||||
children:[
|
||||
{
|
||||
path: "/admin/sys/setting",
|
||||
index: true,
|
||||
element: <SystemPage />,
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -19,6 +19,7 @@ import { emergencyStore } from './emergency';
|
|||
import { patrolStore } from './patrol';
|
||||
import { trainingStore } from './training';
|
||||
import { trainingCatStore } from './trainingCat';
|
||||
import { sysStore } from './sys';
|
||||
|
||||
const store = {
|
||||
usrStore,
|
||||
|
@ -41,7 +42,8 @@ const store = {
|
|||
emergencyStore,
|
||||
patrolStore,
|
||||
trainingStore,
|
||||
trainingCatStore
|
||||
trainingCatStore,
|
||||
sysStore,
|
||||
};
|
||||
|
||||
export default store;
|
|
@ -0,0 +1,15 @@
|
|||
// 档案分类
|
||||
import BaseStore from "./baseStore";
|
||||
import { TagDataType } from "@/model/userModel";
|
||||
|
||||
class SysConfig {
|
||||
static LIST: string = "sys/banner"
|
||||
static ADD: string = "sys/banner"
|
||||
}
|
||||
class SysStore extends BaseStore<TagDataType> {
|
||||
constructor() {
|
||||
super(SysConfig)
|
||||
}
|
||||
}
|
||||
export const sysStore = new SysStore()
|
||||
|
|
@ -1,28 +1,60 @@
|
|||
import { action, makeObservable } from "mobx";
|
||||
import { action, makeObservable, observable, runInAction } from "mobx";
|
||||
// 档案分类
|
||||
import BaseStore from "./baseStore";
|
||||
import { TagDataType } from "@/model/userModel";
|
||||
import baseHttp from "@/service/base";
|
||||
|
||||
|
||||
class TrainingConfig {
|
||||
static LIST: string = "training/list"
|
||||
static ADD: string = "training"
|
||||
static DELETE: string = "training"
|
||||
static EDIT: string = "training"
|
||||
static ACCESS: string = "training/accept"
|
||||
static tran_user: string = "training/user"
|
||||
static addScores: string = "scoreMgmt/scores"
|
||||
|
||||
}
|
||||
class TrainingStore extends BaseStore<TagDataType> {
|
||||
constructor() {
|
||||
super(TrainingConfig)
|
||||
makeObservable(this, {
|
||||
access: action
|
||||
access: action,
|
||||
id: observable,
|
||||
userList: observable,
|
||||
getUserListByTraning: action,
|
||||
setUserScore: action,
|
||||
})
|
||||
}
|
||||
async setUserScore(ids: Array<any>) {
|
||||
let res= await baseHttp.post(TrainingConfig.addScores, {"list": ids})
|
||||
if (res.data==="ok"){
|
||||
this.getUserListByTraning();
|
||||
return true
|
||||
}
|
||||
}
|
||||
async access(id: number, param: any) {
|
||||
await baseHttp.put(TrainingConfig.ACCESS + "/" + id, param)
|
||||
this.getlist()
|
||||
}
|
||||
async getUserListByTraning() {
|
||||
let res = await baseHttp.get(TrainingConfig.tran_user + "/" + this.id, null)
|
||||
let data: Array<any> = []
|
||||
if (!res?.data?.record) {
|
||||
runInAction(() => {
|
||||
this.userList = data;
|
||||
})
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < res.data.record.length; i++) {
|
||||
data.push({
|
||||
key: res.data.record[i].identity,
|
||||
...res.data.record[i]
|
||||
})
|
||||
}
|
||||
this.userList = data;
|
||||
}
|
||||
id!: string
|
||||
userList!: Array<any>
|
||||
}
|
||||
export const trainingStore = new TrainingStore()
|
||||
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
<!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;
|
||||
const call = () => {
|
||||
console.log(ws);
|
||||
pc?.createOffer().then(function (offer) {
|
||||
pc?.setLocalDescription(offer);
|
||||
ws.send(JSON.stringify({
|
||||
type: "offer", "data": {
|
||||
"to": "576030",
|
||||
"from": "31283192",
|
||||
"description": offer,
|
||||
"media": "video",
|
||||
"session_id": "576030-31283192",
|
||||
}
|
||||
}))
|
||||
// socketService.send({ type: 'offer', data: offer })
|
||||
}).catch(function (error) {
|
||||
// 错误处理
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
}
|
||||
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
.then(stream => {
|
||||
const configuration = {
|
||||
iceServers: [{ urls: 'stun:rw.quwanya.cn/rtc' }]
|
||||
};
|
||||
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))
|
||||
ws = new WebSocket("ws://127.0.0.1:12214/ws?id=admin123")
|
||||
pc.onicecandidate = e => {
|
||||
if (!e.candidate) {
|
||||
return
|
||||
}
|
||||
ws.send(JSON.stringify({ type: 'candidate', data: e }))
|
||||
}
|
||||
ws.addEventListener('open', function (event) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "new", "data": {
|
||||
"name": "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 )",
|
||||
"id": "31283192",
|
||||
"user_agent": "flutter-webrtc/js"
|
||||
}
|
||||
}))
|
||||
});
|
||||
ws.onclose = function (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')
|
||||
}
|
||||
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)
|
||||
|
||||
// let answer = JSON.parse(msg.content.body)
|
||||
// if (!answer) {
|
||||
// return console.log('failed to parse answer')
|
||||
// }
|
||||
// console.log(answer)
|
||||
// this.setOffer(answer.data.description)
|
||||
// break;
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = function (evt) {
|
||||
console.log("ERROR: " + evt.data)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,146 @@
|
|||
<!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;
|
||||
|
||||
const call = () => {
|
||||
console.log(ws);
|
||||
pc?.createOffer().then(function (offer) {
|
||||
pc?.setLocalDescription(offer);
|
||||
ws.send(JSON.stringify({
|
||||
type: "offer", "data": {
|
||||
"to": "467056",
|
||||
"from": "31283192",
|
||||
"description": offer,
|
||||
"media": "video",
|
||||
"session_id": "467056-31283192",
|
||||
}
|
||||
}))
|
||||
// socketService.send({ type: 'offer', data: offer })
|
||||
}).catch(function (error) {
|
||||
// 错误处理
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
}
|
||||
// wss://rw.quwanya.cn/ws
|
||||
|
||||
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
.then(stream => {
|
||||
const configuration = {
|
||||
iceServers: [{ urls: 'stun:127.0.0.1:19302' }]
|
||||
};
|
||||
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))
|
||||
ws = new WebSocket("ws://rw.quwanya.cn:12217/ws")
|
||||
pc.onicecandidate = e => {
|
||||
if (!e.candidate) {
|
||||
return
|
||||
}
|
||||
ws.send(JSON.stringify({ type: 'candidate', data: e }))
|
||||
}
|
||||
ws.addEventListener('open', function (event) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "new", "data": {
|
||||
"name": "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 )",
|
||||
"id": "31283192",
|
||||
"user_agent": "flutter-webrtc/js"
|
||||
}
|
||||
}))
|
||||
});
|
||||
ws.onclose = function (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')
|
||||
}
|
||||
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)
|
||||
|
||||
// let answer = JSON.parse(msg.content.body)
|
||||
// if (!answer) {
|
||||
// return console.log('failed to parse answer')
|
||||
// }
|
||||
// console.log(answer)
|
||||
// this.setOffer(answer.data.description)
|
||||
// break;
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = function (evt) {
|
||||
console.log("ERROR: " + evt.data)
|
||||
}
|
||||
}).catch(window.alert)
|
||||
</script>
|
||||
|
||||
</html>
|
|
@ -1,5 +1,12 @@
|
|||
class Config {
|
||||
// static baseUrl = "https://rw.quwanya.cn/";
|
||||
// static uploadUrl = "https://rw.quwanya.cn/";
|
||||
// static ws = "wss://rw.quwanya.cn/ws?id=admin";
|
||||
|
||||
|
||||
static baseUrl = "http://127.0.0.1:12214/";
|
||||
static uploadUrl = "http://127.0.0.1:12214/";
|
||||
static ws = "ws://rw.quwanya.cn:12217/ws";
|
||||
|
||||
}
|
||||
export default Config;
|
|
@ -1,3 +1,5 @@
|
|||
import Config from "./config"
|
||||
|
||||
export type AutoReconnectOptions = boolean | {
|
||||
maxRetries?: number
|
||||
retryInterval?: number
|
||||
|
@ -35,7 +37,7 @@ export type AutoReconnectOptions = boolean | {
|
|||
}
|
||||
|
||||
public connect() {
|
||||
this.ws = new WebSocket('ws://127.0.0.1:12214/ws?id=admin')
|
||||
this.ws = new WebSocket(Config.ws)
|
||||
this.ws.onopen = () => {
|
||||
this.connectionStatus = ConnectionStatus.Connected
|
||||
this.emit('connected', null)
|
||||
|
|
|
@ -1,12 +1,114 @@
|
|||
import SocketService from "./socket";
|
||||
|
||||
const socketService = SocketService.getInstance();
|
||||
class WebRtc {
|
||||
private mediaStream: MediaStream | Blob | null = null;
|
||||
private pee: RTCPeerConnection | null = null;
|
||||
private video: HTMLVideoElement | null = null;
|
||||
|
||||
async init() {
|
||||
let constraints = { audio: false, video: true };
|
||||
this.video = document.getElementById('rtcVideo') as HTMLVideoElement;
|
||||
this.mediaStream = await navigator.mediaDevices.getUserMedia(constraints) as MediaStream | Blob
|
||||
socketService.on("message", (e) => {
|
||||
let msg = JSON.parse(e)
|
||||
switch (msg.type) {
|
||||
case "answer":
|
||||
let answer = JSON.parse(msg.content.body)
|
||||
if (!answer) {
|
||||
return console.log('failed to parse answer')
|
||||
}
|
||||
console.log(answer)
|
||||
this.setOffer(answer.data.description)
|
||||
break;
|
||||
case "offer":
|
||||
let offer = msg.data.description
|
||||
this.pee?.setRemoteDescription(offer)
|
||||
this.pee?.createAnswer().then(answer => {
|
||||
this.pee?.setLocalDescription(answer)
|
||||
socketService.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",
|
||||
}
|
||||
}))
|
||||
})
|
||||
break;
|
||||
case 'candidate':
|
||||
this.pee?.setRemoteDescription(msg.data.description)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
this.createOffer();
|
||||
}
|
||||
async createOffer() {
|
||||
try {
|
||||
const configuration = {
|
||||
iceServers: [{ urls: 'stun:127.0.0.1:19302' }]
|
||||
};
|
||||
const peerConnection = new RTCPeerConnection(configuration);
|
||||
this.pee = peerConnection
|
||||
// 获取远方流添加到页面播放
|
||||
peerConnection.ontrack = event => {
|
||||
const remoteVideo = document.querySelector('#remoteVideo') as HTMLVideoElement;
|
||||
remoteVideo.autoplay = true
|
||||
remoteVideo.controls = true
|
||||
remoteVideo.srcObject = event.streams[0];
|
||||
event.track.onmute = function (event) {
|
||||
remoteVideo.play()
|
||||
}
|
||||
};
|
||||
await this.getMedia(peerConnection)
|
||||
this.sendOffer()
|
||||
peerConnection.onicecandidate = e => {
|
||||
if (!e.candidate) {
|
||||
return
|
||||
}
|
||||
socketService.send({ type: 'candidate', data: e.candidate })
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
sendOffer() {
|
||||
let that = this;
|
||||
that.pee?.createOffer().then(function (offer) {
|
||||
that.pee?.setLocalDescription(offer);
|
||||
socketService.send(JSON.stringify({
|
||||
type: "offer", "data": {
|
||||
"to": "848401",
|
||||
"from": "31283192",
|
||||
"description": offer,
|
||||
"media": "video",
|
||||
"session_id": "848401-31283192",
|
||||
}
|
||||
}))
|
||||
}).catch(function (error) {
|
||||
// 错误处理
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
setOffer(offer) {
|
||||
this.pee?.setRemoteDescription(offer)
|
||||
}
|
||||
addIceCandidate(candidate) {
|
||||
console.log('addIceCandidate');
|
||||
this.pee?.addIceCandidate(candidate)
|
||||
}
|
||||
async getMedia(pee: RTCPeerConnection) {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: true,
|
||||
audio: true
|
||||
});
|
||||
this.mediaStream = stream;
|
||||
stream.getTracks().forEach(track => pee.addTrack(track, stream))
|
||||
this.calls();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
calls() {
|
||||
if (this.video) {
|
||||
|
@ -15,6 +117,7 @@ class WebRtc {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
close() {
|
||||
this.video?.pause();
|
||||
(this.mediaStream as MediaStream)?.getTracks().forEach(track => track.stop());
|
||||
|
|
Loading…
Reference in New Issue