nodejs与前端js大文件、切片、视频流相关技术示例

nodejs服务代码

const express = require("express");
const fs = require("fs");

const app = express();
// 展示html页面
app.get("/", function (req, res) {
    res.sendFile(__dirname + "/index.html");
});

// nodejs切片读取文件示例
if (!Date.now()) {
    let i = 0
    const readableStream = fs.createReadStream('asai.mp4') + fs.createReadStream('asai1.mp4');
    readableStream.on('data', (chunk) => {
        i++
        console.log(666.123465, i, chunk);
    });
    readableStream.on('end', () => {
        console.log('数据读取完毕');
    });
}

// nodejs合并切片示例
// 方案一
if (!Date.now()) {
    const writeStream = fs.createWriteStream('asai-a.mp4');
    let readStream
    function concatFiles(readfiles) {
        if (readfiles && readfiles[0]) {
            console.log(666.30002, readfiles[0])
            readStream = fs.createReadStream(readfiles[0]);
            readStream.pipe(writeStream, { end: false })
            readStream.on('end', () => {
                readfiles.shift()
                concatFiles(readfiles);
            });
        } else {
            writeStream.close();
        }
        readStream.on('error', (error) => { // 监听错误事件,关闭可写流,防止内存泄漏
            console.error(666.789, error);
            writeStream.close();
        });
    }
    concatFiles(['asai.mp4', 'asai1.mp4'])
}

// 方案二
if (!Date.now()) {
    // 读取一个文件,使用fs读取文件获取一个Buffer类型数据
    const buffer = fs.readFileSync('asai.mp4')
    // 将文件进行切分
    const file1 = buffer.subarray(0, 3000000)
    const file2 = buffer.subarray(3000000)
    // 全并多个切片文件
    const allfile = Buffer.concat([file1, file2])
    console.log(666.2008, allfile)

    // 读取文件切片
    const s1 = fs.readFileSync('asai.mp4')
    const s2 = fs.readFileSync('asai1.mp4')
    // 将读取的文件切片合并
    const bb = Buffer.concat([s1, s2])
    // 将合并的切片数据,写到一个新文件中
    fs.writeFileSync('asai-c.mp4', bb)
}

// 多视频切片发送示例(咱不支持)
const videoData = {
    list: [
        // { path: 'asai1.mp4', size: fs.statSync('asai1.mp4').size },
        { path: 'asai.mp4', size: fs.statSync('asai.mp4').size },
    ],
    index: 0,
    curStart: 0,
    curSize: 0,
    totalSize: 0
}
videoData.totalSize = videoData.list.reduce((prev, cur) => {
    return prev + cur.size
}, 0)


app.get("/video", function (req, res) {
    const range = req.headers.range;
    if (range) {
        resVideo(res, range)
    } else {
        const path = 'asai.mp4'
        const fileSize = fs.statSync(path).size
        let head = {
            'Content-Length': fileSize,
            'Content-Type': 'video/mp4',
        };
        res.writeHead(200, head);
        fs.createReadStream(path).pipe(res);
        res.status(400).send("Requires Range header");
    }
});

app.listen(8000, function () {
    console.log("Listening on port 8000!");
});

