@alessmicrosystems/mpegts.js 1.8.1
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/LICENSE +202 -0
- package/README.md +158 -0
- package/README_ja.md +153 -0
- package/README_zh.md +157 -0
- package/d.ts/mpegts.d.ts +524 -0
- package/d.ts/src/core/mse-events.d.ts +9 -0
- package/d.ts/src/core/transmuxing-events.d.ts +24 -0
- package/d.ts/src/demux/aac.d.ts +44 -0
- package/d.ts/src/demux/ac3.d.ts +70 -0
- package/d.ts/src/demux/av1-parser.d.ts +77 -0
- package/d.ts/src/demux/av1.d.ts +11 -0
- package/d.ts/src/demux/base-demuxer.d.ts +55 -0
- package/d.ts/src/demux/h264.d.ts +40 -0
- package/d.ts/src/demux/h265.d.ts +65 -0
- package/d.ts/src/demux/klv.d.ts +17 -0
- package/d.ts/src/demux/mp3.d.ts +6 -0
- package/d.ts/src/demux/mpeg4-audio.d.ts +28 -0
- package/d.ts/src/demux/pat-pmt-pes.d.ts +106 -0
- package/d.ts/src/demux/patpmt.d.ts +40 -0
- package/d.ts/src/demux/pes-private-data.d.ts +14 -0
- package/d.ts/src/demux/pgs-data.d.ts +9 -0
- package/d.ts/src/demux/scte35.d.ts +250 -0
- package/d.ts/src/demux/sei.d.ts +8 -0
- package/d.ts/src/demux/smpte2038.d.ts +22 -0
- package/d.ts/src/demux/ts-demuxer.d.ts +124 -0
- package/d.ts/src/player/live-latency-chaser.d.ts +10 -0
- package/d.ts/src/player/live-latency-synchronizer.d.ts +10 -0
- package/d.ts/src/player/loading-controller.d.ts +19 -0
- package/d.ts/src/player/mse-player.d.ts +30 -0
- package/d.ts/src/player/player-engine-dedicated-thread-worker.d.ts +2 -0
- package/d.ts/src/player/player-engine-dedicated-thread.d.ts +48 -0
- package/d.ts/src/player/player-engine-main-thread.d.ts +50 -0
- package/d.ts/src/player/player-engine-worker-cmd-def.d.ts +25 -0
- package/d.ts/src/player/player-engine-worker-msg-def.d.ts +54 -0
- package/d.ts/src/player/player-engine-worker.d.ts +2 -0
- package/d.ts/src/player/player-engine.d.ts +16 -0
- package/d.ts/src/player/player-events.d.ts +21 -0
- package/d.ts/src/player/seeking-handler.d.ts +22 -0
- package/d.ts/src/player/startup-stall-jumper.d.ts +14 -0
- package/d.ts/src/utils/typedarray-equality.d.ts +2 -0
- package/dist/mpegts.js +3 -0
- package/dist/mpegts.js.LICENSE.txt +7 -0
- package/dist/mpegts.js.map +1 -0
- package/package.json +53 -0
- package/src/config.js +67 -0
- package/src/core/features.js +88 -0
- package/src/core/media-info.js +127 -0
- package/src/core/media-segment-info.js +230 -0
- package/src/core/mse-controller.js +599 -0
- package/src/core/mse-events.ts +28 -0
- package/src/core/transmuxer.js +346 -0
- package/src/core/transmuxing-controller.js +628 -0
- package/src/core/transmuxing-events.ts +43 -0
- package/src/core/transmuxing-worker.js +286 -0
- package/src/demux/aac.ts +397 -0
- package/src/demux/ac3.ts +335 -0
- package/src/demux/amf-parser.js +243 -0
- package/src/demux/av1-parser.ts +629 -0
- package/src/demux/av1.ts +103 -0
- package/src/demux/base-demuxer.ts +69 -0
- package/src/demux/demux-errors.js +26 -0
- package/src/demux/exp-golomb.js +116 -0
- package/src/demux/flv-demuxer.js +1854 -0
- package/src/demux/h264.ts +187 -0
- package/src/demux/h265-parser.js +501 -0
- package/src/demux/h265.ts +214 -0
- package/src/demux/klv.ts +40 -0
- package/src/demux/mp3.ts +7 -0
- package/src/demux/mpeg4-audio.ts +45 -0
- package/src/demux/pat-pmt-pes.ts +132 -0
- package/src/demux/pes-private-data.ts +16 -0
- package/src/demux/pgs-data.ts +11 -0
- package/src/demux/scte35.ts +723 -0
- package/src/demux/sei.ts +99 -0
- package/src/demux/smpte2038.ts +89 -0
- package/src/demux/sps-parser.js +298 -0
- package/src/demux/ts-demuxer.ts +2405 -0
- package/src/index.js +4 -0
- package/src/io/fetch-stream-loader.js +266 -0
- package/src/io/io-controller.js +647 -0
- package/src/io/loader.js +134 -0
- package/src/io/param-seek-handler.js +85 -0
- package/src/io/range-seek-handler.js +52 -0
- package/src/io/speed-sampler.js +93 -0
- package/src/io/websocket-loader.js +151 -0
- package/src/io/xhr-moz-chunked-loader.js +211 -0
- package/src/io/xhr-msstream-loader.js +307 -0
- package/src/io/xhr-range-loader.js +366 -0
- package/src/mpegts.js +95 -0
- package/src/player/live-latency-chaser.ts +66 -0
- package/src/player/live-latency-synchronizer.ts +79 -0
- package/src/player/loading-controller.ts +142 -0
- package/src/player/mse-player.ts +150 -0
- package/src/player/native-player.js +262 -0
- package/src/player/player-engine-dedicated-thread.ts +479 -0
- package/src/player/player-engine-main-thread.ts +463 -0
- package/src/player/player-engine-worker-cmd-def.ts +62 -0
- package/src/player/player-engine-worker-msg-def.ts +102 -0
- package/src/player/player-engine-worker.ts +370 -0
- package/src/player/player-engine.ts +35 -0
- package/src/player/player-errors.js +39 -0
- package/src/player/player-events.ts +40 -0
- package/src/player/seeking-handler.ts +205 -0
- package/src/player/startup-stall-jumper.ts +86 -0
- package/src/remux/aac-silent.js +56 -0
- package/src/remux/mp4-generator.js +866 -0
- package/src/remux/mp4-remuxer.js +778 -0
- package/src/utils/browser.js +128 -0
- package/src/utils/exception.js +73 -0
- package/src/utils/logger.js +140 -0
- package/src/utils/logging-control.js +165 -0
- package/src/utils/polyfill.js +68 -0
- package/src/utils/typedarray-equality.ts +69 -0
- package/src/utils/utf8-conv.js +84 -0
- package/src/utils/webworkify-webpack.js +202 -0
- package/tsconfig.json +16 -0
- package/tslint.json +1 -0
- package/types/index.d.ts +3 -0
- package/types/test-flv.ts +8 -0
- package/types/tsconfig.json +24 -0
- package/webpack.config.js +55 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2023 zheng qian. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* @author zheng qian <xqq@xqq.im>
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import Log from '../utils/logger';
|
|
20
|
+
import LoggingControl from '../utils/logging-control';
|
|
21
|
+
import { IllegalStateException } from '../utils/exception';
|
|
22
|
+
import MediaInfo from '../core/media-info';
|
|
23
|
+
import MSEEvents from '../core/mse-events';
|
|
24
|
+
import MSEController from '../core/mse-controller';
|
|
25
|
+
import Transmuxer from "../core/transmuxer";
|
|
26
|
+
import TransmuxingEvents from '../core/transmuxing-events';
|
|
27
|
+
import PlayerEvents from './player-events';
|
|
28
|
+
import { ErrorTypes } from './player-errors';
|
|
29
|
+
import {
|
|
30
|
+
WorkerCommandPacket,
|
|
31
|
+
WorkerCommandPacketInit,
|
|
32
|
+
WorkerCommandPacketLoggingConfig,
|
|
33
|
+
WorkerCommandPacketUnbufferedSeek,
|
|
34
|
+
WorkerCommandPacketTimeUpdate,
|
|
35
|
+
WorkerCommandPacketReadyStateChange,
|
|
36
|
+
} from './player-engine-worker-cmd-def.js';
|
|
37
|
+
import {
|
|
38
|
+
WorkerMessagePacket,
|
|
39
|
+
WorkerMessagePacketMSEInit,
|
|
40
|
+
WorkerMessagePacketMSEEvent,
|
|
41
|
+
WorkerMessagePacketPlayerEvent,
|
|
42
|
+
WorkerMessagePacketPlayerEventError,
|
|
43
|
+
WorkerMessagePacketPlayerEventExtraData,
|
|
44
|
+
WorkerMessagePacketTransmuxingEventInfo,
|
|
45
|
+
WorkerMessagePacketTransmuxingEventRecommendSeekpoint,
|
|
46
|
+
WorkerMessagePacketBufferedPositionChanged,
|
|
47
|
+
WorkerMessagePacketLogcatCallback,
|
|
48
|
+
} from './player-engine-worker-msg-def.js';
|
|
49
|
+
|
|
50
|
+
const PlayerEngineWorker = (self: DedicatedWorkerGlobalScope) => {
|
|
51
|
+
const TAG: string = 'PlayerEngineWorker';
|
|
52
|
+
|
|
53
|
+
const logcat_callback: (type: string, str: string) => void = onLogcatCallback.bind(this);
|
|
54
|
+
|
|
55
|
+
let media_data_source: any = null;
|
|
56
|
+
let config: any = null;
|
|
57
|
+
|
|
58
|
+
let mse_controller: MSEController = null;
|
|
59
|
+
let transmuxer: Transmuxer = null;
|
|
60
|
+
|
|
61
|
+
let mse_source_opened: boolean = false;
|
|
62
|
+
let has_pending_load: boolean = false;
|
|
63
|
+
|
|
64
|
+
let media_element_current_time: number = 0;
|
|
65
|
+
let media_element_ready_state: number = 0;
|
|
66
|
+
|
|
67
|
+
let destroyed = false;
|
|
68
|
+
|
|
69
|
+
self.addEventListener('message', (e: MessageEvent) => {
|
|
70
|
+
if (destroyed) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const command_packet = e.data as WorkerCommandPacket;
|
|
75
|
+
const cmd = command_packet.cmd;
|
|
76
|
+
|
|
77
|
+
switch (cmd) {
|
|
78
|
+
case 'logging_config': {
|
|
79
|
+
const packet = command_packet as WorkerCommandPacketLoggingConfig;
|
|
80
|
+
LoggingControl.applyConfig(packet.logging_config);
|
|
81
|
+
|
|
82
|
+
if (packet.logging_config.enableCallback === true) {
|
|
83
|
+
LoggingControl.addLogListener(logcat_callback);
|
|
84
|
+
} else {
|
|
85
|
+
LoggingControl.removeLogListener(logcat_callback);
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case 'init': {
|
|
90
|
+
const packet = command_packet as WorkerCommandPacketInit;
|
|
91
|
+
media_data_source = packet.media_data_source;
|
|
92
|
+
config = packet.config;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'destroy':
|
|
96
|
+
destroy();
|
|
97
|
+
break;
|
|
98
|
+
case 'initialize_mse':
|
|
99
|
+
initializeMSE();
|
|
100
|
+
break;
|
|
101
|
+
case 'shutdown_mse':
|
|
102
|
+
shutdownMSE();
|
|
103
|
+
break;
|
|
104
|
+
case 'load':
|
|
105
|
+
load();
|
|
106
|
+
break;
|
|
107
|
+
case 'unload':
|
|
108
|
+
unload();
|
|
109
|
+
break;
|
|
110
|
+
case 'unbuffered_seek': {
|
|
111
|
+
const packet = command_packet as WorkerCommandPacketUnbufferedSeek;
|
|
112
|
+
mse_controller.flush();
|
|
113
|
+
transmuxer.seek(packet.milliseconds);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case 'switch_audio_pid': {
|
|
117
|
+
if (transmuxer) {
|
|
118
|
+
(transmuxer as any).switchAudioPid((command_packet as any).pid);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case 'timeupdate': {
|
|
123
|
+
const packet = command_packet as WorkerCommandPacketTimeUpdate;
|
|
124
|
+
media_element_current_time = packet.current_time;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case 'readystatechange': {
|
|
128
|
+
const packet = command_packet as WorkerCommandPacketReadyStateChange;
|
|
129
|
+
media_element_ready_state = packet.ready_state;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'pause_transmuxer':
|
|
133
|
+
transmuxer.pause();
|
|
134
|
+
break;
|
|
135
|
+
case 'resume_transmuxer':
|
|
136
|
+
transmuxer.resume();
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
function destroy(): void {
|
|
142
|
+
if (transmuxer) {
|
|
143
|
+
unload();
|
|
144
|
+
}
|
|
145
|
+
if (mse_controller) {
|
|
146
|
+
shutdownMSE();
|
|
147
|
+
}
|
|
148
|
+
destroyed = true;
|
|
149
|
+
|
|
150
|
+
self.postMessage({
|
|
151
|
+
msg: 'destroyed',
|
|
152
|
+
} as WorkerMessagePacket);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function initializeMSE(): void {
|
|
156
|
+
Log.v(TAG, 'Initializing MediaSource in DedicatedWorker');
|
|
157
|
+
mse_controller = new MSEController(config);
|
|
158
|
+
mse_controller.on(MSEEvents.SOURCE_OPEN, onMSESourceOpen.bind(this));
|
|
159
|
+
mse_controller.on(MSEEvents.UPDATE_END, onMSEUpdateEnd.bind(this));
|
|
160
|
+
mse_controller.on(MSEEvents.BUFFER_FULL, onMSEBufferFull.bind(this));
|
|
161
|
+
mse_controller.on(MSEEvents.ERROR, onMSEError.bind(this));
|
|
162
|
+
mse_controller.initialize({
|
|
163
|
+
getCurrentTime: () => media_element_current_time,
|
|
164
|
+
getReadyState: () => media_element_ready_state,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
let handle = mse_controller.getHandle();
|
|
168
|
+
self.postMessage({
|
|
169
|
+
msg: 'mse_init',
|
|
170
|
+
handle: handle,
|
|
171
|
+
} as WorkerMessagePacketMSEInit, [handle]);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function shutdownMSE(): void {
|
|
175
|
+
if (mse_controller) {
|
|
176
|
+
mse_controller.shutdown();
|
|
177
|
+
mse_controller.destroy();
|
|
178
|
+
mse_controller = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function load(): void {
|
|
183
|
+
if (media_data_source == null || config == null) {
|
|
184
|
+
throw new IllegalStateException('Worker not initialized');
|
|
185
|
+
}
|
|
186
|
+
if (transmuxer) {
|
|
187
|
+
throw new IllegalStateException('Transmuxer has been initialized');
|
|
188
|
+
}
|
|
189
|
+
if (has_pending_load) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (config.deferLoadAfterSourceOpen && !mse_source_opened) {
|
|
193
|
+
has_pending_load = true;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
transmuxer = new Transmuxer(media_data_source, config);
|
|
198
|
+
|
|
199
|
+
transmuxer.on(TransmuxingEvents.INIT_SEGMENT, (type: string, is: any) => {
|
|
200
|
+
mse_controller.appendInitSegment(is);
|
|
201
|
+
});
|
|
202
|
+
transmuxer.on(TransmuxingEvents.MEDIA_SEGMENT, (type: string, ms: any) => {
|
|
203
|
+
mse_controller.appendMediaSegment(ms);
|
|
204
|
+
self.postMessage({
|
|
205
|
+
msg: 'buffered_position_changed',
|
|
206
|
+
buffered_position_milliseconds: ms.info.endDts,
|
|
207
|
+
} as WorkerMessagePacketBufferedPositionChanged);
|
|
208
|
+
});
|
|
209
|
+
transmuxer.on(TransmuxingEvents.LOADING_COMPLETE, () => {
|
|
210
|
+
mse_controller.endOfStream();
|
|
211
|
+
self.postMessage({
|
|
212
|
+
msg: 'player_event',
|
|
213
|
+
event: PlayerEvents.LOADING_COMPLETE,
|
|
214
|
+
} as WorkerMessagePacketPlayerEvent);
|
|
215
|
+
});
|
|
216
|
+
transmuxer.on(TransmuxingEvents.RECOVERED_EARLY_EOF, () => {
|
|
217
|
+
self.postMessage({
|
|
218
|
+
msg: 'player_event',
|
|
219
|
+
event: PlayerEvents.RECOVERED_EARLY_EOF,
|
|
220
|
+
} as WorkerMessagePacketPlayerEvent);
|
|
221
|
+
});
|
|
222
|
+
transmuxer.on(TransmuxingEvents.IO_ERROR, (detail: any, info: any) => {
|
|
223
|
+
self.postMessage({
|
|
224
|
+
msg: 'player_event',
|
|
225
|
+
event: PlayerEvents.ERROR,
|
|
226
|
+
error_type: ErrorTypes.NETWORK_ERROR,
|
|
227
|
+
error_detail: detail,
|
|
228
|
+
info: info,
|
|
229
|
+
} as WorkerMessagePacketPlayerEventError);
|
|
230
|
+
});
|
|
231
|
+
transmuxer.on(TransmuxingEvents.DEMUX_ERROR, (detail: any, info: any) => {
|
|
232
|
+
self.postMessage({
|
|
233
|
+
msg: 'player_event',
|
|
234
|
+
event: PlayerEvents.ERROR,
|
|
235
|
+
error_type: ErrorTypes.MEDIA_ERROR,
|
|
236
|
+
error_detail: detail,
|
|
237
|
+
info: info,
|
|
238
|
+
} as WorkerMessagePacketPlayerEventError);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
transmuxer.on(TransmuxingEvents.MEDIA_INFO, (mediaInfo: MediaInfo) => {
|
|
242
|
+
emitTransmuxingEventsInfo(TransmuxingEvents.MEDIA_INFO, mediaInfo);
|
|
243
|
+
});
|
|
244
|
+
transmuxer.on(TransmuxingEvents.STATISTICS_INFO, (statInfo: any) => {
|
|
245
|
+
emitTransmuxingEventsInfo(TransmuxingEvents.STATISTICS_INFO, statInfo);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
transmuxer.on(TransmuxingEvents.RECOMMEND_SEEKPOINT, (milliseconds: number) => {
|
|
249
|
+
emitTransmuxingEventsRecommendSeekpoint(milliseconds);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
transmuxer.on(TransmuxingEvents.METADATA_ARRIVED, (metadata: any) => {
|
|
253
|
+
emitPlayerEventsExtraData(PlayerEvents.METADATA_ARRIVED, metadata);
|
|
254
|
+
});
|
|
255
|
+
transmuxer.on(TransmuxingEvents.SCRIPTDATA_ARRIVED, (data: any) => {
|
|
256
|
+
emitPlayerEventsExtraData(PlayerEvents.SCRIPTDATA_ARRIVED, data);
|
|
257
|
+
});
|
|
258
|
+
transmuxer.on((TransmuxingEvents as any).TRACKS_UPDATED, (data: any) => {
|
|
259
|
+
emitPlayerEventsExtraData((PlayerEvents as any).TRACKS_UPDATED, data);
|
|
260
|
+
});
|
|
261
|
+
transmuxer.on(TransmuxingEvents.TIMED_ID3_METADATA_ARRIVED, (timed_id3_metadata: any) => {
|
|
262
|
+
emitPlayerEventsExtraData(PlayerEvents.TIMED_ID3_METADATA_ARRIVED, timed_id3_metadata);
|
|
263
|
+
});
|
|
264
|
+
transmuxer.on(TransmuxingEvents.PGS_SUBTITLE_ARRIVED, (pgs_data: any) => {
|
|
265
|
+
emitPlayerEventsExtraData(PlayerEvents.PGS_SUBTITLE_ARRIVED, pgs_data);
|
|
266
|
+
});
|
|
267
|
+
transmuxer.on(TransmuxingEvents.SYNCHRONOUS_KLV_METADATA_ARRIVED, (synchronous_klv_metadata: any) => {
|
|
268
|
+
emitPlayerEventsExtraData(PlayerEvents.SYNCHRONOUS_KLV_METADATA_ARRIVED, synchronous_klv_metadata);
|
|
269
|
+
});
|
|
270
|
+
transmuxer.on(TransmuxingEvents.ASYNCHRONOUS_KLV_METADATA_ARRIVED, (asynchronous_klv_metadata: any) => {
|
|
271
|
+
emitPlayerEventsExtraData(PlayerEvents.ASYNCHRONOUS_KLV_METADATA_ARRIVED, asynchronous_klv_metadata);
|
|
272
|
+
});
|
|
273
|
+
transmuxer.on(TransmuxingEvents.SMPTE2038_METADATA_ARRIVED, (smpte2038_metadata: any) => {
|
|
274
|
+
emitPlayerEventsExtraData(PlayerEvents.SMPTE2038_METADATA_ARRIVED, smpte2038_metadata);
|
|
275
|
+
});
|
|
276
|
+
transmuxer.on(TransmuxingEvents.SEI_ARRIVED, (sei_data: any) => {
|
|
277
|
+
emitPlayerEventsExtraData(PlayerEvents.SEI_ARRIVED, sei_data);
|
|
278
|
+
});
|
|
279
|
+
transmuxer.on(TransmuxingEvents.SCTE35_METADATA_ARRIVED, (scte35_metadata: any) => {
|
|
280
|
+
emitPlayerEventsExtraData(PlayerEvents.SCTE35_METADATA_ARRIVED, scte35_metadata);
|
|
281
|
+
});
|
|
282
|
+
transmuxer.on(TransmuxingEvents.PES_PRIVATE_DATA_DESCRIPTOR, (descriptor: any) => {
|
|
283
|
+
emitPlayerEventsExtraData(PlayerEvents.PES_PRIVATE_DATA_DESCRIPTOR, descriptor);
|
|
284
|
+
});
|
|
285
|
+
transmuxer.on(TransmuxingEvents.PES_PRIVATE_DATA_ARRIVED, (private_data: any) => {
|
|
286
|
+
emitPlayerEventsExtraData(PlayerEvents.PES_PRIVATE_DATA_ARRIVED, private_data);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
transmuxer.open();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function unload(): void {
|
|
293
|
+
if (mse_controller) {
|
|
294
|
+
mse_controller.flush();
|
|
295
|
+
}
|
|
296
|
+
if (transmuxer) {
|
|
297
|
+
transmuxer.close();
|
|
298
|
+
transmuxer.destroy();
|
|
299
|
+
transmuxer = null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function onMSESourceOpen(): void {
|
|
304
|
+
mse_source_opened = true;
|
|
305
|
+
if (has_pending_load) {
|
|
306
|
+
has_pending_load = false;
|
|
307
|
+
load();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function onMSEUpdateEnd(): void {
|
|
312
|
+
self.postMessage({
|
|
313
|
+
msg: 'mse_event',
|
|
314
|
+
event: MSEEvents.UPDATE_END,
|
|
315
|
+
} as WorkerMessagePacketMSEEvent);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function onMSEBufferFull(): void {
|
|
319
|
+
Log.v(TAG, 'MSE SourceBuffer is full, report to main thread');
|
|
320
|
+
self.postMessage({
|
|
321
|
+
msg: 'mse_event',
|
|
322
|
+
event: MSEEvents.BUFFER_FULL,
|
|
323
|
+
} as WorkerMessagePacketMSEEvent);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function onMSEError(info: any): void {
|
|
327
|
+
self.postMessage({
|
|
328
|
+
msg: 'player_event',
|
|
329
|
+
event: PlayerEvents.ERROR,
|
|
330
|
+
error_type: ErrorTypes.MEDIA_ERROR,
|
|
331
|
+
error_detail: ErrorTypes.MEDIA_MSE_ERROR,
|
|
332
|
+
info: info,
|
|
333
|
+
} as WorkerMessagePacketPlayerEventError);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function emitTransmuxingEventsRecommendSeekpoint(milliseconds: number) {
|
|
337
|
+
self.postMessage({
|
|
338
|
+
msg: 'transmuxing_event',
|
|
339
|
+
event: TransmuxingEvents.RECOMMEND_SEEKPOINT,
|
|
340
|
+
milliseconds: milliseconds,
|
|
341
|
+
} as WorkerMessagePacketTransmuxingEventRecommendSeekpoint);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function emitTransmuxingEventsInfo(event: TransmuxingEvents, info: any) {
|
|
345
|
+
self.postMessage({
|
|
346
|
+
msg: 'transmuxing_event',
|
|
347
|
+
event: event,
|
|
348
|
+
info: info,
|
|
349
|
+
} as WorkerMessagePacketTransmuxingEventInfo);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function emitPlayerEventsExtraData(event: PlayerEvents, extraData: any) {
|
|
353
|
+
self.postMessage({
|
|
354
|
+
msg: 'player_event',
|
|
355
|
+
event: event,
|
|
356
|
+
extraData: extraData,
|
|
357
|
+
} as WorkerMessagePacketPlayerEventExtraData);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function onLogcatCallback(type: string, str: string): void {
|
|
361
|
+
self.postMessage({
|
|
362
|
+
msg: 'logcat_callback',
|
|
363
|
+
type: type,
|
|
364
|
+
logcat: str,
|
|
365
|
+
} as WorkerMessagePacketLogcatCallback);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
export default PlayerEngineWorker;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2023 zheng qian. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* @author zheng qian <xqq@xqq.im>
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type MediaInfo from "../core/media-info";
|
|
20
|
+
|
|
21
|
+
export default interface PlayerEngine {
|
|
22
|
+
destroy(): void;
|
|
23
|
+
on(event: string, listener: (...args: any[]) => void): void;
|
|
24
|
+
off(event: string, listener: (...args: any[]) => void): void;
|
|
25
|
+
attachMediaElement(mediaElement: HTMLMediaElement): void;
|
|
26
|
+
detachMediaElement(): void;
|
|
27
|
+
load(): void;
|
|
28
|
+
unload(): void;
|
|
29
|
+
play(): Promise<void>;
|
|
30
|
+
pause(): void;
|
|
31
|
+
seek(seconds: number): void;
|
|
32
|
+
readonly mediaInfo: MediaInfo | undefined;
|
|
33
|
+
readonly statisticsInfo: any | undefined;
|
|
34
|
+
switchAudioPid(pid: number): void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2016 Bilibili. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* @author zheng qian <xqq@xqq.im>
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {LoaderErrors} from '../io/loader.js';
|
|
20
|
+
import DemuxErrors from '../demux/demux-errors.js';
|
|
21
|
+
|
|
22
|
+
export const ErrorTypes = {
|
|
23
|
+
NETWORK_ERROR: 'NetworkError',
|
|
24
|
+
MEDIA_ERROR: 'MediaError',
|
|
25
|
+
OTHER_ERROR: 'OtherError'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const ErrorDetails = {
|
|
29
|
+
NETWORK_EXCEPTION: LoaderErrors.EXCEPTION,
|
|
30
|
+
NETWORK_STATUS_CODE_INVALID: LoaderErrors.HTTP_STATUS_CODE_INVALID,
|
|
31
|
+
NETWORK_TIMEOUT: LoaderErrors.CONNECTING_TIMEOUT,
|
|
32
|
+
NETWORK_UNRECOVERABLE_EARLY_EOF: LoaderErrors.UNRECOVERABLE_EARLY_EOF,
|
|
33
|
+
|
|
34
|
+
MEDIA_MSE_ERROR: 'MediaMSEError',
|
|
35
|
+
|
|
36
|
+
MEDIA_FORMAT_ERROR: DemuxErrors.FORMAT_ERROR,
|
|
37
|
+
MEDIA_FORMAT_UNSUPPORTED: DemuxErrors.FORMAT_UNSUPPORTED,
|
|
38
|
+
MEDIA_CODEC_UNSUPPORTED: DemuxErrors.CODEC_UNSUPPORTED
|
|
39
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2016 Bilibili. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* @author zheng qian <xqq@xqq.im>
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
enum PlayerEvents {
|
|
20
|
+
ERROR = 'error',
|
|
21
|
+
LOADING_COMPLETE = 'loading_complete',
|
|
22
|
+
RECOVERED_EARLY_EOF = 'recovered_early_eof',
|
|
23
|
+
MEDIA_INFO = 'media_info',
|
|
24
|
+
METADATA_ARRIVED = 'metadata_arrived',
|
|
25
|
+
SCRIPTDATA_ARRIVED = 'scriptdata_arrived',
|
|
26
|
+
TRACKS_UPDATED = 'tracks_updated',
|
|
27
|
+
TIMED_ID3_METADATA_ARRIVED = 'timed_id3_metadata_arrived',
|
|
28
|
+
PGS_SUBTITLE_ARRIVED = 'pgs_subtitle_arrived',
|
|
29
|
+
SYNCHRONOUS_KLV_METADATA_ARRIVED = 'synchronous_klv_metadata_arrived',
|
|
30
|
+
ASYNCHRONOUS_KLV_METADATA_ARRIVED = 'asynchronous_klv_metadata_arrived',
|
|
31
|
+
SMPTE2038_METADATA_ARRIVED = 'smpte2038_metadata_arrived',
|
|
32
|
+
SEI_ARRIVED = 'sei_arrived',
|
|
33
|
+
SCTE35_METADATA_ARRIVED = 'scte35_metadata_arrived',
|
|
34
|
+
PES_PRIVATE_DATA_DESCRIPTOR = 'pes_private_data_descriptor',
|
|
35
|
+
PES_PRIVATE_DATA_ARRIVED = 'pes_private_data_arrived',
|
|
36
|
+
STATISTICS_INFO = 'statistics_info',
|
|
37
|
+
DESTROYING = 'destroying'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default PlayerEvents;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2023 zheng qian. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* @author zheng qian <xqq@xqq.im>
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import Browser from '../utils/browser';
|
|
20
|
+
import { IDRSampleList } from '../core/media-segment-info';
|
|
21
|
+
|
|
22
|
+
class SeekingHandler {
|
|
23
|
+
|
|
24
|
+
private readonly TAG: string = 'SeekingHandler';
|
|
25
|
+
|
|
26
|
+
private _config: any = null;
|
|
27
|
+
private _media_element: HTMLMediaElement = null;
|
|
28
|
+
private _always_seek_keyframe: boolean = false;
|
|
29
|
+
private _on_unbuffered_seek: (milliseconds: number) => void = null;
|
|
30
|
+
|
|
31
|
+
private _request_set_current_time: boolean = false;
|
|
32
|
+
private _seek_request_record_clocktime?: number = null;
|
|
33
|
+
private _idr_sample_list: IDRSampleList = new IDRSampleList();
|
|
34
|
+
|
|
35
|
+
private e?: any = null;
|
|
36
|
+
|
|
37
|
+
public constructor(
|
|
38
|
+
config: any,
|
|
39
|
+
media_element: HTMLMediaElement,
|
|
40
|
+
on_unbuffered_seek: (milliseconds: number) => void
|
|
41
|
+
) {
|
|
42
|
+
this._config = config;
|
|
43
|
+
this._media_element = media_element;
|
|
44
|
+
this._on_unbuffered_seek = on_unbuffered_seek;
|
|
45
|
+
|
|
46
|
+
this.e = {
|
|
47
|
+
onMediaSeeking: this._onMediaSeeking.bind(this),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
let chrome_need_idr_fix = (Browser.chrome &&
|
|
51
|
+
(Browser.version.major < 50 ||
|
|
52
|
+
(Browser.version.major === 50 && Browser.version.build < 2661)));
|
|
53
|
+
this._always_seek_keyframe = (chrome_need_idr_fix || Browser.msedge || Browser.msie) ? true : false;
|
|
54
|
+
if (this._always_seek_keyframe) {
|
|
55
|
+
this._config.accurateSeek = false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this._media_element.addEventListener('seeking', this.e.onMediaSeeking);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public destroy(): void {
|
|
62
|
+
this._idr_sample_list.clear();
|
|
63
|
+
this._idr_sample_list = null;
|
|
64
|
+
this._media_element.removeEventListener('seeking', this.e.onMediaSeeking);
|
|
65
|
+
this._media_element = null;
|
|
66
|
+
this._on_unbuffered_seek = null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public seek(seconds: number): void {
|
|
70
|
+
const direct_seek: boolean = this._isPositionBuffered(seconds);
|
|
71
|
+
let direct_seek_to_video_begin: boolean = false;
|
|
72
|
+
|
|
73
|
+
if (seconds < 1.0 && this._media_element.buffered.length > 0) {
|
|
74
|
+
const video_begin_time = this._media_element.buffered.start(0);
|
|
75
|
+
if ((video_begin_time < 1.0 && seconds < video_begin_time) || Browser.safari) {
|
|
76
|
+
direct_seek_to_video_begin = true;
|
|
77
|
+
// Workaround for Safari: Seek to 0 may cause video stuck, use 0.1 to avoid
|
|
78
|
+
seconds = Browser.safari ? 0.1 : video_begin_time;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (direct_seek_to_video_begin) {
|
|
83
|
+
this.directSeek(seconds);
|
|
84
|
+
} else if (direct_seek) {
|
|
85
|
+
if (!this._always_seek_keyframe) {
|
|
86
|
+
this.directSeek(seconds);
|
|
87
|
+
} else {
|
|
88
|
+
// For some old browsers we have to seek to keyframe
|
|
89
|
+
// Seek to nearest keyframe if possible
|
|
90
|
+
const idr = this._getNearestKeyframe(Math.floor(seconds * 1000));
|
|
91
|
+
if (idr != null) {
|
|
92
|
+
seconds = idr.dts / 1000;
|
|
93
|
+
}
|
|
94
|
+
this.directSeek(seconds);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
this._idr_sample_list.clear();
|
|
98
|
+
this._on_unbuffered_seek(Math.floor(seconds * 1000)); // In milliseconds
|
|
99
|
+
if (this._config.accurateSeek) {
|
|
100
|
+
this.directSeek(seconds);
|
|
101
|
+
}
|
|
102
|
+
// else: Wait for recommend_seekpoint callback
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public directSeek(seconds: number): void {
|
|
107
|
+
this._request_set_current_time = true;
|
|
108
|
+
this._media_element.currentTime = seconds;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public appendSyncPoints(syncpoints: any[]): void {
|
|
112
|
+
this._idr_sample_list.appendArray(syncpoints);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Handle seeking request from browser's progress bar or HTMLMediaElement.currentTime setter
|
|
116
|
+
private _onMediaSeeking(e: Event): void {
|
|
117
|
+
if (this._request_set_current_time) {
|
|
118
|
+
this._request_set_current_time = false;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let target: number = this._media_element.currentTime;
|
|
123
|
+
const buffered: TimeRanges = this._media_element.buffered;
|
|
124
|
+
|
|
125
|
+
// Handle seeking to video begin (near 0.0s)
|
|
126
|
+
if (target < 1.0 && buffered.length > 0) {
|
|
127
|
+
let video_begin_time = buffered.start(0);
|
|
128
|
+
if ((video_begin_time < 1.0 && target < video_begin_time) || Browser.safari) {
|
|
129
|
+
// Safari may get stuck if currentTime set to 0, use 0.1 to avoid
|
|
130
|
+
let target: number = Browser.safari ? 0.1 : video_begin_time;
|
|
131
|
+
this.directSeek(target);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Handle in-buffer seeking (usually nothing to do)
|
|
137
|
+
if (this._isPositionBuffered(target)) {
|
|
138
|
+
if (this._always_seek_keyframe) {
|
|
139
|
+
const idr = this._getNearestKeyframe(Math.floor(target * 1000));
|
|
140
|
+
if (idr != null) {
|
|
141
|
+
target = idr.dts / 1000;
|
|
142
|
+
this.directSeek(target);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// else: Prepare for unbuffered seeking
|
|
149
|
+
// Defer the unbuffered seeking since the seeking bar maybe still being draged
|
|
150
|
+
this._seek_request_record_clocktime = SeekingHandler._getClockTime();
|
|
151
|
+
window.setTimeout(this._pollAndApplyUnbufferedSeek.bind(this), 50);
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private _pollAndApplyUnbufferedSeek(): void {
|
|
156
|
+
if (this._seek_request_record_clocktime == null) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const record_time: number = this._seek_request_record_clocktime;
|
|
161
|
+
if (record_time <= SeekingHandler._getClockTime() - 100) {
|
|
162
|
+
const target = this._media_element.currentTime;
|
|
163
|
+
this._seek_request_record_clocktime = null;
|
|
164
|
+
if (!this._isPositionBuffered(target)) {
|
|
165
|
+
this._idr_sample_list.clear();
|
|
166
|
+
this._on_unbuffered_seek(Math.floor(target * 1000)); // In milliseconds
|
|
167
|
+
// Update currentTime if using accurateSeek, or wait for recommend_seekpoint callback
|
|
168
|
+
if (this._config.accurateSeek) {
|
|
169
|
+
this.directSeek(target);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
window.setTimeout(this._pollAndApplyUnbufferedSeek.bind(this), 50);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private _isPositionBuffered(seconds: number): boolean {
|
|
178
|
+
const buffered = this._media_element.buffered;
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < buffered.length; i++) {
|
|
181
|
+
const from = buffered.start(i);
|
|
182
|
+
const to = buffered.end(i);
|
|
183
|
+
if (seconds >= from && seconds < to) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private _getNearestKeyframe(dts: number): any {
|
|
192
|
+
return this._idr_sample_list.getLastSyncPointBeforeDts(dts);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private static _getClockTime(): number {
|
|
196
|
+
if (self.performance && self.performance.now) {
|
|
197
|
+
return self.performance.now();
|
|
198
|
+
} else {
|
|
199
|
+
return Date.now();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export default SeekingHandler;
|