# キーボードで再生できない、または字幕ボタンのない動画

Source: https://easeweb.dev/learn/video-player-controls
Topics: 画像とメディア > カスタム動画プレーヤーがキーボードで使え、すべてのボタンに名前がある (WCAG 2.1.1, 4.1.2; EN 301 549 7.1.1, 7.2.1)
The fix: ポスター画像の上の再生ボタンを、名前のあるHTML標準のボタンにして、押したらフォーカスを操作バーの再生ボタンへ移し、操作バーははじめから表示します。動画に字幕や音声解説版があれば、操作バーに字幕ボタンと音声解説ボタンも置きます。または、ブラウザー標準の操作部品を使い、プレーヤーのすぐ隣に音声解説ボタンを置きます。
Test with: keyboard, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F59 https://www.w3.org/WAI/WCAG22/Techniques/failures/F15 https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf https://www.w3.org/WAI/media/av/player/ https://www.w3.org/WAI/ARIA/apg/patterns/button/ https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/hidden https://developer.mozilla.org/en-US/docs/Web/API/TextTrack/mode https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc

よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。

## 場面

コーヒー豆の焙煎店には淹れ方の動画があり、確認済みの字幕と音声解説版もそろっています。店はサイトの見た目に合わせて、独自のプレーヤーを作ってもらいました。ポスター画像の真ん中に丸い再生ボタンがあり、再生が始まると、再生位置の線、再生、時間、音量、CCとADが並ぶ暗い操作バーが出ます。丸い再生ボタンはクリックの処理を付けたdivで、操作バーはこのボタンをクリックするまで隠れています。マウスなら、すべて動きます。キーボードでは、Tabキーを押すと見出しから動画の下の「ハウスブレンドを購入」へ一気に移ってしまいます。再生ボタンで止まらないので、動画は始まらず、字幕ボタンのある操作バーも出てきません。スクリーンリーダーでページを読み進めても、動画のある場所に押せるものは見つかりません。再生ボタンに役割も名前もないからです。

ボタンは正しく作ったのに、何かが抜けているプレーヤーもよくあります。再生ボタンも操作バーも、名前のある本物のボタンとスライダーですが、操作バーには再生、再生位置、ミュート、音量しか描かれていません。キーボードで動画を始められ、スクリーンリーダーでも何の操作部品かがわかります。しかし、動画に付いている字幕ファイルは一度も表示されず、サーバーにある音声解説版も再生されません。字幕も音声解説版も、オンにする手段がプレーヤーにないからです。

## 確認できる問題

達成基準2.1.1 キーボードは、マウスでできることはすべてキーボードでもできることを求めます。動画を始めるには、divをクリックするしかありません。divはフォーカスを受け取らず、クリック以外の操作に反応しないため、キーボードでは始められず、操作バーの機能もすべて、この最初のクリックの先に閉じ込められます。達成基準4.1.2 名前・役割・値は、どの操作部品も名前と役割を伝えることを求めます。このdivはどちらも伝えません。三角形はCSSで描いているので読む文字がなく、押せることを示すものもありません。W3Cの失敗例F59は、スクリプトでdivを操作部品にしながら役割を与えない、まさにこの問題を挙げています。失敗例F15は、アクセシビリティAPIを使わない独自の操作部品という、より広い場合を扱っています。

2つ目のプレーヤーは、両方の達成基準を満たします。満たしていないのは、音声付きの動画を再生するものすべてにEN 301 549が求める2つの要件です。7.1.1は動画にある字幕を表示する操作モードを、7.2.1は動画にある音声解説を選んで再生する仕組みを求めます。字幕トラックは動画にあり、音声解説版はサーバーにありますが、このプレーヤーではどちらも出せません。ブラウザー標準の操作部品なら字幕を表示できましたが、独自の操作バーに置き換えた際に、字幕の操作が抜けてしまいました。どちらの項目もWCAGにはありませんが、EN 301 549の表A.1は、Webページに求める要件としてどちらも挙げています。この表の項目に適合すれば、EUのWebアクセシビリティ指令に適合していると推定されます。

## 利用者への影響

ショップは、商品の説明に動画をよく使います。1つ目のプレーヤーで淹れ方がわかるのは、マウスを使う人だけです。2つ目のプレーヤーでは、字幕と音声解説版を最も必要とする人のために用意しておきながら、オンにする手段がありません。

## 自動チェックとAIの限界

クリックの処理を付けたdivには役割がないため、axeには確かめる手がかりがありません。EaseWebの診断は、動画プレーヤーの中のdivやspanのうち、要素自体にポインターのカーソルが設定され、クラスに `play`、`pause`、`mute`、`captions` のような語を含み、Tabキーで一度も届かないものを報告します。映像の4分の1以上を覆う再生用の層は、プレーヤーの中にほかに届くものがない場合だけ報告します。クラス名がこれらの語と違う操作部品や、ポインターのカーソルが出ない操作部品は、このチェックでは見つかりません。ただし、`onclick` 属性やボタンの役割を持つものは、Tabキーで届かない操作部品として報告されます。再生ボタンがアイコンだけの本物のボタンなら、名前がないことをaxeが `button-name` として報告します。再生ボタンが消えたときにフォーカスが移るかは、どのチェックにもわかりません。独自の操作部品を持つプレーヤーについては、診断の動画ごとの確認項目に、近くで字幕ボタンが見つかったかどうかが書かれます。見つからなければ、プレーヤーで字幕を表示でき、音声解説版を再生できるかを人が確かめるよう求めます。AIによる修正では、divに `role="button"` と `tabindex="0"` を付けただけで終わることがあります。これでは、Tabで止まってもEnterキーやSpaceキーで何も起きません。また、本物のボタンにしても、フォーカスがあるまま隠してしまい、フォーカスの行き場がなくなることがあります。

## 修正前：クリックしか受け付けない、ポスター画像の上の再生ボタン

```html
<div class="player">
  <video src="brew-guide.webm" poster="brew-guide.jpg">
    <track kind="captions" srclang="en" label="English"
      src="brew-guide.en.vtt">
  </video>
  <div class="big-play"></div>
  <div class="player-bar" hidden>
    <input type="range" class="seek" aria-label="Position"
      min="0" step="1" value="0">
    <button type="button" class="play" aria-label="Play"></button>
    <span class="time">0:00</span>
    <button type="button" class="mute" aria-label="Mute"
      aria-pressed="false"></button>
    <input type="range" class="volume" aria-label="Volume"
      min="0" max="1" step="0.1" value="1">
    <button type="button" class="captions"
      aria-pressed="false">CC<span class="visually-hidden"> captions</span></button>
    <button type="button" class="described"
      aria-pressed="false">AD<span class="visually-hidden"> audio description</span></button>
  </div>
</div>
```

```js
const player = document.querySelector('.player');
const video = player.querySelector('video');
const start = player.querySelector('.big-play');
const bar = player.querySelector('.player-bar');
const captions = player.querySelector('.captions');
const described = player.querySelector('.described');
const SOURCES = { false: 'brew-guide.webm', true: 'brew-guide.ad.webm' };

// The play button over the poster starts the video and gives way to the bar.
start.addEventListener('click', () => {
  video.play();
  start.hidden = true;
  bar.hidden = false;
});

// … the bar's play, position, mute and volume handlers

captions.addEventListener('click', () => {
  const on = captions.getAttribute('aria-pressed') !== 'true';
  video.textTracks[0].mode = on ? 'showing' : 'hidden';
  captions.setAttribute('aria-pressed', on);
});

// Swap to the described version, or back, at the same place in the video.
described.addEventListener('click', () => {
  const on = described.getAttribute('aria-pressed') !== 'true';
  const { currentTime, paused } = video;
  video.src = SOURCES[on];
  video.addEventListener('loadedmetadata', () => {
    video.currentTime = currentTime;
    if (!paused) video.play();
  }, { once: true });
  described.setAttribute('aria-pressed', on);
});
```

再生ボタンは `click` にしか反応しないdivで、Tabの止まり先も、役割も、名前もありません。三角形はCSSで描いているため、読むものも何もありません。プレーヤーのほかの部分は、すべてこのボタンの先にあります。操作バーは最初のクリックまで隠れているので、操作バーのボタンがどれほど正しく作られていても、キーボードの利用者はたどり着けません。

## 修正前：字幕と音声解説のボタンがない操作バー

```html
<div class="player">
  <video src="brew-guide.webm" poster="brew-guide.jpg">
    <track kind="captions" srclang="en" label="English"
      src="brew-guide.en.vtt">
  </video>
  <button type="button" class="big-play" aria-label="Play video"></button>
  <div class="player-bar">
    <input type="range" class="seek" aria-label="Position"
      min="0" step="1" value="0">
    <button type="button" class="play" aria-label="Play"></button>
    <span class="time">0:00</span>
    <button type="button" class="mute" aria-label="Mute"
      aria-pressed="false"></button>
    <input type="range" class="volume" aria-label="Volume"
      min="0" max="1" step="0.1" value="1">
  </div>
</div>
```

