자바스크립트를 활성화 해주세요

유튜브 플레이어 컨트롤러 제작기 - 02

 ·   ·  ☕ 12 min read  ·  ✍️ Yogo

지난 포스트에서 미디어 컨트롤러를 제작하고 동작하는 것을 확인하였습니다.
다만, 포스트 마지막에 남겼듯이 유튜브 다양한 기능 제어를 위해서는 미디어 컨트롤러만으로는 한계점이 있습니다. 당연히도 그럴 것이 기본적으로 표준 규격 컨트롤로서의 역할만 가능 하기 때문입니다.

그럼 유튜브 전용 컨트롤을 위한 요구사항 부터 정리해보겠습니다.

요구사항

  • 범용 미디어 제어 외에 브라우저내에 유튜브 플레이어 직접 제어
    • 윈도우 볼륨이 아닌 유튜브 플레이어 자체 볼륨만 제어
    • 플레이 리스트가 아니더라도 다음 영상으로 이동
    • 유튜브 챕터 이동 제어
    • 유튜브 탐색 (앞으로 감기/뒤로 감기)
    • 유튜브 미니/극장모드/최대화 토글
    • 브라우저가 백그라운드에 있어도 제어가 가능 할 것
  • 위 기능이 모두 가능하고도 미디어 컨트롤러 기능이 유지가 되어야 할 것

신규 요구사항은 모두 유튜브 커스텀 제어를 위한 것들입니다. 범용 기능으로는 한계가 있으니 커스터마이징을 통하여 위의 기능을 수행가능하도록 하는 것이 목표입니다.

유튜브 플레이어 제어

유튜브는 페이지 내에서 다음과 같은 단축키1를 지원합니다.

단축키 기능
스페이스바 탐색 막대가 선택된 경우 재생/일시중지, 버튼에 포커스가 있는 경우 버튼 활성화
키보드의 미디어 재생/일시중지 키 재생/일시중지
k 플레이어에서 일시중지/재생
m 동영상 음소거/음소거 해제
키보드의 미디어 중지 키 중지
키보드의 다음 트랙 미디어 키 재생목록의 다음 트랙으로 이동
탐색바에서 왼쪽/오른쪽 화살표 5초 뒤로/앞으로 탐색
j 플레이어에서 10초 뒤로 탐색
l 플레이어에서 10초 앞으로 탐색
. 동영상이 일시중지된 경우 다음 프레임으로 건너뛰기
, 동영상이 일시중지된 경우 이전 프레임으로 돌아가기
> 동영상 재생 속도 높이기
< 동영상 재생 속도 줄이기
탐색바에서 Home/End 키 동영상의 시작/끝부분으로 탐색
탐색바에서 위쪽/아래쪽 화살표 볼륨 5% 높임/낮춤
탐색바에서 숫자 1~9(숫자 패드 아님) 동영상의 10~90% 부분 탐색
탐색바에서 숫자 0(숫자 패드 아님) 동영상의 시작 부분 탐색
/ 검색창으로 이동
f 전체화면 활성화. 이미 전체화면 모드인 경우 F를 다시 누르거나 Esc 키를 누르면 전체화면 모드가 종료됩니다.
c 자막이 있는 경우 자막 표시. 자막을 숨기려면 C를 다시 누릅니다.
Shift+N 다음 동영상으로 이동. 재생목록을 사용하는 경우에는 재생목록의 다음 동영상으로 이동합니다. 재생목록을 사용하지 않는 경우 다음 YouTube 추천 동영상으로 이동합니다.
Shift+P 이전 동영상으로 이동. 이 단축키는 재생목록을 사용하는 경우에만 작동합니다.
i 미니 플레이어 열기

단축키 만으로 유튜브 플레이어 대부분 기능을 직접적으로 제어가 가능한 것을 볼 수 있습니다.
위 정보로 판단해 볼 때 유튜브 제어를 위한 가장 쉬운 방법으로는 브라우저에 키 입력 값을 직간접적으로 전달 하는 것 입니다.

USB 키보드로서 키 입력 전달하기

가장 단순하고 직접적인 방법은 현재 미디어 컨트롤러 역할만 가능한 디바이스를 키보드 역할을 추가적으로 부여해서 키보드 입력 값을 직접 전송하도록 하는 것 입니다.

