@antmedia/web_player 2.8.0-SNAPSHOT
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/.project +17 -0
- package/.settings/.jsdtscope +7 -0
- package/.settings/org.eclipse.wst.jsdt.ui.superType.container +1 -0
- package/.settings/org.eclipse.wst.jsdt.ui.superType.name +1 -0
- package/LICENSE +201 -0
- package/README.md +1 -0
- package/api-extractor.json +38 -0
- package/codecov.yml +6 -0
- package/dist/_commonjsHelpers-ed042b00.js +10 -0
- package/dist/aframe-master-42bb78a9.js +7139 -0
- package/dist/browser/web_player.js +976 -0
- package/dist/dash.all.min-84806d51.js +36 -0
- package/dist/es/_commonjsHelpers-7d1333e8.js +7 -0
- package/dist/es/aframe-master-a6146619.js +7137 -0
- package/dist/es/dash.all.min-4a2772b6.js +34 -0
- package/dist/es/index.d.ts +227 -0
- package/dist/es/index.js +1 -0
- package/dist/es/video-js.min-8b4dfe88.js +3 -0
- package/dist/es/video.es-22056625.js +31061 -0
- package/dist/es/videojs-contrib-quality-levels.es-5f5b5f23.js +287 -0
- package/dist/es/videojs-hls-quality-selector.es-3c54e1cd.js +391 -0
- package/dist/es/videojs-webrtc-plugin-b9e4da27.js +3 -0
- package/dist/es/videojs-webrtc-plugin.es-f41400f7.js +7649 -0
- package/dist/es/web_player.js +1262 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.js +7 -0
- package/dist/video-js.min-7e4ae47a.js +5 -0
- package/dist/video.es-72122d04.js +31067 -0
- package/dist/videojs-contrib-quality-levels.es-ef3cec9e.js +289 -0
- package/dist/videojs-hls-quality-selector.es-562309df.js +393 -0
- package/dist/videojs-webrtc-plugin-d30c3e7a.js +5 -0
- package/dist/videojs-webrtc-plugin.es-ac81d249.js +7651 -0
- package/dist/web_player.js +1265 -0
- package/karma.conf.cjs +74 -0
- package/package.json +68 -0
- package/rollup.config.browser.cjs +16 -0
- package/rollup.config.module.cjs +42 -0
- package/src/index.js +1 -0
- package/src/web_player.js +1133 -0
- package/test/embedded-player.test.js +864 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,1262 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* loglevel - https://github.com/pimterry/loglevel
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2013 Tim Perry
|
|
5
|
+
* Licensed under the MIT license.
|
|
6
|
+
*/
|
|
7
|
+
(function (root, definition) {
|
|
8
|
+
window.log = definition();
|
|
9
|
+
})(undefined, function () {
|
|
10
|
+
// Slightly dubious tricks to cut down minimized file size
|
|
11
|
+
var noop = function noop() {};
|
|
12
|
+
var undefinedType = "undefined";
|
|
13
|
+
var isIE = typeof window !== undefinedType && typeof window.navigator !== undefinedType && /Trident\/|MSIE /.test(window.navigator.userAgent);
|
|
14
|
+
var logMethods = ["trace", "debug", "info", "warn", "error"];
|
|
15
|
+
|
|
16
|
+
// Cross-browser bind equivalent that works at least back to IE6
|
|
17
|
+
function bindMethod(obj, methodName) {
|
|
18
|
+
var method = obj[methodName];
|
|
19
|
+
if (typeof method.bind === 'function') {
|
|
20
|
+
return method.bind(obj);
|
|
21
|
+
} else {
|
|
22
|
+
try {
|
|
23
|
+
return Function.prototype.bind.call(method, obj);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Missing bind shim or IE8 + Modernizr, fallback to wrapping
|
|
26
|
+
return function () {
|
|
27
|
+
return Function.prototype.apply.apply(method, [obj, arguments]);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Trace() doesn't print the message in IE, so for that case we need to wrap it
|
|
34
|
+
function traceForIE() {
|
|
35
|
+
if (console.log) {
|
|
36
|
+
if (console.log.apply) {
|
|
37
|
+
console.log.apply(console, arguments);
|
|
38
|
+
} else {
|
|
39
|
+
// In old IE, native console methods themselves don't have apply().
|
|
40
|
+
Function.prototype.apply.apply(console.log, [console, arguments]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (console.trace) console.trace();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Build the best logging method possible for this env
|
|
47
|
+
// Wherever possible we want to bind, not wrap, to preserve stack traces
|
|
48
|
+
function realMethod(methodName) {
|
|
49
|
+
if (methodName === 'debug') {
|
|
50
|
+
methodName = 'log';
|
|
51
|
+
}
|
|
52
|
+
if (typeof console === undefinedType) {
|
|
53
|
+
return false; // No method possible, for now - fixed later by enableLoggingWhenConsoleArrives
|
|
54
|
+
} else if (methodName === 'trace' && isIE) {
|
|
55
|
+
return traceForIE;
|
|
56
|
+
} else if (console[methodName] !== undefined) {
|
|
57
|
+
return bindMethod(console, methodName);
|
|
58
|
+
} else if (console.log !== undefined) {
|
|
59
|
+
return bindMethod(console, 'log');
|
|
60
|
+
} else {
|
|
61
|
+
return noop;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// These private functions always need `this` to be set properly
|
|
66
|
+
|
|
67
|
+
function replaceLoggingMethods(level, loggerName) {
|
|
68
|
+
/*jshint validthis:true */
|
|
69
|
+
for (var i = 0; i < logMethods.length; i++) {
|
|
70
|
+
var methodName = logMethods[i];
|
|
71
|
+
this[methodName] = i < level ? noop : this.methodFactory(methodName, level, loggerName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Define log.log as an alias for log.debug
|
|
75
|
+
this.log = this.debug;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// In old IE versions, the console isn't present until you first open it.
|
|
79
|
+
// We build realMethod() replacements here that regenerate logging methods
|
|
80
|
+
function enableLoggingWhenConsoleArrives(methodName, level, loggerName) {
|
|
81
|
+
return function () {
|
|
82
|
+
if (typeof console !== undefinedType) {
|
|
83
|
+
replaceLoggingMethods.call(this, level, loggerName);
|
|
84
|
+
this[methodName].apply(this, arguments);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// By default, we use closely bound real methods wherever possible, and
|
|
90
|
+
// otherwise we wait for a console to appear, and then try again.
|
|
91
|
+
function defaultMethodFactory(methodName, level, loggerName) {
|
|
92
|
+
/*jshint validthis:true */
|
|
93
|
+
return realMethod(methodName) || enableLoggingWhenConsoleArrives.apply(this, arguments);
|
|
94
|
+
}
|
|
95
|
+
function Logger(name, defaultLevel, factory) {
|
|
96
|
+
var self = this;
|
|
97
|
+
var currentLevel;
|
|
98
|
+
defaultLevel = defaultLevel == null ? "WARN" : defaultLevel;
|
|
99
|
+
var storageKey = "loglevel";
|
|
100
|
+
if (typeof name === "string") {
|
|
101
|
+
storageKey += ":" + name;
|
|
102
|
+
} else if (typeof name === "symbol") {
|
|
103
|
+
storageKey = undefined;
|
|
104
|
+
}
|
|
105
|
+
function persistLevelIfPossible(levelNum) {
|
|
106
|
+
var levelName = (logMethods[levelNum] || 'silent').toUpperCase();
|
|
107
|
+
if (typeof window === undefinedType || !storageKey) return;
|
|
108
|
+
|
|
109
|
+
// Use localStorage if available
|
|
110
|
+
try {
|
|
111
|
+
window.localStorage[storageKey] = levelName;
|
|
112
|
+
return;
|
|
113
|
+
} catch (ignore) {}
|
|
114
|
+
|
|
115
|
+
// Use session cookie as fallback
|
|
116
|
+
try {
|
|
117
|
+
window.document.cookie = encodeURIComponent(storageKey) + "=" + levelName + ";";
|
|
118
|
+
} catch (ignore) {}
|
|
119
|
+
}
|
|
120
|
+
function getPersistedLevel() {
|
|
121
|
+
var storedLevel;
|
|
122
|
+
if (typeof window === undefinedType || !storageKey) return;
|
|
123
|
+
try {
|
|
124
|
+
storedLevel = window.localStorage[storageKey];
|
|
125
|
+
} catch (ignore) {}
|
|
126
|
+
|
|
127
|
+
// Fallback to cookies if local storage gives us nothing
|
|
128
|
+
if (typeof storedLevel === undefinedType) {
|
|
129
|
+
try {
|
|
130
|
+
var cookie = window.document.cookie;
|
|
131
|
+
var location = cookie.indexOf(encodeURIComponent(storageKey) + "=");
|
|
132
|
+
if (location !== -1) {
|
|
133
|
+
storedLevel = /^([^;]+)/.exec(cookie.slice(location))[1];
|
|
134
|
+
}
|
|
135
|
+
} catch (ignore) {}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// If the stored level is not valid, treat it as if nothing was stored.
|
|
139
|
+
if (self.levels[storedLevel] === undefined) {
|
|
140
|
+
storedLevel = undefined;
|
|
141
|
+
}
|
|
142
|
+
return storedLevel;
|
|
143
|
+
}
|
|
144
|
+
function clearPersistedLevel() {
|
|
145
|
+
if (typeof window === undefinedType || !storageKey) return;
|
|
146
|
+
|
|
147
|
+
// Use localStorage if available
|
|
148
|
+
try {
|
|
149
|
+
window.localStorage.removeItem(storageKey);
|
|
150
|
+
return;
|
|
151
|
+
} catch (ignore) {}
|
|
152
|
+
|
|
153
|
+
// Use session cookie as fallback
|
|
154
|
+
try {
|
|
155
|
+
window.document.cookie = encodeURIComponent(storageKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC";
|
|
156
|
+
} catch (ignore) {}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/*
|
|
160
|
+
*
|
|
161
|
+
* Public logger API - see https://github.com/pimterry/loglevel for details
|
|
162
|
+
*
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
self.name = name;
|
|
166
|
+
self.levels = {
|
|
167
|
+
"TRACE": 0,
|
|
168
|
+
"DEBUG": 1,
|
|
169
|
+
"INFO": 2,
|
|
170
|
+
"WARN": 3,
|
|
171
|
+
"ERROR": 4,
|
|
172
|
+
"SILENT": 5
|
|
173
|
+
};
|
|
174
|
+
self.methodFactory = factory || defaultMethodFactory;
|
|
175
|
+
self.getLevel = function () {
|
|
176
|
+
return currentLevel;
|
|
177
|
+
};
|
|
178
|
+
self.setLevel = function (level, persist) {
|
|
179
|
+
if (typeof level === "string" && self.levels[level.toUpperCase()] !== undefined) {
|
|
180
|
+
level = self.levels[level.toUpperCase()];
|
|
181
|
+
}
|
|
182
|
+
if (typeof level === "number" && level >= 0 && level <= self.levels.SILENT) {
|
|
183
|
+
currentLevel = level;
|
|
184
|
+
if (persist !== false) {
|
|
185
|
+
// defaults to true
|
|
186
|
+
persistLevelIfPossible(level);
|
|
187
|
+
}
|
|
188
|
+
replaceLoggingMethods.call(self, level, name);
|
|
189
|
+
if (typeof console === undefinedType && level < self.levels.SILENT) {
|
|
190
|
+
return "No console available for logging";
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
throw "log.setLevel() called with invalid level: " + level;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
self.setDefaultLevel = function (level) {
|
|
197
|
+
defaultLevel = level;
|
|
198
|
+
if (!getPersistedLevel()) {
|
|
199
|
+
self.setLevel(level, false);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
self.resetLevel = function () {
|
|
203
|
+
self.setLevel(defaultLevel, false);
|
|
204
|
+
clearPersistedLevel();
|
|
205
|
+
};
|
|
206
|
+
self.enableAll = function (persist) {
|
|
207
|
+
self.setLevel(self.levels.TRACE, persist);
|
|
208
|
+
};
|
|
209
|
+
self.disableAll = function (persist) {
|
|
210
|
+
self.setLevel(self.levels.SILENT, persist);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Initialize with the right level
|
|
214
|
+
var initialLevel = getPersistedLevel();
|
|
215
|
+
if (initialLevel == null) {
|
|
216
|
+
initialLevel = defaultLevel;
|
|
217
|
+
}
|
|
218
|
+
self.setLevel(initialLevel, false);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/*
|
|
222
|
+
*
|
|
223
|
+
* Top-level API
|
|
224
|
+
*
|
|
225
|
+
*/
|
|
226
|
+
|
|
227
|
+
var defaultLogger = new Logger();
|
|
228
|
+
var _loggersByName = {};
|
|
229
|
+
defaultLogger.getLogger = function getLogger(name) {
|
|
230
|
+
if (typeof name !== "symbol" && typeof name !== "string" || name === "") {
|
|
231
|
+
throw new TypeError("You must supply a name when creating a logger.");
|
|
232
|
+
}
|
|
233
|
+
var logger = _loggersByName[name];
|
|
234
|
+
if (!logger) {
|
|
235
|
+
logger = _loggersByName[name] = new Logger(name, defaultLogger.getLevel(), defaultLogger.methodFactory);
|
|
236
|
+
}
|
|
237
|
+
return logger;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Grab the current global log variable in case of overwrite
|
|
241
|
+
var _log = typeof window !== undefinedType ? window.log : undefined;
|
|
242
|
+
defaultLogger.noConflict = function () {
|
|
243
|
+
if (typeof window !== undefinedType && window.log === defaultLogger) {
|
|
244
|
+
window.log = _log;
|
|
245
|
+
}
|
|
246
|
+
return defaultLogger;
|
|
247
|
+
};
|
|
248
|
+
defaultLogger.getLoggers = function getLoggers() {
|
|
249
|
+
return _loggersByName;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// ES6 default export, for compatibility
|
|
253
|
+
defaultLogger['default'] = defaultLogger;
|
|
254
|
+
return defaultLogger;
|
|
255
|
+
});
|
|
256
|
+
var Logger = window.log;
|
|
257
|
+
var Logger_1 = Logger;
|
|
258
|
+
|
|
259
|
+
//ask if adaptive m3u8 file
|
|
260
|
+
|
|
261
|
+
if (!String.prototype.endsWith) {
|
|
262
|
+
String.prototype.endsWith = function (searchString, position) {
|
|
263
|
+
var subjectString = this.toString();
|
|
264
|
+
if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
|
|
265
|
+
position = subjectString.length;
|
|
266
|
+
}
|
|
267
|
+
position -= searchString.length;
|
|
268
|
+
var lastIndex = subjectString.lastIndexOf(searchString, position);
|
|
269
|
+
return lastIndex !== -1 && lastIndex === position;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
*
|
|
274
|
+
* @param {string} sParam
|
|
275
|
+
* @param {string=} search
|
|
276
|
+
* @returns
|
|
277
|
+
*/
|
|
278
|
+
function getUrlParameter(sParam, search) {
|
|
279
|
+
if (typeof search === undefined || search == null) {
|
|
280
|
+
search = window.location.search;
|
|
281
|
+
}
|
|
282
|
+
var sPageURL = decodeURIComponent(search.substring(1)),
|
|
283
|
+
sURLVariables = sPageURL.split('&'),
|
|
284
|
+
sParameterName,
|
|
285
|
+
i;
|
|
286
|
+
for (i = 0; i < sURLVariables.length; i++) {
|
|
287
|
+
sParameterName = sURLVariables[i].split('=');
|
|
288
|
+
if (sParameterName[0] === sParam) {
|
|
289
|
+
return sParameterName[1] === undefined ? true : sParameterName[1];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
var getUrlParameter_1 = getUrlParameter;
|
|
294
|
+
|
|
295
|
+
const STATIC_VIDEO_HTML = "<video id='video-player' class='video-js vjs-default-skin vjs-big-play-centered' controls playsinline></video>";
|
|
296
|
+
class WebPlayer {
|
|
297
|
+
static DEFAULT_PLAY_ORDER = ["webrtc", "hls"];
|
|
298
|
+
static DEFAULT_PLAY_TYPE = ["mp4", "webm"];
|
|
299
|
+
static HLS_EXTENSION = "m3u8";
|
|
300
|
+
static WEBRTC_EXTENSION = "webrtc";
|
|
301
|
+
static DASH_EXTENSION = "mpd";
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* streamsFolder: streams folder. Optional. Default value is "streams"
|
|
305
|
+
*/
|
|
306
|
+
static STREAMS_FOLDER = "streams";
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Video HTML content. It's by default STATIC_VIDEO_HTML
|
|
310
|
+
*/
|
|
311
|
+
videoHTMLContent;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* video player Id. It's by default "video-player"
|
|
315
|
+
*/
|
|
316
|
+
videoPlayerId;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* "playOrder": the order which technologies is used in playing. Optional. Default value is "webrtc,hls".
|
|
320
|
+
* possible values are "hls,webrtc","webrtc","hls","vod","dash"
|
|
321
|
+
* It will be taken from url parameter "playOrder".
|
|
322
|
+
*/
|
|
323
|
+
playOrder;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* currentPlayType: current play type in playOrder
|
|
327
|
+
*/
|
|
328
|
+
currentPlayType;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* "is360": if true, player will be 360 degree player. Optional. Default value is false.
|
|
332
|
+
* It will be taken from url parameter "is360".
|
|
333
|
+
*/
|
|
334
|
+
is360 = false;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* "streamId": stream id. Mandatory. If it is not set, it will be taken from url parameter "id".
|
|
338
|
+
* It will be taken from url parameter "id".
|
|
339
|
+
*/
|
|
340
|
+
streamId;
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* "playType": play type. Optional. It's used for vod. Default value is "mp4,webm".
|
|
344
|
+
* It can be "mp4,webm","webm,mp4","mp4","webm","mov" and it's used for vod.
|
|
345
|
+
* It will be taken from url parameter "playType".
|
|
346
|
+
*/
|
|
347
|
+
playType;
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* "token": token. It's required when stream security for playback is enabled .
|
|
351
|
+
* It will be taken from url parameter "token".
|
|
352
|
+
*/
|
|
353
|
+
token;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* autoplay: if true, player will be started automatically. Optional. Default value is true.
|
|
357
|
+
* autoplay is false by default for mobile devices because of mobile browser's autoplay policy.
|
|
358
|
+
* It will be taken from url parameter "autoplay".
|
|
359
|
+
*/
|
|
360
|
+
autoPlay = true;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* mute: if true, player will be started muted. Optional. Default value is true.
|
|
364
|
+
* default value is true because of browser's autoplay policy.
|
|
365
|
+
* It will be taken from url parameter "mute".
|
|
366
|
+
*/
|
|
367
|
+
mute = true;
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* targetLatency: target latency in seconds. Optional. Default value is 3.
|
|
371
|
+
* It will be taken from url parameter "targetLatency".
|
|
372
|
+
* It's used for dash(cmaf) playback.
|
|
373
|
+
*/
|
|
374
|
+
targetLatency = 3;
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* subscriberId: subscriber id. Optional. It will be taken from url parameter "subscriberId".
|
|
378
|
+
*/
|
|
379
|
+
subscriberId;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* subscriberCode: subscriber code. Optional. It will be taken from url parameter "subscriberCode".
|
|
383
|
+
*/
|
|
384
|
+
subscriberCode;
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* window: window object
|
|
388
|
+
*/
|
|
389
|
+
window;
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* video player container element
|
|
393
|
+
*/
|
|
394
|
+
containerElement;
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* player placeholder element
|
|
398
|
+
*/
|
|
399
|
+
placeHolderElement;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* videojs player
|
|
403
|
+
*/
|
|
404
|
+
videojsPlayer;
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* dash player
|
|
408
|
+
*/
|
|
409
|
+
dashPlayer;
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Ice servers for webrtc
|
|
413
|
+
*/
|
|
414
|
+
iceServers;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* ice connection state
|
|
418
|
+
*/
|
|
419
|
+
iceConnected;
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* flag to check if error callback is called
|
|
423
|
+
*/
|
|
424
|
+
errorCalled;
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* scene for 360 degree player
|
|
428
|
+
*/
|
|
429
|
+
aScene;
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* player listener
|
|
433
|
+
*/
|
|
434
|
+
playerListener;
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* webRTCDataListener
|
|
438
|
+
*/
|
|
439
|
+
webRTCDataListener;
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Field to keep if tryNextMethod is already called
|
|
443
|
+
*/
|
|
444
|
+
tryNextTechTimer;
|
|
445
|
+
constructor(configOrWindow, containerElement, placeHolderElement) {
|
|
446
|
+
WebPlayer.DEFAULT_PLAY_ORDER = ["webrtc", "hls"];
|
|
447
|
+
WebPlayer.DEFAULT_PLAY_TYPE = ["mp4", "webm"];
|
|
448
|
+
WebPlayer.HLS_EXTENSION = "m3u8";
|
|
449
|
+
WebPlayer.WEBRTC_EXTENSION = "webrtc";
|
|
450
|
+
WebPlayer.DASH_EXTENSION = "mpd";
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* streamsFolder: streams folder. Optional. Default value is "streams"
|
|
454
|
+
*/
|
|
455
|
+
WebPlayer.STREAMS_FOLDER = "streams";
|
|
456
|
+
WebPlayer.VIDEO_PLAYER_ID = "video-player";
|
|
457
|
+
|
|
458
|
+
// Initialize default values
|
|
459
|
+
this.setDefaults();
|
|
460
|
+
|
|
461
|
+
// Check if the first argument is a config object or a Window object
|
|
462
|
+
if (!this.isWindow(configOrWindow)) {
|
|
463
|
+
// New config object mode
|
|
464
|
+
Logger_1.info("config object mode");
|
|
465
|
+
Object.assign(this, configOrWindow);
|
|
466
|
+
this.window = window;
|
|
467
|
+
} else {
|
|
468
|
+
// Backward compatibility mode
|
|
469
|
+
Logger_1.info("getting from url mode");
|
|
470
|
+
this.window = configOrWindow;
|
|
471
|
+
|
|
472
|
+
// Use getUrlParameter for backward compatibility
|
|
473
|
+
this.initializeFromUrlParams();
|
|
474
|
+
}
|
|
475
|
+
this.containerElement = containerElement;
|
|
476
|
+
this.placeHolderElement = placeHolderElement;
|
|
477
|
+
if (this.streamId == null) {
|
|
478
|
+
var message = "Stream id is not set.Please add your stream id to the url as a query parameter such as ?id={STREAM_ID} to the url";
|
|
479
|
+
Logger_1.error(message);
|
|
480
|
+
//TODO: we may need to show this message on directly page
|
|
481
|
+
alert(message);
|
|
482
|
+
throw new Error(message);
|
|
483
|
+
}
|
|
484
|
+
if (!this.httpBaseURL) {
|
|
485
|
+
let appName = this.window.location.pathname.substring(0, this.window.location.pathname.lastIndexOf("/") + 1);
|
|
486
|
+
let path = this.window.location.hostname + ":" + this.window.location.port + appName + this.streamId + ".webrtc";
|
|
487
|
+
this.websocketURL = "ws://" + path;
|
|
488
|
+
if (location.protocol.startsWith("https")) {
|
|
489
|
+
this.websocketURL = "wss://" + path;
|
|
490
|
+
}
|
|
491
|
+
this.httpBaseURL = location.protocol + "//" + this.window.location.hostname + ":" + this.window.location.port + appName;
|
|
492
|
+
} else if (!this.websocketURL) {
|
|
493
|
+
this.websocketURL = this.httpBaseURL.replace("http", "ws");
|
|
494
|
+
if (!this.websocketURL.endsWith("/")) {
|
|
495
|
+
this.websocketURL += "/";
|
|
496
|
+
}
|
|
497
|
+
this.websocketURL += this.streamId + ".webrtc";
|
|
498
|
+
}
|
|
499
|
+
this.dom = this.window.document;
|
|
500
|
+
this.containerElement.innerHTML = this.videoHTMLContent;
|
|
501
|
+
this.setPlayerVisible(false);
|
|
502
|
+
}
|
|
503
|
+
isWindow(configOrWindow) {
|
|
504
|
+
//accept that it's a window if it's a Window instance or it has location.href
|
|
505
|
+
//location.href is used in test environment
|
|
506
|
+
return configOrWindow instanceof Window || configOrWindow.location && configOrWindow.location.href;
|
|
507
|
+
}
|
|
508
|
+
initialize() {
|
|
509
|
+
return this.loadVideoJSComponents().then(() => {
|
|
510
|
+
return this.loadDashScript();
|
|
511
|
+
}).then(() => {
|
|
512
|
+
if (this.is360 && !window.AFRAME) {
|
|
513
|
+
return import('./aframe-master-a6146619.js').then(function (n) { return n.a; });
|
|
514
|
+
}
|
|
515
|
+
}).catch(e => {
|
|
516
|
+
Logger_1.error("Scripts are not loaded. The error is " + e);
|
|
517
|
+
throw e;
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
loadDashScript() {
|
|
521
|
+
if (this.playOrder.includes("dash") && !this.dashjsLoaded) {
|
|
522
|
+
return import('./dash.all.min-4a2772b6.js').then(function (n) { return n.d; }).then(dashjs => {
|
|
523
|
+
window.dashjs = dashjs.default;
|
|
524
|
+
this.dashjsLoaded = true;
|
|
525
|
+
console.log("dash.all.min.js is loaded");
|
|
526
|
+
});
|
|
527
|
+
} else {
|
|
528
|
+
return Promise.resolve();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
setDefaults() {
|
|
532
|
+
this.playOrder = WebPlayer.DEFAULT_PLAY_ORDER;
|
|
533
|
+
this.currentPlayType = null;
|
|
534
|
+
this.is360 = false;
|
|
535
|
+
this.streamId = null;
|
|
536
|
+
this.playType = WebPlayer.DEFAULT_PLAY_TYPE;
|
|
537
|
+
this.token = null;
|
|
538
|
+
this.autoPlay = true;
|
|
539
|
+
this.mute = true;
|
|
540
|
+
this.targetLatency = 3;
|
|
541
|
+
this.subscriberId = null;
|
|
542
|
+
this.subscriberCode = null;
|
|
543
|
+
this.window = null;
|
|
544
|
+
this.containerElement = null;
|
|
545
|
+
this.placeHolderElement = null;
|
|
546
|
+
this.videojsPlayer = null;
|
|
547
|
+
this.dashPlayer = null;
|
|
548
|
+
this.iceServers = '[ { "urls": "stun:stun1.l.google.com:19302" } ]';
|
|
549
|
+
this.iceConnected = false;
|
|
550
|
+
this.errorCalled = false;
|
|
551
|
+
this.tryNextTechTimer = -1;
|
|
552
|
+
this.aScene = null;
|
|
553
|
+
this.playerListener = null;
|
|
554
|
+
this.webRTCDataListener = null;
|
|
555
|
+
this.websocketURL = null;
|
|
556
|
+
this.httpBaseURL = null;
|
|
557
|
+
this.videoHTMLContent = STATIC_VIDEO_HTML;
|
|
558
|
+
this.videoPlayerId = "video-player";
|
|
559
|
+
this.videojsLoaded = false;
|
|
560
|
+
this.dashjsLoaded = false;
|
|
561
|
+
}
|
|
562
|
+
initializeFromUrlParams() {
|
|
563
|
+
// Fetch parameters from URL and set to class properties
|
|
564
|
+
this.streamId = getUrlParameter_1("id", this.window.location.search) || this.streamId;
|
|
565
|
+
if (this.streamId == null) {
|
|
566
|
+
//check name variable for compatibility with older versions
|
|
567
|
+
|
|
568
|
+
this.streamId = getUrlParameter_1("name", this.window.location.search) || this.streamId;
|
|
569
|
+
if (this.streamId == null) {
|
|
570
|
+
Logger_1.warn("Please use id parameter instead of name parameter.");
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
this.is360 = getUrlParameter_1("is360", this.window.location.search) === "true" || this.is360;
|
|
574
|
+
this.playType = getUrlParameter_1("playType", this.window.location.search)?.split(',') || this.playType;
|
|
575
|
+
this.token = getUrlParameter_1("token", this.window.location.search) || this.token;
|
|
576
|
+
let autoPlayLocal = getUrlParameter_1("autoplay", this.window.location.search);
|
|
577
|
+
if (autoPlayLocal === "false") {
|
|
578
|
+
this.autoPlay = false;
|
|
579
|
+
} else {
|
|
580
|
+
this.autoPlay = true;
|
|
581
|
+
}
|
|
582
|
+
let muteLocal = getUrlParameter_1("mute", this.window.location.search);
|
|
583
|
+
if (muteLocal === "false") {
|
|
584
|
+
this.mute = false;
|
|
585
|
+
} else {
|
|
586
|
+
this.mute = true;
|
|
587
|
+
}
|
|
588
|
+
let localTargetLatency = getUrlParameter_1("targetLatency", this.window.location.search);
|
|
589
|
+
if (localTargetLatency != null) {
|
|
590
|
+
let latencyInNumber = Number(localTargetLatency);
|
|
591
|
+
if (!isNaN(latencyInNumber)) {
|
|
592
|
+
this.targetLatency = latencyInNumber;
|
|
593
|
+
} else {
|
|
594
|
+
Logger_1.warn("targetLatency parameter is not a number. It will be ignored.");
|
|
595
|
+
this.targetLatency = this.targetLatency || 3; // Default value or existing value
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
this.subscriberId = getUrlParameter_1("subscriberId", this.window.location.search) || this.subscriberId;
|
|
599
|
+
this.subscriberCode = getUrlParameter_1("subscriberCode", this.window.location.search) || this.subscriberCode;
|
|
600
|
+
let playOrder = getUrlParameter_1("playOrder", this.window.location.search);
|
|
601
|
+
this.playOrder = playOrder ? playOrder.split(',') : this.playOrder;
|
|
602
|
+
}
|
|
603
|
+
loadWebRTCComponents() {
|
|
604
|
+
if (this.playOrder.includes("webrtc")) {
|
|
605
|
+
return import('./videojs-webrtc-plugin-b9e4da27.js').then(css => {
|
|
606
|
+
Logger_1.info("videojs-webrtc-plugin.css is loaded");
|
|
607
|
+
const styleElement = this.dom.createElement('style');
|
|
608
|
+
styleElement.textContent = css.default.toString(); // Assuming css module exports a string
|
|
609
|
+
this.dom.head.appendChild(styleElement);
|
|
610
|
+
return import('./videojs-webrtc-plugin.es-f41400f7.js').then(videojsWebrtcPluginLocal => {
|
|
611
|
+
Logger_1.info("videojs-webrtc-plugin is loaded");
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
} else {
|
|
615
|
+
return Promise.resolve();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* load scripts dynamically
|
|
620
|
+
*/
|
|
621
|
+
loadVideoJSComponents() {
|
|
622
|
+
if (this.playOrder.includes("hls") || this.playOrder.includes("vod") || this.playOrder.includes("webrtc")) {
|
|
623
|
+
//it means we're going to use videojs
|
|
624
|
+
//load videojs css
|
|
625
|
+
if (!this.videojsLoaded) {
|
|
626
|
+
return import('./video-js.min-8b4dfe88.js').then(css => {
|
|
627
|
+
const styleElement = this.dom.createElement('style');
|
|
628
|
+
styleElement.textContent = css.default.toString(); // Assuming css module exports a string
|
|
629
|
+
this.dom.head.appendChild(styleElement);
|
|
630
|
+
}).then(() => {
|
|
631
|
+
return import('./video.es-22056625.js').then(function (n) { return n.c; });
|
|
632
|
+
}).then(videojs => {
|
|
633
|
+
window.videojs = videojs.default;
|
|
634
|
+
this.videojsLoaded = true;
|
|
635
|
+
}).then(() => {
|
|
636
|
+
return import('./videojs-contrib-quality-levels.es-5f5b5f23.js');
|
|
637
|
+
}).then(() => {
|
|
638
|
+
return import('./videojs-hls-quality-selector.es-3c54e1cd.js');
|
|
639
|
+
}).then(() => {
|
|
640
|
+
return this.loadWebRTCComponents();
|
|
641
|
+
});
|
|
642
|
+
} else {
|
|
643
|
+
return Promise.resolve();
|
|
644
|
+
}
|
|
645
|
+
} else {
|
|
646
|
+
return Promise.resolve();
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* enable 360 player
|
|
652
|
+
*/
|
|
653
|
+
enable360Player() {
|
|
654
|
+
this.aScene = this.dom.createElement("a-scene");
|
|
655
|
+
var elementId = this.dom.getElementsByTagName("video")[0].id;
|
|
656
|
+
this.aScene.innerHTML = "<a-videosphere src=\"#" + elementId + "\" rotation=\"0 180 0\" style=\"background-color: antiquewhite\"></a-videosphere>";
|
|
657
|
+
this.dom.body.appendChild(this.aScene);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* set player visibility
|
|
662
|
+
* @param {boolean} visible
|
|
663
|
+
*/
|
|
664
|
+
setPlayerVisible(visible) {
|
|
665
|
+
this.containerElement.style.display = visible ? "block" : "none";
|
|
666
|
+
if (this.placeHolderElement) {
|
|
667
|
+
this.placeHolderElement.style.display = visible ? "none" : "block";
|
|
668
|
+
}
|
|
669
|
+
if (this.is360) {
|
|
670
|
+
if (visible) {
|
|
671
|
+
this.enable360Player();
|
|
672
|
+
} else if (this.aScene != null) {
|
|
673
|
+
var elements = this.dom.getElementsByTagName("a-scene");
|
|
674
|
+
while (elements.length > 0) {
|
|
675
|
+
this.dom.body.removeChild(elements[0]);
|
|
676
|
+
elements = this.dom.getElementsByTagName("a-scene");
|
|
677
|
+
}
|
|
678
|
+
this.aScene = null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
handleWebRTCInfoMessages(infos) {
|
|
683
|
+
if (infos["info"] == "ice_connection_state_changed") {
|
|
684
|
+
Logger_1.debug("ice connection state changed to " + infos["obj"].state);
|
|
685
|
+
if (infos["obj"].state == "completed" || infos["obj"].state == "connected") {
|
|
686
|
+
this.iceConnected = true;
|
|
687
|
+
} else if (infos["obj"].state == "failed" || infos["obj"].state == "disconnected" || infos["obj"].state == "closed") {
|
|
688
|
+
//
|
|
689
|
+
Logger_1.warn("Ice connection is not connected. tryNextTech to replay");
|
|
690
|
+
this.tryNextTech();
|
|
691
|
+
}
|
|
692
|
+
} else if (infos["info"] == "closed") {
|
|
693
|
+
//this means websocket is closed and it stops the playback - tryNextTech
|
|
694
|
+
Logger_1.warn("Websocket is closed. tryNextTech to replay");
|
|
695
|
+
this.tryNextTech();
|
|
696
|
+
} else if (infos["info"] == "resolutionChangeInfo") {
|
|
697
|
+
Logger_1.info("Resolution is changing");
|
|
698
|
+
this.videojsPlayer.pause();
|
|
699
|
+
setTimeout(() => {
|
|
700
|
+
this.videojsPlayer.play();
|
|
701
|
+
}, 1000);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Play the stream via videojs
|
|
707
|
+
* @param {*} streamUrl
|
|
708
|
+
* @param {*} extension
|
|
709
|
+
* @returns
|
|
710
|
+
*/
|
|
711
|
+
playWithVideoJS(streamUrl, extension) {
|
|
712
|
+
var type;
|
|
713
|
+
if (extension == "mp4") {
|
|
714
|
+
type = "video/mp4";
|
|
715
|
+
} else if (extension == "webm") {
|
|
716
|
+
type = "video/webm";
|
|
717
|
+
} else if (extension == "mov") {
|
|
718
|
+
type = "video/mp4";
|
|
719
|
+
alert("Browsers do not support to play mov format");
|
|
720
|
+
} else if (extension == "avi") {
|
|
721
|
+
type = "video/mp4";
|
|
722
|
+
alert("Browsers do not support to play avi format");
|
|
723
|
+
} else if (extension == "m3u8") {
|
|
724
|
+
type = "application/x-mpegURL";
|
|
725
|
+
} else if (extension == "mpd") {
|
|
726
|
+
type = "application/dash+xml";
|
|
727
|
+
} else if (extension == "webrtc") {
|
|
728
|
+
type = "video/webrtc";
|
|
729
|
+
} else {
|
|
730
|
+
Logger_1.warn("Unknown extension: " + extension);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
var preview = this.streamId;
|
|
734
|
+
if (this.streamId.endsWith("_adaptive")) {
|
|
735
|
+
preview = streamId.substring(0, streamId.indexOf("_adaptive"));
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
//same videojs is being use for hls, vod and webrtc streams
|
|
739
|
+
this.videojsPlayer = videojs(this.videoPlayerId, {
|
|
740
|
+
poster: "previews/" + preview + ".png",
|
|
741
|
+
liveui: extension == "m3u8" ? true : false,
|
|
742
|
+
liveTracker: {
|
|
743
|
+
trackingThreshold: 0
|
|
744
|
+
},
|
|
745
|
+
html5: {
|
|
746
|
+
vhs: {
|
|
747
|
+
limitRenditionByPlayerDimensions: false
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
controls: true,
|
|
751
|
+
class: 'video-js vjs-default-skin vjs-big-play-centered',
|
|
752
|
+
muted: this.mute,
|
|
753
|
+
preload: "auto",
|
|
754
|
+
autoplay: this.autoPlay
|
|
755
|
+
});
|
|
756
|
+
this.videojsPlayer.on('error', e => {
|
|
757
|
+
Logger_1.warn("There is an error in playback: " + e);
|
|
758
|
+
// We need to add this kind of check. If we don't add this kind of checkpoint, it will create an infinite loop
|
|
759
|
+
if (!this.errorCalled) {
|
|
760
|
+
this.errorCalled = true;
|
|
761
|
+
setTimeout(() => {
|
|
762
|
+
this.tryNextTech();
|
|
763
|
+
this.errorCalled = false;
|
|
764
|
+
}, 2500);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
//webrtc specific events
|
|
769
|
+
if (extension == "webrtc") {
|
|
770
|
+
this.videojsPlayer.on('webrtc-info', (event, infos) => {
|
|
771
|
+
//Logger.warn("info callback: " + JSON.stringify(infos));
|
|
772
|
+
this.handleWebRTCInfoMessages(infos);
|
|
773
|
+
});
|
|
774
|
+
this.videojsPlayer.on('webrtc-error', (event, errors) => {
|
|
775
|
+
//some of the possible errors, NotFoundError, SecurityError,PermissionDeniedError
|
|
776
|
+
Logger_1.warn("error callback: " + JSON.stringify(errors));
|
|
777
|
+
if (errors["error"] == "no_stream_exist" || errors["error"] == "WebSocketNotConnected" || errors["error"] == "not_initialized_yet" || errors["error"] == "data_store_not_available" || errors["error"] == "highResourceUsage" || errors["error"] == "unauthorized_access" || errors["error"] == "user_blocked") {
|
|
778
|
+
//handle high resource usage and not authroized errors && websocket disconnected
|
|
779
|
+
//Even if webrtc adaptor has auto reconnect scenario, we dispose the videojs immediately in tryNextTech
|
|
780
|
+
// so that reconnect scenario is managed here
|
|
781
|
+
|
|
782
|
+
this.tryNextTech();
|
|
783
|
+
} else if (errors["error"] == "notSetRemoteDescription") {
|
|
784
|
+
/*
|
|
785
|
+
* If getting codec incompatible or remote description error, it will redirect HLS player.
|
|
786
|
+
*/
|
|
787
|
+
Logger_1.warn("notSetRemoteDescription error. Redirecting to HLS player.");
|
|
788
|
+
this.playIfExists("hls");
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
this.videojsPlayer.on("webrtc-data-received", (event, obj) => {
|
|
792
|
+
Logger_1.warn("webrtc-data-received: " + JSON.stringify(obj));
|
|
793
|
+
if (this.webRTCDataListener != null) {
|
|
794
|
+
this.webRTCDataListener(obj);
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
//hls specific calls
|
|
800
|
+
if (extension == "m3u8") {
|
|
801
|
+
videojs.Vhs.xhr.beforeRequest = options => {
|
|
802
|
+
let securityParams = this.getSecurityQueryParams();
|
|
803
|
+
if (!options.uri.includes(securityParams)) {
|
|
804
|
+
if (!options.uri.endsWith("?")) {
|
|
805
|
+
options.uri = options.uri + "?";
|
|
806
|
+
}
|
|
807
|
+
options.uri += securityParams;
|
|
808
|
+
}
|
|
809
|
+
Logger_1.debug("hls request: " + options.uri);
|
|
810
|
+
return options;
|
|
811
|
+
};
|
|
812
|
+
this.videojsPlayer.ready(() => {
|
|
813
|
+
// If it's already added to player, no need to add again
|
|
814
|
+
if (typeof this.videojsPlayer.hlsQualitySelector === "function") {
|
|
815
|
+
this.videojsPlayer.hlsQualitySelector({
|
|
816
|
+
displayCurrentQuality: true
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// If there is no adaptive option in m3u8 no need to show quality selector
|
|
821
|
+
let qualityLevels = this.videojsPlayer.qualityLevels();
|
|
822
|
+
qualityLevels.on('addqualitylevel', function (event) {
|
|
823
|
+
let qualityLevel = event.qualityLevel;
|
|
824
|
+
if (qualityLevel.height) {
|
|
825
|
+
qualityLevel.enabled = true;
|
|
826
|
+
} else {
|
|
827
|
+
qualityLevels.removeQualityLevel(qualityLevel);
|
|
828
|
+
qualityLevel.enabled = false;
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
//videojs is being used to play mp4, webm, m3u8 and webrtc
|
|
835
|
+
//make the videoJS visible when ready is called except for webrtc
|
|
836
|
+
//webrtc fires ready event all cases so we use "play" event to make the player visible
|
|
837
|
+
|
|
838
|
+
//this setting is critical to play in mobile
|
|
839
|
+
if (extension == "mp4" || extension == "webm" || extension == "m3u8") {
|
|
840
|
+
this.makeVideoJSVisibleWhenReady();
|
|
841
|
+
}
|
|
842
|
+
this.videojsPlayer.on('ended', () => {
|
|
843
|
+
//reinit to play after it ends
|
|
844
|
+
Logger_1.warn("stream is ended");
|
|
845
|
+
this.setPlayerVisible(false);
|
|
846
|
+
//for webrtc, this event can be called by two reasons
|
|
847
|
+
//1. ice connection is not established, it means that there is a networking issug
|
|
848
|
+
//2. stream is ended
|
|
849
|
+
if (this.currentPlayType != "vod") {
|
|
850
|
+
//if it's vod, it means that stream is ended and no need to replay
|
|
851
|
+
|
|
852
|
+
if (this.iceConnected) {
|
|
853
|
+
//if iceConnected is true, it means that stream is really ended for webrtc
|
|
854
|
+
|
|
855
|
+
//initialize to play again if the publishing starts again
|
|
856
|
+
this.playIfExists(this.playOrder[0]);
|
|
857
|
+
} else if (this.currentPlayType == "hls") {
|
|
858
|
+
//if it's hls, it means that stream is ended
|
|
859
|
+
|
|
860
|
+
this.setPlayerVisible(false);
|
|
861
|
+
if (this.playOrder[0] = "hls") {
|
|
862
|
+
//do not play again if it's hls because it play last seconds again, let the server clear it
|
|
863
|
+
setTimeout(() => {
|
|
864
|
+
this.playIfExists(this.playOrder[0]);
|
|
865
|
+
}, 10000);
|
|
866
|
+
} else {
|
|
867
|
+
this.playIfExists(this.playOrder[0]);
|
|
868
|
+
}
|
|
869
|
+
//TODO: what if the stream is hls vod then it always re-play
|
|
870
|
+
} else {
|
|
871
|
+
//if iceConnected is false, it means that there is a networking issue for webrtc
|
|
872
|
+
this.tryNextTech();
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
if (this.playerListener != null) {
|
|
876
|
+
this.playerListener("ended");
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
//webrtc plugin sends play event. On the other hand, webrtc plugin sends ready event for every scenario.
|
|
881
|
+
//so no need to trust ready event for webrt play
|
|
882
|
+
this.videojsPlayer.on("play", () => {
|
|
883
|
+
this.setPlayerVisible(true);
|
|
884
|
+
if (this.playerListener != null) {
|
|
885
|
+
this.playerListener("play");
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
this.iceConnected = false;
|
|
889
|
+
this.videojsPlayer.src({
|
|
890
|
+
src: streamUrl,
|
|
891
|
+
type: type,
|
|
892
|
+
withCredentials: true,
|
|
893
|
+
iceServers: this.iceServers,
|
|
894
|
+
reconnect: false //webrtc adaptor has auto reconnect scenario, just disable it, we manage it here
|
|
895
|
+
});
|
|
896
|
+
if (this.autoPlay) {
|
|
897
|
+
this.videojsPlayer.play().catch(e => {
|
|
898
|
+
Logger_1.warn("Problem in playback. The error is " + e);
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
makeVideoJSVisibleWhenReady() {
|
|
903
|
+
this.videojsPlayer.ready(() => {
|
|
904
|
+
this.setPlayerVisible(true);
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* check if stream exists via http
|
|
910
|
+
* @param {*} streamsfolder
|
|
911
|
+
* @param {*} streamId
|
|
912
|
+
* @param {*} extension
|
|
913
|
+
* @returns
|
|
914
|
+
*/
|
|
915
|
+
checkStreamExistsViaHttp(streamsfolder, streamId, extension) {
|
|
916
|
+
var streamPath = this.httpBaseURL;
|
|
917
|
+
if (!streamId.startsWith(streamsfolder)) {
|
|
918
|
+
streamPath += streamsfolder + "/";
|
|
919
|
+
}
|
|
920
|
+
streamPath += streamId;
|
|
921
|
+
if (extension != null && extension != "") {
|
|
922
|
+
//if there is extension, add it and try if _adaptive exists
|
|
923
|
+
streamPath += "_adaptive" + "." + extension;
|
|
924
|
+
}
|
|
925
|
+
streamPath = this.addSecurityParams(streamPath);
|
|
926
|
+
return fetch(streamPath, {
|
|
927
|
+
method: 'HEAD'
|
|
928
|
+
}).then(response => {
|
|
929
|
+
if (response.status == 200) {
|
|
930
|
+
// adaptive m3u8 & mpd exists,play it
|
|
931
|
+
return new Promise(function (resolve, reject) {
|
|
932
|
+
resolve(streamPath);
|
|
933
|
+
});
|
|
934
|
+
} else {
|
|
935
|
+
//adaptive not exists, try mpd or m3u8 exists.
|
|
936
|
+
streamPath = this.httpBaseURL + streamsfolder + "/" + streamId + "." + extension;
|
|
937
|
+
streamPath = this.addSecurityParams(streamPath);
|
|
938
|
+
return fetch(streamPath, {
|
|
939
|
+
method: 'HEAD'
|
|
940
|
+
}).then(response => {
|
|
941
|
+
if (response.status == 200) {
|
|
942
|
+
return new Promise(function (resolve, reject) {
|
|
943
|
+
resolve(streamPath);
|
|
944
|
+
});
|
|
945
|
+
} else {
|
|
946
|
+
Logger_1.warn("No stream found");
|
|
947
|
+
return new Promise(function (resolve, reject) {
|
|
948
|
+
reject("resource_is_not_available");
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
addSecurityParams(streamPath) {
|
|
956
|
+
var securityParams = this.getSecurityQueryParams();
|
|
957
|
+
if (securityParams != null && securityParams != "") {
|
|
958
|
+
streamPath += "?" + securityParams;
|
|
959
|
+
}
|
|
960
|
+
return streamPath;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* try next tech if current tech is not working
|
|
965
|
+
*/
|
|
966
|
+
tryNextTech() {
|
|
967
|
+
if (this.tryNextTechTimer == -1) {
|
|
968
|
+
this.destroyDashPlayer();
|
|
969
|
+
this.destroyVideoJSPlayer();
|
|
970
|
+
this.setPlayerVisible(false);
|
|
971
|
+
var index = this.playOrder.indexOf(this.currentPlayType);
|
|
972
|
+
if (index == -1 || index == this.playOrder.length - 1) {
|
|
973
|
+
index = 0;
|
|
974
|
+
} else {
|
|
975
|
+
index++;
|
|
976
|
+
}
|
|
977
|
+
this.tryNextTechTimer = setTimeout(() => {
|
|
978
|
+
this.tryNextTechTimer = -1;
|
|
979
|
+
this.playIfExists(this.playOrder[index]);
|
|
980
|
+
}, 3000);
|
|
981
|
+
} else {
|
|
982
|
+
Logger_1.debug("tryNextTech is already scheduled no need to schedule again");
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* play stream throgugh dash player
|
|
988
|
+
* @param {string"} streamUrl
|
|
989
|
+
*/
|
|
990
|
+
playViaDash(streamUrl) {
|
|
991
|
+
this.destroyDashPlayer();
|
|
992
|
+
this.dashPlayer = dashjs.MediaPlayer().create();
|
|
993
|
+
this.dashPlayer.extend("RequestModifier", () => {
|
|
994
|
+
return {
|
|
995
|
+
modifyRequestHeader: function (xhr, {
|
|
996
|
+
url
|
|
997
|
+
}) {
|
|
998
|
+
return xhr;
|
|
999
|
+
},
|
|
1000
|
+
modifyRequestURL: url => {
|
|
1001
|
+
var modifiedUrl = "";
|
|
1002
|
+
var securityParams = this.getSecurityQueryParams();
|
|
1003
|
+
if (!url.includes(securityParams)) {
|
|
1004
|
+
if (!url.endsWith("?")) {
|
|
1005
|
+
url += "?";
|
|
1006
|
+
}
|
|
1007
|
+
modifiedUrl = url + securityParams;
|
|
1008
|
+
Logger_1.warn(modifiedUrl);
|
|
1009
|
+
return modifiedUrl;
|
|
1010
|
+
}
|
|
1011
|
+
return url;
|
|
1012
|
+
},
|
|
1013
|
+
modifyRequest(request) {}
|
|
1014
|
+
};
|
|
1015
|
+
});
|
|
1016
|
+
this.dashPlayer.updateSettings({
|
|
1017
|
+
streaming: {
|
|
1018
|
+
delay: {
|
|
1019
|
+
liveDelay: this.targetLatency
|
|
1020
|
+
},
|
|
1021
|
+
liveCatchup: {
|
|
1022
|
+
maxDrift: 0.5,
|
|
1023
|
+
playbackRate: 0.5,
|
|
1024
|
+
latencyThreshold: 60
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
this.dashPlayer.initialize(this.containerElement.firstChild, streamUrl, this.autoPlay);
|
|
1029
|
+
this.dashPlayer.setMute(this.mute);
|
|
1030
|
+
this.dashLatencyTimer = setInterval(() => {
|
|
1031
|
+
Logger_1.warn("live latency: " + this.dashPlayer.getCurrentLiveLatency());
|
|
1032
|
+
}, 2000);
|
|
1033
|
+
this.makeDashPlayerVisibleWhenInitialized();
|
|
1034
|
+
this.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_PLAYING, event => {
|
|
1035
|
+
Logger_1.warn("playback started");
|
|
1036
|
+
this.setPlayerVisible(true);
|
|
1037
|
+
if (this.playerListener != null) {
|
|
1038
|
+
this.playerListener("play");
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
this.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_ENDED, () => {
|
|
1042
|
+
Logger_1.warn("playback ended");
|
|
1043
|
+
this.destroyDashPlayer();
|
|
1044
|
+
this.setPlayerVisible(false);
|
|
1045
|
+
//streaming can be started again so try to play again with preferred tech
|
|
1046
|
+
if (this.playOrder[0] = "dash") {
|
|
1047
|
+
//do not play again if it's dash because it play last seconds again, let the server clear it
|
|
1048
|
+
setTimeout(() => {
|
|
1049
|
+
this.playIfExists(this.playOrder[0]);
|
|
1050
|
+
}, 10000);
|
|
1051
|
+
} else {
|
|
1052
|
+
this.playIfExists(this.playOrder[0]);
|
|
1053
|
+
}
|
|
1054
|
+
if (this.playerListener != null) {
|
|
1055
|
+
this.playerListener("ended");
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
this.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_ERROR, event => {
|
|
1059
|
+
this.tryNextTech();
|
|
1060
|
+
});
|
|
1061
|
+
this.dashPlayer.on(dashjs.MediaPlayer.events.ERROR, event => {
|
|
1062
|
+
this.tryNextTech();
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
makeDashPlayerVisibleWhenInitialized() {
|
|
1066
|
+
this.dashPlayer.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, event => {
|
|
1067
|
+
Logger_1.warn("Stream initialized");
|
|
1068
|
+
//make the player visible in mobile devices
|
|
1069
|
+
this.setPlayerVisible(true);
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* destroy the dash player
|
|
1075
|
+
*/
|
|
1076
|
+
destroyDashPlayer() {
|
|
1077
|
+
if (this.dashPlayer) {
|
|
1078
|
+
this.dashPlayer.destroy();
|
|
1079
|
+
this.dashPlayer = null;
|
|
1080
|
+
clearInterval(this.dashLatencyTimer);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* destroy the videojs player
|
|
1086
|
+
*/
|
|
1087
|
+
destroyVideoJSPlayer() {
|
|
1088
|
+
if (this.videojsPlayer) {
|
|
1089
|
+
this.videojsPlayer.dispose();
|
|
1090
|
+
this.videojsPlayer = null;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Destory the player
|
|
1096
|
+
*/
|
|
1097
|
+
destroy() {
|
|
1098
|
+
this.destroyVideoJSPlayer();
|
|
1099
|
+
this.destroyDashPlayer();
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* play the stream with the given tech
|
|
1104
|
+
* @param {string} tech
|
|
1105
|
+
*/
|
|
1106
|
+
async playIfExists(tech) {
|
|
1107
|
+
this.currentPlayType = tech;
|
|
1108
|
+
this.destroyVideoJSPlayer();
|
|
1109
|
+
this.destroyDashPlayer();
|
|
1110
|
+
this.setPlayerVisible(false);
|
|
1111
|
+
this.containerElement.innerHTML = this.videoHTMLContent;
|
|
1112
|
+
Logger_1.warn("Try to play the stream " + this.streamId + " with " + this.currentPlayType);
|
|
1113
|
+
switch (this.currentPlayType) {
|
|
1114
|
+
case "hls":
|
|
1115
|
+
//TODO: Test case for hls
|
|
1116
|
+
//1. Play stream with adaptive m3u8 for live and VoD
|
|
1117
|
+
//2. Play stream with m3u8 for live and VoD
|
|
1118
|
+
//3. if files are not available check nextTech is being called
|
|
1119
|
+
return this.checkStreamExistsViaHttp(WebPlayer.STREAMS_FOLDER, this.streamId, WebPlayer.HLS_EXTENSION).then(streamPath => {
|
|
1120
|
+
this.playWithVideoJS(streamPath, WebPlayer.HLS_EXTENSION);
|
|
1121
|
+
Logger_1.warn("incoming stream path: " + streamPath);
|
|
1122
|
+
}).catch(error => {
|
|
1123
|
+
Logger_1.warn("HLS stream resource not available for stream:" + this.streamId + " error is " + error + ". Try next play tech");
|
|
1124
|
+
this.tryNextTech();
|
|
1125
|
+
});
|
|
1126
|
+
case "dash":
|
|
1127
|
+
return this.checkStreamExistsViaHttp(WebPlayer.STREAMS_FOLDER, this.streamId + "/" + this.streamId, WebPlayer.DASH_EXTENSION).then(streamPath => {
|
|
1128
|
+
this.playViaDash(streamPath);
|
|
1129
|
+
}).catch(error => {
|
|
1130
|
+
Logger_1.warn("DASH stream resource not available for stream:" + this.streamId + " error is " + error + ". Try next play tech");
|
|
1131
|
+
this.tryNextTech();
|
|
1132
|
+
});
|
|
1133
|
+
case "webrtc":
|
|
1134
|
+
return this.playWithVideoJS(this.addSecurityParams(this.websocketURL), WebPlayer.WEBRTC_EXTENSION);
|
|
1135
|
+
case "vod":
|
|
1136
|
+
//TODO: Test case for vod
|
|
1137
|
+
//1. Play stream with mp4 for VoD
|
|
1138
|
+
//2. Play stream with webm for VoD
|
|
1139
|
+
//3. Play stream with playOrder type
|
|
1140
|
+
|
|
1141
|
+
var lastIndexOfDot = this.streamId.lastIndexOf(".");
|
|
1142
|
+
var extension;
|
|
1143
|
+
if (lastIndexOfDot != -1) {
|
|
1144
|
+
//if there is a dot in the streamId, it means that this is extension, use it. make the extension empty
|
|
1145
|
+
this.playType[0] = "";
|
|
1146
|
+
extension = this.streamId.substring(lastIndexOfDot + 1);
|
|
1147
|
+
} else {
|
|
1148
|
+
//we need to give extension to playWithVideoJS
|
|
1149
|
+
extension = this.playType[0];
|
|
1150
|
+
}
|
|
1151
|
+
return this.checkStreamExistsViaHttp(WebPlayer.STREAMS_FOLDER, this.streamId, this.playType[0]).then(streamPath => {
|
|
1152
|
+
//we need to give extension to playWithVideoJS
|
|
1153
|
+
this.playWithVideoJS(streamPath, extension);
|
|
1154
|
+
}).catch(error => {
|
|
1155
|
+
Logger_1.warn("VOD stream resource not available for stream:" + this.streamId + " and play type " + this.playType[0] + ". Error is " + error);
|
|
1156
|
+
if (this.playType.length > 1) {
|
|
1157
|
+
Logger_1.warn("Try next play type which is " + this.playType[1] + ".");
|
|
1158
|
+
this.checkStreamExistsViaHttp(WebPlayer.STREAMS_FOLDER, this.streamId, this.playType[1]).then(streamPath => {
|
|
1159
|
+
this.playWithVideoJS(streamPath, this.playType[1]);
|
|
1160
|
+
}).catch(error => {
|
|
1161
|
+
Logger_1.warn("VOD stream resource not available for stream:" + this.streamId + " and play type error is " + error);
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
*
|
|
1170
|
+
* @returns {String} query string for security
|
|
1171
|
+
*/
|
|
1172
|
+
getSecurityQueryParams() {
|
|
1173
|
+
var queryString = "";
|
|
1174
|
+
if (this.token != null) {
|
|
1175
|
+
queryString += "&token=" + this.token;
|
|
1176
|
+
}
|
|
1177
|
+
if (this.subscriberId != null) {
|
|
1178
|
+
queryString += "&subscriberId=" + this.subscriberId;
|
|
1179
|
+
}
|
|
1180
|
+
if (this.subscriberCode != null) {
|
|
1181
|
+
queryString += "&subscriberCode=" + this.subscriberCode;
|
|
1182
|
+
}
|
|
1183
|
+
return queryString;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* play the stream with videojs player or dash player
|
|
1188
|
+
*/
|
|
1189
|
+
play() {
|
|
1190
|
+
if (this.streamId.startsWith(WebPlayer.STREAMS_FOLDER)) {
|
|
1191
|
+
//start videojs player because it directly try to play stream from streams folder
|
|
1192
|
+
var lastIndexOfDot = this.streamId.lastIndexOf(".");
|
|
1193
|
+
var extension = this.streamId.substring(lastIndexOfDot + 1);
|
|
1194
|
+
this.playOrder = ["vod"];
|
|
1195
|
+
if (!this.httpBaseURL.endsWith("/")) {
|
|
1196
|
+
this.httpBaseURL += "/";
|
|
1197
|
+
}
|
|
1198
|
+
this.containerElement.innerHTML = this.videoHTMLContent;
|
|
1199
|
+
if (extension == WebPlayer.DASH_EXTENSION) {
|
|
1200
|
+
this.playViaDash(this.httpBaseURL + this.addSecurityParams(this.streamId), extension);
|
|
1201
|
+
} else {
|
|
1202
|
+
this.playWithVideoJS(this.httpBaseURL + this.addSecurityParams(this.streamId), extension);
|
|
1203
|
+
}
|
|
1204
|
+
} else {
|
|
1205
|
+
this.playIfExists(this.playOrder[0]);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/**
|
|
1210
|
+
* mute or unmute the player
|
|
1211
|
+
* @param {boolean} mutestatus true to mute the player
|
|
1212
|
+
*/
|
|
1213
|
+
mutePlayer(mutestatus) {
|
|
1214
|
+
this.mute = mutestatus;
|
|
1215
|
+
if (this.videojsPlayer) {
|
|
1216
|
+
this.videojsPlayer.muted(mutestatus);
|
|
1217
|
+
}
|
|
1218
|
+
if (this.dashPlayer) {
|
|
1219
|
+
this.dashPlayer.setMute(mutestatus);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
*
|
|
1225
|
+
* @returns {boolean} true if player is muted
|
|
1226
|
+
*/
|
|
1227
|
+
isMuted() {
|
|
1228
|
+
return this.mute;
|
|
1229
|
+
}
|
|
1230
|
+
addPlayerListener(playerListener) {
|
|
1231
|
+
this.playerListener = playerListener;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* WebRTC data listener
|
|
1236
|
+
* @param {*} webRTCDataListener
|
|
1237
|
+
*/
|
|
1238
|
+
addWebRTCDataListener(webRTCDataListener) {
|
|
1239
|
+
this.webRTCDataListener = webRTCDataListener;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
*
|
|
1244
|
+
* @param {*} data
|
|
1245
|
+
*/
|
|
1246
|
+
sendWebRTCData(data) {
|
|
1247
|
+
try {
|
|
1248
|
+
if (this.videojsPlayer && this.currentPlayType == "webrtc") {
|
|
1249
|
+
this.videojsPlayer.sendDataViaWebRTC(data);
|
|
1250
|
+
return true;
|
|
1251
|
+
} else {
|
|
1252
|
+
Logger_1.warn("Player is not ready or playType is not WebRTC");
|
|
1253
|
+
}
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
// Handle the error here
|
|
1256
|
+
Logger_1.error("An error occurred while sending WebRTC data: ", error);
|
|
1257
|
+
}
|
|
1258
|
+
return false;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
export { STATIC_VIDEO_HTML, WebPlayer };
|