서론
여느 기술자나 다 그렇듯, 필자 또한 미래적인 느낌이 나는 것들에 흥미가 있고 좋아한다. 그런 성향 때문인지, 가끔 집에 남는 기기가 생기면 그 기기에 '미래적인 느낌의 소프트웨어'를 직접 개발해서 넣으며 스트레스를 풀곤 한다. 이번에도 집에 안드로이드 태블릿이 하나 남게 되어 무얼 만들까 고민하다가, 예전에 봤던 애니메이션에서 보여준 미래적인 시계에서 아이디어를 얻어 바로 컴퓨터를 켜고 자리에 앉아 코드를 써내려가기 시작했다.
몇 시간이 흘러, 충분히 미래지향적인 시계를 만들 수 있었다. 디지털 시계, 아날로그 시계 기능 뿐 아니라 웹에서 지원하는 MediaDevices.getUserMedia(constraints) 메서드 이용해 만든 거울 기능, 그리고 컴퓨터에서 지금 재생하고 있는 미디어 정보까지 표시해주게끔 만들었다.
이 시계를 만들며 가장 노력을 들인 것이 바로 위 사진에서 보이는 현재 음악 정보 표시 기능이다. 이 기능을 어떻게 구현할지 고민한 결과, 다음과 같이 요약할 수 있었다:
- Chrome Extension을 만들고 재생 중인 미디어가 있다면 이를 엔드포인트에 전송
- Windows에서 현재 재생 중인 미디어 불러오기
필자가 선택한 것은 후자였는데, 작은 규모의 프로젝트를 위해 익스텐션 프로젝트를 하나 더 만들기에는 애매하다는 이유에서였다. 'Windows에서 현재 재생 중인 미디어 불러오기'라고만 써 둔다면 잘 와닿지 않을 수 있지만, 이미 Windows 내에서 이 방식을 사용하는 UI가 존재한다.
그렇다면, 해당 UI가 어떤 방식으로 현재 재생 중인 미디어를 불러오는지, 그리고 필자가 해당 기능을 구현하는 데에 있어 어떤 실수들을 했을지 살펴보고자 한다.
Step 1.
NodeRT 설치하기
Windows 8에서는 WinRT(Windows Runtime) API를 지원하고, Windows 10에서는 WinRT를 계승한 UWP(Universal Windows Platform) API를 지원한다. 바로 이 API들이 현재 미디어를 불러오는 역할을 한다. 이를 알게 된 계기는 WinRT를 활용하여 작성된 파이썬 코드를 찾은 것이었는데, 이 코드에서 아이디어를 얻고 Node.js에서 WinRT를 사용하는 방법을 구글링해 보았다. 그리고 찾은 것이 NodeRT였다.
NodeRT는 WinRT API나 UWP API를 Node.js에서 사용할 수 있게끔 만들어주는 라이브러리이다. NodeRT를 사용함으로써 현재 Windows 환경의 온갖 정보들을 가져올 수 있다. 저장소의 샘플에도 있는 내용이지만, 현재 위치를 가져와야 하는 Geolocation 서비스나 화면 캡쳐 등의 여러 기능들을 추가할 수 있다는 장점이 있다.
NodeRT를 사용하려면 먼저 Windows SDK를 설치해야 한다. Visual Studio Installer가 설치되어 있다면 SDK 체크만 하고 설치하면 되지만, 그렇지 않다면 Microsoft Developer Page의 Windows SDK 아카이브에 방문하여 직접 설치할 수 있다.
SDK를 설치하였다면, 본격적으로 NodeRT를 프로젝트에서 사용해 보자. GitHub의 README에서는 Windows 10의 Redstone 4 버전 SDK를 설치하게끔 설명하고 있지만, npm에서 조금만 검색해 보면 Windows 11 SDK를 사용하는 패키지들을 찾아볼 수 있다. 필자는 Windows 11 SDK를 사용하는 패키지를 설치했다.
npm i @nodert-win11/windows.media.control
NodeRT 내의 기능은 각각의 패키지로 구분되어 있는데, 필자가 사용할 기능은 'Windows.Media.Control' 네임스페이스 내에 있으므로 windows.media.control 패키지만 설치하면 된다.
Step 2.
미디어 불러오기
WinRT에서는 미디어 세션들을 묶어 매니저를 통해 접근하도록 유도하고 있다. 여기서 사용하는 매니저는 GlobalSystemMediaTransportControlsSessionManager 클래스로, 해당 클래스의 .RequestAsync() 메서드를 호출하여 세션들이 담긴 인스턴스를 가져올 수 있다. 매니저 인스턴스를 가져오는 과정까지의 코드를 작성한다면 다음과 같이 작성할 수 있다.
const mediaControl = require('@nodert-win11/windows.media.control');
const mediaManager = mediaControl.GlobalSystemMediaTransportControlsSessionManager;
mediaManager.requestAsync((_, manager) => {
console.log(manager);
});
유의해야 할 것은, NodeRT에 구현된 비동기 메서드들이 JS의 await 키워드를 지원하지 않고 오로지 Callback 함수만을 받는다는 것과 결괏값이 Callback 함수의 첫 번째 파라미터가 아닐 수 있다는 것이다. 위 코드에서도, 첫 번째 인자가 매니저 인스턴스가 아닌 undefined이기에 두 번째 인자를 manager라고 작명한 것임을 알 수 있다. 필자는 이 점들을 인지하지 못해서 몇 시간을 낭비했다.
.RequestAsync() 메서드로 불러온 매니저 인스턴스는 .GetCurrentSession()과 .GetSession() 메서드를 가진다. 물론 여러 세션들이 있겠지만, 현재 활성화해 둔 창의 미디어가 제일 중요할 것이라고 생각한다면 필자가 사용해야 할 메서드는 .GetCurrentSession() 메서드일 것이다.
const mediaControl = require('@nodert-win11/windows.media.control');
const mediaManager = mediaControl.GlobalSystemMediaTransportControlsSessionManager;
mediaManager.requestAsync((_, manager) => {
const currentSession = manager.getCurrentSession();
});
이 메서드는 세션 클래스인 GlobalSystemMediaTransportControlsSession 클래스를 반환한다. 이 세션 인스턴스는 현재 미디어의 정보와 그 정보를 가져오는 메서드를 포함한다. 이 세션 클래스의 .TryGetMediaPropertiesAsync() 메서드를 통해 정보(GlobalSystemMediaTransportControlsSessionMediaProperties 클래스)를 가져올 수 있다.
const mediaControl = require('@nodert-win11/windows.media.control');
const mediaManager = mediaControl.GlobalSystemMediaTransportControlsSessionManager;
mediaManager.requestAsync((_, manager) => {
const currentSession = manager.getCurrentSession();
if (currentSession != null) {
currentSession.tryGetMediaPropertiesAsync(async (_, info) => {
const object = {};
const keys = [
"albumArtist", "albumTitle", "albumTrackCount",
"artist", "genres", "playbackType",
"subtitle", "thumbnail", "title",
"trackNumber"
];
for (const k of keys) {
object[k] = info[k];
}
console.log(object);
});
}
});
이제 위 코드를 실행하면 다음과 같은 형식으로 미디어의 정보가 표시될 것이다.
{
"albumArtist": "",
"albumTitle": "",
"albumTrackCount": 0,
"artist": "Novelbright",
"genres": {
"__winRtInstance__": true
},
"playbackType": 1,
"subtitle": "",
"thumbnail": {
"__winRtInstance__": true
},
"title": "Novelbright - ツキミソウ [Official Live Video at ZeppTokyo]",
"trackNumber": 0
}
Extra 1.
썸네일에 대해
위 미디어의 정보 형식을 보면 알 수 있듯, 썸네일에 대한 정보를 제대로 보여주지 못한다. 필자가 원한 것은 썸네일 버퍼 데이터를 Blob이나 Base64로 변환한 것이었는데, 여러 문제가 겹쳐 의도대로 되지는 않았다.
원래 로드되는 썸네일은 IRandomAccessStreamReference 인터페이스의 구현체로, .OpenReadAsync() 메서드를 호출하여 스트림 인터페이스인 IRandomAccessStreamWithContentType의 구현체를 얻을 수 있다. 이 구현체는 실제 스트림을 읽고 버퍼에 삽입하는 .ReadAsync(IBuffer, UInt32, InputStreamOptions) 메서드를 가지고 있는데, NodeRT에서는 이 메서드에 접근할 수 없었다.
해당 내용에 대해 찾아보니, nodert-streams 패키지에서 스트림에 관한 내용을 따로 구축하고 있다고 명시하고 있었다. 하지만 유지보수가 잘 되어있지 않은지 Visual Studio 버전과 관련한 오류가 지속적으로 발생하는 것을 보고 썸네일 구현은 포기하였다.
마치며
이번 글에서는 NodeRT에 대해 살펴보고, 실제로 사용해보는 내용을 다루었다. Node.js로 개발하는데도 C# UWP 레퍼런스를 봐야 한다는 것은 오묘했고 썸네일을 구현하지 못한 것은 아쉬웠지만, 충분히 원하는 기능을 만들었으니 나쁘지는 않다고 생각하고 있다.
이 글이 당신에게 유익했거나 참고가 되었다면 아래의 공감 버튼을 한 번 클릭해 주시길 부탁드린다. 필자는 여러분의 의견에 따라 이 글이 실제로 독자에게 도움이 되는지 판단하고, 양질의 컨텐츠를 생산하기 위해 노력하게 된다. 앞으로 여러분들에게 도움이 되는 글을 작성하기 위해서는 도움이 필요하다.
이 글에 오류가 있거나, 지나치게 편향적이거나 주관적인 서술이 존재한다고 생각되거나, 이해를 방해하는 내용, 문체 등을 포함하고 있다고 느껴진다면 언제든지 댓글로 피드백을 작성해 주시어 오류 수정에 도움을 주실 수 있다.
글을 읽어주시어 진심으로 감사드린다.
'개발일지 > 웹' 카테고리의 다른 글
텍스트 에디터로 메일 전송 페이지 만들기: 웹메일 클라이언트 개발하기 6 (0) | 2024.07.28 |
---|---|
웹메일들은 어떻게 메일을 보여주는가: 웹메일 클라이언트 개발하기 5 (0) | 2024.07.23 |
로그인 페이지 구현해보기: 웹메일 클라이언트 개발하기 4 (0) | 2024.07.20 |
웹 우체국으로 편지 보내는 방법: 웹메일 클라이언트 개발하기 3 (0) | 2024.07.19 |
메일은 어떻게 받아오는가: 웹메일 클라이언트 개발하기 2 (0) | 2024.07.16 |