はじめに

私たちを取り巻くWeb技術は、もはや社会的なインフラとしてめまぐるしく進化しています。HTMLやCSSはもちろんのこと、JavaScriptやライブラリ、フレームワークなど、それぞれがニーズにキャッチアップする形で、機能強化を繰り返しています。その中でも、Web技術の中核に位置するにもかかわらず、意外と見過ごされがちなのがHTMLの進化です。本連載は、このHTMLと関連するJavaScript APIにフォーカスして、その新機能を手軽に試していただこうというものです。理解も利用もたやすいHTMLなので、ライトな気持ちで「こんなことができるようになったのか」を感じていただきます。

[NOTE]サンプルについて
本記事の配布サンプルは、以下のURLから入手できます。新機能を試していくので、ブラウザは最新である方がよいでしょう。本記事のサンプルは、執筆時点で最新のGoogle Chromeで動作することを確認しています。
https://github.com/wateryinhare62/mynavi_html

連載第6回の目的

この回では、音楽や動画をミニウインドウで再生(ピクチャ・イン・ピクチャ)できるPicture-in-Picture APIを試します。video要素をミニウインドウに移動して動画を再生したり、ミニウインドウを閉じ手元に戻す例を紹介します。また、Google Chrome独自の機能である、iframe要素などをミニウインドウに移動して動画を再生する例も紹介します。

  • 図1 Picture-in-Pictureでミニウインドウを生成

    図1 Picture-in-Pictureでミニウインドウを生成

Picture-in-Pictureとは

Picture-in-Pictureとは、動画の再生をブラウザウインドウではなく専用のミニウインドウ(浮遊ウインドウ、フローティングウインドウとも呼ぶ)で行える機能です。通常、動画はブラウザウインドウが隠れてしまうと視聴することはできません。Picture-in-Pictureを使うと、常に最前景に表示されるミニウインドウを通じて、動画の視聴を継続できます。YouTubeなどの動画配信サイトではおなじみの機能です。
Picture-in-Pictureは、基本的にはvideo要素(HTMLVideoElementオブジェクト)とJavaScript APIの組み合わせで動作します。Picture-in-Pictureを利用できるブラウザでは、DocumentオブジェクトとHTMLVideoElementオブジェクトにいくつかのプロパティ、メソッド、イベントが拡張され、それらを通じてミニウインドウの操作を行うようになっています。また、疑似属性も拡張されており、ミニウインドウにて再生中のもとのvideo要素外観をカスタマイズすることも可能です。
Picture-in-PictureのブラウザサポートはLimited availabilityとなっており、具体的にはFirefoxブラウザ(スマートフォン版を含む)で使用できません。
以降、いくつかのシンプルな例を通じてPicture-in-Pictureの基本的な使い方を紹介していきます。

基本的なPicture-in-Picture

Picture-in-Pictureの基本は、動画を再生するvideo要素と、その要素にPicture-in-Pictureモードへの移行をリクエストするメソッドです。ボタンなどにイベントハンドラを設定し、クリックでPicture-in-Pictureモードに移行するという流れになります(リスト1)。

リスト1:pip_basic.html

<video id="picture-in-picture" controls width="450px" height="300px">
  <source src="sample1.mp4">
</video>

<button id="start">Picture-in-Pictureをはじめる</button>

Picture-in-Pictureとは直接の関係はありませんが、ここで使われているvideo要素の属性は以下の通りです。

  • controls:停止や再生のコントロールパネルを表示する
  • width:動画の表示領域の幅
  • height:動画の表示領域の高さ

video要素のコンテンツとしてsource要素を配置し、そのsrc属性に動画ファイルへのパスを指定します(video要素そのものにsrc属性を配置しても可)。再生できる動画の形式の代表的なものはWebM、MP4などです。
JavaScriptコードはリスト2の通りです。

リスト2:pip_basic.js

