...
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.init({
flashMediaProviderSwfLocation: '../../../../media-provider.swf',
mediaProvidersReadyCallback: function (mediaProviders) {
//hide remote video if current media provider is Flash
if (mediaProviders[0] == "Flash") {
$("#fecForm").hide();
$("#stereoForm").hide();
$("#sendAudioBitrateForm").hide();
$("#cpuOveruseDetectionForm").hide();
}
if (Flashphoner.isUsingTemasys()) {
$("#audioInputForm").hide();
$("#videoInputForm").hide();
}
}
}) |
...
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaDevices(null, true).then(function (list) { list.audio.forEach(function (device) { var audio = document.getElementById("audioInput");... }); var i; list.video.forEach(function (device) { var deviceInList = false; ... }); for (i = 0; i < audio..options.length; i++) { }).catch(function (error) { $("#notifyFlash").text("Failed to if (audio.options[i].value == device.id) {get media devices"); }); |
3. Получение граничных параметров для аудио и видео со страницы клиента
getConstraints() код
Code Block | ||||
---|---|---|---|---|
| ||||
function getConstraints() { deviceInList constraints = true;{ audio: $("#sendAudio").is(':checked'), break;video: $("#sendVideo").is(':checked'), customStream: $("#sendCanvasStream").is(':checked') }; if }(constraints.audio) { if (!deviceInList) constraints.audio = { var option = document.createElement("option");deviceId: $('#audioInput').val() }; option.text = device.label || device.id;if ($("#fec").is(':checked')) option.valueconstraints.audio.fec = device.id$("#fec").is(':checked'); audio.appendChild(option);if ($("#sendStereoAudio").is(':checked')) } }constraints.audio.stereo = $("#sendStereoAudio").is(':checked'); list.video.forEach(function (device) {if (parseInt($('#sendAudioBitrate').val()) > 0) console.log(device); constraints.audio.bitrate = parseInt($('#sendAudioBitrate').val()); } varif (constraints.video = document.getElementById("videoInput");) { if var i;(constraints.customStream) { varconstraints.customStream deviceInList = falsecanvas.captureStream(30); for (i constraints.video = 0false; i < video.options.length; i++) { } else { if (video.options[i].value == device.id) constraints.video = { deviceId: deviceInList = true;{exact: $('#videoInput').val()}, width: parseInt($('#sendWidth').val()), break; }height: parseInt($('#sendHeight').val()) }; if (Browser.isSafariWebRTC(!deviceInList) { var option = document.createElement("option");&& Browser.isiOS() && Flashphoner.getMediaProviders()[0] === "WebRTC") { optionconstraints.video.textwidth = device.label || device.id {min: parseInt($('#sendWidth').val()), max: 640}; optionconstraints.video.valueheight = device.id; if (option.text.toLowerCase().indexOf("back") >= 0 && video.children.length > 0) { {min: parseInt($('#sendHeight').val()), max: 480}; } video.insertBefore(option, video.children[0]);if (parseInt($('#sendVideoMinBitrate').val()) > 0) } else {constraints.video.minBitrate = parseInt($('#sendVideoMinBitrate').val()); if (parseInt($('#sendVideoMaxBitrate').val()) > 0) constraints.video.appendChild(option.maxBitrate = parseInt($('#sendVideoMaxBitrate').val()); if (parseInt($('#fps').val()) > }0) } constraints.video.frameRate }); $("#url").val(setURL() + "/" + createUUID(8= parseInt($('#fps').val()); //set initial button callback} onStopped();} if (list.audio.length === 0) { $("#sendAudio").prop('checked', false).prop('disabled', true); } if (list.video.length === 0) { $("#sendVideo").prop('checked', false).prop('disabled', true); } }).catch(function (error) { $("#notifyFlash").text("Failed to get media devices"); }); |
3. Получение граничных параметров для аудио и видео со страницы клиента
getConstraints() код
Code Block | ||||
---|---|---|---|---|
| ||||
function getConstraints() {
constraints = {
audio: $("#sendAudio").is(':checked'),
video: $("#sendVideo").is(':checked'),
customStream: $("#sendCanvasStream").is(':checked')
};
if (constraints.audio) {
constraints.audio = {
deviceId: $('#audioInput').val()
};
if ($("#fec").is(':checked'))
constraints.audio.fec = $("#fec").is(':checked');
if ($("#sendStereoAudio").is(':checked'))
constraints.audio.stereo = $("#sendStereoAudio").is(':checked');
if (parseInt($('#sendAudioBitrate').val()) > 0)
constraints.audio.bitrate = parseInt($('#sendAudioBitrate').val());
}
if (constraints.video) {
if (constraints.customStream) {
constraints.customStream = canvas.captureStream(30);
constraints.video = false;
} else {
constraints.video = {
deviceId: {exact: $('#videoInput').val()},
width: parseInt($('#sendWidth').val()),
height: parseInt($('#sendHeight').val())
};
if (Browser.isSafariWebRTC() && Browser.isiOS() && Flashphoner.getMediaProviders()[0] === "WebRTC") {
constraints.video.width = {min: parseInt($('#sendWidth').val()), max: 640};
constraints.video.height = {min: parseInt($('#sendHeight').val()), max: 480};
}
if (parseInt($('#sendVideoMinBitrate').val()) > 0)
constraints.video.minBitrate = parseInt($('#sendVideoMinBitrate').val());
if (parseInt($('#sendVideoMaxBitrate').val()) > 0)
constraints.video.maxBitrate = parseInt($('#sendVideoMaxBitrate').val());
if (parseInt($('#fps').val()) > 0)
constraints.video.frameRate = parseInt($('#fps').val());
}
}
return constraints;
} |
4. Получение доступа к медиаустройствам для локального тестирования
Flashphoner.getMediaAccess() код
В метод передаются граничные параметры для аудио и видео (constrains), а также localVideo - div-элемент, в котором будет отображаться видео с выбранной камеры.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaAccess(getConstraints(), localVideo).then(function (disp) {
$("#testBtn").text("Release").off('click').click(function () {
$(this).prop('disabled', true);
stopTest();
}).prop('disabled', false);
window.AudioContext = window.AudioContext || window.webkitAudioContext;
if (Flashphoner.getMediaProviders()[0] == "WebRTC" && window.AudioContext) {
for (i = 0; i < localVideo.children.length; i++) {
if (localVideo.children[i] && localVideo.children[i].id.indexOf("-LOCAL_CACHED_VIDEO") != -1) {
var stream = localVideo.children[i].srcObject;
audioContextForTest = new AudioContext();
var microphone = audioContextForTest.createMediaStreamSource(stream);
var javascriptNode = audioContextForTest.createScriptProcessor(1024, 1, 1);
microphone.connect(javascriptNode);
javascriptNode.connect(audioContextForTest.destination);
javascriptNode.onaudioprocess = function (event) {
var inpt_L = event.inputBuffer.getChannelData(0);
var sum_L = 0.0;
for (var i = 0; i < inpt_L.length; ++i) {
sum_L += inpt_L[i] * inpt_L[i];
}
$("#micLevel").text(Math.floor(Math.sqrt(sum_L / inpt_L.length) * 100));
}
}
}
} else if (Flashphoner.getMediaProviders()[0] == "Flash") {
micLevelInterval = setInterval(function () {
$("#micLevel").text(disp.children[0].getMicrophoneLevel());
}, 500);
}
testStarted = true;
}).catch(function (error) {
$("#testBtn").prop('disabled', false);
testStarted = false;
}); |
5. Подключение к серверу.
Flashphoner.createSession() код
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) {
//session connected, start streaming
startStreaming(session);
}).on(SESSION_STATUS.DISCONNECTED, function () {
setStatus(SESSION_STATUS.DISCONNECTED);
onStopped();
}).on(SESSION_STATUS.FAILED, function () {
setStatus(SESSION_STATUS.FAILED);
onStopped();
}); |
6. Получение от сервера события, подтверждающего успешное соединение.
ConnectionStatusEvent ESTABLISHED код
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) {
//session connected, start streaming
startStreaming(session);
}).on(SESSION_STATUS.DISCONNECTED, function () {
setStatus(SESSION_STATUS.DISCONNECTED);
onStopped();
}).on(SESSION_STATUS.FAILED, function () {
setStatus(SESSION_STATUS.FAILED);
onStopped();
}); |
7. Публикация видеопотока
session.createStream(), publishStream.publish() код
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({
name: streamName,
display: localVideo,
cacheLocalResources: true,
constraints: constraints,
mediaConnectionConstraints: mediaConnectionConstraints
}).on(STREAM_STATUS.PUBLISHING, function (publishStream) {
$("#testBtn").prop('disabled', true);
var video = document.getElementById(publishStream.id());
//resize local if resolution is available
if (video.videoWidth > 0 && video.videoHeight > 0) {
resizeLocalVideo({target: video});
}
enableMuteToggles(true);
if ($("#muteVideoToggle").is(":checked")) {
muteVideo();
}
if ($("#muteAudioToggle").is(":checked")) {
muteAudio();
}
//remove resize listener in case this video was cached earlier
video.removeEventListener('resize', resizeLocalVideo);
video.addEventListener('resize', resizeLocalVideo);
setStatus(STREAM_STATUS.PUBLISHING);
//play preview
var constraints = {
audio: $("#playAudio").is(':checked'),
video: $("#playVideo").is(':checked')
};
if (constraints.video) {
constraints.video = {
width: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveWidth').val()) : 0,
height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0,
bitrate: (!$("#receiveDefaultBitrate").is(":checked")) ? $("#receiveBitrate").val() : 0,
quality: (!$("#receiveDefaultQuality").is(":checked")) ? $('#quality').val() : 0
};
}
previewStream = session.createStream({
name: streamName,
display: remoteVideo,
constraints: constraints
}).on(STREAM_STATUS.PLAYING, function (previewStream) {
document.getElementById(previewStream.id()).addEventListener('resize', function (event) {
$("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight);
resizeVideo(event.target);
});
//enable stop button
onStarted(publishStream, previewStream);
//wait for incoming stream
if (Flashphoner.getMediaProviders()[0] == "WebRTC") {
setTimeout(function () {
detectSpeech(previewStream);
}, 3000);
}
drawSquare();
}).on(STREAM_STATUS.STOPPED, function () {
publishStream.stop();
}).on(STREAM_STATUS.FAILED, function () {
//preview failed, stop publishStream
if (publishStream.status() == STREAM_STATUS.PUBLISHING) {
setStatus(STREAM_STATUS.FAILED);
publishStream.stop();
}
});
previewStream.play();
}).on(STREAM_STATUS.UNPUBLISHED, function () {
setStatus(STREAM_STATUS.UNPUBLISHED);
//enable start button
onStopped();
}).on(STREAM_STATUS.FAILED, function () {
setStatus(STREAM_STATUS.FAILED);
//enable start button
onStopped();
});
publishStream.publish(); |
8. Получение от сервера события, подтверждающего успешную публикацию потока
StreamStatusEvent PUBLISHING код
При получении данного события создается превью-видеопоток при помощи createStream() и вызывается play() для его воспроизведения.
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({
name: streamName,
display: localVideo,
cacheLocalResources: true,
constraints: constraints,
mediaConnectionConstraints: mediaConnectionConstraints
}).on(STREAM_STATUS.PUBLISHING, function (publishStream) {
$("#testBtn").prop('disabled', true);
var video = document.getElementById(publishStream.id());
//resize local if resolution is available
if (video.videoWidth > 0 && video.videoHeight > 0) {
resizeLocalVideo({target: video});
}
enableMuteToggles(true);
if ($("#muteVideoToggle").is(":checked")) {
muteVideo();
}
if ($("#muteAudioToggle").is(":checked")) {
muteAudio();
}
//remove resize listener in case this video was cached earlier
video.removeEventListener('resize', resizeLocalVideo);
video.addEventListener('resize', resizeLocalVideo);
setStatus(STREAM_STATUS.PUBLISHING);
//play preview
var constraints = {
audio: $("#playAudio").is(':checked'),
video: $("#playVideo").is(':checked')
};
if (constraints.video) {
constraints.video = {
width: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveWidth').val()) : 0,
height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0,
bitrate: (!$("#receiveDefaultBitrate").is(":checked")) ? $("#receiveBitrate").val() : 0,
quality: (!$("#receiveDefaultQuality").is(":checked")) ? $('#quality').val() : 0
};
}
previewStream = session.createStream({
name: streamName,
display: remoteVideo,
constraints: constraints
}).on(STREAM_STATUS.PLAYING, function (previewStream) {
document.getElementById(previewStream.id()).addEventListener('resize', function (event) {
$("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight);
resizeVideo(event.target);
});
//enable stop button
onStarted(publishStream, previewStream);
//wait for incoming stream
if (Flashphoner.getMediaProviders()[0] == "WebRTC") {
setTimeout(function () {
detectSpeech(previewStream);
}, 3000);
}
drawSquare();
}).on(STREAM_STATUS.STOPPED, function () {
publishStream.stop();
}).on(STREAM_STATUS.FAILED, function () {
//preview failed, stop publishStream
if (publishStream.status() == STREAM_STATUS.PUBLISHING) {
setStatus(STREAM_STATUS.FAILED);
publishStream.stop();
}
});
previewStream.play();
}).on(STREAM_STATUS.UNPUBLISHED, function () {
setStatus(STREAM_STATUS.UNPUBLISHED);
//enable start button
onStopped();
}).on(STREAM_STATUS.FAILED, function () {
setStatus(STREAM_STATUS.FAILED);
//enable start button
onStopped();
});
publishStream.publish(); |
9. Остановка воспроизведения превью-видеопотока.
previewStream.stop() код
Code Block | ||||
---|---|---|---|---|
| ||||
$("#publishBtn").text("Stop").off('click').click(function () {
$(this).prop('disabled', true);
previewStream.stop();
}).prop('disabled', false); |
10. Получение от сервера события, подтверждающего остановку воспроизведения
StreamStatusEvent STOPPED код
Code Block | ||||
---|---|---|---|---|
| ||||
previewStream = session.createStream({ name: streamName, display: remoteVideo, constraints: constraints }).on(STREAM_STATUS.PLAYING, function (previewStream) { document.getElementById(previewStream.id()).addEventListener('resize', function (event) { $("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight); resizeVideo(event.target); }); //enable stop button onStarted(publishStream, previewStream); //wait for incoming stream if (Flashphoner.getMediaProviders()[0] == "WebRTC") { setTimeout(function () { detectSpeech(previewStream); }, 3000); } drawSquare(); }).on(STREAM_STATUS.STOPPED, function () { publishStream.stop(); }).on(STREAM_STATUS.FAILED, function (return constraints; } |
4. Получение доступа к медиаустройствам для локального тестирования
Flashphoner.getMediaAccess() код
В метод передаются граничные параметры для аудио и видео (constrains), а также localVideo - div-элемент, в котором будет отображаться видео с выбранной камеры.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaAccess(getConstraints(), localVideo).then(function (disp) { //preview failed, stop publishStream if (publishStream.status() == STREAM_STATUS.PUBLISHING$("#testBtn").text("Release").off('click').click(function () { setStatus(STREAM_STATUS.FAILED$(this).prop('disabled', true); publishStream.stopstopTest(); } }); previewStream.play(); |
11. Остановка публикации видеопотока после остановки воспроизведения превью-потока.
publishStream.stop() код
Code Block | ||||
---|---|---|---|---|
| ||||
previewStream = session.createStream({ .prop('disabled', false); ... testStarted name: streamName,= true; }).catch(function (error) { display: remoteVideo, $("#testBtn").prop('disabled', false); testStarted = false; constraints: constraints }); |
5. Подключение к серверу.
Flashphoner.createSession() код
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(STREAMSESSION_STATUS.PLAYINGESTABLISHED, function (previewStreamsession) { //session connected, start streaming document.getElementById(previewStream.id()).addEventListener('resize'startStreaming(session); }).on(SESSION_STATUS.DISCONNECTED, function (event) { setStatus(SESSION_STATUS.DISCONNECTED); $onStopped("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight);); }).on(SESSION_STATUS.FAILED, function () { setStatus(SESSION_STATUS.FAILED); resizeVideoonStopped(event.target); }); |
6. Получение от сервера события, подтверждающего успешное соединение.
ConnectionStatusEvent ESTABLISHED код
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function }); (session) { //enable stop buttonsession connected, start streaming onStarted(publishStream, previewStreamstartStreaming(session); }).on(SESSION_STATUS.DISCONNECTED, function () { //wait for incoming stream... if (Flashphoner.getMediaProviders()[0] == "WebRTC"}).on(SESSION_STATUS.FAILED, function () { ... }); |
7. Публикация видеопотока
session.createStream(), publishStream.publish() код
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream setTimeout(function () = session.createStream({ name: streamName, display: localVideo, detectSpeech(previewStream); cacheLocalResources: true, constraints: }constraints, 3000); } mediaConnectionConstraints: mediaConnectionConstraints ... drawSquare(}); }).on(STREAM_STATUS.STOPPED, function () { publishStream.publish(); |
8. Получение от сервера события, подтверждающего успешную публикацию потока
StreamStatusEvent PUBLISHING код
При получении данного события создается превью-видеопоток при помощи createStream() и вызывается play() для его воспроизведения.
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({ publishStream.stop(); name: streamName, }).on(STREAM_STATUS.FAILED, function () { display: localVideo, cacheLocalResources: true, //preview failed, stopconstraints: publishStreamconstraints, mediaConnectionConstraints: mediaConnectionConstraints if (publishStream}).statuson() == STREAM_STATUS.PUBLISHING, function (publishStream) { setStatus(STREAM_STATUS.FAILED); $("#testBtn").prop('disabled', true); var video = document.getElementById(publishStream.stopid()); //resize local if resolution }is available }); previewStream.play(); |
12. Получение от сервера события, подтверждающего успешную остановку публикации
StreamStatusEvent UNPUBLISHED код
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({ name: streamName, if (video.videoWidth > 0 && video.videoHeight > 0) { displayresizeLocalVideo({target: localVideo,video}); cacheLocalResources: true,} constraints: constraints,enableMuteToggles(true); mediaConnectionConstraints: mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function (publishStreamif ($("#muteVideoToggle").is(":checked")) { $("#testBtn").prop('disabled', true muteVideo(); } var video = document.getElementById(publishStream.id());if ($("#muteAudioToggle").is(":checked")) { //resize local if resolution is available muteAudio(); if (video.videoWidth > 0 && video.videoHeight > 0) { resizeLocalVideo({target: video}); } //remove resize listener in case this video was cached earlier }video.removeEventListener('resize', resizeLocalVideo); enableMuteToggles(truevideo.addEventListener('resize', resizeLocalVideo); if ($("#muteVideoToggle").is(":checked")) {setStatus(STREAM_STATUS.PUBLISHING); //play preview muteVideo(); var constraints = }{ if audio: ($("#muteAudioToggle#playAudio").is("':checked"')) {, muteAudio();video: $("#playVideo").is(':checked') }; //remove resize listener in case this video was cached earlier if (constraints.video) { constraints.video = video.removeEventListener('resize', resizeLocalVideo); { video.addEventListener('resize', resizeLocalVideo); width: (!$("#receiveDefaultSize").is(":checked")) ? setStatus(STREAM_STATUS.PUBLISHING); parseInt($('#receiveWidth').val()) : 0, //play preview var constraints = { height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0, audiobitrate: (!$("#playAudio#receiveDefaultBitrate").is('":checked'), ")) ? $("#receiveBitrate").val() : 0, videoquality: (!$("#playVideo#receiveDefaultQuality").is('":checked")) ? $('#quality').val() : 0 }; } if (constraints.video) previewStream = session.createStream({ name: streamName, constraints.video = { display: remoteVideo, width: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveWidth').val()) : 0, constraints: constraints ... height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0, }); previewStream.play(); }).on(STREAM_STATUS.UNPUBLISHED, function () { ... bitrate: (!$("#receiveDefaultBitrate" }).is(":checked")) ? $("#receiveBitrate").val() : 0, on(STREAM_STATUS.FAILED, function () { ... }); publishStream.publish(); |
9. Остановка воспроизведения превью-видеопотока.
previewStream.stop() код
Code Block | ||||
---|---|---|---|---|
| ||||
quality: (!$("#receiveDefaultQuality#publishBtn").istext(":checkedStop")) ? $.off('#qualityclick').valclick(function () : 0 { $(this).prop('disabled', true); } previewStream.stop(); } }).prop('disabled', false); |
10. Получение от сервера события, подтверждающего остановку воспроизведения
StreamStatusEvent STOPPED код
Code Block | ||||
---|---|---|---|---|
| ||||
previewStream = session.createStream({ name: streamName, display: remoteVideo, constraints: constraints }).on(STREAM_STATUS.PLAYING, function (previewStream) { document.getElementById(previewStream.id()).addEventListener('resize', function (event) { $("#playResolution"}).texton(event.target.videoWidth + "x" + event.target.videoHeight); resizeVideo(event.target);STREAM_STATUS.STOPPED, function () { }publishStream.stop(); }).on(STREAM_STATUS.FAILED, function //enable stop button() { onStarted(publishStream, previewStream);... }); //wait for incoming stream previewStream.play(); |
11. Остановка публикации видеопотока после остановки воспроизведения превью-потока.
publishStream.stop() код
Code Block | ||||
---|---|---|---|---|
| ||||
previewStream = if (Flashphoner.getMediaProviders()[0] == "WebRTC") session.createStream({ setTimeout(function () {name: streamName, display: remoteVideo, detectSpeech(previewStream); constraints: constraints }).on(STREAM_STATUS.PLAYING, function 3000(previewStream); } { drawSquare();... }).on(STREAM_STATUS.STOPPED, function () { publishStream.stop(); }).on(STREAM_STATUS.FAILED, function () { //preview failed, stop publishStream ... }); if (publishStreampreviewStream.status() == STREAM_STATUS.PUBLISHING) play(); |
12. Получение от сервера события, подтверждающего успешную остановку публикации
StreamStatusEvent UNPUBLISHED код
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({ name: streamName, setStatus(STREAM_STATUS.FAILED); display: localVideo, cacheLocalResources: true, publishStream.stop(); constraints: constraints, mediaConnectionConstraints: }mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function });(publishStream) { previewStream.play();... }).on(STREAM_STATUS.UNPUBLISHED, function () { setStatus(STREAM_STATUS.UNPUBLISHED); //enable start button onStopped(); }).on(STREAM_STATUS.FAILED, function () { setStatus(STREAM_STATUS.FAILED); //enable start button onStopped();... }); publishStream.publish(); |
...