HTML5 비디오 플레이어 마크업 작업기

Posted by in Research

서론

네이버 메인에는 광고가 군데군데 들어가 있는데, 저는 이 중에 롤링보드라는 영역의 비디오 플레이어 마크업 작업을 진행해야 하는 상황이었습니다. 기존에는 플래시로 광고 영상 플레이어가 구현이 되어 있었기 때문에, 이를 순수 HTML5로 변환하는 작업이었습니다.

 


그림 1: 네이버 메인 롤링보드 영역

처음 해보는 플레이어 마크업 이었기 때문에, 어떻게 시작을 해야 할지 약간 감이 잡히지 않아서 본격적인 마크업을 시작하기 전에 다른 서비스를 레퍼런스 삼아 분석해 보기로 하였고, 주요 비디오 스트리밍 서비스 사이트 3개(유투브비메오네이버TV)를 조사해 보았습니다.

이 글에서는 레퍼런스 사이트들의 UI와 마크업 구조를 먼저 살펴보고, 실제 구현했던 마크업 구조는 어떤 식으로 개발하였는지 그리고 작업을 하면서 헷갈렸었거나 추가하면 어땠을까 하고 생각해봤던 ARIA 속성을 정리해 보도록 하겠습니다.

레퍼런스 사전 조사

비디오 플레이어 외관 UI 디자인과 내부 마크업 구조를 먼저 살펴본 후, 가장 구현 방식이 궁금했던 탐색바 부분을 집중적으로 살펴 보기로 하겠습니다. 이 글에서 나오는 소스 코드는 원본에서 다루고자 하는 내용만 간략하게 발췌해 둔 것입니다.

1. 플레이어 외관 UI

네이버 TV와 유튜브 플레이어 외관은 거의 비슷하나, 오른쪽 하단 컨트롤 영역 중 가장 왼쪽에 위치한 버튼의 용도가 다릅니다. 유튜브는 설정 버튼 클릭시 확장 메뉴가 나타나면서 옵션 메뉴(자막, 특수 효과, 동영상 화질) 설정을 할 수 있는 반면에, 네이버 TV에서는 동영상 화질 설정 버튼으로 사용되고 있습니다.


그림 2: 네이버 TV 플레이어 UI


그림 3: 유투브 플레이어 UI

비메오 플레이어는 영상 탐색바를 컨트롤 영역 위에 배치하지 않고, 컨트롤 버튼과 함께 하단에 일렬로 배치했습니다. 다른 두 플레이어에서는 탐색바가 위에 따로 위치해 있기 때문에, 비메오 플레이어를 보다가 다른 두 플레이어를 보면 상대적으로 중앙 영역이 매우 텅 비어 있는 것 같습니다.

그림 4: 비메오 플레이어 UI

탐색바의 왼쪽에는 재생/일시정지 버튼 하나가 크게 자리를 잡고 있습니다. (그냥 재미로 추측해보자면, 사람들이 영상 재생에서 가장 많이 사용하면서 주가 되는 기능이 재생/일시정지 이기 때문에 강조를 시킨듯 합니다.) 음량 조절 버튼은 아이콘에 마우스를 호버 할 때 레벨 슬라이더가 등장하면 그 때 클릭하여 조절하는 방식이 아니라, 바로 레벨을 조절할 수 있도록 슬라이더를 항상 노출시켜두고 있습니다. 마우스 호버 후에 클릭까지 해야 하는 사용자 인터렉션 과정이 클릭 한단계로 줄어들었습니다. 우측 상단에는 컨트롤 영역에 들어가기엔 마이너하고, 빼먹자니 아쉬운 부가적인 기능(좋아하기/나중에보기/컬렉션에 추가/공유) 버튼을 수직정렬로 배치해 두었습니다.

2. 컨테이너 마크업

유튜브

플레이어 영역과 컨텐츠(비디오 제목 및 설명) 영역 사이의 관계를 ARIA 역할(Role) 속성을 사용해 정립해 두었습니다. role=“main”이 동영상을 제외한 페이지의 나머지 부분을 감싸는 div#content에 적용이 되었습니다. div#content에 들어가는 컨텐츠는 현재 동영상의 정보, 댓글 영역 및 다음에 재생될 동영상 목록입니다. 주인공격이라고 생각했던 플레이어 영역(div#player)이 role=“complementary”속성을 가지고 있음을 볼 수 있습니다.