```js
const player = document.querySelector('.player');
const video = player.querySelector('video');
const start = player.querySelector('.big-play');
const bar = player.querySelector('.player-bar');

// The play button over the poster starts the video and gives way to the bar.
start.addEventListener('click', () => {
  video.play();
  start.hidden = true;
  bar.hidden = false;
  // It is gone now, so focus moves on to the bar's play button.
  bar.querySelector('.play').focus();
});

// … the bar's play, position, mute and volume handlers
```

再生ボタンは「Play video」という名前のHTML標準のボタンで、操作バーの操作部品もボタンとスライダーなので、プレーヤーは2.1.1と4.1.2を満たします。しかし、ブラウザー標準の操作部品の代わりに独自の操作部品を使いながら、字幕をオンにする手段を用意していません。`track` は動画にありますが、そのモードは `disabled` のままです。音声解説版の `brew-guide.ad.webm` はサーバーにありますが、プレーヤーには再生する手段がありません。

## 修正後：再生ボタンと、字幕と音声解説のある操作バー

```html
<div class="player">
  <video src="brew-guide.webm" poster="brew-guide.jpg">
    <track kind="captions" srclang="en" label="English"
      src="brew-guide.en.vtt">
  </video>
  <button type="button" class="big-play" aria-label="Play video"></button>
  <div class="player-bar">
    <input type="range" class="seek" aria-label="Position"
      min="0" step="1" value="0">
    <button type="button" class="play" aria-label="Play"></button>
    <span class="time">0:00</span>
    <button type="button" class="mute" aria-label="Mute"
      aria-pressed="false"></button>
    <input type="range" class="volume" aria-label="Volume"
      min="0" max="1" step="0.1" value="1">
    <button type="button" class="captions"
      aria-pressed="false">CC<span class="visually-hidden"> captions</span></button>
    <button type="button" class="described"
      aria-pressed="false">AD<span class="visually-hidden"> audio description</span></button>
  </div>
</div>
```

```js
const player = document.querySelector('.player');
const video = player.querySelector('video');
const start = player.querySelector('.big-play');
const bar = player.querySelector('.player-bar');
const captions = player.querySelector('.captions');
const described = player.querySelector('.described');
const SOURCES = { false: 'brew-guide.webm', true: 'brew-guide.ad.webm' };

// The play button over the poster starts the video and gives way to the bar.
start.addEventListener('click', () => {
  video.play();
  start.hidden = true;
  bar.hidden = false;
  // It is gone now, so focus moves on to the bar's play button.
  bar.querySelector('.play').focus();
});

// … the bar's play, position, mute and volume handlers

captions.addEventListener('click', () => {
  const on = captions.getAttribute('aria-pressed') !== 'true';
  video.textTracks[0].mode = on ? 'showing' : 'hidden';
  captions.setAttribute('aria-pressed', on);
});

// Swap to the described version, or back, at the same place in the video.
described.addEventListener('click', () => {
  const on = described.getAttribute('aria-pressed') !== 'true';
  const { currentTime, paused } = video;
  video.src = SOURCES[on];
  video.addEventListener('loadedmetadata', () => {
    video.currentTime = currentTime;
    if (!paused) video.play();
  }, { once: true });
  described.setAttribute('aria-pressed', on);
});
```

ポスター画像の上の再生ボタンはHTML標準のボタンなので、キー操作を書かなくても、Tabキーで届き、EnterキーとSpaceキーで押せ、スクリーンリーダーは「Play video、ボタン」と読みます。押すとこのボタンは消えるため、スクリプトはフォーカスを操作バーの再生ボタンへ移します。このボタンは、動画が再生されるとPauseと読まれます。この1行がないと、ブラウザーはフォーカスをページそのものに戻し、フォーカスはプレーヤーから離れてしまいます。操作バーは再生ボタンを押す前から表示しているので、字幕と音声解説も再生と同じく1回押すだけで済み、動画を始める前にオンにしておけます。操作バーのCCボタンは動画にすでにある字幕トラックを表示し、ADボタンは、[音声解説がなく、画面の情報が伝わらない動画](/ja/learn/audio-description)と同じ方法で、再生中の位置のまま音声解説版に入れ替えます。どちらもオンかどうかを `aria-pressed` で伝え、名前は表示されている文字で始まります。

