토이 프로젝트/Spring&Java 갖고놀기

Spring Boot Web Chatting : 스프링 부트로 실시간 채팅 만들기 (7) WebRTC 를 이용한 실시간 화면 공유

TerianP 2022. 10. 31.
728x90

이전 글에 이어서 WebRTC 를 사용해서 실시간 화면 공유를 구현해보았습니다.

저번 글에 '화면 공유 기능 목표!!' 라고 이야기했던게 사실 오래 걸릴 줄 알았는데 의외로 금방? 끝났습니다ㅋㅋ

라고 이전에 글을 썼었는데...생각해보니 공유 되는 나의 화면을 나만 볼 수 있었고, 내 화면을 다른 사람이 보는 내가 생각했던 진짜 '화면 공유' 기능이 아니었다는 걸 깨달았습니다. 결국 빠르게 글을 잠금표시로 전환해두고 코드를 뜯어고치기 시작했습니다.

솔직히 금방 끝나서 다행이다! 했는데 결국 이렇게 또 한주가 가버렸네요ㅠ.ㅠ 

 

JS 부분만 고치면 되는거여서 Java 부분의 코드 수정은 전혀 없었습니다

 

코드는 언제나 처럼 git 참고!!

https://github.com/SeJonJ/Spring-WebSocket-WebRTC-Chatting

 

GitHub - SeJonJ/Spring-WebSocket-WebRTC-Chatting: SpringBoot WebSocket Chatting

SpringBoot WebSocket Chatting. Contribute to SeJonJ/Spring-WebSocket-WebRTC-Chatting development by creating an account on GitHub.

github.com

 

0. MediaStream

화면 공유를 하기위해서는 WebRTC 에서 지원하는 기중 중 하나인 navigator.mediaDevices.getDisplayMedia 를 사용한다. 사실 이 부분은 이전 화상 채팅 코드에서 "유저의 비디오 카메라, 마이크 미디어"를 가져오기 위해서 navigator.mediaDevices.getUserMedia()  를 사용했던 것을 생각하면 된다.

 

이렇게 화면 정보(이하 화면 비디오 스트림)을 가져온 후 다른 사용자에게 이를 공유하기 위해서는 다른 peer 에게 보내는 Track 에 담기는 정보를 "나의 카메라 비디오 스트림" 에서 "화면 비디오 스트림" 으로 교체해야한다.

  • 여기서 Track 이란 현재 MediaStream 을 구성하는 각 요소를 의미한다.
  • Track 는 오디오, 비디오, 자막 총 3개의 stream 으로 구성된다. 때문에 Track 객체는 track[0] = 오디오, track[1] = 비디오 의 배열 구조로 되어있다
  • MediaStream 이란 video stream 과 audio steam 등의 미디어 스트림을 다루는 객체를 이야기한다 
  • stream(스트림)이란 쉽게 생각하자면 비디오와 오디오 데이터라고 생각하자 
정리하자면 상대방에게 보내는 track 에서 나의 웹캠 videoStream 대신 공유 화면에 해당하는 videoStream 으로 변경하면 상대방의 remoteVideo 가 그에 맞게 변경된다.
이렇듯 Track 에서 steam 을 교체 - replace - 하기 위해서는 아래와 같은 과정을 거친다. 아래 과정을 잘 생각하며 코드에 달린 주석을 참고하자!

1. myPeerConnection 에서 sender 를 가져온다. sender 란 나와 연결된 다른 peer 로 생각하면 된다.
2. sender 객체에서 replaceTrack 함수를 활용해서 stream 을 교체한다.
3. 이때 shareView 의 경우 VideoStream 의 경우 audio 까지 포함해서 화면 공유를 시작한 것이 아니라면 Track[0] 에 videoStream 이 들어있다. 때문에 replaceTrack 의 파라미터에 shareView.getTrack[0] 을 넣는다.
4. 화면 공유 취소 시 원래 화상 화면으로 되돌리기 위해서는 다시 Track 를 localstream 으로 교체해주면 된다! 이때 localStream 에는 audio 와 video 모두 들어가 있음으로 video 에 해당하는 Track[1] 만 꺼내서 교체해준다.

콘솔창에 찍어보면 실제로 MediaStreamTrack 가 배열로 되어있는 것을 확인 할 수 있다

 

 