if (!document.pictureInPictureEnabled) {    (1)
  alert('Picture-in-Pictureが利用可能ではありません。');
} else {
  const startButton = document.querySelector("#start");
  const pictureInPicture = document.querySelector("#picture-in-picture");

  startButton.addEventListener("click", async () => {
    await pictureInPicture.requestPictureInPicture();       (2)
  });
}

JavaScriptコードには、Picture-in-Pictureの利用において重要なプロパティおよびメソッドが2つ使われています。

  • (1)pictureInPictureEnabledプロパティ(Documentオブジェクト):ブラウザがPicture-in-Pictureをサポートする場合true
  • (2)requestPictureInPictureメソッド(HTMLVideoElementオブジェクト):指定するvideo要素(HTMLVideoElement)にPicture-on-Pictureモードへの移行をリクエストする

pictureInPictureEnabledプロパティがfalseとなる場合にはPicture-in-Pictureは利用できないので、requestPictureInPictureメソッドをはじめとするPicture-in-Picture関連の機能の呼び出しは全てエラーとなります。

[NOTE]disablepictureinpicture属性
video要素にdisablepictureinpicture属性を付与すると、その要素はPicture-in-Pictureを利用できなくなります。この状態でrequestPictureInPictureメソッドを呼び出すとエラーとなります。この属性が指定されているかは、video要素のdisablePictureInPictureプロパティでチェックできるので、厳密を期すなら要素についても利用の可否をチェックした方がよいでしょう。

HTMLを表示させると、動画のサムネールとボタンが表示されます。ボタンをクリックするとPicture-in-Pictureウィンドウが画面右下に表示され、ブラウザウインドウには「ピクチャー イン ピクチャーを再生しています」と表示されます(図2)。requestPictureInPictureメソッドには引数はなく、video要素のサイズに準じたものに自動的に調整されます。

  • 図2:基本的なPicture-in-Picture

    図2 基本的なPicture-in-Picture

Picture-in-Pictureウィンドウ右上のアイコンをクリックすると、Picture-in-Pictureモードを解除してブラウザ画面での再生に戻すことができます。

[NOTE]Picture-in-Pictureウィンドウなどの外観はブラウザ依存
Picture-in-Pictureウィンドウや、動画再生中のvideo要素などの外観はブラウザ依存です。本記事では、Google Chromeにおけるものを紹介しています。

メソッドによるPicture-in-Pictureモードの解除

アイコン操作によらずに、メソッドでPicture-in-Pictureモードを解除することができます(リスト3)。

リスト3:pip_restore.js

…略…
startButton.addEventListener("click", async () => {
  if (document.pictureInPictureElement) {   (1)
    await document.exitPictureInPicture();  (2)
  } else {
    await pictureInPicture.requestPictureInPicture();
  }
});
…略…

このJavaScriptコードにも、Picture-in-Pictureの利用において重要なプロパティおよびメソッドが2つ使われています。

  • (1)pictureInPictureElementプロパティ(Documentオブジェクト):Picture-in-Pictureウインドウが表示されている場合非null(Elementオブジェクト)
  • (2)exitPictureInPictureメソッド(Documentオブジェクト):Picture-in-Pictureウインドウを閉じる

Picture-in-Pictureウインドウが表示されていれば、DocumentオブジェクトのpictureInPictureElementプロパティが非nullとなるので、これにより処理を振り分けられます。非nullの場合はPicture-in-Pictureウインドウが表示されているということなので、exitPictureInPictureメソッドによりウインドウを閉じます。nullの場合はリクエストするということになります。
HTMLを表示させると、動画のサムネールとボタンが表示されるので、ボタンをクリックするとPicture-in-Pictureウィンドウが画面右下に表示されます。再度ボタンをクリックすると、Picture-in-Pictureウィンドウが閉じます。見た目としては「基本的なPicture-in-Picture」項と同じなので、スクリーンショットは割愛します。

video要素のスタイル変更

Picture-in-Pictureウインドウが表示されている間、ブラウザウインドウの動画は「ピクチャー イン ピクチャーを再生しています」と表示されて黒背景とされます。CSSの:picture-in-picture疑似要素を使うと、この表示をカスタマイズできます(リスト4)。

リスト4:pip_styled.css

:picture-in-picture {
  box-shadow: 10px 5px 5px red; /* オフセット10px+5px、長さ5px、色は赤 */
}

この例では、box-shadowプロパティを使ってPicture-in-Pictureウインドウ表示中のvideo要素に赤い影を付けています。もちろん、その他のCSSプロパティを指定して、サイズを変更したり、可視と不可視を切り替えたり、表示色を切り替えたりすることも可能です。
HTMLを表示させてボタンをクリックしてPicture-in-Pictureウィンドウを表示させると、video要素の該当部分には赤い影が表示されます(図3)。

  • 図4 video要素のスタイル変更

    図3 video要素のスタイル変更

Picture-in-Pictureイベントの捕捉

Picture-in-PictureによりHTMLVideoElementオブジェクトなどに幾つかのイベントが追加されており、それを捕捉することで以下の3つの状況で処理をカスタマイズできます。

  • Picture-in-Pictureモードへの移行(HTMLVideoElementオブジェクトのenterpictureinpictureイベント)
  • Picture-in-Pictureモードの解除(HTMLVideoElementオブジェクトのleavepictureinpictureイベント)
  • Picture-in-Pictureウインドウのサイズ変更(PictureInPictureWindowオブジェクトのresizeイベント)

これらのイベントを利用して、Picture-in-Pictureウインドウ操作ボタンの表示を切り替える例です(リスト4)。

リスト4:pip_event.js

…略…
const startButton = document.querySelector("#start");
const pictureInPicture = document.querySelector("#picture-in-picture");
…略…
// enterpictureinpictureイベント
pictureInPicture.addEventListener("enterpictureinpicture", () => {  (1)
  startButton.textContent = 'Picture-in-Pictureをやめる';
});

// leavepictureinpictureイベント
pictureInPicture.addEventListener("leavepictureinpicture", () => {  (2)
  startButton.textContent = 'Picture-in-Pictureをはじめる';
});
…略…

注目するのは(1)(2)のイベントハンドラの設定です。それぞれ、enterpictureinpictureイベントとleavepictureinpictureイベントに対応しています。処理内容は、ボタンのテキストを書き換えているのみです。
HTMLを表示させボタンをクリックすると、Picture-in-Pictureウィンドウが画面右下に表示されてボタンのテキストが変わります。再度ボタンをクリックすると、Picture-in-Pictureウィンドウが閉じてテキストも変わります(図4)。

  • 図5 Picture-in-Pictureイベントの捕捉

  • 図4 Picture-in-Pictureイベントの捕捉

    図4 Picture-in-Pictureイベントの捕捉

resizeイベントの例は特に示しませんでしたが、リサイズ中のログ記録、動画サイズの調整に利用できます(Chromeでは動画サイズは自動的に伸縮します)。

iframe要素のPicture-in-Picture

HTML標準としてのPicture-in-Pictureは、HTMLVideoElementの拡張であるため、video要素に利用が限定されています。しかしながら、audio要素やiframe要素など、音声や埋め込み動画をPicture-in-Pictureで再生したいというニーズを踏まえて、Google Chromeでは独自の拡張でこれを可能にしています(Chrome 111以降)。独自の拡張なので、利用方法はHTML標準のものと変わってきますが、参考として紹介します。
HTML標準と大きく違うのは、videoやaudioなどのメディア要素を、明示的にPicture-in-Pictureウインドウへ移動し、ウインドウ破棄時にはこれも明示的にブラウザウインドウに移動する処理が必要であるということです。このため、Picture-in-Pictureウインドウでの再生中は、ブラウザウインドウにはメディア要素は表示されません。
実際のコードで見てみます。まずはHTMLです(リスト5)。

リスト5:pip_iframe.html

<div id="pip-container">    (1)
  <iframe id="picture-in-picture" width="480px" height="330px"  (2)
  srcdoc='<video src="sample1.mp4" width="450px" height="300px" controls></video>'>
  </iframe>