## 修正後：ブラウザー標準の操作部品と、音声解説ボタン

```html
<div class="player">
  <video src="brew-guide.webm" poster="brew-guide.jpg" controls>
    <track kind="captions" srclang="en" label="English"
      src="brew-guide.en.vtt">
  </video>
  <button type="button" class="described"
    aria-pressed="false">Audio description</button>
</div>
```

```js
const player = document.querySelector('.player');
const video = player.querySelector('video');
const described = player.querySelector('.described');
const SOURCES = { false: 'brew-guide.webm', true: 'brew-guide.ad.webm' };

// Swap to the described version, or back, at the same place in the video.
described.addEventListener('click', () => {
  const on = described.getAttribute('aria-pressed') !== 'true';
  const { currentTime, paused } = video;
  video.src = SOURCES[on];
  video.addEventListener('loadedmetadata', () => {
    video.currentTime = currentTime;
    if (!paused) video.play();
  }, { once: true });
  described.setAttribute('aria-pressed', on);
});
```

`controls` 属性を付けると、ブラウザーが自前の再生ボタンと操作バーを描きます。どの操作部品もブラウザーが作り、名前を付け、キーボードで使えるようにします。動画に字幕トラックがあれば、字幕をオンにする手段も入ります。隠れた操作バーも、引き継ぐフォーカスもありません。この操作部品に足りないのは音声解説版のボタンだけなので、サイトはプレーヤーのすぐ下に、再生中の位置のまま音声解説版に入れ替えるボタンを足します。操作部品の見た目、名前、どのキーで動くかはブラウザーごとに違うため、対応するすべてのブラウザーで試してください。字幕の操作をどこに置くかもブラウザーが決めます。Chromeは操作部品のオーバーフローメニューの中、音量より1段下に置くため、Section 508の503.4.1を満たさず、動画が主な内容のページではEN 301 549の7.3も満たしません。詳しくは[操作バーに字幕のボタンがない動画プレーヤー](/ja/learn/caption-controls)で説明しています。

## どの方法を選ぶか

どちらも2.1.1と4.1.2を満たし、字幕と音声解説版をオンにできます。再生ボタンと操作バーをHTML標準のボタンと範囲入力で作る方法は、ショップ独自の見た目を保ち、CCとADを音量の隣に置けますが、本来ならブラウザーが用意してくれる上のスクリプトが要ります。ブラウザー標準の操作部品は属性1つで済みますが、サイトの見た目に合わせられず、音声解説ボタンは操作部品の外に置くことになり、Chromeでは字幕が音量より1段下のメニューに入ります。HTML標準のボタンとスライダーを使ってください。Section 508の503.4.1とEN 301 549の7.3が求めるとおり、どのブラウザーでも字幕を音量の隣に置けるのは、2つのうちこちらだけです。ブラウザー標準の操作部品は、そのどちらも適用されないページで、プレーヤーを作り直すまでの応急の修正です。

## 実装の判断

再生ボタンには、何を再生するかがわかる名前を付けます。ページに動画が1つなら「Play video」で足ります。複数あるなら、「Play: How to brew our house blend」のように、`aria-label` か、動画の見出しを指す `aria-labelledby` で、それぞれに別の名前を付けます。マウスのための便利な仕掛けとして、ポスター画像全体がクリックに反応してもかまいません。ただし、キーボードのための丸いボタンは残します。

操作バーは、動画を始める前から表示しておきます。最初に押すまで再生ボタンしか見せないプレーヤーでは、字幕と音声解説が再生より1段下になり、字幕が必要な人は字幕なしで動画を始めるしかありません。EN 301 549の7.3は、動画が主な内容のページで、これらを主な操作と同じ階層に置くよう求めています。詳しくは[操作バーに字幕のボタンがない動画プレーヤー](/ja/learn/caption-controls)で説明しています。ポスター画像をすっきり見せたいデザインなら、ポスター画像の上で、再生ボタンの隣に字幕ボタンを置きます。下の「デザインの案」にも、この形の例があります。

操作バーの操作部品にも、ほかの操作部品と同じ決まりが当てはまります。