1-1. webrtc_client.js : 화면 공유

- 맨 위에 #view_on 과 #view_off 를 클릭했을 때 이벤트를 꼭 넣어주자. 각각 on 을 눌렀을 때 비디오 공유 시작, off 를 눌렀을 때 비디오 공유 중지를 의미한다.

- 위에 설명한 1~4 번 과정을 기억하자. 이것만 기억하고 응용하면 또 다르게 만들 수 있을듯하다.

/*  화면 공유를 위한 변수 선언 */
const screenHandler = new ScreenHandler();
let shareView = null;

/**
 * ScreenHandler
 * @constructor
 */
function ScreenHandler() {
    // let localStream = null;

    log('Loaded ScreenHandler', arguments);

    // REF https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#Properties_of_shared_screen_tracks
    const constraints = {
        audio: true,
        video: {
            width: 1980, // 최대 너비
            height: 1080, // 최대 높이
            frameRate: 50, // 최대 프레임
        },
    };

    /**
     * 스크린캡쳐 API를 브라우저 호환성 맞게 리턴합니다.
     * navigator.mediaDevices.getDisplayMedia 호출 (크롬 72 이상 지원)
     * navigator.getDisplayMedia 호출 (크롬 70 ~ 71 실험실기능 활성화 or Edge)
     */
    function getCrossBrowserScreenCapture() {
        if (navigator.getDisplayMedia) {
            return navigator.getDisplayMedia(constraints);
        } else if (navigator.mediaDevices.getDisplayMedia) {
            return navigator.mediaDevices.getDisplayMedia(constraints);
        }
    }

    /**
     * 스크린캡쳐 API를 호출합니다.
     * @returns shareView
     */
    async function start() {

        try {
            shareView = await getCrossBrowserScreenCapture();
        } catch (err) {
            log('Error getDisplayMedia', err);
        }

        return shareView;
    }

    /**
     * 스트림의 트렉을 stop()시켜 스트림이 전송을 중지합니다.
     */
    function end() {

        shareView.getTracks().forEach((track) => {
            // log("화면 공유 중지")
            track.stop();
        });

        // // 전송 중단 시 share-video 부분 hide
        // $("#share-video").hide();
    }

    /**
     * extends
     */
    this.start = start;
    this.end = end;

}

/**
 * screenHandler를 통해 스크린 API를 호출합니다
 * 원격 화면을 화면 공유 화면으로 교체
 */
async function startScreenShare() {

    // 스크린 API 호출 & 시작
    await screenHandler.start();

    // 1. myPeerConnection 에 연결된 다른 sender 쪽으로 - 즉 다른 Peer 쪽으로 -
    // 2. shareView 의 Track 에서 0번째 인덱스에 들어있는 값 - 즉 videoStream 로 - 교체한다.
    await myPeerConnection.getSenders().forEach((sender)=>{ // 연결된 sender 로 보내기위한 반복문

        // 3. track 를 shareView 트랙으로 교체
        sender.replaceTrack(shareView.getTracks()[0])

    })


    /**
     * 화면 공유 중지 눌렀을 때 이벤트
     */
    shareView.getVideoTracks()[0].addEventListener('ended', () =>{
        // log('screensharing has ended')

        // 4. 화면 공유 중지 시 Track 를 localstream 의 videoStram 로 교체함
        myPeerConnection.getSenders().forEach((sender) =>{
            sender.replaceTrack(localStream.getTracks()[1]);
        })

        // $("#share-video").hide();

    });

    // console.dir(shareView.getTracks());
    // console.dir(localStream.getTracks());

}

/*
*  video off 버튼을 통해 스크린 API 종료
* */
async function stopScreenShare(){

    // screen share 종료
    await screenHandler.end();
    // myPeerConnection
    await myPeerConnection.getSenders().forEach((sender) =>{
        
        // 4. 화면 공유 중지 시 Track 를 localstream 의 videoStram 로 교체함
        sender.replaceTrack(localStream.getTracks()[1]);
    })
}

 

1-2. webrtc_client.js : peer connect, disconnect event

- 다른 peer 과 connect 되거나 disconnect 되었을 때 이벤트 처리

- 최종적으로 connect 되었을 때 remoteVideo 를 show 하거나 discconnect 되었을 때 remoteVideo 를 hide 시킨다.

