네 안녕하세요 죽지도 않고 돌아온 저 입니다 *^.^*
저번 작업에 이어서 웹에서 처리해여 할 작업들을 정리 하겠습니다.
저같은 경우는 node기반으로 웹을 작업했는데요. 다른분은들 편한걸로 사용하시면 될것 같아요
먼저 node 를 설치해 줍니다.
Node.js — 어디서든 JavaScript를 실행하세요
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
nodejs.org
설치 완료시
노드 설치가 종료되면
원하는 경로에 웹을 실행할 관련 js 파일들과 html 파일을 만들어줍니다.
저의 경우 프로젝트를 새로 파서 만들어줬어요
폴더 안에
js 파일을 만들어줍니다.
저의 경우 이렇게
두가지 만들어 주었습니다.
이후 cmd 창으로 접근합니다.
"python3 -m http.server 8000" 명령어를 실행해 서버를 띄워줍니다.
당연히 js도 띄워 줄텐데요
서버에서 처리될 js는 아래와 같이 작업했습니다.
signaling_server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
console.log('WebSocket server 진입');
let viewers = [];
wss.on('connection', (ws) => {
console.log('New connection established');
ws.on('message', (message) => {
try {
const parsed = JSON.parse(message);
if (parsed.role === 'viewer') {
console.log('Viewer connected');
viewers.push(ws);
ws.send(JSON.stringify({ type: 'connected', message: 'Viewer ready' }));
} else if (parsed.role === 'sender') {
console.log('Sender connected');
ws.on('message', (data) => {
if (Buffer.isBuffer(data)) {
console.log("Received frame buffer, size:", data.length); // 사이즈 확인
viewers.forEach((viewer) => {
if (viewer.readyState === WebSocket.OPEN) {
viewer.send(data);
}
});
}
});
}
} catch (err) {
// Binary frame fallback
if (Buffer.isBuffer(message)) {
viewers.forEach((viewer) => {
if (viewer.readyState === WebSocket.OPEN) {
viewer.send(message);
}
});
}
}
});
ws.on('close', () => {
viewers = viewers.filter((v) => v !== ws);
});
});
1. WebSocket 서버 시작
const wss = new WebSocket.Server({ port: 8080 });
2. 접속 처리
wss.on('connection', (ws) => {
console.log('New connection established');
}
3. Viewer 처리
if (parsed.role === 'viewer') {
console.log('Viewer connected');
viewers.push(ws);
ws.send(JSON.stringify({ type: 'connected', message: 'Viewer ready' }));
}
viewer는 viewers 배열에 등록
연결 확인용 메시지 보냄
4. Sender 처리
else if (parsed.role === 'sender') {
console.log('Sender connected');
ws.on('message', (data) => {
if (Buffer.isBuffer(data)) {
}
});
}
sender는 추가 message 이벤트에서 JPEG 프레임을 계속 보냄
Buffer.isBuffer(data)로 바이너리 확인
연결된 모든 viewer에게 프레임 전송
5. Binary fallback (예외 상황)
catch (err) {
if (Buffer.isBuffer(message)) {
viewers.forEach((viewer) => viewer.send(message));
}
}
JSON 파싱 실패한 메시지도 JPEG일 수 있음 → 그대로 viewer에게 전달
6. 연결 해제 처리
ws.on('close', () => {
viewers = viewers.filter((v) => v !== ws);
});
viewer가 연결 끊으면 viewers 배열에서 제거
이제 node signaling_server.js 로 실행하기
마지막으로 HTML쪽은
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Viewer</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
background-color: black;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
canvas {
display: block;
background-color: black;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let imgAspect = null;
const ws = new WebSocket('ws://내 IP:8080');
ws.binaryType = 'arraybuffer';
ws.onopen = () => {
console.log('WebSocket connected (viewer)');
ws.send(JSON.stringify({ role: 'viewer' }));
};
ws.onmessage = (event) => {
if (typeof event.data === 'string') {
console.log('Message:', event.data);
return;
}
const blob = new Blob([event.data], { type: 'image/jpeg' });
const img = new Image();
img.onload = () => {
if (!imgAspect) {
// 최초 프레임에서 비율 계산 후 canvas 사이즈 맞춤
imgAspect = img.width / img.height;
const winAspect = window.innerWidth / window.innerHeight;
if (winAspect > imgAspect) {
canvas.height = window.innerHeight;
canvas.width = canvas.height * imgAspect;
} else {
canvas.width = window.innerWidth;
canvas.height = canvas.width / imgAspect;
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
img.src = URL.createObjectURL(blob);
};
window.addEventListener('resize', () => {
imgAspect = null; // 리사이즈 시 비율 다시 계산
});
</script>
</body>
</html>
canvas 에화면을 보여줌
1. WebSocket 연결
const ws = new WebSocket('ws://내IP:8080');
ws.binaryType = 'arraybuffer';
2. 연결되면 역할 전송
ws.onopen = () => {
console.log('WebSocket connected (viewer)');
ws.send(JSON.stringify({ role: 'viewer' }));
};
3. 프레임 수신 처리
ws.onmessage = (event) => {
if (typeof event.data === 'string') {
console.log('Message:', event.data);
return;
}
const blob = new Blob([event.data], { type: 'image/jpeg' });
const img = new Image();
img.onload = () => {
if (!imgAspect) {
// 최초 프레임에서 비율 계산
imgAspect = img.width / img.height;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
img.src = URL.createObjectURL(blob);
};
자 실행화면을 보셔야죠~
실시간으로 계속 해서 받아오고 있습니다.
웹에서는
이렇게 나오고 있는데요. 제가 테블릿을 가로모드로 사용중이기 때문에 저렇게 나오는거랍니다!.
(영상이 제생되고 있어요 캡쳐라 사진처럼 보이는데 벌이 움직이는 동영상입니다!ㅎㅎ)
참고로 동영상은 이거에요!
https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4

개발일지: 오늘은 여기까지!
요즘 글을 쓰면서 느끼는 건데, 역시 코드는 한 번 더 정리해야겠다는 생각이 드네요.
오늘은 이쯤에서 마무리하고, 코드를 좀 더 다듬은 뒤에 게시물도 함께 수정해보려 해요 :)
하루하루 조금씩 더 성장하는 개발자가 되기를 오늘도 다짐해 봅니다.
그리고 한 가지 소식!
당분간은 한국을 떠나 태국에서 개발을 이어가게 될 것 같아요.
좋은? 기회가 생겨서 태국 현지 사업에 참여하게 되었거든요.
정확한 출국 일정은 아직 확정되진 않았지만, 7월 중으로 출발할 예정입니다.
기간은 성과에 따라 달라지겠지만 1년에서 3년 예상한다고 하네요..^^;
제 블로그를 찾아주시는 분들이 많진 않지만,
태국에서도 변함없이 꾸준히 개발과 블로그를 이어가볼게요!
그럼 다들 건강하게 잘 지내세요 🙏
(블로그 방문자 수가 쑥쑥 늘어나길 기도하며! )
'kotlin' 카테고리의 다른 글
Foreground 를 통한 안드로이드 시스템이 앱을 백그라운드로 밀거나 강제 종료하는 걸 막는 방법 (3) | 2025.07.01 |
---|---|
flutter 에서 kotlin 연결 후 YOLO 모델을 사용해 안경(glasses) 착용 여부 감지 (3) | 2025.06.19 |
flutter - kotlin MediaProject를 이용한 실시간 내 디바이스 화면 web에 공유 (1부) (0) | 2025.06.09 |
kotlin 문법 정리 (2) (0) | 2023.07.08 |
kotlin 문법 정리 (1) (2) | 2023.06.17 |