참고로 USB 디바이스는 하나의 디바이스에서 다 기능 또는 역할을 부여할 수 있습니다. 우리가 사용하는 키보드 중에서 미디어 컨트롤러 기능이 포함된 키보드는 엄밀하게 따지자면 키보드 기능 뿐만 아니라 컨슈머 컨트롤러 기능이 포함된 복합 디바이스이고 이와 유사하게 정의한다면 키보드 입력을 전달 할 수 있습니다.

그런데 이 방식의 경우 해결해야 할 문제가 하나 있습니다. 키보드 입력은 원칙적으로는 활성화 된 창에서만 그 입력이 유효합니다. 예를 들어 브라우저가 백그라운드에 있거나 아니면 유튜브가 띄워진 탭이 선택 되어 있지 않으면 아무리 키 입력을 한들 이 방법은 유효하지 않게 됩니다.

결국 이 방법은 좋은 해결 방법이 될 수 없고, 브라우저에 좀 더 직접적으로 키 입력 정보를 전달할 다른 수단이 필요합니다.

별도의 애플리케이션을 통한 간접 전달

직접적인 키 입력으로는 한계가 있기 때문에 간접적인 방식으로 전용 애플리케이션을 개발하는 것을 고려하였습니다. 키 입력을 전달하는데 있어서 난이도의 문제는 어떨지 모르더라도 브라우저 프로세스에 키 입력을 전달하는 것이 충분히 가능할 것이라고 예상을 하고 검증전에 우선은 USB 디바이스와 전용 애플리케이션간 데이터 공유 방법을 우선 검토하였습니다.

보통은 USB 드라이버와 인터페이스를 통하여 기기에 직접 접근하게 되는데, 과거 LibUSB2 기반으로 만든 디바이스와 데이터 통신을 했던 기억이 남아 있어서 관련 정보를 찾아보았습니다. 결론만 남기자면 HID 디바이스의 경우 별도의 드라이버가 필요없므으로 OS 레벨에서 직접적으로 데이터를 주고 받을 수 있기 때문에 LibUSB를 사용에 대한 고려는 사실 할 필요가 없었습니다. 대신 .NET용 공식 HID API는 확인하지 못하여서 랩퍼(native) API를 찾아서 간단하게 디바이스를 검색하고 연결상태를 확인하는 테스트를 진행하였습니다.

검증

다음으로는 브라우져 프로세스에 키 입력 정보를 보내는 방법을 찾고 검증하는 과정이 남아있었습니다.

이 부분도 결론만 말하자면 키 입력 정보를 보낼 수 있으나 강제로 활성화 시켜서 넘겨야 하는 방법 밖에는 찾지 못하였고 이 또한 좋은 방법은 아니기 때문에 조금 더 복잡하더라도 좀 더 나은 방법이 있는지 확인이 필요했습니다.

그러던 중 새로운 접근 방법을 찾게 됩니다.

WebHID345

최근 웹에서 HID 디바이스와 웹 페이지간 직접 정보를 주고받을 수 있는 규격의 API가 새로 생긴 것을 확인했습니다.

근래에 웹 페이지에서 로컬 파일 접근을 위한 API가 정식이 되면서 직접적으로 접근이 허용되었는데, 웹에서도 USB HID 디바이스와 직접 통신할 수 있는 방법이 마련된 것 입니다. 아직은 Draft 단계이지만 Chrome 기반 브라우저에서는 이미 지원을 하고 있어서 구현이 가능한 것을 확인했습니다.

하지만 WebHID를 사용하려면 고려해야할 점이 있습니다. WebHID를 사용하려면 스크립트로 코드를 생성해서 동작을 시켜야하는데, 유튜브는 별도의 서비스이므로 사용자 임의의 스크립트를 실행하여 직접적인 제어를 직접 할 수 없기 때문입니다.

개인적으로는 사이트나 페이지를 만들어 유튜브 데이터 API6를 이용한 별도의 플레이어를 구성하는 방법도 사용할 수 는 있겠지만, 이는 범용적이지도 못하고 실험적인 수준에서 그치게 됩니다.

하지만 방법이 아예 없는 것은 아닙니다. 마지막 방법은 ‘크롬 확장프로그램’을 만드는 것 입니다.

확장프로그램의 경우 기존 웹 사이트의 스타일을 재 정의한다거나 아니면 사용자 임의의 스크립트를 삽입하고 실행할 수 있습니다.

