koa使用静态目录如何支持视屏调节时间进度

1 篇文章 0 订阅
1 篇文章 0 订阅

koa使用静态目录如何支持视屏调节时间进度

背景

搭建基于koa的VueSSR的时候,有个功能需要上传下载预览视屏。实现思路,首先设置静态目录(koa-static),上传时直接保存文件到静态目录,预览和下载都通过返回对应静态目录文件的路径进行操作。

问题

当前端通过video的播放的时候,画面能正常播放,但是令人惊讶的事情发生了,居然不能手动调节进度,无论是拖拽进度条还是点击都无效。

过程

  1. 第一反应是前端设置有问题,但是前端控制只有一个设置control,并没有其余的单独设置。于是猜想是不是视屏有问题,换了一个视屏,但是仍然如此。
  2. 多次使用过video从来没发现过这种情况,如果下载视屏,准备本地调试。令人惊讶的事情发生了,本地测试居然是好的。那么问题确定了,就是服务返回过程的一些东西丢失了或者格式错误导致video识别视屏时候产生bug,所以才不能调节进度。
  3. 面向百度编程开始,关键字koa返回视屏无法调节进度。出现这个bug是因为返回的视屏没有文件信息Content-Length,所以无法设置进度。
  • 解决方法一:通过接口读取文件,然后修改返回头信息并且返回文件流。
    代码:

    async (ctx, next) => {
            let {
                filepath
            } = ctx.query;
            if (!path) return next();
            filepath= path.resolve(config.public,'.'+ filepath);
            console.log(filepath)
            let stats = fs.statSync(filepath);
            ctx.set("Accept-Ranges", "bytes")
            ctx.set("Content-Length", stats.size)
            ctx.set("Content-Type", "video/mp4")
            ctx.body = fs.createReadStream(filepath)
            next();
        }
    
    

    整体思路是:通过接口传递文件路径,然后根据文件路径读取文件的信息,设置头信息Content-Length,然后返回视屏的文件流。
    这方法亲测可行,但是切换进度或频繁请求过程中会遇到一个很尴尬的bug:

      Error: write ECONNRESET
      at afterWriteDispatched (internal/stream_base_commons.js:156:25)
    

    这个bug是因为node自带的http模块,因为切换和频繁请求过程中,导致一些TCP没有正常关闭,所以这个错误,但是并不会影响你正在进行的请求。
    解决方案:
    1:增加错误监听,别直接抛出错误影响整个程序的运行既可以,因为不影响内容。
    2:更换node版本8.12.0(实际是更换了http模块版本),这版本的node疑似修复了换这个问题。
    优化:因为我们调节进度的时候,我们所需的视屏流只是当前视屏点后续的内容,所以我们可以返回对应视屏的范围:

    async (ctx, next) => {
        let {
            filepath
        } = ctx.query;
        if (!path) return next();
        let range = ctx.headers.range; // 实际上请求头的 Range 视屏调节请求会自动带上开始内容 类似这样 Range: bytes=26214400-
        if (!range) {
            // 初始化请求不会带上range 造一个 并且返回200
            range = "bytes=0-";
            ctx.status = 200
        } else {
            // 带range的请求返回 206 表明返回目标url上的部分内容
            ctx.status = 206
        }
        let startBytes = range.replace(/bytes=/, "").split("-")[0];
        startBytes= Number(startBytes);
        filepath = path.resolve(config.public, '.' + filepath);
        let stats = fs.statSync(filepath);
        ctx.set("Accept-Ranges", "bytes")
        console.log("bytes " + startBytes + "-" + (stats.size - 1) + "/" + stats.size)
        ctx.set("Content-Length", stats.size - startBytes)
        ctx.set("Content-Range", "bytes " + startBytes + "-" + (stats.size - 1) + "/" + stats.size)
        ctx.set("Content-Type", "video/mp4")
        ctx.body = fs.createReadStream(filepath, {
            start: startBytes,
            end: stats.size
        })
        next();
    }
    
    
  • 解决方法二:采用koa-range+koa-static搭建静态文件目录:
    代码:

    const static = require('koa-static')
    const range = require('koa-range');
    app.use(range)
    app.use(static(config.public))
    
    

    完事。
    其原理和方法基本一致,不过实现顺序有些差异,各种情况更加完善。

    1. koa-range实现设置返回头的Content-RangeContent-Length,并将ctx.body中的Stream按照请求头中的Range进行切割,再返回给ctx.body。 这里实现使用了await next(),所以koa-range必须放在koa-static前面,才能正确获取在koa-static中间件中设置在ctx.body的文件流。
      主要逻辑代码如下:
    var range = ctx.header.range;
    ctx.set('Accept-Ranges', 'bytes');
    ...
    var ranges = rangeParse(range); // [[start,end]] 这里采用了双层数据 获取到的是一组
    ...
    var firstRange = ranges[0];
    var start = firstRange[0];
    var end = firstRange[1];
    ...
    var rawBody = ctx.body;
    var len = rawBody.length;
    ...
    if (!Buffer.isBuffer(rawBody)) {
    if (rawBody instanceof Stream.Readable) {
      len = ctx.length || '*';
      rawBody = rawBody.pipe(slice(start, end + 1));
    } else if (typeof rawBody !== 'string') {
      rawBody = new Buffer(JSON.stringify(rawBody));
      len = rawBody.length;
    } else {
      rawBody = new Buffer(rawBody);
      len = rawBody.length;
    }
    }
    ...
     if (rawBody instanceof Stream) {
        ctx.body = rawBody;
    } else {
        ctx.body = rawBody.slice.apply(rawBody, args);
    }
    
    

    ...的部分省略了一些逻辑判断,koa-range组件在请求头存在Range,并且ctx.body中内容为Stream或者能转化为Stream的时候的时候,会设置对应返回头的参数,并对ctx.body中的Stream进行切割后部分返回。

    1. koa-static也使用await next(),它的实际逻辑会在这个中间件后续的中间件的同步逻辑走完之后再调用,也就是说,如果我们有其他的路由,放在当前组件的前后都没有影响,koa-static会保证最后再调用自己的逻辑。
      koa-static的主要逻辑:当ctx.body内没有内容,并且状态不是404的是情况下,j将当前访问当做请求静态文件来尝试获取静态文件,调用koa-send进行返回文件操作:
      if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return
        // response is already handled
        if (ctx.body != null || ctx.status !== 404) return // eslint-disable-line
    
        try {
        await send(ctx, ctx.path, opts)
        } catch (err) {
        if (err.status !== 404) {
            throw err
        }
        }
    

    koa-send:所做的事情就读取对应的路径,请求文件,如果没有该文件,就返回404,和错误信息。如果能找到就把文件读取文件流存到ctx.body。(这个存了文件流,再经过koa-range才会返回,这也是为什么要koa-range在koa-static前面)。

    let stats
    try {
        stats = await stat(path)
    
        // Format the path to serve static file servers
        // and not require a trailing slash for directories,
        // so that you can do both `/directory` and `/directory/`
        if (stats.isDirectory()) {
        if (format && index) {
            path += `/${index}`
            stats = await stat(path)
        } else {
            return
        }
        }
    } catch (err) {
        const notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR']
        if (notfound.includes(err.code)) {
        throw createError(404, err)
        }
        err.status = 500
        throw err
    }
    
    if (setHeaders) setHeaders(ctx.res, path, stats)
    
    // stream
    ctx.set('Content-Length', stats.size)
    if (!ctx.response.get('Last-Modified')) ctx.set('Last-Modified', stats.mtime.toUTCString())
    if (!ctx.response.get('Cache-Control')) {
        const directives = [`max-age=${(maxage / 1000 | 0)}`]
        if (immutable) {
        directives.push('immutable')
        }
        ctx.set('Cache-Control', directives.join(','))
    }
    if (!ctx.type) ctx.type = type(path, encodingExt)
    ctx.body = fs.createReadStream(path)
    
    1. 最后的最后一定要注意的是:
      • 方法一中一定要错误处理
      • 方法二顺序一定要对
      • 方法一不能和方法二同时进行,会导致方法一接口返回视屏流也被处理无法播放。

收获

了解了头部信息range的相关知识和返回文件流的一些基本操作以及koa-rangekoa-static+koa-send的工作原理。而且这种返回的视屏可以使用a标签进行下载。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
使用 Koa 框架时,设置静态目录使用 koa2-swagger-ui 可能会产生冲突,因为它们都涉及到路由的处理。 当你使用 Koa 的 `koa-static` 中间件设置静态目录时,该中间件会将请求路径匹配到指定的静态文件,并返回给客户端。这意味着如果你的静态目录的路径与 Swagger UI 的路由路径相同,会导致冲突。 为了避免冲突,您可以考虑以下两种解决方案之一: 1. 将 Swagger UI 的路由路径设置为不与静态目录冲突的路径。例如,将 Swagger UI 的路由路径设置为 `/api-docs` 或其他不与现有静态目录路径冲突的路径。 2. 在 Koa 的路由处理中间件之前使用 `koa2-swagger-ui` 中间件。这样可以确保 Swagger UI 的路由优先于其他路由处理中间件执行,避免冲突。 下面是一个示例代码片段,演示了如何在 Koa 设置静态目录使用 koa2-swagger-ui: ```javascript const Koa = require('koa'); const serve = require('koa-static'); const koaSwagger = require('koa2-swagger-ui'); const app = new Koa(); // 设置静态目录 app.use(serve('public')); // 设置 Swagger UI app.use(koaSwagger({ routePrefix: '/api-docs', // 设置 Swagger UI 的路由路径 swaggerOptions: { url: '/swagger.json', // 设置 Swagger JSON 文件的路径 }, })); // 添加其他路由处理中间件 // ... app.listen(3000, () => { console.log('Server started on port 3000'); }); ``` 在上述示例静态目录设置为 `public`,Swagger UI 的路由路径设置为 `/api-docs`。这样就可以避免静态目录和 Swagger UI 的冲突。 请根据您的具体需求和项目结构进行适当的调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值