Huny's Dev Blog
huny.dev

getDisplayMedia 화면 공유에서 소리가 없을 때: 오디오 트랙·마이크·재생 상태 분리하기

화면 공유가 성공해도 오디오 트랙이 없을 수 있다. getDisplayMedia 반환값, 마이크와 화면 소리의 독립 제어, 종료 처리·자동재생 제한을 예제와 상태 함수 6개 테스트로 점검한다.

Hun Jang
Hun Jang Nov 7, 2026

화면 공유 소리와 마이크 소리를 서로 다른 경로로 다루는 개념 일러스트다. 실제 브라우저 화면이나 전송 구조를 재현한 도식은 아니다.

화면은 잘 보이는데 공유한 동영상의 소리는 들리지 않는다. getDisplayMedia({ video: true, audio: true })도 성공했으니 오디오 전송 코드부터 의심하기 쉽다. 하지만 먼저 확인할 것은 반환된 스트림이다. 화면 공유 요청은 성공하면서 오디오 트랙은 하나도 반환되지 않을 수 있다.

따라서 화면 공유 기능은 요청 옵션, 실제 캡처 결과, 원격 재생 상태를 나눠 설계해야 한다. 마이크와 화면 소리의 음량을 각각 조절하려면 두 소스도 분리해서 전달한다. 이 글은 그 경계를 기준으로 작은 예제와 점검 순서를 정리한다.

기준일은 2026-11-07이다. 공식 문서를 확인했고, 아래 상태 판별 함수는 Node.js v24.22.1에서 가짜 트랙 객체를 사용한 6개 테스트를 실행했다. 실제 브라우저의 화면 선택창, OS 오디오 캡처, WebRTC 전송과 스피커 출력은 이번 검증에 포함하지 않았다.

audio: true인데 오디오가 없는 이유

getDisplayMedia()의 결과에는 영상 트랙이 필요하지만 오디오 트랙은 선택적이다. 브라우저가 제공하는 기능, 운영체제, 사용자가 선택한 탭·창·전체 화면, 선택창의 오디오 공유 설정에 따라 결과가 달라진다. W3C Screen Capture 초안도 오디오를 요청했더라도 영상만 반환하는 동작을 허용한다. W3C Screen Capture

처음부터 audio: false로 요청한 경우와 audio: true로 요청했지만 트랙이 없는 경우를 구분하면 안내 문구도 정확해진다.

  • 요청하지 않음: “화면 소리 공유 꺼짐”
  • 요청했지만 트랙 없음: “화면은 공유 중이지만 오디오 트랙은 제공되지 않음”
  • 오디오 트랙이 live 상태: “화면 오디오 트랙 연결됨”
  • 반환됐던 오디오 트랙 종료: “화면 오디오 종료됨”

트랙이 없다는 결과만으로 “이 브라우저는 오디오를 지원하지 않는다”거나 “사용자가 체크를 해제했다”고 단정하지 않는다. 반대로 readyState === "live"도 실제로 소리가 발생하고 수신자에게 들린다는 증거는 아니다. 소스가 조용하거나, 트랙이 비활성화됐거나, 재생이 차단된 상황은 별도로 확인해야 한다.

화면 소리와 마이크 입력은 별도 기능이다

화면 공유의 오디오는 선택한 공유 대상과 관련된 소리다. 발표자의 마이크를 켜는 동작과 같지 않다. 마이크 입력이 필요하면 별도의 getUserMedia({ audio: true }) 권한·수명 관리 흐름을 둔다. 화면 공유가 끝났을 때 마이크도 반드시 끝나야 한다는 규칙은 없다. MDN getUserMedia()

실무에서는 다음과 같이 소유 범위를 나누는 편이 명확하다.

  • 마이크 컨트롤러: 마이크 스트림, 마이크 음소거, 기기 변경을 관리한다.
  • 화면 공유 컨트롤러: 공유 영상과 그에 딸린 오디오 트랙만 관리한다.
  • 수신 재생 컨트롤러: 각 원격 소스의 재생 여부와 로컬 음량을 관리한다.

