Optimise YouTube stream function

This commit is contained in:
absidue 2021-12-06 17:28:37 +01:00
parent 5400d90221
commit ebff7a8578
3 changed files with 73 additions and 5 deletions

View File

@ -101,7 +101,7 @@ function deciper_signature(tokens: string[], signature: string) {
switch (token.slice(0, 2)) { switch (token.slice(0, 2)) {
case 'sw': case 'sw':
pos = parseInt(token.slice(2)); pos = parseInt(token.slice(2));
sig = swappositions(sig, pos); swappositions(sig, pos);
break; break;
case 'rv': case 'rv':
sig.reverse(); sig.reverse();
@ -122,13 +122,11 @@ function deciper_signature(tokens: string[], signature: string) {
* Function to swap positions in a array * Function to swap positions in a array
* @param array array * @param array array
* @param position position to switch with first element * @param position position to switch with first element
* @returns new array with swapped positions.
*/ */
function swappositions(array: string[], position: number) { function swappositions(array: string[], position: number) {
const first = array[0]; const first = array[0];
array[0] = array[position]; array[0] = array[position];
array[position] = first; array[position] = first;
return array;
} }
/** /**
* Sets Download url with some extra parameter * Sets Download url with some extra parameter

View File

@ -37,3 +37,10 @@ export interface InfoData {
video_details: YouTubeVideo; video_details: YouTubeVideo;
related_videos: string[]; related_videos: string[];
} }
export interface StreamInfoData {
LiveStreamData: LiveStreamData;
html5player: string;
format: Partial<formatData>[];
video_details: Pick<YouTubeVideo, 'url' | 'durationInSec'>;
}

View File

@ -2,7 +2,7 @@ import { ProxyOptions as Proxy, request } from './../../Request/index';
import { format_decipher } from './cipher'; import { format_decipher } from './cipher';
import { YouTubeVideo } from '../classes/Video'; import { YouTubeVideo } from '../classes/Video';
import { YouTubePlayList } from '../classes/Playlist'; import { YouTubePlayList } from '../classes/Playlist';
import { InfoData } from './constants'; import { InfoData, StreamInfoData } from './constants';
interface InfoOptions { interface InfoOptions {
proxy?: Proxy[]; proxy?: Proxy[];
@ -219,6 +219,69 @@ export async function video_basic_info(url: string, options: InfoOptions = {}):
related_videos: related related_videos: related
}; };
} }
/**
* Gets the data required for streaming from YouTube url, ID or html body data and deciphers it.
*
* Internal function used by {@link stream} instead of {@link video_info}
* because it only extracts the information required for streaming.
*
* @param url YouTube url or ID or html body data
* @param options Video Info Options
* - `Proxy[]` proxy : sends data through a proxy
* - `boolean` htmldata : given data is html data or not
* @returns Deciphered Video Info {@link StreamInfoData}.
*/
export async function video_stream_info(url: string, options: InfoOptions = {}): Promise<StreamInfoData> {
let body: string;
if (options.htmldata) {
body = url;
} else {
if (yt_validate(url) !== 'video') throw new Error('This is not a YouTube Watch URL');
const video_id: string = extractID(url);
const new_url = `https://www.youtube.com/watch?v=${video_id}&has_verified=1`;
body = await request(new_url, {
proxies: options.proxy ?? [],
headers: { 'accept-language': 'en-US,en;q=0.9' },
cookies: true
});
}
if (body.indexOf('Our systems have detected unusual traffic from your computer network.') !== -1)
throw new Error('Captcha page: YouTube has detected that you are a bot!');
const player_data = body
.split('var ytInitialPlayerResponse = ')?.[1]
?.split(';</script>')[0]
.split(/;\s*(var|const|let)/)[0];
if (!player_data) throw new Error('Initial Player Response Data is undefined.');
const player_response = JSON.parse(player_data);
if (player_response.playabilityStatus.status !== 'OK')
throw new Error(
`While getting info from url\n${
player_response.playabilityStatus.errorScreen.playerErrorMessageRenderer?.reason.simpleText ??
player_response.playabilityStatus.errorScreen.playerKavRenderer?.reason.simpleText
}`
);
const html5player = `https://www.youtube.com${body.split('"jsUrl":"')[1].split('"')[0]}`;
const duration = Number(player_response.videoDetails.lengthSeconds);
const video_details = {
url: `https://www.youtube.com/watch?v=${player_response.videoDetails.videoId}`,
durationInSec: (duration < 0 ? 0 : duration) || 0
};
const format = [];
format.push(...(player_response.streamingData.formats ?? []));
format.push(...(player_response.streamingData.adaptiveFormats ?? []));
const LiveStreamData = {
isLive: player_response.videoDetails.isLiveContent,
dashManifestUrl: player_response.streamingData?.dashManifestUrl ?? null,
hlsManifestUrl: player_response.streamingData?.hlsManifestUrl ?? null
};
return await decipher_info({
LiveStreamData,
html5player,
format,
video_details
});
}
/** /**
* Function to convert seconds to [hour : minutes : seconds] format * Function to convert seconds to [hour : minutes : seconds] format
* @param seconds seconds to convert * @param seconds seconds to convert
@ -276,7 +339,7 @@ export async function video_info(url: string, options: InfoOptions = {}): Promis
* @param data Data - {@link InfoData} * @param data Data - {@link InfoData}
* @returns Deciphered Video Info {@link InfoData} * @returns Deciphered Video Info {@link InfoData}
*/ */
export async function decipher_info(data: InfoData) { export async function decipher_info<T extends InfoData | StreamInfoData>(data: T): Promise<T> {
if (data.LiveStreamData.isLive === true && data.LiveStreamData.dashManifestUrl !== null) { if (data.LiveStreamData.isLive === true && data.LiveStreamData.dashManifestUrl !== null) {
return data; return data;
} else if (data.format[0].signatureCipher || data.format[0].cipher) { } else if (data.format[0].signatureCipher || data.format[0].cipher) {