-
Notifications
You must be signed in to change notification settings - Fork 7
/
parser.js
142 lines (114 loc) · 3.94 KB
/
parser.js
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import jDataView from 'jdataview';
// Offsets
export const BIF_INDEX_OFFSET = 64;
export const FRAMEWISE_SEPARATION_OFFSET = 16;
export const NUMBER_OF_BIF_IMAGES_OFFSET = 12;
export const VERSION_OFFSET = 8;
// Metadata
export const BIF_INDEX_ENTRY_LENGTH = 8;
// Magic Number
// SEE: https://sdkdocs.roku.com/display/sdkdoc/Trick+Mode+Support#TrickModeSupport-MagicNumber
export const MAGIC_NUMBER = new Uint8Array([
'0x89',
'0x42',
'0x49',
'0x46',
'0x0d',
'0x0a',
'0x1a',
'0x0a',
]);
/**
* Validate the file identifier against the magic number.
*
* @returns {boolean} isValid
*/
function validate(magicNumber) {
let isValid = true;
MAGIC_NUMBER.forEach((byte, i) => {
if (byte !== magicNumber[i]) {
isValid = false;
return;
}
});
return isValid;
}
/**
* Parsing and read BIF file format.
*
* @param {ArrayBuffer} arrayBuffer
*/
export class BIFParser {
constructor(arrayBuffer) {
// Magic Number
// SEE: https://sdkdocs.roku.com/display/sdkdoc/Trick+Mode+Support#TrickModeSupport-MagicNumber
const magicNumber = new Uint8Array(arrayBuffer).slice(0, 8);
if (!validate(magicNumber)) {
throw new Error('Invalid BIF file.');
}
this.arrayBuffer = arrayBuffer;
this.data = new jDataView(arrayBuffer); // eslint-disable-line new-cap
// Framewise Separation
// SEE: https://sdkdocs.roku.com/display/sdkdoc/Trick+Mode+Support#TrickModeSupport-FramewiseSeparation
this.framewiseSeparation = this.data.getUint32(FRAMEWISE_SEPARATION_OFFSET, true) || 1000;
// Number of BIF images
// SEE: https://sdkdocs.roku.com/display/sdkdoc/Trick+Mode+Support#TrickModeSupport-NumberofBIFimages
this.numberOfBIFImages = this.data.getUint32(NUMBER_OF_BIF_IMAGES_OFFSET, true);
// Version
// SEE: https://sdkdocs.roku.com/display/sdkdoc/Trick+Mode+Support#TrickModeSupport-Version
this.version = this.data.getUint32(VERSION_OFFSET, true);
this.bifIndex = this.generateBIFIndex(true);
}
/**
* Create the BIF index
* SEE: https://sdkdocs.roku.com/display/sdkdoc/Trick+Mode+Support#TrickModeSupport-BIFindex
*
* @returns {Array} bifIndex
*/
generateBIFIndex() {
const bifIndex = [];
for (
// BIF index starts at byte 64 (BIF_INDEX_OFFSET)
let i = 0, bifIndexEntryOffset = BIF_INDEX_OFFSET;
i < this.numberOfBIFImages;
i += 1, bifIndexEntryOffset += BIF_INDEX_ENTRY_LENGTH
) {
const bifIndexEntryTimestampOffset = bifIndexEntryOffset;
const bifIndexEntryAbsoluteOffset = bifIndexEntryOffset + 4;
const nextBifIndexEntryAbsoluteOffset = bifIndexEntryAbsoluteOffset + BIF_INDEX_ENTRY_LENGTH;
// Documented example, items within `[]`are used to generate the frame.
// 64, 65, 66, 67 | 68, 69, 70, 71
// [Frame 0 timestamp] | [absolute offset of frame]
// 72, 73, 74, 75 | 76, 77, 78, 79
// Frame 1 timestamp | [absolute offset of frame]
const offset = this.data.getUint32(bifIndexEntryAbsoluteOffset, true);
const nextOffset = this.data.getUint32(nextBifIndexEntryAbsoluteOffset, true);
const timestamp = this.data.getUint32(bifIndexEntryTimestampOffset, true);
bifIndex.push({
offset,
timestamp,
length: nextOffset - offset,
});
}
return bifIndex;
}
/**
* Return image data for a specific frame of a movie.
*
* @param {number} second
* @returns {string} imageData
*/
getImageDataAtSecond(second) {
const image = 'data:image/jpeg;base64,';
// since frames are defined at an interval of `this.framewiseSeparation`,
// we need to convert the time into an appropriate frame number.
const frameNumber = Math.floor(second / (this.framewiseSeparation / 1000));
const frame = this.bifIndex[frameNumber];
if (!frame) {
return image;
}
return `${image}${btoa(String.fromCharCode.apply(null,
new Uint8Array(this.arrayBuffer.slice(frame.offset, frame.offset + frame.length))
))}`;
}
}