Fighting the Modern Web. We create an extension for downloading videos from VK

  • Permanent errors

  • Low frame rate at 720p and above

  • Graphic artifacts at 480p and below

I decided to use the fail-safe method of downloading videos and watching them locally. However, I could not find links to mp4 files in the sources of the desktop version of VK. A few years ago, it worked. Apparently now everything works through a more cunning streaming. Perhaps for the same reason, third-party services for downloading videos no longer work.

After spending a little time, I found out that links to files can be fished out of mobile version of VK. Pulling them out with your hands every time, confusing the quality of the video, turned out to be inconvenient. This is how the idea of ​​a browser extension was born: I would like that when you go to a page with any video, links to download it in any of the available qualities are displayed under the player.

Implementation

To start, let’s create manifest.json — a file containing information about the extension. In addition to the required fields, add the field content_scriptscontaining the following rule: when going to any page on the domain m.vk.com and fully load the DOM to execute the code in the file content-script.js. Such a code is called embedded script. A manifest can contain a huge variety of fields. Detailed documentation on this – Here.

// manifest.json

{
  "manifest_version": 3,
  "name": "VK Video Downloader",
  "version": "1.0.0",
  "permissions": ["activeTab"],
  "action": {},
  "content_scripts": [
    {
      "js": ["content-script.js"],
      "matches": ["https://m.vk.com/*"],
      "run_at": "document_idle"
    }
  ]
}
// content-script.js

alert('Hello, World!');
Download the extension to the browser.  We go to the desired URL.  We are satisfied with the result.
Download the extension to the browser. We go to the desired URL. We are satisfied with the result.

If we go to any video page and look at the HTML tree, we’ll see something like this:

<div class="VideoPage__video">
  <video class="vv_inline_video">
    <source src="https://vkvd97.mycdn.me/video.m3u8?..." type="application/vnd.apple.mpegurl">
    <source src="https://vkvd97.mycdn.me/...type=1&amp;..." type="video/mp4">
    <source src="https://vkvd97.mycdn.me/...type=0&amp;..." type="video/mp4">
    <source src="https://vkvd97.mycdn.me/...type=4&amp;..." type="video/mp4">
  </video>
</div>

We are interested in attribute values src tags <source>responsible for mp4 files. And I did not just mark the url parameter type in each of them. It is he who is responsible for the quality of the video. Having once downloaded all possible variations of one video, I deduced a correspondence table:

The value of the type parameter

Video quality

0

240p

1

360p

2

480p

3

720p

4

144p

5

1080p

Yes, 144p is out of the general logic and has type 4. Perhaps the echoes of some kind of legacy, or maybe I just don’t know something and someone will enlighten me in the comments 🙂

We implement a function that collects and returns the data we need in a format convenient for further processing:

// content-script.js

function getVideoSources() {
  const sourceTags = document.querySelectorAll(
    'video source[type="video/mp4"]'
  );
  let videoSources = {};

  for (const tag of sourceTags) {
    if (tag.src.includes('&type=4')) {
      videoSources['144p'] = tag.src;
    } else if (tag.src.includes('&type=0')) {
      videoSources['240p'] = tag.src;
    } else if (tag.src.includes('&type=1')) {
      videoSources['360p'] = tag.src;
    } else if (tag.src.includes('&type=2')) {
      videoSources['480p'] = tag.src;
    } else if (tag.src.includes('&type=3')) {
      videoSources['720p'] = tag.src;
    } else if (tag.src.includes('&type=5')) {
      videoSources['1080p'] = tag.src;
    }
  }

  return videoSources;
}

console.log(getVideoSources());

Now, reloading the extension and opening the page of any video, we will see something like this in the browser console (the number of fields will vary depending on the maximum quality of the video):

{
  "144p": "https://vkvd97.mycdn.me/...type=4&amp;...",
  "240p": "https://vkvd97.mycdn.me/...type=0&amp;...",
  "360p": "https://vkvd97.mycdn.me/...type=1&amp;..."
}

It would seem that the task is practically solved, but it was at this stage of development that I noticed a nuance that made me break my head.

When entering the URL of the page with the video in the address bar and pressing Enter, the script worked. When switching to a page from another VK page, it didn’t work, but once it was reloaded, it still worked.

As far as I was able to find out, the fact is that when navigating through VK, a full transition to a new page does not always occur. In many cases, the DOM is simply rebuilt, which is not a condition for the inline script to fire.

We’ll have to act differently. We will monitor DOM changes in real time and fetch data only if we are on the video page (the path starts from /video-) and noticed the appearance of a video player container in the document (it has the class VideoPage__video). Such a wonderful thing as MutationObserver. Also, it is worth considering that the container may not be drawn immediately. Therefore, when going to a page with a video, we will look for it every 100 ms until we find it.

// content-script.js

let lastUrl = location.href;

new MutationObserver(() => {
  if (location.href !== lastUrl) {
    lastUrl = location.href;
  }
  if (location.pathname.includes('/video-')) {
    const checker = setInterval(() => {
      if (
        document.querySelector('div.VideoPage__video')
      ) {
        clearInterval(checker);
        getVideoSources();
      }
    }, 100);
  }
}).observe(document, { subtree: true, childList: true });

function getVideoSources() {...}

Amazing. Now, as soon as the player appears on the page (no matter how you got to this page), the data we need is displayed in the console. Let’s implement the functionality for displaying them directly on the page, slightly adjusting them to the VK layout:

// content-script.js

function createDownloadPanel(videoSources) {
  const label = document.createElement('span');
  label.innerText="Скачать:";
  label.style.marginRight="2px";

  const panel = document.createElement('div');
  panel.id = 'vkVideoDownloaderPanel';
  panel.style.margin = '8px 12px';
  panel.appendChild(label);

  for (const [quality, url] of Object.entries(videoSources)) {
    const aTag = document.createElement('a');
    aTag.href = url;
    aTag.innerText = quality;
    aTag.style.margin = '0 2px';
    panel.appendChild(aTag);
  }

  return panel;
}

function showPanel(panel) {
  document.querySelector('div.VideoPage__video').after(panel);
}
Combining all the code above we get the following result
Combining all the code above we get the following result

A little later, I modified the extension a bit:

  • Added processing of videos embedded from third-party sites (in this case, the player container does not contain <video>A <iframe>)

  • Fixed bugs due to which the function responsible for drawing the panel for downloading the n-th number of times was idle, without drawing anything

  • Added icons

  • Supplemented manifest.json

Take a look at final version and you can use it by going to my github. Already received several thanks in the mail for posting this extension. It might be useful for you too.

Thank you for reading to the end.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *