Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/FE] WebRTC 페이지를 추가한다. #38

Merged
merged 5 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
VITE_SPRING_URL=https://example.com/api/

# AI 서비스의 URL을 입력합니다.
VITE_AI_URL=https://example.com/ai/
VITE_AI_URL=https://example.com/ai/

# 시그널링 서비스의 URL을 입력합니다.
VITE_SIGNALING_SERVICE_URL=ws://example.com/socket/
5 changes: 5 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import styled from "styled-components";
import "./App.scss";
import { ReducerContext } from "./reducer/context.js";
import ReservationListPage from "./pages/Reservation/ReservationListPage.jsx";
import ReservationMeetingPage from "./pages/Reservation/ReservationMeetingPage.jsx";

const Container = styled.div`
margin-top: 60px;
Expand Down Expand Up @@ -61,6 +62,10 @@ function App() {
/>
<Route path="/theramakeassgin" element={<TheraMakeAssignPage />} />
<Route path="/untact/list" element={<ReservationListPage />} />
<Route
path="/untact/meeting/:uuid"
element={<ReservationMeetingPage />}
/>
</Routes>
</Container>
</Router>
Expand Down
11 changes: 10 additions & 1 deletion src/components/Reservation/ReservationCreateModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
reserveCreateReducer,
} from "../../reducer/reservation-create.js";
import dayjs from "dayjs";
import { createReservation } from "../../librarys/api/reservation.js";

const Container = styled.div`
display: flex;
Expand Down Expand Up @@ -69,6 +70,7 @@ export const ReservationCreateModal = () => {
reserveCreateReducer,
intialReserveCreateState,
);

const [times, setTimes] = useState(createTimes());
const { index, available, description } = state;

Expand All @@ -93,7 +95,14 @@ export const ReservationCreateModal = () => {
});
}

function onComplete() {
async function onComplete() {
// const res = await createReservation(
// state.adminId,
// "ldh",
// state.description,
// [state.year, state.month + 1, state.date].join("-"),
// state.index,
// );
console.log(state);
}

Expand Down
11 changes: 9 additions & 2 deletions src/components/Reservation/ReservationItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dayjs from "dayjs";
import classNames from "classnames";
import { useDispatch } from "react-redux";
import { show } from "../../redux/modalSlice.js";
import { useNavigate } from "react-router-dom";

const Container = styled.div`
height: 110px;
Expand Down Expand Up @@ -94,8 +95,9 @@ const dummyText = `그러나 한 시와 강아지, 가을 보고, 새워 까닭

const notReadyText = `아직 비대면 진료 요약이 생성되지 않았습니다.`;

const ReservationItem = ({ name, role, dept, date, index }) => {
const ReservationItem = ({ id, name, role, dept, date, index }) => {
const dispatch = useDispatch();
const navigate = useNavigate();

const image = useMemo(() => {
switch (role) {
Expand Down Expand Up @@ -130,7 +132,11 @@ const ReservationItem = ({ name, role, dept, date, index }) => {
if (isDone) {
return <Btn type="disabled">종료되었습니다</Btn>;
} else if (isOpen) {
return <Btn type="primary">입장</Btn>;
return (
<Btn type="primary" onClick={() => navigate("/untact/meeting/" + id)}>
입장
</Btn>
);
} else {
return <Btn type="disabled">예약 시간이 아닙니다</Btn>;
}
Expand Down Expand Up @@ -186,6 +192,7 @@ const ReservationItem = ({ name, role, dept, date, index }) => {
};

ReservationItem.propTypes = {
id: PropTypes.string,
name: PropTypes.string,
role: PropTypes.string,
date: PropTypes.string,
Expand Down
1 change: 1 addition & 0 deletions src/components/Reservation/ReservationList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const ReservationList = () => {
{list.map((item) => (
<ReservationItem
key={item.rno}
id={item.rno}
date={item.date}
index={item.index}
dept="한림대학교"
Expand Down
21 changes: 21 additions & 0 deletions src/librarys/api/reservation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,24 @@ export async function getAdminReservationList(id, page = undefined) {
const response = await axios.get("/reservation-admin/" + id, { params });
return response.data;
}

export async function createReservation(
admin_id,
user_id,
content,
date,
index,
) {
const axios = getSpringAxios();

const data = {
admin_id,
user_id,
content,
date,
index,
};
console.log(data);
const response = await axios.post("/reservation/", data);
return response.data;
}
6 changes: 3 additions & 3 deletions src/librarys/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ export const CATEGORY_TYPE = {
THIGH: "허벅지",
};

export const ROLE_LIST = Object.entries(ROLE_TYPE).map((key, value) => ({
export const ROLE_LIST = Object.entries(ROLE_TYPE).map((key, value) => [
key,
value,
}));
]);

export const CATEGORY_LIST = Object.entries(CATEGORY_TYPE).map(
(key, value) => ({
([key, value]) => ({
key,
value,
}),
Expand Down
238 changes: 238 additions & 0 deletions src/librarys/webrtc/rtc-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import RTCSignalingClient from "./rtc-signaling.js";
import { registerEvents } from "./util.js";

const CONFIG = {
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
};

export class RTCClient extends EventTarget {
id = null;
role = null;

/** @type {RTCSignalingClient} */
signaling = null;

/** @type {RTCPeerConnection} */
peer = null;

/** @type {RTCDataChannel} */
dataChannel = null;

/** @type {MediaStream} */
clientStream = null;

/** @type {MediaStream} */
remoteStream = null;

get readyState() {
return this.peer.connectionState;
}

constructor() {
super();

const signalingEvents = {
offer: this._onOffer,
answer: this._onAnswer,
candidate: this._onCandidate,
disconnect: this._onDisconnect,
};

this.signaling = new RTCSignalingClient();
registerEvents(this.signaling, signalingEvents, this);

this.setRTCPeer();
}

async connect(id, role, stream) {
if (this.signaling.readyState) {
return;
}
this.id = id;
this.role = role;
this.setClientStream(stream);
await this.signaling.connect(this.id);
await this.call();
}

disconnect() {
if (this.dataChannel) {
this.dataChannel.close();
this.dataChannel = null;
}

this.peer.close();
this.signaling.send("disconnect", {});

this.setRTCPeer();
this.setClientStream(this.clientStream);
this.remoteStream = null;

this.dispatchEvent(new CustomEvent("disconnect"));
}

destory() {
if (this.dataChannel) {
this.dataChannel.close();
this.dataChannel = null;
}

this.peer.close();
this.signaling.send("disconnect", {});
this.signaling.disconnect();

this.clientStream.getTracks().forEach((track) => track.stop());

this.clientStream = null;
this.remoteStream = null;

this.dispatchEvent(new CustomEvent("disconnect"));
}

async call() {
if (!this.signaling.readyState) {
return;
}

this.log("Data channel을 만듭니다.");
this.setDataChannel(this.peer.createDataChannel("default"));

this.log("Offer를 보냅니다.");
const offer = await this.peer.createOffer();
await this.peer.setLocalDescription(offer);

this.signaling.send("offer", offer);
}

async answer() {
if (!this.signaling.readyState) {
return;
}

this.log("Answer를 보냅니다.");

const answer = await this.peer.createAnswer();
await this.peer.setLocalDescription(answer);

this.signaling.send("answer", answer);
}

sendMessage(message) {
if (this.dataChannel && this.dataChannel.readyState === "open") {
this.dataChannel.send(message);
} else {
throw new Error("[RTCClient] 아직 연결이 열리지 않았습니다.");
}
}

setRTCPeer() {
const peerEvents = {
connectionstatechange: this._onConnectionStateChange,
icecandidate: this._onIceCandidate,
datachannel: this._onDataChannel,
track: this._onTrack,
};

this.peer = new RTCPeerConnection(CONFIG);
registerEvents(this.peer, peerEvents, this);
}

async setRemoteDescription(payload) {
const remoteDescription = new RTCSessionDescription(payload);
await this.peer.setRemoteDescription(remoteDescription);
}

setDataChannel(channel) {
this.dataChannel = channel;
this.dataChannel.addEventListener("open", (event) => {
this.log("data open", event);
});
this.dataChannel.addEventListener("message", (event) => {
this.log("data message", event);
});
}

setClientStream(stream) {
this.clientStream = stream;
this.clientStream.getTracks().forEach((track) => {
this.peer.addTrack(track, this.clientStream);
});
}

// Peer Events
_onIceCandidate(event) {
const candidate = event.candidate;
if (candidate) {
this.log("Candidate 정보를 전달합니다.");
this.signaling.send("candidate", candidate);
}
}

_onConnectionStateChange(event) {
this.log("연결 상태가 변경되었습니다:", this.peer.connectionState);
if (["disconnected", "failed"].includes(this.peer.connectionState)) {
this.disconnect();
} else if (this.peer.connectionState === "connected") {
this.dispatchEvent(new CustomEvent("open"));
}
}

_onDataChannel(event) {
if (event.channel) {
this.log("Channel 데이터를 받았습니다:", event.channel);
this.setDataChannel(event.channel);
this.dispatchEvent(new CustomEvent("channelopen"));
}
}

_onTrack(event) {
if (event.streams) {
this.log("MediaStream을 받았습니다:", event.streams);
this.remoteStream = event.streams[0];
this.dispatchEvent(
new CustomEvent("stream", { detail: this.remoteStream }),
);
}
}

// Data Channel Events
_onChannelOpen(event) {}
_onChannelMessage(event) {}

// Signaling Events
async _onOffer({ detail: payload }) {
this.log("Offer 정보를 받았습니다.");
// Remote Description 지정
await this.setRemoteDescription(payload);
await this.answer();
}

async _onAnswer({ detail: payload }) {
this.log("Answer 정보를 받았습니다.");
// Remote Description 지정
await this.setRemoteDescription(payload);
}

async _onCandidate({ detail: payload }) {
this.log("Candidate 정보를 받았습니다.", event);
try {
const candidate = new RTCIceCandidate(payload);
await this.peer.addIceCandidate(candidate);
} catch (e) {
this.logError("ice candidate를 받는데 실패했습니다.", e);
}
}

_onDisconnect({ detail: payload }) {
this.log("상대가 연결을 종료했습니다.");
this.disconnect();
}

log(...arg) {
console.log("[RTCClient]", ...arg);
}

logError(...arg) {
console.error("[RTCClient]", ...arg);
}
}
Loading