首页 > 解决方案 > ExpressJS 视频流有烦人的音频问题

问题描述

我一直在尝试运行一个 ExpressJS Web 服务器来提供来自我的文件系统的视频。出于某种原因,每当播放视频时,都会有持续的爆裂声,最终(3-10 分钟后)音频完全中断。重新加载页面将带回音频,但不会停止弹出。

我有 2 种不同的方法来提供视频,但在运行时只使用了 1 种。它们都用于此功能:

app.get(`/${VIDTYPE}/:path`, (req, res) => {
    let p = decode(req.params.path)
    let dir = path.dirname(p)
    let name = path.parse(p).name
    let ext = '.vtt'

    let track = path.resolve(`${dir}/${name}${ext}`)

    console.log({track})

    res.send(
        `<video id="videoplayer" controls width="90%" height="90%">` +
        `<source src="/video/${req.params.path}"/>` +
        `<track default kind="subtitles" label="en" src="/track/${encode(track)}"/>` +
        `</video>`
    )
})

第一种方法是从网上抄来的,感觉比较复杂。

app.get("/video/:path", (req, res) => {
    let decoded = decode(req.params.path)
    console.log("video", {decoded})
    let stat = fs.statSync(decoded)
    let fileSize = stat.size
    let range = req.headers.range

    if (range) {
        const parts = range.replace(/bytes=/, "").split("-")
        const start = parseInt(parts[0], 10)
        const end = parts[1] 
            ? parseInt(parts[1], 10)
            : fileSize-1
        const chunksize = (end-start)+1
        const file = fs.createReadStream(decoded, {start, end})
        const head = {
            'Content-Range': `bytes ${start}-${end}/${fileSize}`,
            'Accept-Ranges': 'bytes',
            'Content-Length': chunksize,
            'Content-Type': 'video/mp4',
        }
        res.writeHead(206, head);
        file.pipe(res);
    } else {
        const head = {
            'Content-Length': fileSize,
            'Content-Type': 'video/mp4',
            }
        res.writeHead(200, head)
        fs.createReadStream(path).pipe(res)
    }
})

第二种方法要简单得多,而且功能似乎相同。他们俩都有音频问题,质量没有明显差异。

app.get("/video2/:path", (req, res) => {
    res.sendFile(decode(req.params.path))
})

完整的代码库将在评论中。所有 mp4 文件都是使用 ffmpeg 从 mkv 转换的,所以这可能会导致问题,但是直接播放时没有一个视频有相同的音频问题。我也将问题隔离到移动设备上。

关于我哪里出错的任何想法?

标签: javascriptnode.jsexpressaudiovideo

解决方案


代码库:

const { json } = require('body-parser')
const { dir } = require('console')
const { response } = require('express')
const express = require('express')
const fs = require("fs")
const path = require("path")

const port = 80

const app = express()

const vid_ext = [
    '.mp4',
    '.webm'
]

const VIDTYPE = "vid"
const DIRTYPE = "dir"

const getAllFiles = function(dirPath) {
    console.log({dirPath});
    files = fs.readdirSync(dirPath)

    arrayOfFiles = [{
        path: path.join(dirPath + "/.."),
        title: "go up(beta)",
        type: DIRTYPE
    }]

    files.forEach(function(file) {
        let p = path.resolve(path.join(dirPath, "/", file))
        if (fs.statSync(dirPath + "/" + file).isDirectory()) {
            arrayOfFiles.push({ 
                path: p,
                title: file,
                type: DIRTYPE
            })
        } else {
            if (vid_ext.includes(path.extname(p))) {
                arrayOfFiles.push({
                    path: p,
                    title: file,
                    type: VIDTYPE
                })
            }
        }
    })

    return arrayOfFiles
}

app.get('/', (request, response) => {
    response.redirect(`/${DIRTYPE}/${encode("../stuff")}`);
});

app.get(`/${DIRTYPE}/:path`, (req, res) => {
    let decoded = decode(req.params.path)
    console.log(DIRTYPE, decoded)

    res.send(dirtable(decoded))
})

app.get("/video/:path", (req, res) => {
    let decoded = decode(req.params.path)
    console.log("video", {decoded})
    let stat = fs.statSync(decoded)
    let fileSize = stat.size
    let range = req.headers.range

    if (range) {
        const parts = range.replace(/bytes=/, "").split("-")
        const start = parseInt(parts[0], 10)
        const end = parts[1] 
            ? parseInt(parts[1], 10)
            : fileSize-1
        const chunksize = (end-start)+1
        const file = fs.createReadStream(decoded, {start, end})
        const head = {
            'Content-Range': `bytes ${start}-${end}/${fileSize}`,
            'Accept-Ranges': 'bytes',
            'Content-Length': chunksize,
            'Content-Type': 'video/mp4',
        }
        res.writeHead(206, head);
        file.pipe(res);
    } else {
        const head = {
            'Content-Length': fileSize,
            'Content-Type': 'video/mp4',
            }
        res.writeHead(200, head)
        fs.createReadStream(path).pipe(res)
    }
})

app.get("/video2/:path", (req, res) => {
    res.sendFile(decode(req.params.path))
})

app.get(`/${VIDTYPE}/:path`, (req, res) => {
    let p = decode(req.params.path)
    let dir = path.dirname(p)
    let name = path.parse(p).name
    let ext = '.vtt'

    let track = path.resolve(`${dir}/${name}${ext}`)

    console.log({track})

    res.send(
        `<video id="videoplayer" controls width="90%" height="90%">` +
        `<source src="/video/${req.params.path}"/>` +
        `<track default kind="subtitles" label="en" src="/track/${encode(track)}"/>` +
        `</video>`
    )
    //res.redirect(`/video2/${req.params.path}`)
})

app.get("/track/:path", (req, res) => {
    res.sendFile(decode(req.params.path))
})

function dirtable(a) {
    let files = getAllFiles(a)

    return (
        "<table><tbody>" + 
        files.map(x => `<td>${linkfrom(x)}</td><td>${x.type}</td>`).reduce((acc, x) => acc + `<tr>${x}</tr>`) +
        "</tbody></table>"
    )
}

function linkfrom(a) {
    return `<a href="/${a.type}/${encode(a.path)}">${a.title}</a>`
}

function encode(h) {
    return Buffer.from(h).toString('base64')
}

function decode(h) {
    return Buffer.from(h, "base64").toString()
}

app.listen(port, console.log(`App Listening to port ${port}`));

推荐阅读