결론은 WebHID를 이용하여 미디어 컨트롤러 디바이스와 통신하고, 컨트롤러 입력 값을 유튜브의 단축키 이벤트로 변환해주는 웹앱을 만들면 됩니다. (말은 참 쉽습니다)

개발 과정

현재 만들어진 미디어 컨트롤러는 Consumer Control 표준 규격의 정보를 전송합니다. WebHID에서는 호스트와 HID 기기간 주고 받는 정보를 읽을 수는 있지만, 이벤트나 데이터를 독점적으로 점유 할 수 없기 때문에 이를 그대로 사용할 수는 없습니다.

예를 들어 엔코더 회전 시 유튜브 빨리 감기 기능이 가능하도록 만든다고 한다면, 컨트롤러로 엔코더를 돌리면 유튜브 영상이 앞으로 이동하면서 볼륨도 조절이 되는 기능이 겹치는 일이 발생합니다.

그러므로 유튜브 전용 제어는 표준이 아닌 독자 규격의 인터페이스가 필요합니다.

커스텀 디스크립터

이전 포스트에서 USB 레포트 디스크립터에 대해서 짧게 설명을 했습니다. USB 기기는 인터페이스를 추가하거나 또는 엔드포인트(데이터를 주고 받을 통로라고 생각하시면 됩니다)를 늘림으로써 논리적 기능이나 통로를 확장하는 방법을 사용할 수 있는데 이 방법 외에도 레포트 규격을 추가하여 다른 성질의 데이터 형식을 주고 받을 수 있습니다.

쉽게 비유하자면 문서를 발송하는데 절차, 방법은 모두 동일한하고 다만 보내는 문서 포맷만 하나 더 늘리는 것 입니다.

간단히 기술적인 설명을 하자면 Vender Defined Page라고 규격화되지 않은 커스텀 레포트 지정이 가능합니다. 주고 받을 데이터는 보내는 데이터는 1바이트만 할당하고, 받는(호스트로부터 전송된) 데이터는 64바이트로 지정하였습니다. (이 크기는 원하는대로 조절하면 됩니다)

그리고 기존 Consumer Control과 보내는 통로가 겹치게 되므로 두 레포트를 구분할 무언가가 필요합니다. 여기에서는 Report ID를 각각 부여하여 레포트 타입이 어떤 형식인지 구분이 될 수 있도록 합니다.

커스텀 데이터는 0x01, 미디어 컨트롤은 0x02로 부여하였고 WebHID에서 데이터를 수신 받을 때 0x01 아이디가 부여된 데이터를 인식하도록 합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const uint8_t hid_report[] =
{  
  0x06, 0x00, 0xFF, // Usage Page = 0xFF00 (Vendor Defined Page 1)
  0x09, 0x01,       // Usage (Vendor Usage 1)
  0xA1, 0x01,       // Colsslection (Application)
  0x85, 0x01,       // Report ID = 1
  0x19, 0x01,       // Usage Minimum
  0x29, 0x01,       // Usage Maximum
  0x75, 0x08,       // Report Size: 8-bit field size
  0x95, 0x01,       // 8bit (Report Size) * 1
  0x81, 0x00,       // Input (Data, Array, Abs)
  0x19, 0x01,       // Usage Minimum
  0x29, 0x40,       // Usage Maximum
  0x91, 0x00,       // Output (Data, Array, Abs): Instantiates output packet fields.
  0xC0,
  
  0x05, 0x0C, // Usage Page (Consumer Device)
  0x09, 0x01, // Usage (Consumer Control)			
  0xA1, 0x01, // Collection (Application)			
  0x85, 0x02,	// Report ID = 2
  0x05, 0x0C, // Usage Page (Consumer Devices)		
  0x15, 0x00, // Logical Minimum (0), bit value
  0x25, 0x01, // Logical Maximum (1), bit value
  0x75, 0x01, // Report Size (1)
  0x95, 0x06, // Report Count (6)
  0x09, USB_HID_CONSUMER_SCAN_NEXT_TRACK,         // Usage
  0x09, USB_HID_CONSUMER_SCAN_PREVIOUS_TRACK,     // Usage    
  0x09, USB_HID_CONSUMER_PLAY_PAUSE,              // Usage (Play / Pause)
  0x09, USB_HID_CONSUMER_MUTE,                    // Usage
  0x09, USB_HID_CONSUMER_VOLUME_INCREMENT,        // Usage
  0x09, USB_HID_CONSUMER_VOLUME_DECREMENT,        // Usage
  0x81, 0x02, // Input (Data, Variable, Absolute)
  0x95, 0x02, // Report Count (2)
  0x81, 0x01, // Input (Constant)
  0xC0            
};