이는 제가 알고있었던 complementary의 정의와는 다른 사용법이라서, 그냥 넘어가기에는 계속 약간의 의아함이 발목을 잡았습니다. W3C의 ARIA 랜드마크 예제 사이트를 살펴보면, complementary의 정의는 다음과 같이 나와있습니다.

complementary landmark is a supporting section of the document, designed to be complementary to the main content at a similar level in the DOM hierarchy, but remains meaningful when separated from the main content.

complementary 랜드마크는 해당 영역이 문서 내용을 보충하는 역할을 하는 절임을 나타냅니다. DOM 구조상 메인 콘텐츠와 같은 위계에 위치하면서, 메인 콘텐츠를 보충하는 영역에 붙이기 위해 만들어진 역할 입니다. 이 랜드마크 영역의 콘텐츠는 메인 콘텐츠에서 따로 떼어내어도 여전히 중요한 의미를 지니고 있습니다.

사용자의 유튜브 사용 주목적은 자신이 보고 싶은 영상 시청이므로, 플레이어 영역이 role='main'을 가지고 나머지 부분들이 complementary가 되지 않아야 하는 생각이 있었는데, 뜯어보니 완전히 반대로 역할이 부여 되어있어서, ‘사람마다 정의를 다르게 받아들일 수도 있겠구나’ 하는 생각이 들었습니다.

비메오

비메오에서는 플레이어 영역에는 아무런 ARIA 역할을 부여하지 않았고, div.clip_main-contentaside.clip-contextual-clip에만 각각 main, complementary 역할을 부여했습니다.
또한 aria-label 속성으로 각 랜드마크에 적합한 레이블을 달아 주어서 접근성 향상을 도모했습니다. Mac 보이스오버에서 음성 테스트를 해보면 aria-label 속성값을 읽어주기 때문에, 레이블을 달아주지 않을 때보다 사용자가 어떤 영역인지 감을 잡는데 많은 도움이 되어 줍니다.

네이버 TV

플레이어와 컨텐츠 영역을 감싸는 div#containerrole=“main"을 부여해 두었습니다.

앞의 두 서비스와는 다르게, 비디오와 관련된 의견을 달 수 있는 댓글창이 ‘함께 재생된 동영상’(3줄) 영역, DA 영역 끝난 다음에야 나옵니다. 대략 세로로 850px 정도 되는 길이를 스크롤 해야 댓글창이 나오는 구조입니다. 사용자들간의 의견 교환 보다는 동영상과 관련 컨텐츠 및 광고 제공에 중점을 두는 서비스 같다는 생각이 듭니다.

동영상 클립에 관한 정보는 플레이어 영역 안의 div#clipInfoArea에 따로 담아두고 있습니다. 이 점 역시 앞의 두 서비스와는 다른 부분입니다. 앞의 두 서비스는 모두 content라는 단어가 들어가는 영역안에 클립과 관련된 정보를 포함해 두었습니다.

3. 컨트롤 마크업

유튜브

div.ytp-chrome-bottom로 탐색바와 div.ytp-chrome-controls 버튼 영역을 모두 감싸두었습니다. 탐색바 하단 영역은 좌, 우로 두 부분으로 나누어서 각각 ytp-left-controls, yap-right-controls라는 클래스명을 붙여주었습니다.

비메오

div.controls라는 부분은 크게 button.playdiv.play-bar, div.sidedock 세가지로 나누어져 있습니다. div.play-bar 안에는 탐색바와 버튼 세 가지(볼륨, 화질 선택, 전체 화면)가 모두 포함되어 있습니다. div.sidedock은 비디오 우측 상단에 뜨는 4가지 부가 기능 버튼으로, role=“toolbar" 속성이 부여되어 있습니다.

참고로 div.sidedock의 클래스명은 Mac OS의 dock UI와 유래가 같거나, 여기서 따온듯 합니다.


그림 5: Mac OS의 dock UI

