• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

melchor629 / node-flac-bindings / 13604713414

01 Mar 2025 12:22PM UTC coverage: 91.073% (+9.3%) from 81.781%
13604713414

push

github

melchor629
fix: native code coverage

159 of 202 branches covered (78.71%)

Branch coverage included in aggregate %.

4 of 5 new or added lines in 2 files covered. (80.0%)

72 existing lines in 2 files now uncovered.

5003 of 5466 relevant lines covered (91.53%)

37359.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.86
/lib/decoder/file-decoder.js
1
import { Readable } from 'node:stream'
1✔
2
import debug from 'debug'
1✔
3
import * as flac from '../api.js'
1✔
4

5
class FileDecoder extends Readable {
1✔
6
  constructor(options = {}) {
1✔
7
    super(options)
13✔
8
    this._debug = debug('flac:decoder:file')
13✔
9
    this._builder = new flac.DecoderBuilder()
13✔
10
    this._dec = undefined
13✔
11
    this._oggStream = options.isOggStream || false
13✔
12
    this._outputAs32 = options.outputAs32 || false
13✔
13
    this._file = options.file
13✔
14
    this._processedSamples = 0
13✔
15
    this._failed = false
13✔
16

17
    if (!this._file) {
13✔
18
      throw new Error('No file passed as argument')
1✔
19
    }
1✔
20

21
    if (this._oggStream && !flac.format.API_SUPPORTS_OGG_FLAC) {
13!
22
      throw new Error('Ogg FLAC is unsupported')
×
23
    }
✔
24

25
    if (options.metadata === true) {
13✔
26
      this._debug('Setting decoder to emit all metadata blocks')
1✔
27
      this._builder.setMetadataRespondAll()
1✔
28
    } else if (Array.isArray(options.metadata)) {
13!
29
      this._debug(`Setting decoder to emit '${options.metadata.join(', ')}' metadata blocks`)
×
30
      for (const type of options.metadata) {
×
31
        this._builder.setMetadataRespond(type)
×
32
      }
×
33
    }
✔
34

35
    if (options.isChainedStream) {
13✔
36
      this._builder.setDecodeChainedStream(true)
1✔
37
    }
1✔
38
  }
13✔
39

40
  get processedSamples() {
1✔
41
    return this._processedSamples
8✔
42
  }
8✔
43

44
  getTotalSamples() {
1✔
45
    if (this._totalSamples === undefined) {
2!
UNCOV
46
      this._totalSamples = this._dec.getTotalSamples()
×
UNCOV
47
    }
×
48
    return this._totalSamples
2✔
49
  }
2✔
50

51
  getChannels() {
1✔
52
    if (this._channels === undefined) {
1!
UNCOV
53
      this._channels = this._dec.getChannels()
×
UNCOV
54
    }
×
55
    return this._channels
1✔
56
  }
1✔
57

58
  getChannelAssignment() {
1✔
59
    if (this._channelAssignment === undefined) {
1✔
60
      this._channelAssignment = this._dec.getChannelAssignment()
1✔
61
    }
1✔
62
    return this._channelAssignment
1✔
63
  }
1✔
64

65
  getBitsPerSample() {
1✔
66
    if (this._bitsPerSample === undefined) {
329!
UNCOV
67
      this._bitsPerSample = this._dec.getBitsPerSample()
×
UNCOV
68
    }
×
69
    return this._bitsPerSample
329✔
70
  }
329✔
71

72
  getOutputBitsPerSample() {
1✔
73
    if (this._outputAs32) {
533✔
74
      return 32
205✔
75
    }
205✔
76

77
    return this.getBitsPerSample()
328✔
78
  }
533✔
79

80
  getSampleRate() {
1✔
81
    if (this._sampleRate === undefined) {
3!
UNCOV
82
      this._sampleRate = this._dec.getSampleRate()
×
UNCOV
83
    }
×
84
    return this._sampleRate
3✔
85
  }
3✔
86

87
  getProgress() {
1✔
88
    if (this._dec || (this._totalSamples && this._sampleRate)) {
1✔
89
      const position = this._processedSamples
1✔
90
      const totalSamples = this.getTotalSamples()
1✔
91
      const percentage = totalSamples ? position / totalSamples : NaN
1!
92
      const totalSeconds = totalSamples ? totalSamples / this.getSampleRate() : NaN
1!
93
      const currentSeconds = position / this.getSampleRate()
1✔
94
      return {
1✔
95
        position,
1✔
96
        totalSamples,
1✔
97
        percentage,
1✔
98
        totalSeconds,
1✔
99
        currentSeconds,
1✔
100
      }
1✔
101
    }
1!
102

UNCOV
103
    return undefined
×
104
  }
1✔
105

106
  _read() {
1✔
107
    if (!this._readLoopPromise) {
449✔
108
      this._readLoopPromise = this._readLoop().then(() => {
19✔
109
        if (this.readableLength === 0 && !this.destroyed) {
19✔
110
          this._read()
14✔
111
        }
14✔
112
      })
19✔
113
    }
19✔
114
  }
449✔
115

116
  async _readLoop() {
1✔
117
    try {
19✔
118
      if (this._dec === undefined) {
19✔
119
        this._debug('Initializing decoder')
12✔
120
        try {
12✔
121
          if (this._oggStream) {
12✔
122
            this._debug('Initializing for Ogg/FLAC')
3✔
123
            this._dec = await this._builder.buildWithOggFileAsync(
3✔
124
              this._file,
3✔
125
              this._writeCbk.bind(this),
3✔
126
              this._metadataCbk.bind(this),
3✔
127
              this._errorCbk.bind(this),
3✔
128
            )
3✔
129
          } else {
12✔
130
            this._debug('Initializing for FLAC')
9✔
131
            this._dec = await this._builder.buildWithFileAsync(
9✔
132
              this._file,
9✔
133
              this._writeCbk.bind(this),
9✔
134
              this._metadataCbk.bind(this),
9✔
135
              this._errorCbk.bind(this),
9✔
136
            )
9✔
137
          }
8✔
138
        } catch (error) {
12✔
139
          const initStatus = error.status || -1
1!
140
          const initStatusString = error.statusString || 'Unknown'
1!
141
          this._debug(`Failed initializing decoder: ${initStatus} ${initStatusString}`)
1✔
142
          throw error
1✔
143
        }
1✔
144
      }
12✔
145

146
      this._debug('Wants data from decoder')
18✔
147
      while (!this.destroyed && this._dec != null) {
19✔
148
        const couldProcess = await this._dec.processSingleAsync()
592✔
149
        const decState = this._dec.getState()
592✔
150
        if (decState === flac.Decoder.State.END_OF_STREAM) {
592✔
151
          this._debug('Decoder reached EOF -> flushing decoder and finishing stream')
11✔
152
          await this._dec.finishAsync()
11✔
153
          this.push(null)
11✔
154
          this._dec = null
11✔
155
        } else if (decState === flac.Decoder.State.END_OF_LINK) {
592✔
156
          this._debug('End of link reached')
2✔
157
          await this._dec.finishLinkAsync()
2✔
158
          this.emit('end-link')
2✔
159
        } else if (!couldProcess && !this.destroyed) {
581!
UNCOV
160
          this._throwDecoderError()
×
UNCOV
161
          return
×
UNCOV
162
        }
×
163
      }
592✔
164
    } catch (e) {
19✔
165
      this.destroy(e)
1✔
166
    }
1✔
167

168
    this._readLoopPromise = null
19✔
169
  }
19✔
170

171
  _writeCbk(frame, buffers) {
1✔
172
    const outBps = this.getOutputBitsPerSample() / 8
533✔
173
    const buff = flac.fns.zipAudio({ samples: frame.header.blocksize, outBps, buffers })
533✔
174

175
    this._processedSamples += frame.header.blocksize
533✔
176
    this.push(buff)
533✔
177
    this._debug(`Received ${frame.header.blocksize} samples (${buff.length} bytes) of decoded data`)
533✔
178

179
    return flac.Decoder.WriteStatus.CONTINUE
533✔
180
  }
533✔
181

182
  _metadataCbk(metadata) {
1✔
183
    if (metadata.type === flac.format.MetadataType.STREAMINFO) {
16✔
184
      this.emit('format', {
13✔
185
        channels: metadata.channels,
13✔
186
        bitDepth: metadata.bitsPerSample,
13✔
187
        bitsPerSample: metadata.bitsPerSample,
13✔
188
        is32bit: this._outputAs32,
13✔
189
        sampleRate: metadata.sampleRate,
13✔
190
        totalSamples: metadata.totalSamples,
13✔
191
      })
13✔
192
      this._totalSamples = metadata.totalSamples
13✔
193
      this._channels = metadata.channels
13✔
194
      this._bitsPerSample = metadata.bitsPerSample
13✔
195
      this._sampleRate = metadata.sampleRate
13✔
196
    }
13✔
197

198
    this.emit('metadata', metadata)
16✔
199
  }
16✔
200

201
  _errorCbk(code) {
1✔
202
    const message = flac.Decoder.ErrorStatusString[code]
×
203
    this._debug(`Decoder called error callback ${code} (${message})`)
×
204
    this.emit('flac-error', { code, message })
×
205
  }
×
206

207
  _throwDecoderError() {
1✔
UNCOV
208
    const error = this._dec.getState()
×
UNCOV
209
    const errorObj = new Error(this._dec.getResolvedStateString())
×
UNCOV
210
    this._debug(`Decoder call failed: ${this._dec.getResolvedStateString()} [${error}]`)
×
UNCOV
211
    errorObj.code = error
×
UNCOV
212
    throw errorObj
×
UNCOV
213
  }
×
214
}
1✔
215

216
export default FileDecoder
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc