2

I'm working on a chrome extension that uses the ytplayer object from a youtube page in my contentscript.js file. In the past the ytplayer object updates every time you navigate to a new youtube video. As of now, i don't see it updating any more.

How to replicate

  1. Go to a youtube video page in chrome (For example, https://www.youtube.com/watch?v=KUh2O8HylUM)
  2. Open the dev tools console
  3. Type in 'ytplayer.config.args' in the console
  4. Capture the ytplayer.config.args object
  5. Navigate to another youtube video by clicking on another video from the recommended list.
  6. Type in ytplayer.config.args again
  7. Compare to previous ytplayer.config.args you captured earlier

You'll see that the ytplayer doesn't update after navigating to the 2nd video. My extension uses this object. The problem is that the object is stale after navigating to another video. When i pull the ytplayer object after every video navigation, I present wrong data because the ytplayer is always from the first video.

Questions.

  1. Is there a way in to get the updated ytplayer?

  2. Is there a way to make the ytplayer reload? (I can add a location.reload() to my js code which fixes the issue but it's hacky and lame)

  3. Is there another way to get the ytplayer.conf.args data from a youtube video page in chrome?

4

1 回答 1

1

Youtube is a modern site that updates the page only partially using the data from the server. It means that there's only one complete page load during which ytplayer is constructed in an inline <script> tag. It also means your content script runs only once.

The proposed solution is to intercept the network communication of the site by overriding XHR open method in the page context to get the new ytplayer object.

manifest.json:

"content_scripts": [{
  "run_at": "document_start",
  "matches": ["https://www.youtube.com/*"],
  "js": ["content.js"]
}]

content.js:

const token = chrome.runtime.id + ':' + performance.now() + ':' + Math.random();

window.addEventListener(token, e => {
  console.log('gotPlayerArgs', e.detail);
  chrome.runtime.sendMessage({
    action: 'gotPlayerArgs',
    data: e.detail,
  });
});

const script = document.createElement('script');
script.textContent = '(' + (token => {
  const origOpen = XMLHttpRequest.prototype.open;
  const dispatch = data => window.dispatchEvent(new CustomEvent(token, {detail: data}));
  const onLoad = e => {
    const json = e.target.response;
    const player = (Array.isArray(json) && json.find(_ => _.player) || {}).player || {};
    dispatch(player.args);
  };
  // get the initial config
  try {
    dispatch(window.ytplayer.config.args);
  } catch (e) {}
  // intercept the subsequent config queries
  XMLHttpRequest.prototype.open = function (method, url) {
    if (url.startsWith('https://www.youtube.com/watch?')) {
      this.addEventListener('load', onLoad);
    }
    return origOpen.apply(this, arguments);
  };
}) + `)("${token}")`;
document.documentElement.appendChild(script);
script.remove();

One caveat still remains: if (1) your extension was updated/reloaded or disabled/re-enabled, and (2) the youtube page is not the first navigation, the initial config object will be wrong. To fix that you can compare the id of the video (extract it from the URL) with the id inside the config ("loaderUrl" property, for example), and if it doesn't match simply get the args via get_video_info endpoint, which is easy to parse (split by &, then by =, then use decodeURIComponent): 'https://www.youtube.com/get_video_info?video_id=' + id + '&hl=en_US&html5=1&el=embedded'

于 2019-02-10T06:41:49.197 回答