물리적인 키의 한계와 기능확장

지난 포스트에서 미디어 컨트롤에 필요한 키는 총 6개이나, 미디어 컨트롤러에서 제어가능한 방법은 키 액션은 8개라고 했었습니다. 유튜브 제어를 위해서 추가적으로 고려한 기능은 이 숫자보다도 많으므로 입력 방법을 늘릴 방법이 필요합니다.

그리고 기존 미디어 컨트롤러 기능도 유지가 되어야 함을 전제로 합니다.

여기서는 팡션 Fn(Function) 키 조합 방식을 사용했습니다.

controller
(여기서는 기능 확장을 위해서 꼭 필요합니다.)

4개의 키가 있는데 왼쪽부터 순서대로 네 번째 키(노란불)가 바로 Fn 키 입니다. 미디어 컨트롤러 모드에서는 일단 아무 기능도 할당되어 있지 않는데데 유튜브 조작 시에는 이 키가 사용됩니다. 다만, 미디어 컨트롤러 기능과 키가 겹치므로 컨트롤러 내부적으로는 모드가 구분되어야 합니다. 가장 오른쪽 사이드에 작은 키가 붙어 있는데 이 키가 모드를 강제로 변경하는 키 입니다.

WebHID에서 연결이 되면 자동으로 모드를 변경할 수 있도록 고려하였습니다.

기본적은 미디어 컨트롤러로서 동작을 하지만 모드 변경키를 누르거나 WebHID에 연결이 되면 불이 들어오는데 이 상태가 유튜브 제어가 가능한 가칭 유튜브 모드 입니다.

controller

불이 꺼지면 노멀 모드, 켜지면 유튜브 모드가 되겠습니다. 유튜브 모드에 대한 키입력 및 조합은 다음과 같이 정하였습니다.

모드 키 입력 동작
노멀 엔코더 시계 시스템 볼륨 업
엔코더 반시계 시스템 볼륨 다운
엔코더 누름 시스템 뮤트
버튼 1 시스템 이전 트랙
버튼 2 시스템 재생/정지
버튼 3 시스템 다음 트랙
버튼 4 Fn (기능 미 할당)
유튜브모드 엔코더 시계 유튜브 볼륨 업
엔코더 반시계 유튜브 볼륨 다운
엔코더 누름 유튜브 뮤트
버튼 1 유튜브 이전 트랙/챕터 영상일 경우 이전챕터
버튼 2 유튜브 재생/정지
버튼 3 유튜브 다음 트랙/단일 영상일 경우 다음 추천영상/챕터 영상일 경우 다음챕터
버튼 4 Fn (단독 사용 불가)
유튜브모드(펑션) Fn + 엔코더 시계 5초 이후 이동
Fn + 엔코더 반시계 5초 이전 이동
Fn + 엔코더 누름 유튜브 뮤트(동일)
Fn + 버튼 1 미니플레이어
Fn + 버튼 2 극장모드
Fn + 버튼 3 전체화면

크롬 확장 프로그램

확장 프로그램 설명부터 만들기에 대한 내용은 별도의 포스트로 작성해야 하기 때문에 여기서는 제작 방법자체 보다는 진행 과정을 남기겠습니다.

몇달 전에 개인 프로젝트로 커뮤니티 메모 관련 기능 확장을 위한 고민을 했고 실제 프로토타입을 구현하고 현재는 작업 보류중인 것이 있습니다.(이것도 언젠가는 한번 공개하지 않을까 싶습니다)

당시 확장 프로그램을 구현하기 위한 플랫폼으로 VueJS, Webpack 기반으로 된 템플릿을 사용하였습니다. 첫 개발 경험을 이 템플릿을 통하여 진행하였기 때문에 처음에는 이 템플릿을 이용하려고 했습니다. 하지만 이 템플릿이 오래전의 것이라 빌드가 원활하지 않아서(npm 모듈을 수정해야함) 사용의 불편함이 있었고, 굳이 별도의 플랫폼을 사용해야 할 정도의 복잡도나 규모가 있는 앱이 아니기 때문에 바닐라JS(순수 자바스크립트)와 JQuery를 이용하여 개발을 시작했습니다.

