UNPKG

7.06 kBJavaScriptView Raw
1import TransmuxWorker from 'worker!./transmuxer-worker.js';
2
3export const handleData_ = (event, transmuxedData, callback) => {
4 const {
5 type,
6 initSegment,
7 captions,
8 captionStreams,
9 metadata,
10 videoFrameDtsTime,
11 videoFramePtsTime
12 } = event.data.segment;
13
14 transmuxedData.buffer.push({
15 captions,
16 captionStreams,
17 metadata
18 });
19
20 // right now, boxes will come back from partial transmuxer, data from full
21 const boxes = event.data.segment.boxes || {
22 data: event.data.segment.data
23 };
24
25 const result = {
26 type,
27 // cast ArrayBuffer to TypedArray
28 data: new Uint8Array(
29 boxes.data,
30 boxes.data.byteOffset,
31 boxes.data.byteLength
32 ),
33 initSegment: new Uint8Array(
34 initSegment.data,
35 initSegment.byteOffset,
36 initSegment.byteLength
37 )
38 };
39
40 if (typeof videoFrameDtsTime !== 'undefined') {
41 result.videoFrameDtsTime = videoFrameDtsTime;
42 }
43
44 if (typeof videoFramePtsTime !== 'undefined') {
45 result.videoFramePtsTime = videoFramePtsTime;
46 }
47
48 callback(result);
49};
50
51export const handleDone_ = ({
52 transmuxedData,
53 callback
54}) => {
55 // Previously we only returned data on data events,
56 // not on done events. Clear out the buffer to keep that consistent.
57 transmuxedData.buffer = [];
58
59 // all buffers should have been flushed from the muxer, so start processing anything we
60 // have received
61 callback(transmuxedData);
62};
63
64export const handleGopInfo_ = (event, transmuxedData) => {
65 transmuxedData.gopInfo = event.data.gopInfo;
66};
67
68export const processTransmux = (options) => {
69 const {
70 transmuxer,
71 bytes,
72 audioAppendStart,
73 gopsToAlignWith,
74 isPartial,
75 remux,
76 onData,
77 onTrackInfo,
78 onAudioTimingInfo,
79 onVideoTimingInfo,
80 onVideoSegmentTimingInfo,
81 onAudioSegmentTimingInfo,
82 onId3,
83 onCaptions,
84 onDone,
85 onEndedTimeline,
86 isEndOfTimeline
87 } = options;
88 const transmuxedData = {
89 isPartial,
90 buffer: []
91 };
92 let waitForEndedTimelineEvent = isEndOfTimeline;
93
94 const handleMessage = (event) => {
95 if (transmuxer.currentTransmux !== options) {
96 // disposed
97 return;
98 }
99
100 if (event.data.action === 'data') {
101 handleData_(event, transmuxedData, onData);
102 }
103 if (event.data.action === 'trackinfo') {
104 onTrackInfo(event.data.trackInfo);
105 }
106 if (event.data.action === 'gopInfo') {
107 handleGopInfo_(event, transmuxedData);
108 }
109 if (event.data.action === 'audioTimingInfo') {
110 onAudioTimingInfo(event.data.audioTimingInfo);
111 }
112 if (event.data.action === 'videoTimingInfo') {
113 onVideoTimingInfo(event.data.videoTimingInfo);
114 }
115 if (event.data.action === 'videoSegmentTimingInfo') {
116 onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
117 }
118 if (event.data.action === 'audioSegmentTimingInfo') {
119 onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo);
120 }
121 if (event.data.action === 'id3Frame') {
122 onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
123 }
124 if (event.data.action === 'caption') {
125 onCaptions(event.data.caption);
126 }
127 if (event.data.action === 'endedtimeline') {
128 waitForEndedTimelineEvent = false;
129 onEndedTimeline();
130 }
131
132 // wait for the transmuxed event since we may have audio and video
133 if (event.data.type !== 'transmuxed') {
134 return;
135 }
136
137 // If the "endedtimeline" event has not yet fired, and this segment represents the end
138 // of a timeline, that means there may still be data events before the segment
139 // processing can be considerred complete. In that case, the final event should be
140 // an "endedtimeline" event with the type "transmuxed."
141 if (waitForEndedTimelineEvent) {
142 return;
143 }
144
145 transmuxer.onmessage = null;
146 handleDone_({
147 transmuxedData,
148 callback: onDone
149 });
150
151 /* eslint-disable no-use-before-define */
152 dequeue(transmuxer);
153 /* eslint-enable */
154 };
155
156 transmuxer.onmessage = handleMessage;
157
158 if (audioAppendStart) {
159 transmuxer.postMessage({
160 action: 'setAudioAppendStart',
161 appendStart: audioAppendStart
162 });
163 }
164
165 // allow empty arrays to be passed to clear out GOPs
166 if (Array.isArray(gopsToAlignWith)) {
167 transmuxer.postMessage({
168 action: 'alignGopsWith',
169 gopsToAlignWith
170 });
171 }
172
173 if (typeof remux !== 'undefined') {
174 transmuxer.postMessage({
175 action: 'setRemux',
176 remux
177 });
178 }
179
180 if (bytes.byteLength) {
181 const buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
182 const byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
183
184 transmuxer.postMessage(
185 {
186 action: 'push',
187 // Send the typed-array of data as an ArrayBuffer so that
188 // it can be sent as a "Transferable" and avoid the costly
189 // memory copy
190 data: buffer,
191 // To recreate the original typed-array, we need information
192 // about what portion of the ArrayBuffer it was a view into
193 byteOffset,
194 byteLength: bytes.byteLength
195 },
196 [ buffer ]
197 );
198 }
199
200 // even if we didn't push any bytes, we have to make sure we flush in case we reached
201 // the end of the segment
202 transmuxer.postMessage({ action: isPartial ? 'partialFlush' : 'flush' });
203
204 if (isEndOfTimeline) {
205 transmuxer.postMessage({ action: 'endTimeline' });
206 }
207};
208
209export const dequeue = (transmuxer) => {
210 transmuxer.currentTransmux = null;
211 if (transmuxer.transmuxQueue.length) {
212 transmuxer.currentTransmux = transmuxer.transmuxQueue.shift();
213 if (typeof transmuxer.currentTransmux === 'function') {
214 transmuxer.currentTransmux();
215 } else {
216 processTransmux(transmuxer.currentTransmux);
217 }
218 }
219};
220
221export const processAction = (transmuxer, action) => {
222 transmuxer.postMessage({ action });
223 dequeue(transmuxer);
224};
225
226export const enqueueAction = (action, transmuxer) => {
227 if (!transmuxer.currentTransmux) {
228 transmuxer.currentTransmux = action;
229 processAction(transmuxer, action);
230 return;
231 }
232 transmuxer.transmuxQueue.push(processAction.bind(null, transmuxer, action));
233};
234
235export const reset = (transmuxer) => {
236 enqueueAction('reset', transmuxer);
237};
238
239export const endTimeline = (transmuxer) => {
240 enqueueAction('endTimeline', transmuxer);
241};
242
243export const transmux = (options) => {
244 if (!options.transmuxer.currentTransmux) {
245 options.transmuxer.currentTransmux = options;
246 processTransmux(options);
247 return;
248 }
249 options.transmuxer.transmuxQueue.push(options);
250};
251
252export const createTransmuxer = (options) => {
253 const transmuxer = new TransmuxWorker();
254
255 transmuxer.currentTransmux = null;
256 transmuxer.transmuxQueue = [];
257 const term = transmuxer.terminate;
258
259 transmuxer.terminate = () => {
260 transmuxer.currentTransmux = null;
261 transmuxer.transmuxQueue.length = 0;
262 return term.call(transmuxer);
263 };
264
265 transmuxer.postMessage({action: 'init', options});
266
267 return transmuxer;
268};
269
270export default {
271 reset,
272 endTimeline,
273 transmux,
274 createTransmuxer
275};