summaryrefslogtreecommitdiff
path: root/node_modules/prism-media/src/opus/OggOpus.js
blob: 9a938cd2faa17e7aec65a9e11e1f3fc0e834ff62 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
const { Transform } = require('stream');

const OGG_PAGE_HEADER_SIZE = 26;
const STREAM_STRUCTURE_VERSION = 0;

const OGGS_HEADER = Buffer.from('OggS'.split('').map(x => x.charCodeAt(0)));
const OPUS_HEAD = Buffer.from('OpusHead'.split('').map(x => x.charCodeAt(0)));
const OPUS_TAGS = Buffer.from('OpusTags'.split('').map(x => x.charCodeAt(0)));

class OggOpusTransform extends Transform {
  constructor() {
    super();
    this._remainder = null;
    this._head = null;
  }

  _transform(chunk, encoding, done) {
    if (this._remainder) {
      chunk = Buffer.concat([this._remainder, chunk]);
      this._remainder = null;
    }

    while (chunk) {
      try {
        const result = this.readPage(chunk);
        if (result) chunk = result;
        else break;
      } catch (err) {
        this.emit('error', err);
      }
    }
    this._remainder = chunk;
    done();
  }

  /**
   * Reads a page from a buffer
   * @param {Buffer} chunk The chunk containing the page
   * @returns {boolean|Buffer}
   */
  readPage(chunk) {
    if (chunk.length < OGG_PAGE_HEADER_SIZE) {
      return false;
    }
    if (!chunk.slice(0, 4).equals(OGGS_HEADER)) {
      throw Error(`capture_pattern is not ${OGGS_HEADER}`);
    }
    if (chunk.readUInt8(4) !== STREAM_STRUCTURE_VERSION) {
      throw Error(`stream_structure_version is not ${STREAM_STRUCTURE_VERSION}`);
    }

    const pageSegments = chunk.readUInt8(26),
      table = chunk.slice(27, 27 + pageSegments);

    let sizes = [], totalSize = 0;

    for (let i = 0; i < pageSegments;) {
      let size = 0, x = 255;
      while (x === 255) {
        x = table.readUInt8(i);
        i++;
        size += x;
      }
      sizes.push(size);
      totalSize += size;
    }

    if (chunk.length < 27 + pageSegments + totalSize) {
      return false;
    }

    let start = 27 + pageSegments;
    for (const size of sizes) {
      const segment = chunk.slice(start, start + size);
      const header = segment.slice(0, 8);
      if (this._head) {
        if (header.equals(OPUS_TAGS)) this.emit('opusTags', segment);
        else this.push(segment);
      } else if (header.equals(OPUS_HEAD)) {
        this._head = segment;
      } else {
        throw Error(`Invalid segment ${segment}`);
      }
      start += size;
    }
    return chunk.slice(start);
  }
}

module.exports = OggOpusTransform;