- 再生ボタンは、名前（PlayかPause）だけを変える。ARIA Authoring Practicesは、切り替えボタンのラベルは状態が変わっても変えてはならないとしているので、ボタンは名前を変えるか、名前を変えずに `aria-pressed` で状態を伝えるかのどちらかにし、両方はしない。
- ミュート、CC、ADは名前を変えずに、オンかどうかを `aria-pressed` で伝える。
- 再生位置のスライダーは、`aria-valuetext` で「12 seconds of 24 seconds」のように時間を言葉で伝え、スクリーンリーダーが単位のない数字を読まないようにする。divで描いた再生位置の線では、`role="slider"`、`tabindex="0"`、矢印キー、HomeキーとEndキー、すべての `aria-value` 属性をスクリプトで用意することになる。`input type="range"` なら、どれも最初からそろっている。

操作バーは、キーボードの利用者に見える場所に置いておきます。再生が始まって数秒で消える操作バーは、フォーカスがプレーヤーに入ったら再び表示し、フォーカスがある間は消さないようにします。そうしないと、キーボードの利用者は見えない操作部品の上をTabキーで移動することになります。どの操作部品にも、ポスター画像の上の再生ボタンにも、暗い操作バーと、映像の明るい部分と暗い部分の両方に対して目立つフォーカスの枠を付けます。詳しくは[フォーカス表示が見えない独自デザインのボタン](/ja/learn/visible-focus)で扱っています。

SpaceキーやKキーで再生、Mキーでミュート、Cキーで字幕といったキーボードショートカットは、あればうれしいものですが、ボタンの代わりにはなりません。プレーヤーにフォーカスがあるときだけ働く1文字のショートカットは、2.1.4で認められています。ページのどこでも働くものは、[オフにできない1文字のショートカット](/ja/learn/character-key-shortcuts)の一つになります。

動画にあるものだけを出します。字幕トラックがあればCCボタンを、音声解説版があればADボタンを置きます。これらのボタンの置き場所と操作の回数は[操作バーに字幕のボタンがない動画プレーヤー](/ja/learn/caption-controls)で、字幕の大きさと色は[スタイルを変えられない字幕、変換で消える字幕](/ja/learn/caption-quality)で扱っています。ページを軽くするために、クリックされてからYouTubeやVimeoのプレーヤーを読み込むポスター画像にも、同じHTML標準の再生ボタンが必要です。埋め込みが読み込まれたあとの操作部品はサービス側のものなので、下の手順で、実際に何が描かれるかを確認してください。

## 修正の確認

1. マウスを使わず、ポスター画像の上か、ブラウザー標準の操作バーにある動画の再生ボタンまでTabキーで移動し、EnterキーかSpaceキーを押す。動画が始まり、フォーカスが操作バーの再生ボタンに移る。操作バーのどの操作部品もTabの止まり先になり、矢印キーで再生位置と音量を変えられる。
2. スクリーンリーダーで、再生前の動画まで移動する。再生ボタンは、何を再生するかと、ボタンであることが読まれる。押したあとは、操作バーの再生ボタンがPauseと読まれ、ミュート、字幕、音声解説はオンかどうかが読まれる。
3. キーボードで、操作バーかプレーヤーの隣のボタンから、字幕と音声解説版をオンにする。字幕が表示され、音声解説版は同じ位置から続く。
4. 再生前と再生中に、Tabキーで移動しながらフォーカスの枠を見る。ポスター画像の明るい部分と暗い部分に重なる再生ボタンにも、操作バーのどの操作部品にも枠が見える。フォーカスがプレーヤーにある間は、操作バーが表示されたままになる。

OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。プレーヤーやそのライブラリーを更新した際は、再度確認します。

## 制限

このサンプルは、どのアプリケーションでも同じ結果になることを保証するものではありません。扱うのは、プレーヤーの操作部品に届くか、何を伝えるか、字幕と音声解説版をオンにできるかです。中身は別のガイドで扱います。字幕は[字幕のない動画と、確認されていない自動字幕](/ja/learn/video-captions)で、音声解説は[音声解説がなく、画面の情報が伝わらない動画](/ja/learn/audio-description)で扱います。音声付きで自動再生される動画は、まず止める手段が必要で、[止められない自動再生の音声](/ja/learn/autoplay-audio)で扱います。ほかのコンテンツと並んで自動で動く動画には一時停止が必要で、[一時停止できない、流れる文字、カルーセル、アニメーション](/ja/learn/moving-content)で扱います。視聴者が使うブラウザーと支援技術で、一連の流れを確認してください。

---

このガイドをコーディングエージェントで使う: MCP https://easeweb.dev/mcp（設定は https://easeweb.dev/ja/services/developers）