function resVideo(res, range) {
    console.log(666.10001, range)
    let [, start, end] = range.match(/(\d*)-(\d*)/);
    if (videoData.list[videoData.index]) {
        // if (start < videoData.curSize) {
        //     start = videoData.curSize
        // }
        // range = 'bytes=1458753-';
        // 保证request的header里面有range,没有range就无法判断需要把哪一部分content写入response
        // const videoPath = "asai.mp4";
        const videoPath = videoData.list[videoData.index].path;
        const videoSize = videoData.list[videoData.index].size;
        // 还需要资源的路径和资源的大小,资源的大小会用来计算哪一部分content要被send
        const CHUNK_SIZE = 10 ** 5; // 10 ** 6 ≈ 1MB
        // const start = Number(range.replace(/\D/g, "")); // 截取开始位置

        // 这里规定每次返回1M的内容,开始位置从request的header里获取并将其转成Number类型
        end = Math.min(end ? end : start + CHUNK_SIZE, videoData.curStart + videoSize - 1); // 截取结束位置


        console.log(666.10002, videoPath, { start, end, size: videoData.totalSize, startv: start - videoData.curStart, endv: end - videoData.curStart, sizev: videoSize }, (start >= videoData.curStart && start < end), videoData)

        // 这里需要使用fs来创建一个videoSteam,使用videoPath和start和end作为参数
        const videoStream = fs.createReadStream(videoPath, { start: start - videoData.curStart, end: end - videoData.curStart });

        // const newRange = `bytes ${start}-${end}/*`
        // const newRange = `bytes ${start}-${end}/1509200`
        // const newRange = `bytes ${start}-${end}/${videoSize}`
        const newRange = `bytes ${start}-${end}/${videoData.totalSize}`


        // 视频发送状态
        console.log(666.789, newRange)

        // 在响应头里面我们需要返回Content的大小,Content-range,Accept-ranges,Content-type。
        const headers = {
            "If-Range": "Etag",
            "Content-Range": newRange,
            "Accept-Ranges": "bytes",
            "Content-Type": "multipart/byteranges",
            // "Content-Type": "video/mp4",
            // "Content-Length": CHUNK_SIZE,
            "Transfer-Encoding": "chunked",// 假如不知道内容长度,代替Content-Length
        };


        // 状态码设置为206表明我们返回的是部分内容。
        res.writeHead(206, headers);


        // 把videoStream pipe到response即可
        videoStream.pipe(res);

        // 手工记录cursize
        videoData.curSize = end

        if (end - videoData.curStart === videoSize - 1 && videoData.list[videoData.index + 1]) {
            videoData.curStart += videoData.list[videoData.index].size
            videoData.index += 1
            console.log(666.123, videoPath, 'send over.', videoData)
        }
    }
}

前端html页面

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>视频/文件切片请求处理nodejs+html</title>
  <style>
    body {
      max-width: 100%;
      height: 100vh;
      background-color: rgb(14, 14, 14);
      display: flex;
      margin: auto;
      align-items: center;
      justify-content: center;
    }
  </style>
</head>

<body>
  <video height="100%" src="/video" controls autoplay muted></video>
</body>

</html>

附件:前端js切片上次

<html lang="zh-cn">

<head>
    <meta title="文件切片合并" />
</head>

<body>
    <!-- 页面选择文件 -->
    <input type="file" id="file" />
    <!-- 将加载的视频文件进行切片后合并,并播放 -->
    <video id="play" controls style="width:500px;height:auto"></video>
</body>
<script>
    file.addEventListener('change', async (e) => {
        // 获取FileList中的File文件
        let file2 = file.files[0]

        // ----------第一种方式-------------------
        // 使用Blob的slice方法切片,使用Blob构造函数进行数据合并
        // 将加载的文件进行切片
        // let s1 = file2.slice(0,100)
        // let s2 = file2.slice(100)

        // 将切片的文件进行数据合并
        // let newFile = new Blob([s1,s2])


        // -----------第二种方式------------------
        // 如何对大文件进行流式读取
        let chunckArr = []

        // 通过Blob获取 ReadableStream 流对象,再获取可读的流读取器
        let reader = file2.stream().getReader()
        let done = false
        while (!done) {
            // 使用流读取器的read方法,获取流队列中的下一个分块数据
            let { value, done: readDone } = await reader.read()
            console.log(value)
            chunckArr.push(value)
            done = readDone
        }
        // 通过Blob构造函数合并流数据
        let newFile = new Blob(chunckArr)

        // 进行页面播放
        play.setAttribute('src', URL.createObjectURL(newFile))
    })

</script>

</html>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿赛工作室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值