- 문제는...!! remoteVideo 가 바로 안 사라지고 약 5초? 정도있다가 사라진다는 점.

function createPeerConnection() {
    myPeerConnection = new RTCPeerConnection(peerConnectionConfig);

    // event handlers for the ICE negotiation process
    myPeerConnection.onicecandidate = handleICECandidateEvent;
    myPeerConnection.ontrack = handleTrackEvent;

    // the following events are optional and could be realized later if needed
    // myPeerConnection.onremovetrack = handleRemoveTrackEvent;

    myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
    // myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
    // myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
}

/** peerConnection 과 관련된 이벤트 처리
 * 다른 peer 와 연결되었을 때 remote_video show 상태로로, 끊졌을때는 remote_video 를 hide 상태로 변경
 * **/
function handleICEConnectionStateChangeEvent(){
    let status = myPeerConnection.iceConnectionState;

    if(status === "connected"){
        log("status : "+status)
        $("#remote_video").show();
    }else if(status === "disconnected"){
        log("status : "+status)

        $("#remote_video").hide();
    }
}

 

2. rtcroom.html

- view_on , view_off 버튼을 html에 넣어주었다.

<div class="mr-2" data-toggle="buttons">
    <label class="btn btn-outline-success" id="view_on">
        <input type="radio" name="options" style="display:none" autocomplete="off">view On
    </label>
    <label class="btn btn-outline-warning active" id="view_off">
        <input type="radio" name="options" style="display:none" autocomplete="off">view Off
    </label>
</div>

 

 

3. 코드 구현 확인

양방향 공유 완성!!


Reference : 꼭! 확인할 것

해당 사이트에서 정말정말 자세하게 설명되어있다.

https://webrtclab.herokuapp.com/screen-share/

 

WebRTC 연구실 - 화면 공유 API 접근하기

Start 확장프로그램이 필요없는 스크린쉐어 API 예제입니다. (getDisplayMedia API) Chrome 72+, Firefox 66+, Edge 18+ (https 필수) 에서 지원합니다.

webrtclab.herokuapp.com

 

webRTC 문서

https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API

 

WebRTC API - Web API | MDN

WebRTC(Web Real-Time Communication)은 웹 애플리케이션과 사이트가 중간자 없이 브라우저 간에 오디오나 영상 미디어를 포착하고 마음대로 스트림할 뿐 아니라, 임의의 데이터도 교환할 수 있도록 하는

developer.mozilla.org

 

https://www.metered.ca/blog/webrtc-screen-sharing/

 

WebRTC Screen Sharing with Javascript

We will build a WebRTC Screen Sharing application in Javascript, that will allow you to share your desktop or window without requiring extension. We will first learn how to use the getDisplayMedia method to capture the entire desktop or an application, and

www.metered.ca

 

https://cryingnavi.github.io/webrtc/2020/10/15/webrtc-sharedscreen.html

 

WebRTC 화면 공유하기

WebRTC에서 화면을 공유하는 방법입니다.

cryingnavi.github.io

 

Media Capture and Streams API 에 대한 설명

https://curryyou.tistory.com/443

 

자바스크립트 Media Capture and Streams API: MediaStream, MediaStreamTrack

# 자바스크립트에서 미디어를 다루는 방법 자바스크립트에서 미디어를 다루는 주요 API는 4가지가 있다. 1. Media Capture and Streams API (=Meida Stream API) - 마이크, 카메라 등을 이용해 들어오는 (Media Strea

curryyou.tistory.com

 

https://geoboy.tistory.com/27

 

WebRTC - Media devices

WebRTC란 무엇인가? WebRTC는 Web real-time communication의 약자이다. WebRTC를 사용하여 앱에 real-time communication 기능을 추가할 수 있다. WebRTC는 비디오, 음성 외에도 다양한 데이터를 p2p로 전송한다. 모든

geoboy.tistory.com

 

webRTC peerDisconnect event

https://stackoverflow.com/questions/21233828/detecting-that-the-peers-browser-was-closed-in-a-webrtc-videochat

 

Detecting that the peer's browser was closed in a webrtc videochat

I've been implementing a webrtc videochat. Everything is working smoothly except for the case when the peer closes the browser. I've been trying to handle this event by implementing an onended

stackoverflow.com

 

댓글