Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to extract nCode from base.js #1301

Open
prateek-chaubey opened this issue Jul 11, 2024 · 51 comments
Open

Unable to extract nCode from base.js #1301

prateek-chaubey opened this issue Jul 11, 2024 · 51 comments

Comments

@prateek-chaubey
Copy link

Since last evening the ytdl isn't working, it's showing all the formats and video info , but the the video urls aren't working , i checked the code and it seems like after YouTube got updated , the ncode extraction isn't working.

@gatecrasher777
Copy link
Contributor

gatecrasher777 commented Jul 11, 2024

Quick fix is to change

let functionName = utils.between(body, `&&(b=a.get("n"))&&(b=`, `(b)`);
to
let functionName = utils.between(body, `&&(b=a.get("n"))&&(b=`, `(b)`) || utils.between(body, `&&(b=String.fromCharCode(110),c=a.get(b))&&(c=`, `(c)`);

At line 58 in lib/sig.js

@prateek-chaubey
Copy link
Author

Thanks it saved my day

@kumarsatish1984
Copy link

I have update the code as described in the thread

let functionName = utils.between(body, &&(b=a.get("n"))&&(b=, (b));
to
let functionName = utils.between(body, &&(b=a.get("n"))&&(b=, (b)) || utils.between(body, &&(b=String.fromCharCode(110),c=a.get(b))&&(c=, (c));

At line 58 in lib/sig.js. But still I am getting error with code: 403.

@gatecrasher777
Copy link
Contributor

I have update the code as described in the thread

let functionName = utils.between(body, &&(b=a.get("n"))&&(b=, (b)); to let functionName = utils.between(body, &&(b=a.get("n"))&&(b=, (b)) || utils.between(body, &&(b=String.fromCharCode(110),c=a.get(b))&&(c=, (c));

At line 58 in lib/sig.js. But still I am getting error with code: 403.

That is issue #1295 and I think it is specific to blocking direct watch URL requests.

In my projects I use the innertube API and have not experienced any 403 messages.

@StepsOnes
Copy link

Quick fix is to change

let functionName = utils.between(body, `&&(b=a.get("n"))&&(b=`, `(b)`); to let functionName = utils.between(body, `&&(b=a.get("n"))&&(b=`, `(b)`) || utils.between(body, `&&(b=String.fromCharCode(110),c=a.get(b))&&(c=`, `(c)`);

At line 58 in lib/sig.js

thank u, bro

@fpsone
Copy link

fpsone commented Jul 14, 2024

Solutions are discuss in #1295

@corwin-of-amber
Copy link

corwin-of-amber commented Jul 27, 2024

Instead of b=String.fromCharCode(110), I now see in the player source, b="nn"[+a.D].
Looks like some obfuscation technique. I wonder if this would be better.

    let functionName = utils.between(body, 'c=a.get(b))&&(c=', '(c)');

#1305 (comment)

@hextor1
Copy link

hextor1 commented Aug 1, 2024

Instead of b=String.fromCharCode(110), I now see in the player source, b="nn"[+a.D]. Looks like some obfuscation technique. I wonder if this would be better.

    let functionName = utils.between(body, 'c=a.get(b))&&(c=', '(c)');

#1305 (comment)

Again not working

@corwin-of-amber
Copy link

It seems that they keep changing it. Now the expression looks like that:

   ...   b=a.j.n||null)&&(b=oDa[0](b),  ...

So, probably some kind of a more semantic approach should be devised.

@StepsOnes
Copy link

StepsOnes commented Aug 1, 2024

like this ?

let functionName = utils.between(body, 'b=a.j.n||null)&&(b=oDa[0]', '(b)');

@corwin-of-amber
Copy link

Without the oDa[0]. But this is still syntactic and if YouTube are going to start changing it periodically from now on, it's going to keep breaking every time.

@hextor1
Copy link

hextor1 commented Aug 1, 2024

Please write the full code here again:- @corwin-of-amber

Without the oDa[0]. But this is still syntactic and if YouTube are going to start changing it periodically from now on, it's going to keep breaking every time.

@StepsOnes
Copy link

StepsOnes commented Aug 1, 2024

Please write the full code here again:- @corwin-of-amber

Without the oDa[0]. But this is still syntactic and if YouTube are going to start changing it periodically from now on, it's going to keep breaking every time.

this is working
let functionName = utils.between(body, 'b=a.j.n||null)&&(b=', '(b)');

@hextor1
Copy link

hextor1 commented Aug 1, 2024

Hello @StepsOnes This one not working Here is my code Please check it if I am wrong share with correct code.

const extractNCode = () => {
let functionName = utils.between(body, 'b=a.j.n||null)&&(b=', '(b)');
if (functionName.includes('[')) functionName = utils.between(body, var ${functionName.split('[')[0]}=[, ]);
if (functionName && functionName.length) {
const functionStart = ${functionName}=function(a);
const ndx = body.indexOf(functionStart);
if (ndx >= 0) {
const subBody = body.slice(ndx + functionStart.length);
const functionBody = var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);;
functions.push(functionBody);
}
}
};

@StepsOnes
Copy link

Hello @StepsOnes This one not working Here is my code Please check it if I am wrong share with correct code.

const extractNCode = () => { let functionName = utils.between(body, 'b=a.j.n||null)&&(b=', '(b)'); if (functionName.includes('[')) functionName = utils.between(body, var ${functionName.split('[')[0]}=[, ]); if (functionName && functionName.length) { const functionStart = ${functionName}=function(a); const ndx = body.indexOf(functionStart); if (ndx >= 0) { const subBody = body.slice(ndx + functionStart.length); const functionBody = var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);; functions.push(functionBody); } } };

It's working for me

const extractNCode = () => {
    let functionName = utils.between(body, 'b=a.j.n||null)&&(b=', '(b)');
    if (functionName.includes('[')) functionName = utils.between(body, `var ${functionName.split('[')[0]}=[`, `]`);
    if (functionName && functionName.length) {
      const functionStart = `${functionName}=function(a)`;
      const ndx = body.indexOf(functionStart);
      if (ndx >= 0) {
        const subBody = body.slice(ndx + functionStart.length);
        const functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);`;
        functions.push(functionBody);
      }
    }
  };

@hextor1
Copy link

hextor1 commented Aug 1, 2024

I am checking it. it's not working properly. I think YouTube is asking for a captcha to download and convert video. @StepsOnes

@gatecrasher777
Copy link
Contributor

gatecrasher777 commented Aug 2, 2024

Rather than trying to find the obfuscated n function name from elsewhere in the player code, this looks for the n code function directly.

 const extractNCode = () => {
    const alphanum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTVUWXYZ.$_0123456789';
    let functionName = '';
    let clue = body.indexOf('enhanced_except');
    if (clue < 0) clue = body.indexOf('String.prototype.split.call(a,"")');
    if (clue < 0) clue = body.indexOf('Array.prototype.join.call(b,"")');
    if (clue > 0) {
        let nstart = body.lastIndexOf('=function(a){', clue) - 1;
        while (nstart && alphanum.includes(body.charAt(nstart))) {
	    functionName = body.charAt(nstart) + functionName;
	    nstart--;
        }
    }
    if (functionName && functionName.length) {
      const functionStart = `${functionName}=function(a)`;
      const ndx = body.indexOf(functionStart);
      if (ndx >= 0) {
        const subBody = body.slice(ndx + functionStart.length);
        const functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);`;
        functions.push(functionBody);
      }
    }
  };

@hextor1
Copy link

hextor1 commented Aug 2, 2024

const extractNCode = () => {
const alphanum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTVUWXYZ.$_0123456789';
let functionName = '';
let clue = body.indexOf('enhanced_except');
if (clue < 0) clue = body.indexOf('String.prototype.split.call(a,"")');
if (clue < 0) clue = body.indexOf('Array.prototype.join.call(b,"")');
if (clue > 0) {
let nstart = body.lastIndexOf('=function(a){', clue) - 1;
while (nstart && alphanum.includes(body.charAt(nstart))) {
functionName = body.charAt(nstart) + functionName;
nstart--;
}
if (functionName && functionName.length) {
const functionStart = ${functionName}=function(a);
const ndx = body.indexOf(functionStart);
if (ndx >= 0) {
const subBody = body.slice(ndx + functionStart.length);
const functionBody = var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);;
functions.push(functionBody);
}
}
};

Can i use this code?

@gatecrasher777
Copy link
Contributor

gatecrasher777 commented Aug 2, 2024

Can i use this code?

Replaces lines 57-69 in lib/sig.js

Doesn't fix 403 issues, though, which is another YouTube barrier entirely.

@hextor1
Copy link

hextor1 commented Aug 2, 2024

Can i use this code?

Replaces lines 57-69 in lib/sig.js

Doesn't fix 403 issues, though, which is another YouTube barrier entirely.

Showing 502 bad gateway error

@gatecrasher777
Copy link
Contributor

Showing 502 bad gateway error

A bracket was missing. Please try again.

@hextor1
Copy link

hextor1 commented Aug 2, 2024

Showing 502 bad gateway error

A bracket was missing. Please try again.

Its working very slow sometime not fetch data. Any better solution?

@gatecrasher777
Copy link
Contributor

Its working very slow sometime not fetch data. Any better solution?

YouTube now requires POST requests with a specific byte array payload in order to download longer high quality files. For me, this is still a work in progress. Maybe someone else has found a solution.

@hextor1
Copy link

hextor1 commented Aug 2, 2024

But do you have any code-level solution where I can change my API code? Its better than previous code.

@StepsOnes
Copy link

it doesn't work again

@gatecrasher777
Copy link
Contributor

gatecrasher777 commented Aug 7, 2024

it doesn't work again

let functionName = utils.between(body, 'b=a.j.n||null)&&(b=', '(b)');
is now required to find

c=a.j[b]||null)&&(c=zDa[0](c);

which it wont. And it will keep breaking time and time again. That is why it is better to locate the function directly. The method I posted above still works.

@StepsOnes
Copy link

it doesn't work again

let functionName = utils.between(body, 'b=a.j.n||null)&&(b=', '(b)'); is now required to find

c=a.j[b]||null)&&(c=zDa[0](c);

which it wont. And it will keep breaking time and time again. That is why it is better to locate the function directly. The method I posted above still works.

image image

I tried your function, it doesn't work

@gatecrasher777
Copy link
Contributor

gatecrasher777 commented Aug 7, 2024

I tried your function, it doesn't work

Downloaded current version
Replaced the extractNcode function in sig.js
Added console.log(functionName); before if (functionName && functionName.length) {
When I run the quick example from the project page

const fs = require('fs');
const ytdl = require('./lib/index.js');
ytdl('http://www.youtube.com/watch?v=aqz-KE-bpKQ') .pipe(fs.createWriteStream('video.mp4'));

The output is

C:\Dev\test_nyc>node app
Ema

and in the directory listing:

2024/08/07  13:38       125 829 121 video.mp4

So yes, my function works.

When I use the videoId (4jnaYhnmYlo) in your example:

2024/08/07  13:56        58 553 785 video.mp4

@hextor1
Copy link

hextor1 commented Aug 8, 2024

I tried your function, it doesn't work

Downloaded current version Replaced the extractNcode function in sig.js Added console.log(functionName); before if (functionName && functionName.length) { When I run the quick example from the project page

const fs = require('fs');
const ytdl = require('./lib/index.js');
ytdl('http://www.youtube.com/watch?v=aqz-KE-bpKQ') .pipe(fs.createWriteStream('video.mp4'));

The output is

C:\Dev\test_nyc>node app
Ema

and in the directory listing:

2024/08/07  13:38       125 829 121 video.mp4

So yes, my function works.

When I use the videoId (4jnaYhnmYlo) in your example:

2024/08/07  13:56        58 553 785 video.mp4

Still not working youtube again fix it

@JoaoEmanuell
Copy link

Rather than trying to find the obfuscated n function name from elsewhere in the player code, this looks for the n code function directly.

 const extractNCode = () => {
    const alphanum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTVUWXYZ.$_0123456789';
    let functionName = '';
    let clue = body.indexOf('enhanced_except');
    if (clue < 0) clue = body.indexOf('String.prototype.split.call(a,"")');
    if (clue < 0) clue = body.indexOf('Array.prototype.join.call(b,"")');
    if (clue > 0) {
        let nstart = body.lastIndexOf('=function(a){', clue) - 1;
        while (nstart && alphanum.includes(body.charAt(nstart))) {
	    functionName = body.charAt(nstart) + functionName;
	    nstart--;
        }
    }
    if (functionName && functionName.length) {
      const functionStart = `${functionName}=function(a)`;
      const ndx = body.indexOf(functionStart);
      if (ndx >= 0) {
        const subBody = body.slice(ndx + functionStart.length);
        const functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);`;
        functions.push(functionBody);
      }
    }
  };

This work for me, but i modified the functionBody to: var ${functionStart}${utils.cutAfterJS(subBody)};return ${functionName}(ncode);;

Thanks for the solution.

@hextor1
Copy link

hextor1 commented Aug 8, 2024

Rather than trying to find the obfuscated n function name from elsewhere in the player code, this looks for the n code function directly.

 const extractNCode = () => {
    const alphanum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTVUWXYZ.$_0123456789';
    let functionName = '';
    let clue = body.indexOf('enhanced_except');
    if (clue < 0) clue = body.indexOf('String.prototype.split.call(a,"")');
    if (clue < 0) clue = body.indexOf('Array.prototype.join.call(b,"")');
    if (clue > 0) {
        let nstart = body.lastIndexOf('=function(a){', clue) - 1;
        while (nstart && alphanum.includes(body.charAt(nstart))) {
	    functionName = body.charAt(nstart) + functionName;
	    nstart--;
        }
    }
    if (functionName && functionName.length) {
      const functionStart = `${functionName}=function(a)`;
      const ndx = body.indexOf(functionStart);
      if (ndx >= 0) {
        const subBody = body.slice(ndx + functionStart.length);
        const functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);`;
        functions.push(functionBody);
      }
    }
  };

This work for me, but i modified the functionBody to: var ${functionStart}${utils.cutAfterJS(subBody)};return ${functionName}(ncode);;

Thanks for the solution.

Not working i am trying still got an error

@JoaoEmanuell
Copy link

Rather than trying to find the obfuscated n function name from elsewhere in the player code, this looks for the n code function directly.

 const extractNCode = () => {
    const alphanum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTVUWXYZ.$_0123456789';
    let functionName = '';
    let clue = body.indexOf('enhanced_except');
    if (clue < 0) clue = body.indexOf('String.prototype.split.call(a,"")');
    if (clue < 0) clue = body.indexOf('Array.prototype.join.call(b,"")');
    if (clue > 0) {
        let nstart = body.lastIndexOf('=function(a){', clue) - 1;
        while (nstart && alphanum.includes(body.charAt(nstart))) {
	    functionName = body.charAt(nstart) + functionName;
	    nstart--;
        }
    }
    if (functionName && functionName.length) {
      const functionStart = `${functionName}=function(a)`;
      const ndx = body.indexOf(functionStart);
      if (ndx >= 0) {
        const subBody = body.slice(ndx + functionStart.length);
        const functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);`;
        functions.push(functionBody);
      }
    }
  };

This work for me, but i modified the functionBody to: var ${functionStart}${utils.cutAfterJS(subBody)};return ${functionName}(ncode);;
Thanks for the solution.

Not working i am trying still got an error

What is the error that is presented to you? An 403?

@hextor1
Copy link

hextor1 commented Aug 9, 2024

Yes showing 403 error progress bar not working still stuck on progress bar

@hextor1
Copy link

hextor1 commented Aug 9, 2024

Here is code which you provided See if i do mistake let me know @JoaoEmanuell

const extractNCode = () => {
const alphanum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTVUWXYZ.$_0123456789';
let functionName = '';
let clue = body.indexOf('enhanced_except');
if (clue < 0) clue = body.indexOf('String.prototype.split.call(a,"")');
if (clue < 0) clue = body.indexOf('Array.prototype.join.call(b,"")');
if (clue > 0) {
let nstart = body.lastIndexOf('=function(a){', clue) - 1;
while (nstart && alphanum.includes(body.charAt(nstart))) {
functionName = body.charAt(nstart) + functionName;
nstart--;
}
}
if (functionName && functionName.length) {
const functionStart = ${functionName}=function(a);
const ndx = body.indexOf(functionStart);
if (ndx >= 0) {
const subBody = body.slice(ndx + functionStart.length);
const functionBody = var ${functionStart}${utils.cutAfterJS(subBody)};return ${functionName}(ncode);;
functions.push(functionBody);
}
}
};

Please see i got stuck on progress bar
image

@corwin-of-amber
Copy link

I did notice that sending the Range header (e.g. Range: bytes=0-10485760) is now mandatory. Without it, 403 is returned. This was not like that before.

@hextor1
Copy link

hextor1 commented Aug 9, 2024

I did notice that sending the Range header (e.g. Range: bytes=0-10485760) is now mandatory. Without it, 403 is returned. This was not like that before.

So what should we do now? @corwin-of-amber

@corwin-of-amber
Copy link

corwin-of-amber commented Aug 9, 2024

I am not sure as it does not seem like node-ytdl-core is even maintained. I am trying to trace what https://github.com/yt-dlp/yt-dlp is doing. Their approach seems to be robust, and they have cached ncode functions (probably hosted somewhere).

@JoaoEmanuell
Copy link

I really don't understand why this isn't working, but distube works fine.

@corwin-of-amber
Copy link

I really don't understand why this isn't working, but distube works fine.

Yes, these guys are using the player API, which is also what yt-dlp is doing. Probably a more stable approach, so perhaps we should just declare distubejs as the "canonical" YouTube downloader for Node.js.

@hextor1
Copy link

hextor1 commented Aug 10, 2024

This library is f*****ed up last 1 month not working. i will not recommended

@TechBroCode
Copy link

Its working very slow sometime not fetch data. Any better solution?

YouTube now requires POST requests with a specific byte array payload in order to download longer high quality files. For me, this is still a work in progress. Maybe someone else has found a solution.

I'm currently working on the same thing too

@TechBroCode
Copy link

I really don't understand why this isn't working, but distube works fine.

Hmm... Were you able to download and watch the video? If so, please share your code. I tried it few days ago but urls returned 403 error

@JoaoEmanuell
Copy link

I really don't understand why this isn't working, but distube works fine.

Hmm... Were you able to download and watch the video? If so, please share your code. I tried it few days ago but urls returned 403 error

My code is a adaptation of react-native-ytdl.

I tested the code in node, but not work (403 error), however the code works in react native, i really don't know why this occurs.

My sig code

Tip: change the "Logger.debug" for "console.log"

In the future, i go adapt the distube to react native, when the node-ytdl-core doesnt's work anymore.

@TechBroCode
Copy link

I really don't understand why this isn't working, but distube works fine.

Hmm... Were you able to download and watch the video? If so, please share your code. I tried it few days ago but urls returned 403 error

My code is a adaptation of react-native-ytdl.

I tested the code in node, but not work (403 error), however the code works in react native, i really don't know why this occurs.

My sig code

Tip: change the "Logger.debug" for "console.log"

In the future, i go adapt the distube to react native, when the node-ytdl-core doesnt's work anymore.

Okay, good. Are you currently making use of ytdl-core or distube ?

@JoaoEmanuell
Copy link

I really don't understand why this isn't working, but distube works fine.

Hmm... Were you able to download and watch the video? If so, please share your code. I tried it few days ago but urls returned 403 error

My code is a adaptation of react-native-ytdl.
I tested the code in node, but not work (403 error), however the code works in react native, i really don't know why this occurs.
My sig code
Tip: change the "Logger.debug" for "console.log"
In the future, i go adapt the distube to react native, when the node-ytdl-core doesnt's work anymore.

Okay, good. Are you currently making use of ytdl-core or distube ?

As a base I'm using ytdl-core but a large part of the sig (like the part of extracting functions) was adapted from distube, I combined part of the two to make it work in react native.

@VintaGist
Copy link

Doesn't fix 403 issues, though, which is another YouTube barrier entirely.

One of the variants to bypass 403 is downloading in chunks.
I use this variant with distube and its worked for me. As example - videoFile with 260Mb+ called 403, loading with chunks bypasses this limitation/error/etc.

.... in cycle ...
const stream = ytdl(url, { quality: "highestvideo", range: { start: start, end: start + chunkSize - 1 }, }); 
....

@TechBroCode
Copy link

Doesn't fix 403 issues, though, which is another YouTube barrier entirely.

One of the variants to bypass 403 is downloading in chunks. I use this variant with distube and its worked for me. As example - videoFile with 260Mb+ called 403, loading with chunks bypasses this limitation/error/etc.

.... in cycle ...
const stream = ytdl(url, { quality: "highestvideo", range: { start: start, end: start + chunkSize - 1 }, }); 
....

Are you able to stream the downloadable video url on a browser?

@VintaGist
Copy link

VintaGist commented Aug 19, 2024

Are you able to stream the downloadable video url on a browser?

Yes, of course.

This a example of code:

index.js (start with node index.js) :

const express = require("express");
const ytdl = require("@distube/ytdl-core");
const app = express();

app.get("/video", async (req, res) => {
    const url = "https://www.youtube.com/watch?v=ckIQOTduNbI";
    const range = req.headers.range;

    if (!range) {
        return res.status(400).send("Requires Range header");
    }

    try {
        const videoInfo = await ytdl.getInfo(url);
        const formats = videoInfo.formats.filter((e) => e.hasVideo && e.contentLength);
        const videoFormat = ytdl.chooseFormat(formats, { quality: "highest" });
        const videoSize = parseInt(videoFormat.contentLength);

        const chunkSize = 10 * 1024 * 1024; // 10MB
        const start = Number(range.replace(/\D/g, ""));
        const end = Math.min(start + chunkSize - 1, videoSize - 1);

        const contentLength = end - start + 1;
        const headers = {
            "Content-Range": `bytes ${start}-${end}/${videoSize}`,
            "Accept-Ranges": "bytes",
            "Content-Length": contentLength,
            "Content-Type": "video/mp4",
        };

        res.writeHead(206, headers);

        const videoStream = ytdl(url, {
            quality: "highest",
            filter: (e) => e.hasVideo && e.contentLength,
            range: { start, end },
        });

        videoStream.on("error", (err) => console.error(err));
        videoStream.on("progress", (chunkLength, downloaded, total) => {
            console.log(`${chunkLength} / ${((downloaded / total) * 100).toFixed(2)}% loaded`);
        });
        videoStream.pipe(res).on("finish", () => {
            console.log("Streaming finished");
        });
    } catch (error) {
        console.error("Error fetching video:", error);
        res.status(500).send("Error fetching video");
    }
});

app.listen(3000, () => {
    console.log("Server is running on port 3000");
});

index.html:

<!DOCTYPE html>
<html lang="uk">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Video Stream</title>
        <style>
            .centered {
                margin-left: auto;
                margin-right: auto;
                margin-top: 1em;
                width: 720px;
            }
        </style>
    </head>
    <body>
        <div class="centered">
            <video id="videoPlayer" width="720" controls autoplay>
                <source src="http://localhost:3000/video" type="video/mp4" />
                Your browser does not support the video tag. Please try again with a modern browser.
            </video>
        </div>
    </body>
</html>

This code works great. However, keep in mind that the audio might be separate.

@TechBroCode
Copy link

Are you able to stream the downloadable video url on a browser?

Yes, of course.

This a example of code:

index.js (start with node index.js) :

const express = require("express");
const ytdl = require("@distube/ytdl-core");
const app = express();

app.get("/video", async (req, res) => {
    const url = "https://www.youtube.com/watch?v=ckIQOTduNbI";
    const range = req.headers.range;

    if (!range) {
        return res.status(400).send("Requires Range header");
    }

    try {
        const videoInfo = await ytdl.getInfo(url);
        const formats = videoInfo.formats.filter((e) => e.hasVideo && e.contentLength);
        const videoFormat = ytdl.chooseFormat(formats, { quality: "highest" });
        const videoSize = parseInt(videoFormat.contentLength);

        const chunkSize = 10 * 1024 * 1024; // 10MB
        const start = Number(range.replace(/\D/g, ""));
        const end = Math.min(start + chunkSize - 1, videoSize - 1);

        const contentLength = end - start + 1;
        const headers = {
            "Content-Range": `bytes ${start}-${end}/${videoSize}`,
            "Accept-Ranges": "bytes",
            "Content-Length": contentLength,
            "Content-Type": "video/mp4",
        };

        res.writeHead(206, headers);

        const videoStream = ytdl(url, {
            quality: "highest",
            filter: (e) => e.hasVideo && e.contentLength,
            range: { start, end },
        });

        videoStream.on("error", (err) => console.error(err));
        videoStream.on("progress", (chunkLength, downloaded, total) => {
            console.log(`${chunkLength} / ${((downloaded / total) * 100).toFixed(2)}% loaded`);
        });
        videoStream.pipe(res).on("finish", () => {
            console.log("Streaming finished");
        });
    } catch (error) {
        console.error("Error fetching video:", error);
        res.status(500).send("Error fetching video");
    }
});

app.listen(3000, () => {
    console.log("Server is running on port 3000");
});

index.html:

<!DOCTYPE html>
<html lang="uk">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Video Stream</title>
        <style>
            .centered {
                margin-left: auto;
                margin-right: auto;
                margin-top: 1em;
                width: 720px;
            }
        </style>
    </head>
    <body>
        <div class="centered">
            <video id="videoPlayer" width="720" controls autoplay>
                <source src="http://localhost:3000/video" type="video/mp4" />
                Your browser does not support the video tag. Please try again with a modern browser.
            </video>
        </div>
    </body>
</html>

This code works great. However, keep in mind that the audio might be separate.

Wow, it's working perfectly now. Now, if i want to download audio and video, i'll repeat same process then merge it with ffmpeg

@VintaGist
Copy link

if i want to download audio and video, i'll repeat same process then merge it with ffmpeg

Yes, it will make the code a bit more complex, but that's exactly how you need to proceed.

@TechBroCode
Copy link

TechBroCode commented Aug 19, 2024

if i want to download audio and video, i'll repeat same process then merge it with ffmpeg

Yes, it will make the code a bit more complex, but that's exactly how you need to proceed.

Okay, thank you so much . Check, i just started following you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

11 participants