@flashphoner/websdk 2.0.205 → 2.0.209
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/docTemplate/README.md +1 -1
- package/examples/demo/sip/phone/phone.js +7 -10
- package/examples/demo/streaming/media_devices_manager/manager.js +2 -5
- package/examples/demo/streaming/screen-sharing/screen-sharing.html +1 -1
- package/examples/demo/streaming/screen-sharing/screen-sharing.js +1 -1
- package/examples/demo/streaming/stream-auto-restore/stream-auto-restore.css +23 -0
- package/examples/demo/streaming/stream-auto-restore/stream-auto-restore.html +152 -0
- package/examples/demo/streaming/stream-auto-restore/stream-auto-restore.js +744 -0
- package/flashphoner-no-flash.js +100 -19
- package/flashphoner-no-flash.min.js +2 -2
- package/flashphoner-no-webrtc.js +98 -17
- package/flashphoner-no-webrtc.min.js +1 -1
- package/flashphoner-no-wsplayer.js +101 -20
- package/flashphoner-no-wsplayer.min.js +2 -2
- package/flashphoner-room-api.js +77 -4
- package/flashphoner-room-api.min.js +2 -2
- package/flashphoner-temasys-flash-websocket-without-adapterjs.js +100 -19
- package/flashphoner-temasys-flash-websocket.js +100 -19
- package/flashphoner-temasys-flash-websocket.min.js +1 -1
- package/flashphoner-webrtc-only.js +98 -17
- package/flashphoner-webrtc-only.min.js +1 -1
- package/flashphoner.js +101 -20
- package/flashphoner.min.js +2 -2
- package/package.json +1 -1
- package/src/flashphoner-core.d.ts +4 -1
- package/src/flashphoner-core.js +74 -1
- package/src/webrtc-media-provider.js +3 -3
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
var SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
|
|
2
|
+
var STREAM_STATUS = Flashphoner.constants.STREAM_STATUS;
|
|
3
|
+
var STREAM_EVENT = Flashphoner.constants.STREAM_EVENT;
|
|
4
|
+
var STREAM_EVENT_TYPE = Flashphoner.constants.STREAM_EVENT_TYPE;
|
|
5
|
+
var STREAM_STATUS_INFO = Flashphoner.constants.STREAM_STATUS_INFO;
|
|
6
|
+
var ERROR_INFO = Flashphoner.constants.ERROR_INFO;
|
|
7
|
+
var PRELOADER_URL = "../../dependencies/media/preloader.mp4";
|
|
8
|
+
var PUBLISH_FAILURE_DETECTOR_MAX_TRIES = 3;
|
|
9
|
+
var PUBLISH_FAILURE_DETECTOR_INTERVAL = 500;
|
|
10
|
+
var RESTART_MAX_TRIES = 100;
|
|
11
|
+
var RESTART_TIMEOUT = 3000;
|
|
12
|
+
var MAX_PINGS_MISSING = 10;
|
|
13
|
+
var PING_CHECK_TIMEOUT = 5000;
|
|
14
|
+
var Browser = Flashphoner.Browser;
|
|
15
|
+
var localVideo;
|
|
16
|
+
var remoteVideo;
|
|
17
|
+
var h264PublishFailureDetector;
|
|
18
|
+
var currentSession;
|
|
19
|
+
var streamPublishing;
|
|
20
|
+
var streamPlaying;
|
|
21
|
+
var streamingRestarter;
|
|
22
|
+
var connection;
|
|
23
|
+
var connectionType;
|
|
24
|
+
|
|
25
|
+
//////////////////////////////////
|
|
26
|
+
/////////////// Init /////////////
|
|
27
|
+
|
|
28
|
+
function init_page() {
|
|
29
|
+
//init api
|
|
30
|
+
try {
|
|
31
|
+
Flashphoner.init();
|
|
32
|
+
} catch (e) {
|
|
33
|
+
$("#notifyFlash").text("Your browser doesn't support WebRTC technology needed for this example");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//local and remote displays
|
|
38
|
+
localVideo = document.getElementById("localVideo");
|
|
39
|
+
remoteVideo = document.getElementById("remoteVideo");
|
|
40
|
+
|
|
41
|
+
// session and streams state objects
|
|
42
|
+
currentSession = sessionState();
|
|
43
|
+
streamPublishing = streamState();
|
|
44
|
+
streamPlaying = streamState();
|
|
45
|
+
// Publish failure detector object #WCS-3382
|
|
46
|
+
h264PublishFailureDetector = codecPublishingFailureDetector();
|
|
47
|
+
// Publishing/playback restarter object #WCS-3410
|
|
48
|
+
streamingRestarter = streamRestarter(function() {
|
|
49
|
+
if (streamPublishing.wasActive) {
|
|
50
|
+
onPublishRestart();
|
|
51
|
+
}
|
|
52
|
+
if (streamPlaying.wasActive && streamPlaying.name != streamPublishing.name) {
|
|
53
|
+
onPlayRestart();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// Start network change detection #WCS-3410
|
|
57
|
+
networkChangeDetector();
|
|
58
|
+
|
|
59
|
+
$("#urlServer").val(setURL());
|
|
60
|
+
var streamName = createUUID(4);
|
|
61
|
+
$("#publishStream").val(streamName);
|
|
62
|
+
$("#playStream").val(streamName);
|
|
63
|
+
$("#bitrateInteval").val(PUBLISH_FAILURE_DETECTOR_INTERVAL);
|
|
64
|
+
$("#bitrateMaxTries").val(PUBLISH_FAILURE_DETECTOR_MAX_TRIES);
|
|
65
|
+
$("#restoreTimeout").val(RESTART_TIMEOUT);
|
|
66
|
+
$("#restoreMaxTries").val(RESTART_MAX_TRIES);
|
|
67
|
+
$("#maxPingsMissing").val(MAX_PINGS_MISSING);
|
|
68
|
+
$("#pingsPeriod").val(PING_CHECK_TIMEOUT);
|
|
69
|
+
onDisconnected();
|
|
70
|
+
onUnpublished();
|
|
71
|
+
onStopped();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function connect() {
|
|
75
|
+
var restoreConnection = $("#restoreConnection").is(':checked');
|
|
76
|
+
var url = $('#urlServer').val();
|
|
77
|
+
var receiveProbes = restoreConnection ? $("#maxPingsMissing").val() : 0;
|
|
78
|
+
var probesInterval = restoreConnection ? $("#pingsPeriod").val() : 0;
|
|
79
|
+
|
|
80
|
+
//create session
|
|
81
|
+
console.log("Create new session with url " + url);
|
|
82
|
+
Flashphoner.createSession({
|
|
83
|
+
urlServer: url,
|
|
84
|
+
receiveProbes: receiveProbes,
|
|
85
|
+
probesInterval: probesInterval
|
|
86
|
+
}).on(SESSION_STATUS.ESTABLISHED, function (session) {
|
|
87
|
+
setStatus("#connectStatus", session.status());
|
|
88
|
+
currentSession.set(url, session);
|
|
89
|
+
onConnected(session);
|
|
90
|
+
if(restoreConnection) {
|
|
91
|
+
if(streamPublishing.wasActive) {
|
|
92
|
+
console.log("A stream was published before disconnection, restart publishing");
|
|
93
|
+
onPublishRestart();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if(streamPlaying.wasActive) {
|
|
97
|
+
console.log("A stream was played before disconnection, restart playback");
|
|
98
|
+
onPlayRestart();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}).on(SESSION_STATUS.DISCONNECTED, function () {
|
|
102
|
+
setStatus("#connectStatus", SESSION_STATUS.DISCONNECTED);
|
|
103
|
+
onDisconnected();
|
|
104
|
+
// Prevent streaming restart if session is manually disconnected
|
|
105
|
+
if (currentSession.isManuallyDisconnected) {
|
|
106
|
+
streamPublishing.clear();
|
|
107
|
+
streamPlaying.clear();
|
|
108
|
+
streamingRestarter.reset();
|
|
109
|
+
currentSession.clear();
|
|
110
|
+
}
|
|
111
|
+
}).on(SESSION_STATUS.FAILED, function () {
|
|
112
|
+
setStatus("#connectStatus", SESSION_STATUS.FAILED);
|
|
113
|
+
onDisconnected();
|
|
114
|
+
if(restoreConnection
|
|
115
|
+
&& (streamPublishing.wasActive || streamPlaying.wasActive)) {
|
|
116
|
+
streamingRestarter.restart($("#restoreTimeout").val(), $("#restoreMaxTries").val());
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function onConnected(session) {
|
|
122
|
+
$("#connectBtn").text("Disconnect").off('click').click(function () {
|
|
123
|
+
$(this).prop('disabled', true);
|
|
124
|
+
currentSession.isManuallyDisconnected = true;
|
|
125
|
+
session.disconnect();
|
|
126
|
+
}).prop('disabled', false);
|
|
127
|
+
onUnpublished();
|
|
128
|
+
onStopped();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function onDisconnected() {
|
|
132
|
+
$("#connectBtn").text("Connect").off('click').click(function () {
|
|
133
|
+
if (validateForm()) {
|
|
134
|
+
$('#urlServer').prop('disabled', true);
|
|
135
|
+
$(this).prop('disabled', true);
|
|
136
|
+
disableForm('reconnectForm', true);
|
|
137
|
+
connect();
|
|
138
|
+
}
|
|
139
|
+
}).prop('disabled', false);
|
|
140
|
+
$('#urlServer').prop('disabled', false);
|
|
141
|
+
onUnpublished();
|
|
142
|
+
onStopped();
|
|
143
|
+
disableForm('bitrateForm', false);
|
|
144
|
+
disableForm('reconnectForm', false);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function onPublishing(stream) {
|
|
148
|
+
$("#publishBtn").text("Stop").off('click').click(function () {
|
|
149
|
+
$(this).prop('disabled', true);
|
|
150
|
+
streamPublishing.isManuallyStopped = true;
|
|
151
|
+
stream.stop();
|
|
152
|
+
}).prop('disabled', false);
|
|
153
|
+
$("#publishInfo").text("");
|
|
154
|
+
// Start publish failure detector by bitrate #WCS-3382
|
|
155
|
+
if($("#checkBitrate").is(':checked')) {
|
|
156
|
+
h264PublishFailureDetector.startDetection(stream, $("#bitrateInteval").val(), $("#bitrateMaxTries").val());
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function onUnpublished() {
|
|
161
|
+
$("#publishBtn").text("Publish").off('click').click(publishBtnClick);
|
|
162
|
+
if (currentSession.getStatus() == SESSION_STATUS.ESTABLISHED) {
|
|
163
|
+
$("#publishBtn").prop('disabled', false);
|
|
164
|
+
$('#publishStream').prop('disabled', false);
|
|
165
|
+
} else {
|
|
166
|
+
$("#publishBtn").prop('disabled', true);
|
|
167
|
+
$('#publishStream').prop('disabled', true);
|
|
168
|
+
}
|
|
169
|
+
h264PublishFailureDetector.stopDetection(streamPublishing.isManuallyStopped || currentSession.isManuallyDisconnected);
|
|
170
|
+
disableForm('bitrateForm', false);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function publishBtnClick(stripCodecs) {
|
|
174
|
+
if (currentSession.getStatus() != SESSION_STATUS.ESTABLISHED) {
|
|
175
|
+
// Prevent stream publishing if session is in wrong state
|
|
176
|
+
console.error("Can't publish, session is not established");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (validateForm()) {
|
|
180
|
+
$('#publishStream').prop('disabled', true);
|
|
181
|
+
$(this).prop('disabled', true);
|
|
182
|
+
disableForm('bitrateForm', true);
|
|
183
|
+
if (Browser.isSafariWebRTC()) {
|
|
184
|
+
Flashphoner.playFirstVideo(localVideo, true, PRELOADER_URL).then(function() {
|
|
185
|
+
publishStream(stripCodecs);
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
publishStream(stripCodecs);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function onPlaying(stream) {
|
|
194
|
+
$("#playBtn").text("Stop").off('click').click(function () {
|
|
195
|
+
$(this).prop('disabled', true);
|
|
196
|
+
stream.stop();
|
|
197
|
+
}).prop('disabled', false);
|
|
198
|
+
$("#playInfo").text("");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function onStopped() {
|
|
202
|
+
$("#playBtn").text("Play").off('click').click(playBtnClick);
|
|
203
|
+
if (Flashphoner.getSessions()[0] && Flashphoner.getSessions()[0].status() == SESSION_STATUS.ESTABLISHED) {
|
|
204
|
+
$("#playBtn").prop('disabled', false);
|
|
205
|
+
$('#playStream').prop('disabled', false);
|
|
206
|
+
} else {
|
|
207
|
+
$("#playBtn").prop('disabled', true);
|
|
208
|
+
$('#playStream').prop('disabled', true);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function playBtnClick() {
|
|
213
|
+
if (currentSession.getStatus() != SESSION_STATUS.ESTABLISHED) {
|
|
214
|
+
// Prevent stream publishing if session is in wrong state
|
|
215
|
+
console.error("Can't play, session is not established");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (validateForm()) {
|
|
219
|
+
$('#playStream').prop('disabled', true);
|
|
220
|
+
$(this).prop('disabled', true);
|
|
221
|
+
if (Flashphoner.getMediaProviders()[0] === "WSPlayer") {
|
|
222
|
+
Flashphoner.playFirstSound();
|
|
223
|
+
} else if (Browser.isSafariWebRTC() || Flashphoner.getMediaProviders()[0] === "MSE") {
|
|
224
|
+
Flashphoner.playFirstVideo(remoteVideo, false, PRELOADER_URL).then(function () {
|
|
225
|
+
playStream();
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
playStream();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function publishStream(stripCodecs) {
|
|
234
|
+
var session = Flashphoner.getSessions()[0];
|
|
235
|
+
var streamName = $('#publishStream').val();
|
|
236
|
+
|
|
237
|
+
session.createStream({
|
|
238
|
+
name: streamName,
|
|
239
|
+
display: localVideo,
|
|
240
|
+
cacheLocalResources: true,
|
|
241
|
+
receiveVideo: false,
|
|
242
|
+
receiveAudio: false,
|
|
243
|
+
stripCodecs: stripCodecs
|
|
244
|
+
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
|
|
245
|
+
setStatus("#publishStatus", STREAM_STATUS.PUBLISHING);
|
|
246
|
+
onPublishing(stream);
|
|
247
|
+
streamPublishing.set(streamName, stream);
|
|
248
|
+
streamingRestarter.reset();
|
|
249
|
+
if ($("#restoreConnection").is(':checked')
|
|
250
|
+
&& streamPlaying.wasActive) {
|
|
251
|
+
console.log("A stream was played before, restart playback");
|
|
252
|
+
onPlayRestart();
|
|
253
|
+
}
|
|
254
|
+
}).on(STREAM_STATUS.UNPUBLISHED, function () {
|
|
255
|
+
setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED);
|
|
256
|
+
onUnpublished();
|
|
257
|
+
if (!streamPlaying.wasActive) {
|
|
258
|
+
// No stream playback< we don't need restart any more
|
|
259
|
+
streamingRestarter.reset();
|
|
260
|
+
} else if (streamPlaying.wasActive && streamPlaying.name == streamPublishing.name) {
|
|
261
|
+
// Prevent playback restart for the same stream
|
|
262
|
+
streamingRestarter.reset();
|
|
263
|
+
}
|
|
264
|
+
streamPublishing.clear();
|
|
265
|
+
}).on(STREAM_STATUS.FAILED, function (stream) {
|
|
266
|
+
setStatus("#publishStatus", STREAM_STATUS.FAILED, stream);
|
|
267
|
+
onUnpublished();
|
|
268
|
+
if ($("#restoreConnection").is(':checked') && stream.getInfo() != ERROR_INFO.LOCAL_ERROR) {
|
|
269
|
+
streamingRestarter.restart($("#restoreTimeout").val(), $("#restoreMaxTries").val());
|
|
270
|
+
}
|
|
271
|
+
}).publish();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function playStream() {
|
|
275
|
+
var session = Flashphoner.getSessions()[0];
|
|
276
|
+
var streamName = $('#playStream').val();
|
|
277
|
+
|
|
278
|
+
session.createStream({
|
|
279
|
+
name: streamName,
|
|
280
|
+
display: remoteVideo
|
|
281
|
+
}).on(STREAM_STATUS.PENDING, function (stream) {
|
|
282
|
+
var video = document.getElementById(stream.id());
|
|
283
|
+
if (!video.hasListeners) {
|
|
284
|
+
video.hasListeners = true;
|
|
285
|
+
video.addEventListener('resize', function (event) {
|
|
286
|
+
resizeVideo(event.target);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}).on(STREAM_STATUS.PLAYING, function (stream) {
|
|
290
|
+
setStatus("#playStatus", stream.status());
|
|
291
|
+
onPlaying(stream);
|
|
292
|
+
streamingRestarter.reset();
|
|
293
|
+
streamPlaying.set(streamName, stream);
|
|
294
|
+
}).on(STREAM_STATUS.STOPPED, function () {
|
|
295
|
+
setStatus("#playStatus", STREAM_STATUS.STOPPED);
|
|
296
|
+
onStopped();
|
|
297
|
+
streamingRestarter.reset();
|
|
298
|
+
streamPlaying.clear();
|
|
299
|
+
}).on(STREAM_STATUS.FAILED, function (stream) {
|
|
300
|
+
setStatus("#playStatus", STREAM_STATUS.FAILED, stream);
|
|
301
|
+
onStopped();
|
|
302
|
+
if ($("#restoreConnection").is(':checked')) {
|
|
303
|
+
streamingRestarter.restart($("#restoreTimeout").val(), $("#restoreMaxTries").val());
|
|
304
|
+
}
|
|
305
|
+
}).play();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
//show connection, or local, or remote stream status
|
|
310
|
+
function setStatus(selector, status, stream) {
|
|
311
|
+
var statusField = $(selector);
|
|
312
|
+
statusField.text(status).removeClass();
|
|
313
|
+
if (status == "PLAYING" || status == "ESTABLISHED" || status == "PUBLISHING") {
|
|
314
|
+
statusField.attr("class", "text-success");
|
|
315
|
+
} else if (status == "DISCONNECTED" || status == "UNPUBLISHED" || status == "STOPPED") {
|
|
316
|
+
statusField.attr("class", "text-muted");
|
|
317
|
+
} else if (status == "FAILED") {
|
|
318
|
+
if (stream) {
|
|
319
|
+
if (stream.published()) {
|
|
320
|
+
switch(stream.getInfo()){
|
|
321
|
+
case STREAM_STATUS_INFO.STREAM_NAME_ALREADY_IN_USE:
|
|
322
|
+
$("#publishInfo").text("Server already has a publish stream with the same name, try using different one").attr("class", "text-muted");
|
|
323
|
+
break;
|
|
324
|
+
case ERROR_INFO.LOCAL_ERROR:
|
|
325
|
+
$("#publishInfo").text("Browser error detected: " + stream.getErrorInfo()).attr("class", "text-muted");
|
|
326
|
+
break;
|
|
327
|
+
default:
|
|
328
|
+
$("#publishInfo").text("Other: "+stream.getInfo()).attr("class", "text-muted");
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
switch(stream.getInfo()){
|
|
333
|
+
case STREAM_STATUS_INFO.SESSION_DOES_NOT_EXIST:
|
|
334
|
+
$("#playInfo").text("Actual session does not exist").attr("class", "text-muted");
|
|
335
|
+
break;
|
|
336
|
+
case STREAM_STATUS_INFO.STOPPED_BY_PUBLISHER_STOP:
|
|
337
|
+
$("#playInfo").text("Related publisher stopped its stream or lost connection").attr("class", "text-muted");
|
|
338
|
+
break;
|
|
339
|
+
case STREAM_STATUS_INFO.SESSION_NOT_READY:
|
|
340
|
+
$("#playInfo").text("Session is not initialized or terminated on play ordinary stream").attr("class", "text-muted");
|
|
341
|
+
break;
|
|
342
|
+
case STREAM_STATUS_INFO.RTSP_STREAM_NOT_FOUND:
|
|
343
|
+
$("#playInfo").text("Rtsp stream not found where agent received '404-Not Found'").attr("class", "text-muted");
|
|
344
|
+
break;
|
|
345
|
+
case STREAM_STATUS_INFO.FAILED_TO_CONNECT_TO_RTSP_STREAM:
|
|
346
|
+
$("#playInfo").text("Failed to connect to rtsp stream").attr("class", "text-muted");
|
|
347
|
+
break;
|
|
348
|
+
case STREAM_STATUS_INFO.FILE_NOT_FOUND:
|
|
349
|
+
$("#playInfo").text("File does not exist, check filename").attr("class", "text-muted");
|
|
350
|
+
break;
|
|
351
|
+
case STREAM_STATUS_INFO.FILE_HAS_WRONG_FORMAT:
|
|
352
|
+
$("#playInfo").text("File has wrong format on play vod, this format is not supported").attr("class", "text-muted");
|
|
353
|
+
break;
|
|
354
|
+
case STREAM_STATUS_INFO.TRANSCODING_REQUIRED_BUT_DISABLED:
|
|
355
|
+
$("#playInfo").text("Transcoding required, but disabled in settings").attr("class", "text-muted");
|
|
356
|
+
break;
|
|
357
|
+
case STREAM_STATUS_INFO.NO_AVAILABLE_TRANSCODERS:
|
|
358
|
+
$("#playInfo").text("No available transcoders for stream").attr("class", "text-muted");
|
|
359
|
+
break;
|
|
360
|
+
default:
|
|
361
|
+
$("#playInfo").text("Other: "+stream.getInfo()).attr("class", "text-muted");
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
statusField.attr("class", "text-danger");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function validateForm() {
|
|
371
|
+
var valid = true;
|
|
372
|
+
$(':text').each(function () {
|
|
373
|
+
if (!$(this).val()) {
|
|
374
|
+
highlightInput($(this));
|
|
375
|
+
valid = false;
|
|
376
|
+
} else {
|
|
377
|
+
removeHighlight($(this));
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
return valid;
|
|
381
|
+
|
|
382
|
+
function highlightInput(input) {
|
|
383
|
+
input.closest('.input-group').addClass("has-error");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function removeHighlight(input) {
|
|
387
|
+
input.closest('.input-group').removeClass("has-error");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function disableForm(formId, disable) {
|
|
392
|
+
$('#' + formId + ' :input').each(function () {
|
|
393
|
+
$(this).prop('disabled', disable);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// H264 publishing failure detector using outgoing video stats in Chrome #WCS-3382
|
|
398
|
+
function codecPublishingFailureDetector() {
|
|
399
|
+
var detector = {
|
|
400
|
+
failed: false,
|
|
401
|
+
codec: "",
|
|
402
|
+
lastBytesSent: 0,
|
|
403
|
+
counter: null,
|
|
404
|
+
publishFailureIntervalID: null,
|
|
405
|
+
startDetection: function(stream, failureCheckInterval, maxBitrateDropsCount) {
|
|
406
|
+
detector.failed = false;
|
|
407
|
+
detector.lastBytesSent = 0;
|
|
408
|
+
detector.counter = counterWithThreshold(maxBitrateDropsCount);
|
|
409
|
+
detector.publishFailureIntervalID = setInterval(function() {
|
|
410
|
+
// Detect publishing failure in Chrome using outgoing streaming stats #WCS-3382
|
|
411
|
+
stream.getStats(function(stat) {
|
|
412
|
+
let videoStats = stat.outboundStream.video;
|
|
413
|
+
if(!videoStats) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
let stats_codec = videoStats.codec;
|
|
417
|
+
let bytesSent = videoStats.bytesSent;
|
|
418
|
+
let bitrate = (bytesSent - detector.lastBytesSent) * 8;
|
|
419
|
+
if (bitrate == 0) {
|
|
420
|
+
detector.counter.inc();
|
|
421
|
+
console.log("Bitrate is 0 (" + detector.counter.getCurrent() + ")");
|
|
422
|
+
if (detector.counter.exceeded()) {
|
|
423
|
+
detector.failed = true;
|
|
424
|
+
console.log("Publishing seems to be failed, stop the stream");
|
|
425
|
+
stream.stop();
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
detector.counter.reset();
|
|
429
|
+
}
|
|
430
|
+
detector.lastBytesSent = bytesSent;
|
|
431
|
+
detector.codec = stats_codec;
|
|
432
|
+
$("#publishInfo").text(detector.codec);
|
|
433
|
+
});
|
|
434
|
+
}, failureCheckInterval);
|
|
435
|
+
},
|
|
436
|
+
stopDetection: function(isManuallyStopped) {
|
|
437
|
+
if (detector.publishFailureIntervalID) {
|
|
438
|
+
clearInterval(detector.publishFailureIntervalID);
|
|
439
|
+
detector.publishFailureIntervalID = null;
|
|
440
|
+
}
|
|
441
|
+
// Clear failed state if streaming is stopped manually
|
|
442
|
+
if (isManuallyStopped) {
|
|
443
|
+
detector.failed = false;
|
|
444
|
+
}
|
|
445
|
+
// Check if bitrate is constantly 0 #WCS-3382
|
|
446
|
+
if (detector.failed) {
|
|
447
|
+
$("#publishInfo").text("Failed to publish " + detector.codec);
|
|
448
|
+
if($("#changeCodec").is(':checked')) {
|
|
449
|
+
// Try to change codec from H264 to VP8 #WCS-3382
|
|
450
|
+
if (detector.codec == "H264") {
|
|
451
|
+
console.log("H264 publishing seems to be failed, trying VP8 by stripping H264");
|
|
452
|
+
let stripCodecs = "H264";
|
|
453
|
+
publishBtnClick(stripCodecs);
|
|
454
|
+
} else if (detector.codec == "VP8") {
|
|
455
|
+
console.log("VP8 publishing seems to be failed, giving up");
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
// Try to republish with the same codec #WCS-3410
|
|
459
|
+
publishBtnClick();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
return(detector);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Restart publishing or playback automatically #WCS-3410
|
|
469
|
+
function streamRestarter(onRestart) {
|
|
470
|
+
let logger = Flashphoner.getLogger();
|
|
471
|
+
var restarter = {
|
|
472
|
+
counter: null,
|
|
473
|
+
restartTimerId: null,
|
|
474
|
+
init: function() {
|
|
475
|
+
restarter.counter = counterWithThreshold(RESTART_MAX_TRIES);
|
|
476
|
+
},
|
|
477
|
+
restart: function(restartTimeout, restartMaxTimes) {
|
|
478
|
+
if (restarter.restartTimerId) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (restartMaxTimes < 1) {
|
|
482
|
+
console.log("Streaming will not be restarted");
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
restarter.counter.set(restartMaxTimes);
|
|
486
|
+
restarter.restartTimerId = setInterval(function(){
|
|
487
|
+
if (restarter.counter.exceeded()) {
|
|
488
|
+
logger.info("Tried to restart for " + restartMaxTimes + " times with " +restartTimeout + " ms interval, cancelled");
|
|
489
|
+
restarter.reset();
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
onRestart();
|
|
493
|
+
restarter.counter.inc();
|
|
494
|
+
}, restartTimeout);
|
|
495
|
+
logger.info("Timer " + restarter.restartTimerId + " started to restart streaming after " + restartTimeout + " ms interval");
|
|
496
|
+
},
|
|
497
|
+
reset: function() {
|
|
498
|
+
if (restarter.restartTimerId) {
|
|
499
|
+
clearInterval(restarter.restartTimerId);
|
|
500
|
+
logger.info("Timer " + restarter.restartTimerId + " stopped");
|
|
501
|
+
restarter.restartTimerId = null;
|
|
502
|
+
}
|
|
503
|
+
restarter.counter.reset();
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
restarter.init();
|
|
507
|
+
|
|
508
|
+
return(restarter);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Function to invoke when publishing restart timeout is fired
|
|
512
|
+
function onPublishRestart() {
|
|
513
|
+
let logger = Flashphoner.getLogger();
|
|
514
|
+
let sessions = Flashphoner.getSessions();
|
|
515
|
+
if (!sessions.length || sessions[0].status() == SESSION_STATUS.FAILED) {
|
|
516
|
+
logger.info("Restart session to publish");
|
|
517
|
+
click("connectBtn");
|
|
518
|
+
} else {
|
|
519
|
+
let streams = sessions[0].getStreams();
|
|
520
|
+
let stream = null;
|
|
521
|
+
let clickButton = false;
|
|
522
|
+
if (streams.length == 0) {
|
|
523
|
+
// No streams in session, try to restart publishing
|
|
524
|
+
logger.info("No streams in session, restart publishing");
|
|
525
|
+
clickButton = true;
|
|
526
|
+
} else {
|
|
527
|
+
// If there is already a stream, check its state and restart publishing if needed
|
|
528
|
+
for (let i = 0; i < streams.length; i++) {
|
|
529
|
+
if (streams[i].name() === $('#publishStream').val()) {
|
|
530
|
+
stream = streams[i];
|
|
531
|
+
if (!isStreamPublishing(stream)) {
|
|
532
|
+
logger.info("Restart stream " + stream.name() + " publishing");
|
|
533
|
+
clickButton = true;
|
|
534
|
+
}
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (!stream) {
|
|
539
|
+
logger.info("Restart stream publishing");
|
|
540
|
+
clickButton = true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (clickButton) {
|
|
544
|
+
click("publishBtn");
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Function to invoke when playing restart timeout is fired
|
|
550
|
+
function onPlayRestart() {
|
|
551
|
+
let logger = Flashphoner.getLogger();
|
|
552
|
+
let sessions = Flashphoner.getSessions();
|
|
553
|
+
if (!sessions.length || sessions[0].status() == SESSION_STATUS.FAILED) {
|
|
554
|
+
logger.info("Restart session to play");
|
|
555
|
+
click("connectBtn");
|
|
556
|
+
} else {
|
|
557
|
+
let streams = sessions[0].getStreams();
|
|
558
|
+
let stream = null;
|
|
559
|
+
let clickButton = false;
|
|
560
|
+
if (streams.length == 0) {
|
|
561
|
+
// No streams in session, try to restart playing
|
|
562
|
+
logger.info("No streams in session, restart playback");
|
|
563
|
+
clickButton = true;
|
|
564
|
+
} else {
|
|
565
|
+
// If there is already a stream, check its state and restart playing if needed
|
|
566
|
+
for (let i = 0; i < streams.length; i++) {
|
|
567
|
+
if (streams[i].name() === $('#playStream').val()) {
|
|
568
|
+
stream = streams[i];
|
|
569
|
+
if (!isStreamPlaying(stream)) {
|
|
570
|
+
logger.info("Restart stream " + stream.name() + " playback");
|
|
571
|
+
clickButton = true;
|
|
572
|
+
}
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (!stream) {
|
|
577
|
+
logger.info("Restart stream playback");
|
|
578
|
+
clickButton = true;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (clickButton) {
|
|
582
|
+
click("playBtn");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Helper function to click a button
|
|
588
|
+
function click(buttonId) {
|
|
589
|
+
let selector = "#" + buttonId;
|
|
590
|
+
if (!$(selector).prop('disabled')) {
|
|
591
|
+
$(selector).click();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Stream publishing status helper function
|
|
596
|
+
function isStreamPublishing(stream) {
|
|
597
|
+
switch(stream.status()) {
|
|
598
|
+
case STREAM_STATUS.PENDING:
|
|
599
|
+
case STREAM_STATUS.PUBLISHING:
|
|
600
|
+
return true;
|
|
601
|
+
default:
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Stream status helper function
|
|
607
|
+
function isStreamPlaying(stream) {
|
|
608
|
+
switch(stream.status()) {
|
|
609
|
+
case STREAM_STATUS.PENDING:
|
|
610
|
+
case STREAM_STATUS.PLAYING:
|
|
611
|
+
case STREAM_STATUS.RESIZE:
|
|
612
|
+
case STREAM_STATUS.SNAPSHOT_COMPLETE:
|
|
613
|
+
case STREAM_STATUS.NOT_ENOUGH_BANDWIDTH:
|
|
614
|
+
return true;
|
|
615
|
+
default:
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Helper counter with threshold
|
|
621
|
+
function counterWithThreshold(threshold) {
|
|
622
|
+
var counter = {
|
|
623
|
+
value: 0,
|
|
624
|
+
threshold: threshold,
|
|
625
|
+
set: function(newThreshold) {
|
|
626
|
+
counter.threshold = newThreshold;
|
|
627
|
+
},
|
|
628
|
+
inc: function() {
|
|
629
|
+
counter.value++;
|
|
630
|
+
},
|
|
631
|
+
reset: function() {
|
|
632
|
+
counter.value = 0;
|
|
633
|
+
},
|
|
634
|
+
exceeded: function() {
|
|
635
|
+
return(counter.value >= counter.threshold);
|
|
636
|
+
},
|
|
637
|
+
getCurrent: function() {
|
|
638
|
+
return(counter.value);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
return(counter);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Session state object
|
|
646
|
+
function sessionState() {
|
|
647
|
+
var session = {
|
|
648
|
+
url: "",
|
|
649
|
+
isManuallyDisconnected: false,
|
|
650
|
+
sdkSession: null,
|
|
651
|
+
set: function(url, sdkSession) {
|
|
652
|
+
session.url = url;
|
|
653
|
+
session.sdkSession = sdkSession;
|
|
654
|
+
session.isManuallyDisconnected = false;
|
|
655
|
+
},
|
|
656
|
+
clear: function() {
|
|
657
|
+
session.url = "";
|
|
658
|
+
session.sdkSession = null;
|
|
659
|
+
session.isManuallyDisconnected = false;
|
|
660
|
+
},
|
|
661
|
+
getStatus: function() {
|
|
662
|
+
if (session.sdkSession) {
|
|
663
|
+
return(session.sdkSession.status());
|
|
664
|
+
}
|
|
665
|
+
return(SESSION_STATUS.DISCONNECTED);
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
return(session);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Stream state object
|
|
673
|
+
function streamState() {
|
|
674
|
+
var stream = {
|
|
675
|
+
name: "",
|
|
676
|
+
wasActive: false,
|
|
677
|
+
isManuallyStopped: false,
|
|
678
|
+
sdkStream: null,
|
|
679
|
+
set: function(name, sdkStream) {
|
|
680
|
+
stream.name = name;
|
|
681
|
+
stream.sdkStream = sdkStream;
|
|
682
|
+
stream.isManuallyStopped = false;
|
|
683
|
+
stream.wasActive = true;
|
|
684
|
+
},
|
|
685
|
+
clear: function() {
|
|
686
|
+
stream.name = "";
|
|
687
|
+
stream.sdkStream = null;
|
|
688
|
+
stream.isManuallyStopped = false;
|
|
689
|
+
stream.wasActive = false;
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
return(stream);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Network change detection using Network Information API #WCS-3410
|
|
697
|
+
function networkChangeDetector() {
|
|
698
|
+
// The API is supported in Chromium browsers and Firefox for Android
|
|
699
|
+
if (Browser.isSafariWebRTC()) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (Browser.isChrome() || (Browser.isFirefox() && Browser.isAndroid())) {
|
|
703
|
+
connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
704
|
+
if (connection) {
|
|
705
|
+
connectionType = connection.type;
|
|
706
|
+
if (Browser.isFirefox()) {
|
|
707
|
+
connection.ontypechange = onNetworkChange;
|
|
708
|
+
} else {
|
|
709
|
+
connection.onchange = onNetworkChange;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function onNetworkChange() {
|
|
716
|
+
if (connection) {
|
|
717
|
+
if (connection.type === undefined) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
// If network type is changed, close the session
|
|
721
|
+
console.log("connectionType = " + connectionType + ", connection.type = " + connection.type);
|
|
722
|
+
if (isNetworkConnected() && connection.type != connectionType) {
|
|
723
|
+
if (currentSession.getStatus() == SESSION_STATUS.ESTABLISHED) {
|
|
724
|
+
let logger = Flashphoner.getLogger();
|
|
725
|
+
logger.info("Close session due to network change from " + connectionType + " to " + connection.type);
|
|
726
|
+
currentSession.sdkSession.disconnect();
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
connectionType = connection.type;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function isNetworkConnected() {
|
|
734
|
+
if (connection) {
|
|
735
|
+
switch (connection.type) {
|
|
736
|
+
case "cellular":
|
|
737
|
+
case "ethernet":
|
|
738
|
+
case "wifi":
|
|
739
|
+
case "wimax":
|
|
740
|
+
return(true);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return(false);
|
|
744
|
+
}
|