52
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

YouTubeの動画広告をスキップする拡張機能を作ってみた

Last updated at Posted at 2020-06-17

はじめに

アウトプットの一環としてObserver系のAPIを使って何か拡張機能を作りたいと思っていたので、YouTubeの広告をスキップするChrome拡張機能を作り、Chromeウェブストアに公開しました。

作ったもの:YT Ad Skip

作業の流れ

拡張機能の公開までにやったことは大まかに3つです。

  1. 処理をJavaScriptで書く
  2. manifest.jsonを用意
  3. Chromeウェブストアに公開申請

1.処理をJavaScriptで書く

完成したコードがこちらです。

yt-ad-skip.js
{

  const hostName = location.hostname;
  const subDomain = hostName.split('.')[0];

  const newStyle = document.createElement('style');
  newStyle.innerText = '.ytp-ad-overlay-container{display:none!important;}';
  document.getElementsByTagName('head').item(0).appendChild(newStyle);

  const returnPlayerId = (v) => {
    switch (v) {
      case 'music':
        return 'player';
        break;
      case 'www':
      default:
        return 'ytd-player';
        break;
    }
  };

  const clickSkipButton = () => {
    const $skipButton = document.getElementsByClassName("ytp-ad-skip-button-container")[0];
    if( $skipButton ) $skipButton.click();
  };

  const obConfig = {
    childList: true,
    subtree: true
  };
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if( mutation.addedNodes.length && mutation.addedNodes[0].className === 'ytp-ad-player-overlay' ) clickSkipButton();
    });
  });

  const playerId = returnPlayerId(subDomain);
  const initInterval = setInterval(() => {
    if( document.getElementById(playerId) != null ) {
      const obTarget = document.getElementById(playerId);
      observer.observe(obTarget, obConfig);
      clearInterval(initInterval);
    }
    clickSkipButton();
  }, 1000);

};

コードの解説

サブドメインを取得

yt-ad-skip.js
const hostName = location.hostname;
const subDomain = hostName.split('.')[0];

「YouTube」と「YouTube Music」で処理を分岐させるため、サブドメインを取得しています。

オーバーレイ広告を非表示

yt-ad-skip.js
const newStyle = document.createElement('style');
newStyle.innerText = '.ytp-ad-overlay-container{display:none!important;}';
document.getElementsByTagName('head').item(0).appendChild(newStyle);

オーバーレイ広告を非表示にするCSSをhead要素に追加しています。
Observerを使うという趣旨から外れるのですが、とりあえずはCSSで対応しました。

監視対象のid名を返す処理を定義

yt-ad-skip.js
const returnPlayerId = (v) => {
  switch (v) {
    case 'music':
      return 'player';
      break;
    case 'www':
    default:
      return 'ytd-player';
      break;
  }
};

サブドメインで条件分岐させ、監視対象であるプレイヤーのid名を返します。

スキップボタンをクリックする処理を定義

yt-ad-skip.js
const clickSkipButton = () => {
  const $skipButton = document.getElementsByClassName("ytp-ad-skip-button-container")[0];
  if( $skipButton ) $skipButton.click();
};

広告の再生時、「広告をスキップ」ボタン(class名:ytp-ad-skip-button-container)が動的に生成されます。
スキップボタンが存在するときに要素をクリックするclickSkipButton()を定義します。

MutationObserverを設定

yt-ad-skip.js
const obConfig = {
  childList: true,  // 監視対象ノードの子ノードの追加・削除を監視します。
  subtree: true  // 監視対象ノードの子孫ノードも監視対象とします。
};

インスタンスメソッドobserve()に渡すオプションを設定します。

yt-ad-skip.js
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if( mutation.addedNodes.length && mutation.addedNodes[0].className === 'ytp-ad-player-overlay' ) clickSkipButton();
  });
});

MutationObserverをインスタンス化。
Callbackとして、スキップボタンのラッパー(class名:ytp-ad-player-overlay)が追加されたときclickSkipButton()を実行します。

ポーリングを用意

今回の場合、MutationObserverでの監視対象はYouTube側が動的に生成している要素です。
場合によっては要素が生成される前にMutationObserverが実行される(拡張機能が実行される)ことがあるため、その対応としてポーリングを用意しています。
※拡張機能が実行されるタイミングは自分で指定できたりするんですが、その辺は後述します。

yt-ad-skip.js
const playerId = returnPlayerId(subDomain);
const initInterval = setInterval(() => {
  if( document.getElementById(playerId) != null ) {
    const obTarget = document.getElementById(playerId);
    observer.observe(obTarget, obConfig);
    clearInterval(initInterval);
  }
  clickSkipButton();
}, 1000);

監視対象のid名を変数に入れ、setIntervalで1秒毎にclickSkipButton()を繰り返し実行すると同時に、監視対象の存在をチェックしています。
監視対象が存在した場合Observerを実行し監視開始、ポーリングをストップします。

2.manifest.jsonを用意する

これまでに記述した処理をブラウザに拡張機能として認識させるためにmanifest.jsonを用意します。
処理を記述したJavaScriptと同じディレクトリに作成します。

manifest.json
{
  "manifest_version": 2,  // マニフェストファイルのバージョン(現在有効なバージョンは2のみ)
  "name": "YT Ad Skip",
  "version": "1.3.0",
  "description": "This plugin will skip YouTube ads.",
  "icons": {
    "16": "icon16.png",
    "48": "icon48.png",
    "128": "icon128.png"
  },
  "content_scripts": [{
    "matches": ["https://*.youtube.com/*"],  // 拡張機能を実行するサイトを設定
    "js": ["yt-ad-skip.js"]  // JavaScriptファイルのパスを指定
  }]
}

必須項目はmanifest_versionnameversionのみです。
その他は適宜記述します。

拡張機能が実行されるタイミングについて

拡張機能が実行されるタイミングを説明する前提として、YouTubeでの読み込みフローは以下のようになっています。

  1. HTMLのロード
  2. HTMLをパース
  3. JavaScriptをロード
  4. JavaScriptをパース・実行
  5. CSSをロード
  6. CSSをパース
  7. CSSOMを構築
  8. 2-2 で実行されたJavaScriptが要素を追加~~、DOMを再構築~~
  9. DOMの構築(DOMContentLoaded)
  10. 画像やフレームなどを読み込み
  11. ページの読み込み完了(load)

※便宜上上記の順番付けをしていますが、実際には各ステップが同時に実行されたり、前ステップの途中で次のステップが実行されたりします。

実行するタイミングを指定する

拡張機能が実行されるタイミングを指定するには2つの方法があります。

  1. JavaScriptで指定
  2. manifest.jsonで指定

1.JavaScriptで指定

DOMContentLoadedやload、onreadystatechange等のイベントによってタイミングを指定します。

2.manifest.jsonで指定

manifest.jsonrun_atプロパティでタイミングを指定します。

manifest.json(記述例)
{
  "content_scripts": [{
    "run_at": "document_idle",
  }]
}

run_atプロパティに設定できる値は以下のとおりです。

設定値 説明 実行タイミング 備考
document_idle ブラウザが、document_endからwindow.onloadイベントが発生した直後までの間に拡張機能を実行するタイミングを選択します。
拡張機能を実行する正確なタイミングは、ドキュメントの複雑さと読み込みにかかる時間に依存し、ページの読み込み速度に合わせて最適化されます。

document_idleで実行されるコンテンツ拡張機能は、window.onloadイベントを待機する必要はなく、DOMが完了した後に実行されることが保証されています。window.onloadの後に拡張機能を実行する必要がある場合、拡張機能はdocument.readyStateプロパティを使用して、onloadが既に発生しているかどうかをチェックすることができます。
46の間で実行されます。 初期値/推奨
document_start 拡張機能はCSSファイルの後に実行されますが、他のDOMが構築されたり他のスクリプトが実行される前に実行されます。 2-5の直後に実行されます。 -
document_end 拡張機能は、DOMが完成した直後に実行されますが、画像やフレームなどのサブリソースがロードされる前に実行されます。 45の間で実行されます。 -

実際に実行されるタイミング

前述のように拡張機能が実行されるタイミングを指定しても、動的な要素が生成されてから実行されるという保証はありません。
YouTubeの読み込みフローでいう6の後に要素が生成されることもあり、それはloadイベントやmanifest.jsonだけでは対応できません。
そのため、ポーリングを用意して監視対象の存在をチェックする必要があります。

3.Chromeウェブストアに公開申請

JavaScriptとmanifest.jsonの他にアイコン画像を用意したら、あとは公開申請を出すだけです。
細かい手順はこちらを参考にしました。

  1. 開発者アカウントを作成($5支払う必要があります)
  2. 必要ファイルをzipにして開発者アカウントのダッシュボードからアップロード
  3. 拡張機能の名前や説明を入力して申請

審査が通ればChromeウェブストアに自作の拡張機能が追加されます。

作ってみて所感

巷の拡張機能を見て「作った人すげぇ」とか思ってましたが、公開するだけなら意外と簡単でした。
技術的に悩んだところもあり、拡張機能が実行されるタイミングの部分はレンダリングについても理解する必要があって、色々と調べてみて知見が広がりました。
Observer系のアウトプットとしてはじめましたが、拡張機能の作り方についても学ぶことができプラスになったと思います。

参考URL

52
51
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
52
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?