button 요소에는 titlearia-label 속성을 공통적으로 넣어 두었습니다.

네이버 TV

유튜브와 동일한 구조로 div.u_rmcplayer_progress라는 영역 안에 탐색바와 하단 컨트롤 영역이 같이 들어 있습니다. 처음에는 클래스명이 progress여서 탐색바 만을 포함하는 영역인 줄 알았는데, 어찌되었든 하단 버튼도 비디오의 ‘진행’ 상태를 조절하는 기능을 하므로 이런 클래스명을 붙인 것 같습니다.

4. 탐색바 마크업

유튜브

div.ytp-progress-bar role="slider" 역할을 부여해 두었습니다. 이 역할의 W3C 스펙 내용을 번역하면 다음과 같습니다.

사용자가 주어진 범위 사이의 값을 선택할 수 있는 인풋 요소임을 나타냅니다. 슬라이더의 크기로 선택할수 있는 값의 범위를 나타내며, thumb(슬라이더 버튼 위치)로 현재값을 나타낼 수 있습니다. 대부분 화살표 키처럼 방향성이 있는 키를 사용하여 값을 빼거나 더할 수 있도록 기능 구현이 되어 있습니다. 개발자는 반드시 aria-valuemin, aria-valuemax, aria-valuenow속성을 써주어야 합니다. 만약 이 속성들이 없다면 HTML 범위 인풋 타입에 명시된 것과 동일한 규칙에 따라서 자동으로 값이 설정됩니다.

이 때문에 유튜브 마크업에서도 관련 속성을 전부 써주었는데, 값의 단위는 ‘초’로 써주고 있습니다. aria-valuetext에는 보조기기에 의해 읽혀질 값을 써줍니다.

draggable 속성은 아직 표준화 되지 않은 전역 속성으로, 해당 요소가 드래그 가능한지 아닌지를 나타내줍니다. 값은 true, false 두 가지를 줄 수 있다고 합니다.

인라인 스타일로는 touch-action:none;이 적혀져 있습니다. touch-action 속성은 터치 스크롤과 더블 탭 확대 등과 같은 사용자 제스쳐 이벤트를 필터링 할 수 있는 속성입니다. caniuse.com에 따르면 사파리 브라우저와 IE9 이하에서 지원이 되지 않습니다. MDN에 따르면 이 속성의 가장 일반적인 사용법은 다음과 같습니다.

The most common usage is to disable all gestures on an element (and its non-scrollable descendants) that provides its own dragging and zooming behavior – such as a map or game surface.

특정 요소에 사용자 정의 드래그 및 확대 동작을 심기 위해서, 요소(그리고 자식 요소들 중 스크롤 불가능한 것들)에 적용 가능한 모든 기본 제스쳐를 차단하는 데 touch-action:none;을 사용하는 것이 이 속성의 가장 일반적인 사용법입니다.

비메오

친절하게도 얼마만큼 동영상이 로드 되었고 플레이 되었는지를 aria-valuetext를 통해 알려주고 있습니. 유튜브에서도 똑같이 재생 상태를 알려주기는 하나, 비메오가 좀 더 단어를 적극적으로 써서 사람말에 가까운 값을 담았습니다. 여기서는 role="slider" 대신에 role="progressbar"를 사용합니다. W3C WAI-ARIA 스펙을 읽어봐도 slider의 설명과 딱히 달라 보이는 것이 없어 보이는데요, 그럼 둘 중에 아무거나 써도 상관 없을까요? 이런 의문을 가지고 더 정보를 찾아보다가, 영문 위키피디아에서 도움이 될 만한 구절을 발견했습니다. (이렇게 내가 원하는 정보를 발견하는 맛에 인터넷을 뒤지나 봅니다.)

Sliders are also combined with progress bars in the playback of streaming media over a network connection (e.g., YouTube videos) in order to show the content buffering position versus the playback position. This is done by superimposing a colored shaded area (progress bar) on top of the slider, indicating whether the user can “jump” forward or not.

