fent / node-ytdl-core

YouTube video downloader in javascript.
MIT License
4.52k stars 799 forks source link

Unable to extract nCode from base.js #1301

Open prateek-chaubey opened 3 months ago

prateek-chaubey commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

Thanks it saved my day

kumarsatish1984 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

Solutions are discuss in #1295

corwin-of-amber commented 3 months ago

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)');

https://github.com/fent/node-ytdl-core/issues/1305#issuecomment-2253373635

hextor1 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

like this ?

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

corwin-of-amber commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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

gatecrasher777 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

Showing 502 bad gateway error

A bracket was missing. Please try again.

hextor1 commented 3 months ago

Showing 502 bad gateway error

A bracket was missing. Please try again.

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

gatecrasher777 commented 3 months ago

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 commented 3 months ago

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

StepsOnes commented 3 months ago

it doesn't work again

gatecrasher777 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 3 months ago

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 commented 2 months ago

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 commented 2 months ago

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

hextor1 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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

corwin-of-amber commented 2 months ago

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 commented 2 months ago

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

TechBroCode commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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.