이는 구현을 위한 설계 권고다. 세 개의 클래스를 반드시 만들라는 뜻은 아니다. 종료 함수가 어떤 트랙을 소유하고 중단할 수 있는지만 명확해도 마이크가 의도치 않게 꺼지는 문제를 줄일 수 있다.

시작 버튼에서 요청하고, 반환된 트랙으로 표시하기

아래는 캡처와 로컬 미리보기만 다루는 예제다. 원격 참가자에게 보내는 WebRTC·SFU 연결 코드는 포함하지 않는다. 보안 컨텍스트에서 실행해야 하며, 지원 여부와 선택 가능한 오디오 범위는 목표 브라우저·OS에서 확인해야 한다. MDN getDisplayMedia()

체크박스에 checked를 넣지 않아 화면 소리는 기본 OFF다. 먼저 반환 결과를 표현하는 함수를 만든다.

이 함수는 트랙의 존재와 수명만 분류한다. 오디오 신호의 크기, 네트워크 수신 여부, 스피커 출력은 판정하지 않는다.

실제 앱에서는 위 상태 값을 한국어 안내 문구로 바꾸고, 오류 이름별로 재시도 방법을 보여 준다. 예제의 로컬 미리보기는 음소거했으므로 공유한 소리가 자기 스피커에서 다시 재생되지 않는다. 옵션 체크박스를 공유 도중 바꿔도 기존 캡처 트랙이 자동으로 추가·제거되지는 않는다. 이 예제에서는 종료한 뒤 다시 시작해야 새 선택을 적용한다.

getDisplayMedia() 호출 앞에 네트워크 요청이나 마이크 권한 요청을 await하는 코드를 넣지 않는다. 일시적 사용자 활성화가 사라져 실패할 수 있기 때문이다. 이전에 허용했어도 다음 호출에서 공유 대상 선택과 허용 절차가 다시 필요하다. W3C 호출 절차

systemAudio: exclude가 모든 소리를 막는 것은 아니다

예제는 전체 시스템 소리를 회의로 되돌려 보내는 위험을 줄이기 위해 systemAudio: 'exclude'를 넣었다. Chrome 문서는 이 설정이 전체 화면 공유에서 시스템 오디오 선택지를 제외하는 동작을 설명한다. 탭 오디오까지 전부 끄는 설정은 아니다. Chrome 화면 공유 제어 문서

또한 표준 초안에서 이 옵션은 브라우저에 전달하는 선호 힌트다. 모든 구현에서 같은 동작을 보장하는 보안 경계로 사용해서는 안 된다. 전체 시스템 소리가 제품에 꼭 필요하다면 별도 선택으로 설명하고, 지원 환경과 다른 참가자의 소리가 다시 전송되는 경로를 확인해야 한다. W3C systemAudio 정의

화면 소리를 전혀 요청하지 않는 선택은 audio: false로 표현한다. 시스템 오디오를 제외해 달라는 요청과 오디오 자체를 요청하지 않는 동작을 구분한다.

종료 버튼과 브라우저의 공유 중단을 같은 함수로 처리하기

앱에서 track.stop()을 호출하면 트랙의 상태는 ended가 되지만, 그 호출 때문에 ended 이벤트가 발생하지는 않는다. 종료 버튼이 stop()만 호출하고 UI 정리를 이벤트에 맡기면 버튼과 미리보기가 공유 중인 상태로 남을 수 있다. MDN MediaStreamTrack.stop()

예제의 finish()는 앱의 종료 버튼과 영상 트랙의 ended 이벤트가 함께 사용한다. 여러 번 불려도 한 번만 정리하도록 closed를 둔다. 화면 공유 스트림의 트랙만 중단하므로 별도 마이크 스트림은 건드리지 않는다.

화면 오디오만 먼저 끝날 수 있으므로 오디오 트랙의 종료도 별도로 표시한다. 이를 화면 공유 전체 종료와 무조건 동일하게 처리하지 않는다. 실제 앱에서는 컴포넌트 해제·방 퇴장 시에도 같은 정리 함수를 연결하고, 선택창이 떠 있는 동안 중복 시작을 막아야 한다. MDN ended 이벤트

수신자가 음량을 각각 조절하려면 소스를 먼저 보존한다

발표자의 말소리는 크게, 공유 동영상 소리는 작게 듣고 싶은 기능을 생각해 보자. 송신 단계에서 두 소리를 하나의 오디오 트랙으로 섞어 버리면 수신 측에서 각각의 볼륨을 신뢰성 있게 되돌려 조절하기 어렵다. 독립 제어가 요구사항이라면 마이크와 화면 오디오를 믹싱하기 전에 분리 보존한다.

전송 계층에는 최소한 참가자, 접속 세션, 소스 종류를 구분할 수 있는 식별 정보를 둔다. 같은 사람이 여러 기기로 접속하거나 공유를 다시 시작해도 다른 소스와 뒤섞이지 않아야 한다. 단순히 MediaStream 객체를 두 개 만들었다고 원격 SDK가 자동으로 의미를 구분해 주는 것은 아니다. 사용하는 SDK의 publication·subscription 식별자에 소스 정보를 연결한다.

수신 측에서는 각 소스에 재생 요소 또는 Web Audio의 GainNode를 따로 둔다. 개별 음량 조절은 그 수신자의 로컬 재생에 적용하고, 송신자의 마이크 상태를 바꾸는 동작과 분리한다. 브라우저별 음량 제어 지원과 오디오 라우팅도 실제 기기에서 확인한다.

화면을 보여 주는 원격 video 요소와 화면 소리를 재생하는 별도 audio 요소에 동일한 오디오 트랙을 동시에 연결하면 중복 재생할 수 있다. 영상 요소에는 영상 트랙만 넣거나 음소거하고, 소리는 한 재생 경로에서만 출력한다.

마지막으로 play()의 Promise를 처리한다. 자동재생 제한으로 거부되면 “소리 재생” 버튼을 보여 주고 사용자의 클릭에서 다시 재생한다. 캡처가 성공하고 원격 트랙이 도착했어도 재생까지 성공한 것은 아니다. MDN HTMLMediaElement.play()

검증한 부분과 실제 기기에서 남은 확인

Node.js에서 실행한 6개 테스트는 describeShare()의 분기만 확인했다. 가짜 객체가 반환하는 readyState와 트랙 목록을 바꿔 다음 결과를 검증했고, 6개 모두 통과했다.

이 결과는 브라우저 호환성이나 실제 녹음·통화 품질을 검증한 결과가 아니다. 캡처 예제 전체와 원격 재생 흐름도 아직 실기기에서 실행하지 않았다. 배포 전에는 목표 OS와 브라우저 버전을 기록하고 다음 순서로 확인한다.

  1. 화면 소리 OFF로 시작해 오디오 트랙이 없는지 확인한다.
  1. ON으로 시작한 뒤 탭·창·전체 화면을 각각 선택한다. 오디오 선택지 유무와 실제 반환 트랙 수를 기록한다.
  1. 영상 전용 결과에서 화면 공유를 유지하고, 오디오가 없다는 안내가 나오는지 확인한다.
  1. 선택창 취소·권한 거부 후 다시 시작할 수 있는지 확인한다.
  1. 앱 종료 버튼과 브라우저 공유 중단을 각각 실행한다. 미리보기·버튼 상태·트랙이 정리되고 별도 마이크는 유지되는지 확인한다.
  1. 두 기기에서 마이크와 화면 소리의 개별 음량, 음소거, 자동재생 차단, 재접속을 확인한다. 소리가 두 번 재생되거나 상대 목소리가 되돌아오는지도 듣는다.

문제는 “화면 공유 성공”이라는 하나의 값으로 너무 많은 단계를 표현할 때 시작된다. 요청한 옵션과 반환된 트랙을 구분하고, 캡처·전송·재생을 따로 확인하면 소리가 사라진 위치를 훨씬 빨리 좁힐 수 있다.

추천 글