Skip to content

Streamablefile throws error for range requests on iOS when playing audio file #14873

@sudarsangp

Description

@sudarsangp

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

When a range request is made from iOS device I get this error:

/node_modules/@nestjs/platform-express/adapters/express-adapter.js:51
                    body.errorLogger(err);
                         ^
TypeError: body.errorLogger is not a function
    at /node_modules/@nestjs/platform-express/adapters/express-adapter.js:51:26
    at node:internal/util:464:12
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

When trying to debug the code using debugger, found this thrown from express-adapter.js file, the err object is shown here:

"Error [ERR_STREAM_PREMATURE_CLOSE]: Premature close
    at __node_internal_captureLargerStackTrace (node:internal/errors:496:5)
    at new NodeError (node:internal/errors:405:5)
    at onclose (node:internal/streams/end-of-stream:154:30)
    at process.processTicksAndRejections (node:internal/process/task_queues:77:11)"

There were cases when the err object was empty and it didn't thrown an error due to this code:

if (err) {
    body.errorLogger(err);
}

I am not able to catch this error using catch everything exception filter

The code which throws the error is here:

@UseFilters(CatchEverythingFilter)
@Get('get-audio')
async getAudio(
    @Request() req,
    @Res({ passthrough: true }) res: ExpressResponse,
  ) {
    const audioFilePath = 'test/hello.mp3'
    let fileToSend;
    const stat = statSync(audioFilePath);
    const fileSize = stat.size;
    const 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;

      if (start >= fileSize) {
        res.status(416).send('Requested range not satisfiable');
        return;
      }

      fileToSend = createReadStream(audioFilePath, { start, end });

      res.status(206);
      res.setHeader('Content-Type', 'audio/mpeg');
      res.setHeader('Content-Length', end - start + 1);
      res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
      res.setHeader('Accept-Ranges', 'bytes');
    } else {
      fileToSend = createReadStream(audioFilePath);
      res.set({
        'Content-Type': 'audio/mpeg',
      });
    }

    return new StreamableFile(fileToSend);
}

Any suggestions on how to resolve this error?
Is there any workaround at-least to catch the error?

Minimum reproduction code

https://stackblitz.com/edit/nestjs-typescript-starter-adjltria?file=test%2Fapp.e2e-spec.ts

Steps to reproduce

I am not sure if this is the correct way to reproduce though:

  1. npm run test:e2e

Expected behavior

A way for error to be caught and not kill the entire backend server.
The reason being we cannot control how client makes a request.

Package

  • I don't know. Or some 3rd-party package
  • @nestjs/common
  • @nestjs/core
  • @nestjs/microservices
  • @nestjs/platform-express
  • @nestjs/platform-fastify
  • @nestjs/platform-socket.io
  • @nestjs/platform-ws
  • @nestjs/testing
  • @nestjs/websockets
  • Other (see below)

Other package

No response

NestJS version

^10.0.0

Packages versions

"@nestjs/platform-express": "^10.0.0",

Node.js version

v18.17.0

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

The issue happens when trying to play the audio on iOS.
Additional context here - https://discord.com/channels/695411232856997968/1354845020548763819/1354845020548763819

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs triageThis issue has not been looked into

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions