UNPKG

7.03 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 const boxes = event.data.segment.boxes || {
21 data: event.data.segment.data
22 };
23
24 const result = {
25 type,
26 // cast ArrayBuffer to TypedArray
27 data: new Uint8Array(
28 boxes.data,
29 boxes.data.byteOffset,
30 boxes.data.byteLength
31 ),
32 initSegment: new Uint8Array(
33 initSegment.data,
34 initSegment.byteOffset,
35 initSegment.byteLength
36 )
37 };
38
39 if (typeof videoFrameDtsTime !== 'undefined') {
40 result.videoFrameDtsTime = videoFrameDtsTime;
41 }
42
43 if (typeof videoFramePtsTime !== 'undefined') {
44 result.videoFramePtsTime = videoFramePtsTime;
45 }
46
47 callback(result);
48};
49
50export const handleDone_ = ({
51 transmuxedData,
52 callback
53}) => {
54 // Previously we only returned data on data events,
55 // not on done events. Clear out the buffer to keep that consistent.
56 transmuxedData.buffer = [];
57
58 // all buffers should have been flushed from the muxer, so start processing anything we
59 // have received
60 callback(transmuxedData);
61};
62
63export const handleGopInfo_ = (event, transmuxedData) => {
64 transmuxedData.gopInfo = event.data.gopInfo;
65};
66
67export const processTransmux = (options) => {
68 const {
69 transmuxer,
70 bytes,
71 audioAppendStart,
72 gopsToAlignWith,
73 remux,
74 onData,
75 onTrackInfo,
76 onAudioTimingInfo,
77 onVideoTimingInfo,
78 onVideoSegmentTimingInfo,
79 onAudioSegmentTimingInfo,
80 onId3,
81 onCaptions,
82 onDone,
83 onEndedTimeline,
84 onTransmuxerLog,
85 isEndOfTimeline
86 } = options;
87 const transmuxedData = {
88 buffer: []
89 };
90 let waitForEndedTimelineEvent = isEndOfTimeline;
91
92 const handleMessage = (event) => {
93 if (transmuxer.currentTransmux !== options) {
94 // disposed
95 return;
96 }
97
98 if (event.data.action === 'data') {
99 handleData_(event, transmuxedData, onData);
100 }
101 if (event.data.action === 'trackinfo') {
102 onTrackInfo(event.data.trackInfo);
103 }
104 if (event.data.action === 'gopInfo') {
105 handleGopInfo_(event, transmuxedData);
106 }
107 if (event.data.action === 'audioTimingInfo') {
108 onAudioTimingInfo(event.data.audioTimingInfo);
109 }
110 if (event.data.action === 'videoTimingInfo') {
111 onVideoTimingInfo(event.data.videoTimingInfo);
112 }
113 if (event.data.action === 'videoSegmentTimingInfo') {
114 onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
115 }
116 if (event.data.action === 'audioSegmentTimingInfo') {
117 onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo);
118 }
119 if (event.data.action === 'id3Frame') {
120 onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
121 }
122 if (event.data.action === 'caption') {
123 onCaptions(event.data.caption);
124 }
125 if (event.data.action === 'endedtimeline') {
126 waitForEndedTimelineEvent = false;
127 onEndedTimeline();
128 }
129 if (event.data.action === 'log') {
130 onTransmuxerLog(event.data.log);
131 }
132
133 // wait for the transmuxed event since we may have audio and video
134 if (event.data.type !== 'transmuxed') {
135 return;
136 }
137
138 // If the "endedtimeline" event has not yet fired, and this segment represents the end
139 // of a timeline, that means there may still be data events before the segment
140 // processing can be considerred complete. In that case, the final event should be
141 // an "endedtimeline" event with the type "transmuxed."
142 if (waitForEndedTimelineEvent) {
143 return;
144 }
145
146 transmuxer.onmessage = null;
147 handleDone_({
148 transmuxedData,
149 callback: onDone
150 });
151
152 /* eslint-disable no-use-before-define */
153 dequeue(transmuxer);
154 /* eslint-enable */
155 };
156
157 transmuxer.onmessage = handleMessage;
158
159 if (audioAppendStart) {
160 transmuxer.postMessage({
161 action: 'setAudioAppendStart',
162 appendStart: audioAppendStart
163 });
164 }
165
166 // allow empty arrays to be passed to clear out GOPs
167 if (Array.isArray(gopsToAlignWith)) {
168 transmuxer.postMessage({
169 action: 'alignGopsWith',
170 gopsToAlignWith
171 });
172 }
173
174 if (typeof remux !== 'undefined') {
175 transmuxer.postMessage({
176 action: 'setRemux',
177 remux
178 });
179 }
180
181 if (bytes.byteLength) {
182 const buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
183 const byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
184
185 transmuxer.postMessage(
186 {
187 action: 'push',
188 // Send the typed-array of data as an ArrayBuffer so that
189 // it can be sent as a "Transferable" and avoid the costly
190 // memory copy
191 data: buffer,
192 // To recreate the original typed-array, we need information
193 // about what portion of the ArrayBuffer it was a view into
194 byteOffset,
195 byteLength: bytes.byteLength
196 },
197 [ buffer ]
198 );
199 }
200
201 if (isEndOfTimeline) {
202 transmuxer.postMessage({ action: 'endTimeline' });
203 }
204 // even if we didn't push any bytes, we have to make sure we flush in case we reached
205 // the end of the segment
206 transmuxer.postMessage({ action: 'flush' });
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};