We're digging through YouTube's code to get links to the original mp4s.
For those who want to get started quickly, the instructions are below.
If you open YouTube on your desktop, the video is loaded in parts by the webm stream.
On the video page (specifically the video), in ytInitialPlayerResponse.streamingData.formats, there is a data array that includes a link to a low-quality mp4 file in 360p. She doesn't work.
But it uses the URL stream from ytInitialPlayerResponse.streamingData.serverAbrStreamingUrl.
There is a list of all available formats (webm, mp4) in ytInitialPlayerResponse.streamingData.adaptiveFormats, but there is no URL.
All links in adaptiveFormats appear as soon as you visit a video page from your mobile. If you don't know, DevTools allows you to emulate different devices (change screen size and set a different UserAgent).
In older videos, the url is in the url field, but in relatively newer ones, it is in the signatureCipher field, which has the following format (separated by an ampersand):
s=EJE4JURJJKKLHFM75uutnjuyuyu8899oiooyioyGy-iyoAc6t6IIYOYOyoo...\nsp=sig\nurl=https://rr3.googlevideo.com/videoplayback?expire=...Adding &sig=<s> to url doesn't help because there's some manipulation in the signature:
Since developers can change the method with a single finger, there's no point in using these algorithms. They won't last long. I tested this while writing this note.
The main idea is to simply discard all webm files, keep the mp4 files, and feed them to the player.
But I still want to make fewer movements, and switching between desktop and mobile modes is tiring. What should I do?
If you try to share a video, it opens an iframe with the url https://www.youtube.com/embed/<video_id>. And there is a POST request to https://www.youtube.com/youtubei/v1/player, which contains all the links to everything.
This is the most important set of data that YouTube needs to provide video data:
{\n\tvideoId: <video_id>,\n\tcontext: {\n\t\tclient: <client_info>,\n\t\tthirdParty: {\n\t\t\t"embedUrl": "https://www.youtube.com/watch?v=" + <video_id>,\n\t\t},\n\t},\n\tplaybackContext: {\n\t\tcontentPlaybackContext: {\n\t\t\tsignatureTimestamp: 20486\n\t\t}\n\t}\n}We make a request, but there are no links. Wonders. You just need to change client_info.clientName to WEB_EMBEDDED_PLAYER, which is initially WEB.
I have the links, now all I have to do is feed the mp4 to the player.
First we filter:
if (e.mimeType.startsWith('video/mp4')) {\n\tmp4.push(e);\n}Updating the player config
window.ytplayer.config = {args: {raw_player_response: <new_video_info>}};Reset the player area #ytd-player > #container
player.innerHTML = '';We create a player with new data using the following steps:
createPlayer = window.yt && window.yt.player && window.yt.player.Application \n\t&& (window.yt.player.Application.createAlternate || window.yt.player.Application.create)All player configurations are available in ytcfg.data_.WEB_PLAYER_CONTEXT_CONFIGS, the one you need is WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH:
createPlayer(\n\tplayer,\n\twindow.ytplayer.config,\n\tytcfg.data_.WEB_PLAYER_CONTEXT_CONFIGS.WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH\n);Attention When using the mp4 player, quality switching does not work. To get each link, you need to recreate the player with the desired quality.
(function () {\n\tlet video_id = (new URLSearchParams(location.search)).get('v');\n\tif (!video_id)\n\t\tthrow new Error('video_id not found');\n\n\tlet client = ytcfg.data_.INNERTUBE_CONTEXT.client;\n\tclient.clientName = 'WEB_EMBEDDED_PLAYER';\n\n\tlet data = {\n\t\tvideoId: video_id,\n\t\tcontext: {\n\t\t\tclient: ytcfg.data_.INNERTUBE_CONTEXT.client,\n\t\t\tthirdParty: {\n\t\t\t\t'embedUrl': 'https://www.youtube.com/watch?v=' + video_id,\n\t\t\t},\n\t\t},\n\t\tplaybackContext: {\n\t\t\tcontentPlaybackContext: {\n\t\t\t\tsignatureTimestamp: 20486\n\t\t\t}\n\t\t}\n\t};\n\n\tfetch('/youtubei/v1/player', {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t},\n\t\tbody: JSON.stringify(data)\n\t}).then(function (response) {\n\t\tif (!response.ok)\n\t\t\tthrow new Error(`HTTP error: ${response.status}`);\n\n\t\treturn response.json();\n\t}).then(function (data) {\n\n\t\tlet mp4 = [];\n\t\tdata.streamingData.adaptiveFormats.forEach(e => {\n\t\t\tif (e.mimeType.startsWith('video/mp4')) {\n\t\t\t\tmp4.push(e);\n\t\t\t}\n\t\t});\n\t\tif (!mp4.length)\n\t\t\tthrow new Error('mp4 links not found');\n\n\t\tdata.streamingData.adaptiveFormats = mp4;\n\t\tdata.streamingData.formats = mp4;\n\t\twindow.ytplayer.config = {\n\t\t\targs: {\n\t\t\t\traw_player_response: data\n\t\t\t}\n\t\t};\n\n\t\tlet player = document.querySelector('#ytd-player > #container');\n\t\tplayer.innerText = '';\n\n\t\tlet createPlayer = window.yt && window.yt.player\n\t\t\t&& window.yt.player.Application\n\t\t\t&& (window.yt.player.Application.createAlternate || window.yt.player.Application.create);\n\n\t\tcreatePlayer(\n\t\t\tplayer,\n\t\t\twindow.ytplayer.config,\n\t\t\tytcfg.data_.WEB_PLAYER_CONTEXT_CONFIGS.WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH\n\t\t);\n\n\t\tlet link = document.createElement('a');\n\t\tlink.setAttribute('target', '_blank');\n\t\tlink.style.cssText = `\n\t\t\tposition: absolute;\n\t\t\tz-index: 1;\n\t\t\tbackground: #fff;\n\t\t\tcolor: #000;\n\t\t`;\n\t\tlink.innerText = 'download';\n\n\t\tlet video = document.querySelector('video');\n\t\tplayer.previousElementSibling && player.previousElementSibling.remove();\n\t\tif (video.getAttribute('src').startsWith('https://')) {\n\t\t\tlink.setAttribute('href', video.getAttribute('src'));\n\t\t\tplayer.before(link);\n\t\t}\n\n\t}).catch(error => {\n\t\tconsole.error('Fetch error:', error.message);\n\t});\n\n})();