Extensionizr7라는 곳을 방문하면 템플릿을 쉽게 만들수 있기 때문에 여기서 최소한으로 필요한 코드를 생성하고 작업을 진행했습니다. 그러나 막상 호기롭게 시작하였으나 작업을 진행할 수록 자잘하게 신경써야할 부분이 많아지면서 그래도 기왕이면 좀 더 개발이 수월한 다른 방법을 찾아보게 됩니다.

그리고 ‘기왕이면 새로운 것을 배우면서 해볼까?‘하는 의식흐름으로 처음 접해보는 스벨트(Svelte)8라는 웹프레임워크 기반의 템플릿을 찾아서 사용하기로 했습니다. 다른 웹 프레임워크 대비 가볍고 심플하기 때문에 적정한 플랫폼이라고 생각했기 때문입니다. 그래서 누군가 만들어놓은 템플릿을 이용하려고 했는데 이 또한 빌드 환경이 구형 버젼이고 크롬 확장 프로그램을 명세하는 Manifest도 v3가 아닌 v2로 되어 있었기 때문에

‘직접 구성해보자’ 라는 의식흐름으로 넘어와서 목이 마른자 직접 우물을 팠습니다.
그리고 새로 만든 템플릿을 기반으로 웹앱을 만들었습니다.

아래 링크는 템플릿만 추려서 공개해놓은 것 입니다.
https://github.com/micro-artwork/chrome-extension-svelte-boilerplate

동작 절차

유튜브 접속 시 플레이어 하단에 아래와 같은 버튼을 만들어 유튜브 플레이어가 있는 페이지에 삽입이 되도록 했습니다.

연결버튼

해당 버튼을 누르면 WebHID를 통해서 디바이스의 Vendor ID와 Product ID를 기반으로 필터링하여 크롬에서 연결을 시도합니다.

연결요청

연결 버튼을 누르면 페어링이 되고 통신이 가능한 상태가 되면 탭 상단에는 조이패드 아이콘이 표시 됩니다.(크롬 자체제공)
그리고 앱에서는 버튼의 아이콘 색을 녹색으로 표시하여 연결이 된 것을 확인할 수 있도록 하였습니다.

연결상태

여기서 다시 연결 버튼을 누르면 연결이 해제 됩니다. 물론 USB 장치를 물리적으로 분리해도 해제 됩니다.
이 경우 조이패드 아이콘도 녹색 아이콘도 원래대로 돌아옵니다.

여기까지 기본적인 동작 절차 입니다.

참고로 편의성을 위해서 USB 장치가 연결되거나 유튜브 화면에 진입하면 자동으로 연결하는 것을 고려하였으나 보안상의 이유로 위와 같이 연결 확인을 거쳐야해서 자동 연결 기능은 현재로서는 불가한 것으로 확인 됩니다.

동작 영상

동작은 다음 화면처럼 가능합니다. GIF 영상은 챕터를 이동 하는 부분입니다.
챕터 이동은 유튜브 사이트에 단축키 공개가 되어 있지 않은데 ‘컨트롤 + 좌/우 화살표’를 눌러서 이동합니다.

다행히도 이 모든 기능은 브라우져가 백그라운드 상태에 있어도 동작을 합니다. 다만 유튜브 영상을 멀티로 동작 시켰을 때에 예외처리 등은 고려하지 않았습니다.

유튜브

https://youtu.be/KZX7UcYx0UY

마무리

볼륨 조절만 편하게 해볼생각으로 가벼운 마음으로 시작한 프로젝트가 다양한 경험을 해보는 프로젝트가 되었습니다.

소스코드는 아래의 링크에서 확인 하실 수 있습니다.

이 다음 프로젝트는 좀 더 심화된 장치를 목표로 스트림덱과 유사항 장치를 구상하였는데 구현을 위해서 검토한 MCU가 반도체 수급 문제로 현재 구할 수가 없는 상태라서 진행여부가 불투명하게 되었습니다. 그래서 디바이스 개발이 필요한 프로젝트는 장기적 유예가 될 확률이 높고 상대적으로 단순하고 재밌는 것들을 찾아서 오도록 하겠습니다.