</div>

Picture-in-Pictureウインドウ破棄でメディア要素をブラウザウインドウに戻す必要があるので、(1)のようにコンテナとなるdiv要素を設けています。(2)は、動画埋め込みのiframe要素です。移動のためにid属性を追加しています。
続けてJavaScriptコードです(リスト6)。HTML標準のPicture-in-Pictureを使わないので、ブラウザサポートのチェックはありません。

リスト6:pip_iframe.js

const startButton = document.querySelector("#start");
const pictureInPicture = document.querySelector("#picture-in-picture");

startButton.addEventListener("click", async () => {
  // Picture-in-Pictureウインドウの表示
  const pictureInPictureWindow = await documentPictureInPicture.requestWindow({ (1)
    width: pictureInPicture.clientWidth,
    height: pictureInPicture.clientHeight,
    copyStyleSheets: true,
  });
  pictureInPictureWindow.document.body.append(pictureInPicture);    (2)

  // Picture-in-Pictureウインドウの破棄
  pictureInPictureWindow.addEventListener("unload", (event) => {    (3)
    const pipContainer = document.querySelector("#pip-container");
    const pictureInPicture = event.target.querySelector("#picture-in-picture");
    pipContainer.append(pictureInPicture);
  });
});

大きく、Picture-in-Pictureウインドウの表示時と破棄時の処理に分かれています。
(1)はPicture-in-Pictureウインドウの表示で、Chromeが用意するdocumentPictureInPictureオブジェクトのrequestWindowメソッドにより、Picture-in-Pictureを表示します。引数のオブジェクトには、以下のプロパティを指定できます。

  • width:Picture-in-Pictureウインドウの幅(この場合はメディア要素の幅)
  • height:Picture-in-Pictureウインドウの高さ(この場合はメディア要素の高さ)
  • copyStyleSheets:メディア要素のスタイルをコピーするときtrue

Picture-in-Pictureウインドウはbody要素を持つので、(2)のようにメディア要素を追加します。この追加は実際には移動となり、ブラウザウインドウからは消失します。
(3)はPicture-in-Pictureウインドウの破棄時に発生するunloadイベントの処理です。行っていることは(2)の逆で、Picture-in-Pictureウインドウにあるメディア要素をブラウザウインドウのコンテナ要素に移動するだけです。
これにより、HTML標準の機能によらずに、Picture-in-Pictureを実現できます。HTMLを表示させてボタンをクリックすると、Picture-in-Pictureウィンドウが画面右下に表示されます。ウインドウ右上のアイコンをクリックすると、Picture-in-Pictureウィンドウが閉じます(図5)。

  • 図6:iframe要素のPicture-in-Picture

  • 図5 iframe要素のPicture-in-Picture

    図5 iframe要素のPicture-in-Picture

[NOTE]YouTube埋め込み動画の再生
ここでは、ローカルの動画ファイルをiframe要素のsrcdoc属性にvideo要素として指定しました。YouTube動画を再生したい場合には、シェア機能で埋め込みリンクを取得して、iframe要素を置き換えてください(id属性の追加を忘れずに)。ただし、2025年実施の利用規約改定で、YouTubeの埋め込み動画はPicture-in-Pictureウィンドウでの再生ができなくなっています。将来的に修正される可能性はありますが、現時点ではそのような状況である旨、ご理解ください。

まとめ

Picture-in-Pictureはいかがでしたでしょうか。動画プレイヤーでよく見かけるミニウインドウでの再生が、意外と簡単な方法で実現できるのをお伝えできたのではないかと思います。次回は、検索結果をブラウザ画面で強調表示できるCSS Custom Highlight APIを紹介します。

WINGSプロジェクト 山内直(著) 山田 祥寛(監修)
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。
RSS
X:@WingsPro_info(公式)@WingsPro_info/wings(メンバーリスト)
Facebook

<著者について>
WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。