hls-streamer 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -190
- package/dist/Interfaces/HlsStreamer.d.ts +20 -0
- package/dist/Interfaces/HlsStreamer.d.ts.map +1 -1
- package/dist/Libs/FileLib.d.ts +22 -1
- package/dist/Libs/FileLib.d.ts.map +1 -1
- package/dist/Libs/FileLib.js +273 -108
- package/dist/Libs/FileLib.js.map +1 -1
- package/dist/Libs/FormatDetector.d.ts +8 -0
- package/dist/Libs/FormatDetector.d.ts.map +1 -0
- package/dist/Libs/FormatDetector.js +63 -0
- package/dist/Libs/FormatDetector.js.map +1 -0
- package/dist/Parsers/AacParser.d.ts +15 -0
- package/dist/Parsers/AacParser.d.ts.map +1 -0
- package/dist/Parsers/AacParser.js +246 -0
- package/dist/Parsers/AacParser.js.map +1 -0
- package/dist/Parsers/FlacParser.d.ts +13 -0
- package/dist/Parsers/FlacParser.d.ts.map +1 -0
- package/dist/Parsers/FlacParser.js +200 -0
- package/dist/Parsers/FlacParser.js.map +1 -0
- package/dist/Parsers/IAudioParser.d.ts +31 -0
- package/dist/Parsers/IAudioParser.d.ts.map +1 -0
- package/dist/Parsers/IAudioParser.js +2 -0
- package/dist/Parsers/IAudioParser.js.map +1 -0
- package/dist/Parsers/Mp3Parser.d.ts +19 -0
- package/dist/Parsers/Mp3Parser.d.ts.map +1 -0
- package/dist/Parsers/Mp3Parser.js +270 -0
- package/dist/Parsers/Mp3Parser.js.map +1 -0
- package/dist/Parsers/OggParser.d.ts +11 -0
- package/dist/Parsers/OggParser.d.ts.map +1 -0
- package/dist/Parsers/OggParser.js +160 -0
- package/dist/Parsers/OggParser.js.map +1 -0
- package/dist/Parsers/ParserFactory.d.ts +10 -0
- package/dist/Parsers/ParserFactory.d.ts.map +1 -0
- package/dist/Parsers/ParserFactory.js +43 -0
- package/dist/Parsers/ParserFactory.js.map +1 -0
- package/dist/Parsers/WavParser.d.ts +9 -0
- package/dist/Parsers/WavParser.d.ts.map +1 -0
- package/dist/Parsers/WavParser.js +135 -0
- package/dist/Parsers/WavParser.js.map +1 -0
- package/dist/cjs/Interfaces/HlsStreamer.d.ts +20 -0
- package/dist/cjs/Interfaces/HlsStreamer.d.ts.map +1 -1
- package/dist/cjs/Libs/FileLib.d.ts +22 -1
- package/dist/cjs/Libs/FileLib.d.ts.map +1 -1
- package/dist/cjs/Libs/FileLib.js +276 -108
- package/dist/cjs/Libs/FileLib.js.map +1 -1
- package/dist/cjs/Libs/FormatDetector.d.ts +8 -0
- package/dist/cjs/Libs/FormatDetector.d.ts.map +1 -0
- package/dist/cjs/Libs/FormatDetector.js +67 -0
- package/dist/cjs/Libs/FormatDetector.js.map +1 -0
- package/dist/cjs/Parsers/AacParser.d.ts +15 -0
- package/dist/cjs/Parsers/AacParser.d.ts.map +1 -0
- package/dist/cjs/Parsers/AacParser.js +250 -0
- package/dist/cjs/Parsers/AacParser.js.map +1 -0
- package/dist/cjs/Parsers/FlacParser.d.ts +13 -0
- package/dist/cjs/Parsers/FlacParser.d.ts.map +1 -0
- package/dist/cjs/Parsers/FlacParser.js +204 -0
- package/dist/cjs/Parsers/FlacParser.js.map +1 -0
- package/dist/cjs/Parsers/IAudioParser.d.ts +31 -0
- package/dist/cjs/Parsers/IAudioParser.d.ts.map +1 -0
- package/dist/cjs/Parsers/IAudioParser.js +3 -0
- package/dist/cjs/Parsers/IAudioParser.js.map +1 -0
- package/dist/cjs/Parsers/Mp3Parser.d.ts +19 -0
- package/dist/cjs/Parsers/Mp3Parser.d.ts.map +1 -0
- package/dist/cjs/Parsers/Mp3Parser.js +274 -0
- package/dist/cjs/Parsers/Mp3Parser.js.map +1 -0
- package/dist/cjs/Parsers/OggParser.d.ts +11 -0
- package/dist/cjs/Parsers/OggParser.d.ts.map +1 -0
- package/dist/cjs/Parsers/OggParser.js +164 -0
- package/dist/cjs/Parsers/OggParser.js.map +1 -0
- package/dist/cjs/Parsers/ParserFactory.d.ts +10 -0
- package/dist/cjs/Parsers/ParserFactory.d.ts.map +1 -0
- package/dist/cjs/Parsers/ParserFactory.js +47 -0
- package/dist/cjs/Parsers/ParserFactory.js.map +1 -0
- package/dist/cjs/Parsers/WavParser.d.ts +9 -0
- package/dist/cjs/Parsers/WavParser.d.ts.map +1 -0
- package/dist/cjs/Parsers/WavParser.js +139 -0
- package/dist/cjs/Parsers/WavParser.js.map +1 -0
- package/dist/cjs/errors/HlsStreamerErrors.d.ts +3 -0
- package/dist/cjs/errors/HlsStreamerErrors.d.ts.map +1 -1
- package/dist/cjs/errors/HlsStreamerErrors.js +8 -1
- package/dist/cjs/errors/HlsStreamerErrors.js.map +1 -1
- package/dist/cjs/index.d.ts +6 -7
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +161 -138
- package/dist/cjs/index.js.map +1 -1
- package/dist/errors/HlsStreamerErrors.d.ts +3 -0
- package/dist/errors/HlsStreamerErrors.d.ts.map +1 -1
- package/dist/errors/HlsStreamerErrors.js +6 -0
- package/dist/errors/HlsStreamerErrors.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +162 -139
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WavParser = void 0;
|
|
4
|
+
class WavParser {
|
|
5
|
+
getFormat() {
|
|
6
|
+
return 'wav';
|
|
7
|
+
}
|
|
8
|
+
canParse(buffer) {
|
|
9
|
+
return buffer.length >= 12 &&
|
|
10
|
+
buffer.toString('ascii', 0, 4) === 'RIFF' &&
|
|
11
|
+
buffer.toString('ascii', 8, 12) === 'WAVE';
|
|
12
|
+
}
|
|
13
|
+
analyze(buffer, opts = {}) {
|
|
14
|
+
const warnings = [];
|
|
15
|
+
const size = opts.fileSize ?? buffer.length;
|
|
16
|
+
if (buffer.length < 44) {
|
|
17
|
+
return {
|
|
18
|
+
format: 'wav',
|
|
19
|
+
size,
|
|
20
|
+
duration: 0,
|
|
21
|
+
audioDataSize: 0,
|
|
22
|
+
frames: [],
|
|
23
|
+
warnings: ['File too small to be valid WAV']
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (!this.canParse(buffer)) {
|
|
27
|
+
return {
|
|
28
|
+
format: 'wav',
|
|
29
|
+
size,
|
|
30
|
+
duration: 0,
|
|
31
|
+
audioDataSize: 0,
|
|
32
|
+
frames: [],
|
|
33
|
+
warnings: ['Not a valid WAV file']
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const riffSize = buffer.readUInt32LE(4);
|
|
37
|
+
let offset = 12;
|
|
38
|
+
let fmtData = null;
|
|
39
|
+
let dataOffset = -1;
|
|
40
|
+
let dataSize = 0;
|
|
41
|
+
while (offset + 8 <= buffer.length) {
|
|
42
|
+
const chunkId = buffer.toString('ascii', offset, offset + 4);
|
|
43
|
+
const chunkSize = buffer.readUInt32LE(offset + 4);
|
|
44
|
+
if (chunkId === 'fmt ') {
|
|
45
|
+
if (chunkSize < 16) {
|
|
46
|
+
warnings.push('Invalid fmt chunk size');
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
fmtData = {
|
|
50
|
+
audioFormat: buffer.readUInt16LE(offset + 8),
|
|
51
|
+
channels: buffer.readUInt16LE(offset + 10),
|
|
52
|
+
sampleRate: buffer.readUInt32LE(offset + 12),
|
|
53
|
+
byteRate: buffer.readUInt32LE(offset + 16),
|
|
54
|
+
blockAlign: buffer.readUInt16LE(offset + 20),
|
|
55
|
+
bitsPerSample: buffer.readUInt16LE(offset + 22)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
else if (chunkId === 'data') {
|
|
59
|
+
dataOffset = offset + 8;
|
|
60
|
+
dataSize = chunkSize;
|
|
61
|
+
}
|
|
62
|
+
offset += 8 + chunkSize;
|
|
63
|
+
if (chunkSize % 2 !== 0) {
|
|
64
|
+
offset++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!fmtData) {
|
|
68
|
+
return {
|
|
69
|
+
format: 'wav',
|
|
70
|
+
size,
|
|
71
|
+
duration: 0,
|
|
72
|
+
audioDataSize: 0,
|
|
73
|
+
frames: [],
|
|
74
|
+
warnings: ['Missing fmt chunk']
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (dataOffset === -1) {
|
|
78
|
+
return {
|
|
79
|
+
format: 'wav',
|
|
80
|
+
size,
|
|
81
|
+
duration: 0,
|
|
82
|
+
audioDataSize: 0,
|
|
83
|
+
frames: [],
|
|
84
|
+
warnings: ['Missing data chunk']
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (fmtData.audioFormat !== 1) {
|
|
88
|
+
warnings.push(`Unsupported audio format: ${fmtData.audioFormat} (only PCM supported)`);
|
|
89
|
+
}
|
|
90
|
+
const bytesPerSample = fmtData.bitsPerSample / 8;
|
|
91
|
+
const totalSamples = dataSize / (bytesPerSample * fmtData.channels);
|
|
92
|
+
const duration = fmtData.sampleRate > 0 ? totalSamples / fmtData.sampleRate : 0;
|
|
93
|
+
const frames = [];
|
|
94
|
+
const samplesPerFrame = fmtData.sampleRate;
|
|
95
|
+
const bytesPerFrame = samplesPerFrame * bytesPerSample * fmtData.channels;
|
|
96
|
+
let frameOffset = dataOffset;
|
|
97
|
+
let frameIndex = 0;
|
|
98
|
+
let remainingBytes = dataSize;
|
|
99
|
+
while (remainingBytes > 0) {
|
|
100
|
+
const frameBytes = Math.min(bytesPerFrame, remainingBytes);
|
|
101
|
+
const frameSamples = frameBytes / (bytesPerSample * fmtData.channels);
|
|
102
|
+
const frameDuration = frameSamples / fmtData.sampleRate;
|
|
103
|
+
frames.push({
|
|
104
|
+
index: frameIndex,
|
|
105
|
+
offset: frameOffset,
|
|
106
|
+
length: frameBytes,
|
|
107
|
+
duration: frameDuration,
|
|
108
|
+
samples: Math.floor(frameSamples),
|
|
109
|
+
sampleRate: fmtData.sampleRate,
|
|
110
|
+
bitrate: Math.round((fmtData.byteRate * 8) / 1000)
|
|
111
|
+
});
|
|
112
|
+
frameOffset += frameBytes;
|
|
113
|
+
remainingBytes -= frameBytes;
|
|
114
|
+
frameIndex++;
|
|
115
|
+
}
|
|
116
|
+
const metadata = {
|
|
117
|
+
format: 'wav',
|
|
118
|
+
size,
|
|
119
|
+
duration,
|
|
120
|
+
audioDataSize: dataSize,
|
|
121
|
+
sampleRate: fmtData.sampleRate,
|
|
122
|
+
channels: fmtData.channels,
|
|
123
|
+
bitDepth: fmtData.bitsPerSample,
|
|
124
|
+
averageBitrate: Math.round((fmtData.byteRate * 8) / 1000),
|
|
125
|
+
frames
|
|
126
|
+
};
|
|
127
|
+
if (warnings.length > 0) {
|
|
128
|
+
metadata.warnings = warnings;
|
|
129
|
+
}
|
|
130
|
+
metadata.metadata = {
|
|
131
|
+
audioFormat: fmtData.audioFormat,
|
|
132
|
+
blockAlign: fmtData.blockAlign,
|
|
133
|
+
riffSize
|
|
134
|
+
};
|
|
135
|
+
return metadata;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.WavParser = WavParser;
|
|
139
|
+
//# sourceMappingURL=WavParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WavParser.js","sourceRoot":"","sources":["../../../src/Parsers/WavParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,4 +13,7 @@ export declare class InvalidRangeError extends HlsStreamerError {
|
|
|
13
13
|
export declare class InvalidParameterError extends HlsStreamerError {
|
|
14
14
|
constructor(parameter: string, value: any);
|
|
15
15
|
}
|
|
16
|
+
export declare class UnsupportedFormatError extends HlsStreamerError {
|
|
17
|
+
constructor(format: string);
|
|
18
|
+
}
|
|
16
19
|
//# sourceMappingURL=HlsStreamerErrors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HlsStreamerErrors.d.ts","sourceRoot":"","sources":["../../../src/errors/HlsStreamerErrors.ts"],"names":[],"mappings":"AAGA,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAKD,qBAAa,iBAAkB,SAAQ,gBAAgB;gBACzC,QAAQ,EAAE,MAAM;CAI7B;AAKD,qBAAa,gBAAiB,SAAQ,gBAAgB;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAKD,qBAAa,iBAAkB,SAAQ,gBAAgB;gBACzC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI/C;AAKD,qBAAa,qBAAsB,SAAQ,gBAAgB;gBAC7C,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG;CAI1C"}
|
|
1
|
+
{"version":3,"file":"HlsStreamerErrors.d.ts","sourceRoot":"","sources":["../../../src/errors/HlsStreamerErrors.ts"],"names":[],"mappings":"AAGA,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAKD,qBAAa,iBAAkB,SAAQ,gBAAgB;gBACzC,QAAQ,EAAE,MAAM;CAI7B;AAKD,qBAAa,gBAAiB,SAAQ,gBAAgB;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAKD,qBAAa,iBAAkB,SAAQ,gBAAgB;gBACzC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI/C;AAKD,qBAAa,qBAAsB,SAAQ,gBAAgB;gBAC7C,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG;CAI1C;AAKD,qBAAa,sBAAuB,SAAQ,gBAAgB;gBAC9C,MAAM,EAAE,MAAM;CAI3B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InvalidParameterError = exports.InvalidRangeError = exports.InvalidFileError = exports.FileNotFoundError = exports.HlsStreamerError = void 0;
|
|
3
|
+
exports.UnsupportedFormatError = exports.InvalidParameterError = exports.InvalidRangeError = exports.InvalidFileError = exports.FileNotFoundError = exports.HlsStreamerError = void 0;
|
|
4
4
|
class HlsStreamerError extends Error {
|
|
5
5
|
constructor(message) {
|
|
6
6
|
super(message);
|
|
@@ -36,4 +36,11 @@ class InvalidParameterError extends HlsStreamerError {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
exports.InvalidParameterError = InvalidParameterError;
|
|
39
|
+
class UnsupportedFormatError extends HlsStreamerError {
|
|
40
|
+
constructor(format) {
|
|
41
|
+
super(`Unsupported audio format: ${format}`);
|
|
42
|
+
this.name = 'UnsupportedFormatError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.UnsupportedFormatError = UnsupportedFormatError;
|
|
39
46
|
//# sourceMappingURL=HlsStreamerErrors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HlsStreamerErrors.js","sourceRoot":"","sources":["../../../src/errors/HlsStreamerErrors.ts"],"names":[],"mappings":";;;AAGA,MAAa,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AALD,4CAKC;AAKD,MAAa,iBAAkB,SAAQ,gBAAgB;IACrD,YAAY,QAAgB;QAC1B,KAAK,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AALD,8CAKC;AAKD,MAAa,gBAAiB,SAAQ,gBAAgB;IACpD,YAAY,OAAe;QACzB,KAAK,CAAC,iBAAiB,OAAO,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AALD,4CAKC;AAKD,MAAa,iBAAkB,SAAQ,gBAAgB;IACrD,YAAY,SAAiB,EAAE,OAAe;QAC5C,KAAK,CAAC,wBAAwB,SAAS,SAAS,OAAO,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AALD,8CAKC;AAKD,MAAa,qBAAsB,SAAQ,gBAAgB;IACzD,YAAY,SAAiB,EAAE,KAAU;QACvC,KAAK,CAAC,sBAAsB,SAAS,MAAM,KAAK,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AALD,sDAKC"}
|
|
1
|
+
{"version":3,"file":"HlsStreamerErrors.js","sourceRoot":"","sources":["../../../src/errors/HlsStreamerErrors.ts"],"names":[],"mappings":";;;AAGA,MAAa,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AALD,4CAKC;AAKD,MAAa,iBAAkB,SAAQ,gBAAgB;IACrD,YAAY,QAAgB;QAC1B,KAAK,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AALD,8CAKC;AAKD,MAAa,gBAAiB,SAAQ,gBAAgB;IACpD,YAAY,OAAe;QACzB,KAAK,CAAC,iBAAiB,OAAO,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AALD,4CAKC;AAKD,MAAa,iBAAkB,SAAQ,gBAAgB;IACrD,YAAY,SAAiB,EAAE,OAAe;QAC5C,KAAK,CAAC,wBAAwB,SAAS,SAAS,OAAO,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AALD,8CAKC;AAKD,MAAa,qBAAsB,SAAQ,gBAAgB;IACzD,YAAY,SAAiB,EAAE,KAAU;QACvC,KAAK,CAAC,sBAAsB,SAAS,MAAM,KAAK,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AALD,sDAKC;AAKD,MAAa,sBAAuB,SAAQ,gBAAgB;IAC1D,YAAY,MAAc;QACxB,KAAK,CAAC,6BAA6B,MAAM,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AALD,wDAKC"}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -5,23 +5,22 @@ export declare class HlsStreamer {
|
|
|
5
5
|
private readonly fileName;
|
|
6
6
|
private readonly baseUrl;
|
|
7
7
|
private readonly enableFastStart;
|
|
8
|
+
private readonly formatOverride;
|
|
8
9
|
private fileInfo?;
|
|
9
|
-
private
|
|
10
|
-
private frameAlignedSegments;
|
|
10
|
+
private segments;
|
|
11
11
|
constructor(options: HlsStreamerOptions);
|
|
12
12
|
private validateOptions;
|
|
13
13
|
private validateFile;
|
|
14
14
|
private getFileInfo;
|
|
15
15
|
getFileBuffer(startByte: number, endByte: number): Promise<Buffer>;
|
|
16
16
|
createM3U8(): Promise<string>;
|
|
17
|
-
private
|
|
17
|
+
private getSegments;
|
|
18
|
+
private computeTargetSizes;
|
|
19
|
+
private computeTargetSizesFromBytes;
|
|
20
|
+
private buildSegmentsWithoutFrameTable;
|
|
18
21
|
private estimateSegmentDuration;
|
|
19
22
|
private buildSegmentUrl;
|
|
20
23
|
private calculateSegmentSize;
|
|
21
|
-
private calculateSegment;
|
|
22
|
-
private getFrameAlignedSegment;
|
|
23
|
-
private findNextMp3Frame;
|
|
24
|
-
private calculatefirst2SegmentSize;
|
|
25
24
|
private padNumber;
|
|
26
25
|
getSegmentDuration(segmentIndex: number): Promise<number>;
|
|
27
26
|
}
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAA8B,MAAM,0BAA0B,CAAC;AAe1F,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,CAAgB;IACjC,OAAO,CAAC,QAAQ,CAA4B;gBAKhC,OAAO,EAAE,kBAAkB;IAYvC,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,YAAY;YAyBN,WAAW;IAkBnB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BlE,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;YA+BrB,WAAW;IAiFzB,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,2BAA2B;IAmBnC,OAAO,CAAC,8BAA8B;IA0BtC,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,SAAS;IAOX,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAchE;AAGD,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -21,6 +21,7 @@ exports.HlsStreamer = void 0;
|
|
|
21
21
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
22
22
|
const node_path_1 = __importDefault(require("node:path"));
|
|
23
23
|
const FileLib_1 = require("./Libs/FileLib");
|
|
24
|
+
const FormatDetector_1 = require("./Libs/FormatDetector");
|
|
24
25
|
const HlsStreamerErrors_1 = require("./errors/HlsStreamerErrors");
|
|
25
26
|
class HlsStreamer {
|
|
26
27
|
constructor(options) {
|
|
@@ -54,31 +55,32 @@ class HlsStreamer {
|
|
|
54
55
|
writable: true,
|
|
55
56
|
value: void 0
|
|
56
57
|
});
|
|
57
|
-
Object.defineProperty(this, "
|
|
58
|
+
Object.defineProperty(this, "formatOverride", {
|
|
58
59
|
enumerable: true,
|
|
59
60
|
configurable: true,
|
|
60
61
|
writable: true,
|
|
61
62
|
value: void 0
|
|
62
63
|
});
|
|
63
|
-
Object.defineProperty(this, "
|
|
64
|
+
Object.defineProperty(this, "fileInfo", {
|
|
64
65
|
enumerable: true,
|
|
65
66
|
configurable: true,
|
|
66
67
|
writable: true,
|
|
67
|
-
value:
|
|
68
|
+
value: void 0
|
|
68
69
|
});
|
|
69
|
-
Object.defineProperty(this, "
|
|
70
|
+
Object.defineProperty(this, "segments", {
|
|
70
71
|
enumerable: true,
|
|
71
72
|
configurable: true,
|
|
72
73
|
writable: true,
|
|
73
|
-
value:
|
|
74
|
+
value: void 0
|
|
74
75
|
});
|
|
75
76
|
this.validateOptions(options);
|
|
76
|
-
this.validateFile(options.filePath);
|
|
77
|
+
this.validateFile(options.filePath, options.format);
|
|
77
78
|
this.filePath = options.filePath;
|
|
78
79
|
this.segmentSize = (options.segmentSizeKB ?? 512) * 1024;
|
|
79
80
|
this.fileName = options.fileName ?? "file";
|
|
80
81
|
this.baseUrl = options.baseUrl ?? "";
|
|
81
82
|
this.enableFastStart = options.enableFastStart ?? false;
|
|
83
|
+
this.formatOverride = options.format || undefined;
|
|
82
84
|
}
|
|
83
85
|
validateOptions(options) {
|
|
84
86
|
if (!options.filePath || typeof options.filePath !== 'string') {
|
|
@@ -92,7 +94,7 @@ class HlsStreamer {
|
|
|
92
94
|
throw new HlsStreamerErrors_1.InvalidParameterError('fileName', options.fileName);
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
|
-
validateFile(filePath) {
|
|
97
|
+
validateFile(filePath, format) {
|
|
96
98
|
if (!node_fs_1.default.existsSync(filePath)) {
|
|
97
99
|
throw new HlsStreamerErrors_1.FileNotFoundError(filePath);
|
|
98
100
|
}
|
|
@@ -100,25 +102,27 @@ class HlsStreamer {
|
|
|
100
102
|
if (!stat.isFile()) {
|
|
101
103
|
throw new HlsStreamerErrors_1.InvalidFileError('Path is not a file');
|
|
102
104
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
if (format) {
|
|
106
|
+
const supportedFormats = ['mp3', 'aac', 'm4a', 'ogg', 'flac', 'wav'];
|
|
107
|
+
if (!supportedFormats.includes(format.toLowerCase())) {
|
|
108
|
+
throw new HlsStreamerErrors_1.UnsupportedFormatError(format);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
if (!FormatDetector_1.FormatDetector.isSupportedExtension(filePath)) {
|
|
113
|
+
const ext = node_path_1.default.extname(filePath);
|
|
114
|
+
throw new HlsStreamerErrors_1.UnsupportedFormatError(ext || 'unknown');
|
|
115
|
+
}
|
|
106
116
|
}
|
|
107
117
|
}
|
|
108
118
|
async getFileInfo() {
|
|
109
119
|
if (!this.fileInfo) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
if (size <= 0) {
|
|
120
|
+
const analysis = await FileLib_1.FileLib.analyzeAudioFile(this.filePath, this.formatOverride);
|
|
121
|
+
if (analysis.size <= 0) {
|
|
113
122
|
throw new HlsStreamerErrors_1.InvalidFileError('File is empty');
|
|
114
123
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
this.fileInfo = {
|
|
119
|
-
size,
|
|
120
|
-
duration: 0
|
|
121
|
-
};
|
|
124
|
+
this.fileInfo = analysis;
|
|
125
|
+
this.segments = undefined;
|
|
122
126
|
}
|
|
123
127
|
return this.fileInfo;
|
|
124
128
|
}
|
|
@@ -130,26 +134,14 @@ class HlsStreamer {
|
|
|
130
134
|
if (endByte > fileInfo.size) {
|
|
131
135
|
throw new HlsStreamerErrors_1.InvalidRangeError(startByte, endByte);
|
|
132
136
|
}
|
|
133
|
-
const
|
|
134
|
-
const searchStartByte = Math.max(0, startByte - 1024);
|
|
135
|
-
const searchEndByte = Math.min(fileInfo.size, startByte + searchBufferSize);
|
|
137
|
+
const length = endByte - startByte;
|
|
136
138
|
const fd = node_fs_1.default.openSync(this.filePath, "r");
|
|
137
139
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const targetOffsetInSearch = startByte - searchStartByte;
|
|
141
|
-
const frameAlignedOffsetInSearch = this.findNextMp3Frame(searchBuffer, Math.max(0, targetOffsetInSearch));
|
|
142
|
-
const actualStartByte = searchStartByte + frameAlignedOffsetInSearch;
|
|
143
|
-
const actualEndByte = Math.min(endByte, fileInfo.size);
|
|
144
|
-
const bufferLength = actualEndByte - actualStartByte;
|
|
145
|
-
if (bufferLength <= 0) {
|
|
146
|
-
const fallbackLength = endByte - startByte;
|
|
147
|
-
const fallbackBuffer = Buffer.alloc(fallbackLength);
|
|
148
|
-
const bytesRead = node_fs_1.default.readSync(fd, fallbackBuffer, 0, fallbackLength, startByte);
|
|
149
|
-
return fallbackBuffer.subarray(0, bytesRead);
|
|
140
|
+
if (length === 0) {
|
|
141
|
+
return Buffer.alloc(0);
|
|
150
142
|
}
|
|
151
|
-
const buffer = Buffer.alloc(
|
|
152
|
-
const bytesRead = node_fs_1.default.readSync(fd, buffer, 0,
|
|
143
|
+
const buffer = Buffer.alloc(length);
|
|
144
|
+
const bytesRead = node_fs_1.default.readSync(fd, buffer, 0, length, startByte);
|
|
153
145
|
return buffer.subarray(0, bytesRead);
|
|
154
146
|
}
|
|
155
147
|
finally {
|
|
@@ -157,40 +149,146 @@ class HlsStreamer {
|
|
|
157
149
|
}
|
|
158
150
|
}
|
|
159
151
|
async createM3U8() {
|
|
160
|
-
const fileInfo = await
|
|
161
|
-
|
|
152
|
+
const [fileInfo, segments] = await Promise.all([
|
|
153
|
+
this.getFileInfo(),
|
|
154
|
+
this.getSegments()
|
|
155
|
+
]);
|
|
156
|
+
if (!segments.length) {
|
|
157
|
+
throw new HlsStreamerErrors_1.InvalidFileError('Unable to generate segments for MP3 file');
|
|
158
|
+
}
|
|
159
|
+
const maxSegmentDuration = segments.reduce((max, segment) => Math.max(max, segment.duration), 0);
|
|
160
|
+
const targetDurationSeconds = Math.max(1, Math.ceil(maxSegmentDuration || fileInfo.duration || 1));
|
|
162
161
|
const m3u8 = [
|
|
163
162
|
'#EXTM3U',
|
|
164
163
|
'#EXT-X-VERSION:6',
|
|
165
164
|
'#EXT-X-PLAYLIST-TYPE:VOD',
|
|
166
|
-
|
|
165
|
+
`#EXT-X-TARGETDURATION:${targetDurationSeconds}`,
|
|
167
166
|
'#EXT-X-MEDIA-SEQUENCE:0',
|
|
168
167
|
];
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
const segmentUrl = this.buildSegmentUrl(start, end, i);
|
|
173
|
-
m3u8.push(`#EXTINF:${estimatedDuration.toFixed(3)}`);
|
|
168
|
+
segments.forEach((segment, index) => {
|
|
169
|
+
const segmentUrl = this.buildSegmentUrl(segment.start, segment.end, index);
|
|
170
|
+
m3u8.push(`#EXTINF:${segment.duration.toFixed(3)}`);
|
|
174
171
|
m3u8.push(segmentUrl);
|
|
175
|
-
}
|
|
172
|
+
});
|
|
176
173
|
m3u8.push('#EXT-X-ENDLIST');
|
|
177
174
|
return m3u8.join('\n');
|
|
178
175
|
}
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
return
|
|
176
|
+
async getSegments() {
|
|
177
|
+
if (this.segments) {
|
|
178
|
+
return this.segments;
|
|
179
|
+
}
|
|
180
|
+
const fileInfo = await this.getFileInfo();
|
|
181
|
+
if (!fileInfo.frames.length) {
|
|
182
|
+
this.segments = this.buildSegmentsWithoutFrameTable(fileInfo);
|
|
183
|
+
return this.segments;
|
|
184
|
+
}
|
|
185
|
+
const targetSizes = this.computeTargetSizes(fileInfo);
|
|
186
|
+
const segments = [];
|
|
187
|
+
const frames = fileInfo.frames;
|
|
188
|
+
let frameCursor = 0;
|
|
189
|
+
const consumeFrames = (targetBytes) => {
|
|
190
|
+
if (frameCursor >= frames.length) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const startFrameIndex = frameCursor;
|
|
194
|
+
let consumedBytes = 0;
|
|
195
|
+
let duration = 0;
|
|
196
|
+
while (frameCursor < frames.length) {
|
|
197
|
+
const frame = frames[frameCursor];
|
|
198
|
+
const nextBytes = consumedBytes + frame.length;
|
|
199
|
+
if (consumedBytes > 0 && nextBytes > targetBytes) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
consumedBytes = nextBytes;
|
|
203
|
+
duration += frame.duration;
|
|
204
|
+
frameCursor++;
|
|
205
|
+
if (consumedBytes >= targetBytes) {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (frameCursor === startFrameIndex && frameCursor < frames.length) {
|
|
210
|
+
const frame = frames[frameCursor];
|
|
211
|
+
consumedBytes += frame.length;
|
|
212
|
+
duration += frame.duration;
|
|
213
|
+
frameCursor++;
|
|
214
|
+
}
|
|
215
|
+
if (frameCursor === startFrameIndex) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const start = frames[startFrameIndex].offset;
|
|
219
|
+
const lastFrame = frames[frameCursor - 1];
|
|
220
|
+
const end = lastFrame.offset + lastFrame.length;
|
|
221
|
+
segments.push({
|
|
222
|
+
start,
|
|
223
|
+
end,
|
|
224
|
+
duration
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
targetSizes.forEach(consumeFrames);
|
|
228
|
+
while (frameCursor < frames.length) {
|
|
229
|
+
consumeFrames(this.segmentSize);
|
|
230
|
+
}
|
|
231
|
+
if (!segments.length) {
|
|
232
|
+
this.segments = this.buildSegmentsWithoutFrameTable(fileInfo);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
this.segments = segments;
|
|
236
|
+
}
|
|
237
|
+
return this.segments;
|
|
238
|
+
}
|
|
239
|
+
computeTargetSizes(fileInfo) {
|
|
240
|
+
const totalBytes = fileInfo.audioDataSize || fileInfo.size;
|
|
241
|
+
return this.computeTargetSizesFromBytes(totalBytes);
|
|
242
|
+
}
|
|
243
|
+
computeTargetSizesFromBytes(totalBytes) {
|
|
244
|
+
if (totalBytes <= 0) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
const targets = [];
|
|
248
|
+
let remaining = totalBytes;
|
|
249
|
+
let index = 0;
|
|
250
|
+
while (remaining > 0) {
|
|
251
|
+
const targetSize = Math.min(this.calculateSegmentSize(index), remaining);
|
|
252
|
+
targets.push(targetSize);
|
|
253
|
+
remaining -= targetSize;
|
|
254
|
+
index++;
|
|
255
|
+
}
|
|
256
|
+
return targets.length ? targets : [totalBytes];
|
|
257
|
+
}
|
|
258
|
+
buildSegmentsWithoutFrameTable(fileInfo) {
|
|
259
|
+
const segments = [];
|
|
260
|
+
const totalBytes = Math.max(fileInfo.size, fileInfo.audioDataSize);
|
|
261
|
+
if (totalBytes <= 0) {
|
|
262
|
+
return segments;
|
|
263
|
+
}
|
|
264
|
+
const targets = this.computeTargetSizesFromBytes(totalBytes);
|
|
265
|
+
let start = 0;
|
|
266
|
+
targets.forEach((targetSize) => {
|
|
267
|
+
const end = Math.min(totalBytes, start + targetSize);
|
|
268
|
+
const duration = this.estimateSegmentDuration(end - start, fileInfo);
|
|
269
|
+
segments.push({ start, end, duration });
|
|
270
|
+
start = end;
|
|
271
|
+
});
|
|
272
|
+
if (start < totalBytes) {
|
|
273
|
+
const duration = this.estimateSegmentDuration(totalBytes - start, fileInfo);
|
|
274
|
+
segments.push({ start, end: totalBytes, duration });
|
|
182
275
|
}
|
|
183
|
-
|
|
184
|
-
const remainingSize = fileSize - firstTwoSegmentsSize;
|
|
185
|
-
return Math.ceil(remainingSize / this.segmentSize) + 2;
|
|
276
|
+
return segments;
|
|
186
277
|
}
|
|
187
|
-
estimateSegmentDuration(segmentSize) {
|
|
188
|
-
|
|
278
|
+
estimateSegmentDuration(segmentSize, fileInfo) {
|
|
279
|
+
if (fileInfo.duration > 0 && fileInfo.audioDataSize > 0) {
|
|
280
|
+
const bytesPerSecond = fileInfo.audioDataSize / fileInfo.duration;
|
|
281
|
+
return segmentSize / bytesPerSecond;
|
|
282
|
+
}
|
|
283
|
+
const estimatedBytesPerSecond = fileInfo.averageBitrate
|
|
284
|
+
? (fileInfo.averageBitrate * 1000) / 8
|
|
285
|
+
: 16000;
|
|
189
286
|
return segmentSize / estimatedBytesPerSecond;
|
|
190
287
|
}
|
|
191
288
|
buildSegmentUrl(start, end, index) {
|
|
192
289
|
const baseUrlPrefix = this.baseUrl ? `/${this.baseUrl}` : '';
|
|
193
|
-
|
|
290
|
+
const ext = this.fileInfo?.format || node_path_1.default.extname(this.filePath).toLowerCase().slice(1) || 'mp3';
|
|
291
|
+
return `${baseUrlPrefix}/${start}/${end}/${this.fileName}${this.padNumber(index, 3)}.${ext}`;
|
|
194
292
|
}
|
|
195
293
|
calculateSegmentSize(segmentIndex) {
|
|
196
294
|
if (!this.enableFastStart) {
|
|
@@ -205,94 +303,19 @@ class HlsStreamer {
|
|
|
205
303
|
return this.segmentSize;
|
|
206
304
|
}
|
|
207
305
|
}
|
|
208
|
-
calculateSegment(segmentIndex, fileSize) {
|
|
209
|
-
let start = 0;
|
|
210
|
-
if (this.enableFastStart && segmentIndex < 2) {
|
|
211
|
-
start = segmentIndex * (this.segmentSize / 4);
|
|
212
|
-
}
|
|
213
|
-
else if (this.enableFastStart) {
|
|
214
|
-
start = (3 * this.segmentSize) / 4 + (segmentIndex - 2) * this.segmentSize;
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
start = segmentIndex * this.segmentSize;
|
|
218
|
-
}
|
|
219
|
-
const segmentSize = this.calculateSegmentSize(segmentIndex);
|
|
220
|
-
const end = Math.min(start + segmentSize, fileSize);
|
|
221
|
-
return { start: Math.floor(start), end: Math.floor(end) };
|
|
222
|
-
}
|
|
223
|
-
async getFrameAlignedSegment(segmentIndex, fileSize) {
|
|
224
|
-
if (this.frameAlignedSegments.has(segmentIndex)) {
|
|
225
|
-
return this.frameAlignedSegments.get(segmentIndex);
|
|
226
|
-
}
|
|
227
|
-
const rawSegment = this.calculateSegment(segmentIndex, fileSize);
|
|
228
|
-
if (segmentIndex === 0) {
|
|
229
|
-
const fd = node_fs_1.default.openSync(this.filePath, "r");
|
|
230
|
-
try {
|
|
231
|
-
const headerBuffer = Buffer.alloc(Math.min(8192, fileSize));
|
|
232
|
-
node_fs_1.default.readSync(fd, headerBuffer, 0, headerBuffer.length, 0);
|
|
233
|
-
const frameStart = this.findNextMp3Frame(headerBuffer, 0);
|
|
234
|
-
const alignedSegment = { start: frameStart, end: rawSegment.end };
|
|
235
|
-
this.frameAlignedSegments.set(segmentIndex, alignedSegment);
|
|
236
|
-
return alignedSegment;
|
|
237
|
-
}
|
|
238
|
-
finally {
|
|
239
|
-
node_fs_1.default.closeSync(fd);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
this.frameAlignedSegments.set(segmentIndex, rawSegment);
|
|
243
|
-
return rawSegment;
|
|
244
|
-
}
|
|
245
|
-
findNextMp3Frame(buffer, startPos) {
|
|
246
|
-
let pos = startPos;
|
|
247
|
-
if (pos === 0 && buffer.length > 10 && buffer.toString('ascii', 0, 3) === 'ID3') {
|
|
248
|
-
const tagSize = ((buffer[6] & 0x7f) << 21) |
|
|
249
|
-
((buffer[7] & 0x7f) << 14) |
|
|
250
|
-
((buffer[8] & 0x7f) << 7) |
|
|
251
|
-
(buffer[9] & 0x7f);
|
|
252
|
-
pos = 10 + tagSize;
|
|
253
|
-
}
|
|
254
|
-
while (pos < buffer.length - 1) {
|
|
255
|
-
if (buffer[pos] === 0xFF && (buffer[pos + 1] & 0xE0) === 0xE0) {
|
|
256
|
-
if (pos + 3 < buffer.length) {
|
|
257
|
-
const header = (buffer[pos] << 24) |
|
|
258
|
-
(buffer[pos + 1] << 16) |
|
|
259
|
-
(buffer[pos + 2] << 8) |
|
|
260
|
-
buffer[pos + 3];
|
|
261
|
-
const version = (header >> 19) & 0x3;
|
|
262
|
-
const layer = (header >> 17) & 0x3;
|
|
263
|
-
const bitrateIndex = (header >> 12) & 0xF;
|
|
264
|
-
const sampleRateIndex = (header >> 10) & 0x3;
|
|
265
|
-
if (version !== 1 && layer !== 0 && bitrateIndex !== 0 &&
|
|
266
|
-
bitrateIndex !== 15 && sampleRateIndex !== 3) {
|
|
267
|
-
return pos;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
pos++;
|
|
272
|
-
}
|
|
273
|
-
return startPos;
|
|
274
|
-
}
|
|
275
|
-
calculatefirst2SegmentSize() {
|
|
276
|
-
return (this.segmentSize / 4) * 3;
|
|
277
|
-
}
|
|
278
306
|
padNumber(value, padding) {
|
|
279
307
|
return value.toString().padStart(padding, '0');
|
|
280
308
|
}
|
|
281
309
|
async getSegmentDuration(segmentIndex) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
return cachedSegment.duration;
|
|
310
|
+
if (!Number.isInteger(segmentIndex) || segmentIndex < 0) {
|
|
311
|
+
throw new HlsStreamerErrors_1.InvalidParameterError('segmentIndex', segmentIndex);
|
|
285
312
|
}
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
end,
|
|
293
|
-
duration
|
|
294
|
-
});
|
|
295
|
-
return duration;
|
|
313
|
+
const segments = await this.getSegments();
|
|
314
|
+
const segment = segments[segmentIndex];
|
|
315
|
+
if (!segment) {
|
|
316
|
+
throw new HlsStreamerErrors_1.InvalidParameterError('segmentIndex', segmentIndex);
|
|
317
|
+
}
|
|
318
|
+
return segment.duration;
|
|
296
319
|
}
|
|
297
320
|
}
|
|
298
321
|
exports.HlsStreamer = HlsStreamer;
|