유튜브 영상과 같이 네트워크 연결 상에서 스트리밍 되는 미디어 재생에 사용되는 슬라이더의 경우에는 진행바(progress bar)와 결합되는 구조를 가집니다. 이는 현재 재생되는 위치와 더불어 컨텐츠가 얼마만큼 버퍼링 되었는지 상태를 알려주기 위함입니다. 슬라이더 위에 음영이 진 유색 영역(진행바)을 노출시키는 방식이 이에 해당합니다. 사용자는 이를 사용해 영상의 앞, 뒤로 “뛰어넘기”가 가능한지 인지할 수 있습니다.

위의 설명에 따라 다시 역할을 부여해 보자면, ’얼마만큼 로딩(버퍼링)이 이루어졌는지’를 표시하는 영역에는 progressbar(여기서는 사용자가 자의로 로딩값을 선택할 수 없습니다), 그리고 사용자가 로딩된 범위 내에서 값을 선택할 수 있는 영역에는 slider 역할을 부여하는 것이 인용문에 제일 충실한 구현방법이라 할 수 있을 것 같습니다.

네이버 TV

마크업 구조는 유튜브와 비슷합니다. ARIA 관련 속성은 div.u_rmc_progress_list에 부여 두었습니다. data 속성으로 각 요소가 어떤 역할을 하는지 써놨는데, 스크립트 쪽에서 사용하려고 써놓은 것 같습니다.

5. 그 밖에 살펴볼 만한 것들

유튜브 볼륨바

  • 음소거 버튼을 누르면 스피커 아이콘 위에 사선이 하나 그어지는 애니메이션이 실행되는데, 개발자 도구에서 보면 SVG DOM이 깜빡이는 것을 보니 SVG로 구현한 것 같습니다.
  • 볼륨 슬라이더 핸들은 div.ytp-volume-slider-handle 요소로 나타냅니다. 가상 요소 ::before, 핸들 요소, 가상 요소::after 순서대로 배치하여 핸들 요소를 드래그하면 자연스럽게 좌우의 요소가 따라 움직이도록 구현해 두었습니다.

롤링보드 HTML 플레이어 마크업 작업

실제 작업 구조

영상 상태바 부분은 아까 위에서 살펴본 영문 위키백과에서 얻은 힌트를 토대로 ARIA 역할을 부여했습니다. 원래는 div.rbp_pc_handle(핸들 요소) 자체에다가 role="slider"를 주려고 했으나, 테스트를 해보니니 role="progressbar" 요소와 동일한 위계를 가지지 않으면 스크린 리더기에서 progressbar 영역까지만 인식하고 다음으로 넘어갔습니다. 그래서 핸들을 감싸는 요소에 role="slider"를 부여해서 스크린 리더기가 둘 다 인식하도록 만들었습니다.

progressbar 영역에는 tabindex를 주지 않으면 탭 탐색 및 보이스 오버 탐색 시 요소를 인식하지 못했습니다. 따라서 tabindex="0"을 부여하여 얼만큼 로딩되었는지 영역도 초점이 잡히도록 수정했습니다.

재생/일시정지 버튼의 경우, 하나의 버튼이 노출될 때 다른 하나는 숨겨진 상태이므로 aria-hidden 속성을 사용하여 스크린 리더가 숨겨진 요소를 인식하지 못하도록 했습니다.

현재 재생 시간을 알려주는 영역은 role="timer"를 부여해 두었습니다.

rolearia-label 같이 한번 정해지면 바뀔 필요가 없는 속성들(이런 속성들을 스펙용어로는 property 속성이라 합니다)을 제외하고는 전부 현재 비디오 재생 상태에 따라 값이 갱신되어야 합니다. 스펙에 이미 정의되어 있는 상태(states)와 프로퍼티(property) 속성의 차이를 한번 알아둔 후에 작업을 시작한다면 어떤 속성의 값들을 스크립트로 변경시켜야 할지 감 잡는데 도움이 될 것 같습니다.

산출물 링크: https://codepen.io/witblog/project/editor/Aogexx/

적용할 때 헷갈렸던 ARIA 속성

aria-hidden, aria-disabled, aria-readonly 세 속성 모두 사전적인 의미만 따져본다면 각각 숨김, 사용불가능, 읽기전용을 뜻하기 때문에 비슷비슷한 것 처럼 느껴집니다. 좀 더 구분을 세분화하려면 스펙을 참고하면 됩니다. 아래 표는 스펙에서 정의한 내용에 근거해 세 속성을 분류해 둔 것입니다. 가질 수 있는 값이 모두 true 혹은 false로 불리언 값임을 제외한다면 서로 조금씩 특성이 다르기 때문에, 적용하려는 컨텐츠의 성격이 어떠한지 아래 표의 기준으로 잘 따져보아야 할 것 같습니다.

기준
aria-hidden
aria-disabled
aria-readonly
가질 수 있는 값 true || false(default) true || false(default) true || false(default)
요소 인식 가능함(perceivable) X O O
요소 수정 가능함(editable) X X X
요소 조작 가능함(operable) X X O
요소 사용 불가능함(disabled) O O X

추가하면 어떨까? 하고 생각했던 ARIA 속성

적을까 말까 고민하다가 결국 뺀 ARIA 속성으로는 aria-controlsaria-owns가 있습니다. aria-controls의 속성값은 해당 속성을 가지고 있는 요소가 조작하고 있는, 혹은 앞으로 조작 할 요소의 id 값을 넣어 주면 됩니다. 반대로 aria-owns에는 해당 요소의 상태를 조작하고 있는 요소의 id 속성값을 넣어 주면 됩니다.

쉽게 말하자면 aria-controls는 요소의 종들을, aria-owns는 요소의 주인님을 알려주는 역할을 합니다. 다만 주의할 점은 DOM 구조 상의 위계로 요소 사이의 주종관계가 명확히 나타나고 있을 때에는 굳이 이 속성들을 써줄 필요가 없습니다. 탭과 탭 패널 UI처럼, 주종관계로 묶이는 요소들이 부득이하게 DOM 구조 상으로 얽히지 못할 때 써주면 됩니다.

플레이어에서 위 속성들을 적용해 보자면, 비디오 재생 조작과 관련된 모든 요소들(재생 탐색바, 재생/일시정지 버튼, 볼륨 버튼 등)에 aria-controls 속성을 사용해 비디오 요소의 id 값을 넣고, 비디오 소스 요소에는 aria-owns 속성을 사용해 이 요소를 조작하고 있는 모든 요소들의 id 값을 스페이스로 공백을 주어 나열해 주면 될 것입니다. 그러나 앞에도 언급했듯이, 본 플레이어는 30초 짜리 영상 재생용이라서 이 속성들까지 사용해 세세한 구조를 명시해 놓기에는 약간 과한 감이 들었기 때문에, 그리고 DOM 위계 자체도 서로 연관관계를 알아보지 못할 만큼 아무데나 위치 시켜두지 않았기 때문에 적용을 하지 않았습니다. (사용해도 괜찮을 것 같다는 이유로 ARIA 속성을 계속 추가하다보면 HTML이 좀 지저분해지는 감이 들어서 주저한 것도 있긴 합니다.)

마치며

찾다보면 무언가 나오지 않을까 막연한 기대에 시작했던 조사인데, 뜻밖에 이것저것 많이 알게 되고 작업도 마무리 할 수 있어서 보람찬 기분이 듭니다. 역시 맨 땅에 헤딩하는 것 보다는, 다른 사람의 것들을 토대로 나의 논리를 구축하는 것이 편하다는 것을 다시 한번 느낀 작업이었습니다.

플레이어 처럼 인터렉션 요소가 많이 들어가는 UI도 부분으로 쪼개서 나누어 보면 요소마다 저마다 제 역할을 하고 있음을 볼 수 있었습니다. ARIA 속성과 같은 경우에는 (마크업 자체도 마찬가지지만) 적절해 보이는 역할과 속성이 무엇인지 그 경계가 너무 흐릿해서, 이것저것 넣다가 마크업이 너무 불어나는 경우도 있었습니다. 이 상황에서 필요한 속성만 딱 골라내어 사용하는 것이 아직 많이 어려운 것 같습니다.

그래도 이번 플레이어 작업은 처음이라 10 정도 겁을 먹고 시작했다면 다음 번에는 한 7.5정도 느끼지 않을까 조심스레 예상해 봅니다.

참고 사이트