Trello API add base64 data image as an attachement - api
I'd like to send base64 image as an attachment to a trello card through the API
POST /1/cards/[card id or shortlink]/attachments
There's a file field but it does not specify how should look the data there.
Refs: https://developers.trello.com/advanced-reference/card#post-1-cards-card-id-or-shortlink-attachments
Any idea?
Short answer: Trello's API only works to attach binary data via multipart/form-data. Examples below.
Long answer:
The Trello API to add attachments and images is frustratingly under-documented. They do have a coffeescript Client.js for those of us using Javascript to simplify all the basic operations: https://trello.com/1/client.coffee
Using the vanilla Client.js file I have been able to attach links and text files. While the CURL example shows pulling a binary file in from a hard drive, that doesn't work for those of us completely on a server or client where we don't have permissions to create a file.
From a LOT of trial and error, I've determined all binary data (images, documents, etc.) must be attached using multipart/form-data. Here is a jQuery snippet that will take a URL to an item, get it into memory, and then send it to Trello.
var opts = {'key': 'YOUR TRELLO KEY', 'token': 'YOUR TRELLO TOKEN'};
var xhr = new XMLHttpRequest();
xhr.open('get', params); // params is a URL to a file to grab
xhr.responseType = 'blob'; // Use blob to get the file's mimetype
xhr.onload = function() {
var fileReader = new FileReader();
fileReader.onload = function() {
var filename = (params.split('/').pop().split('#')[0].split('?')[0]) || params || '?'; // Removes # or ? after filename
var file = new File([this.result], filename);
var form = new FormData(); // this is the formdata Trello needs
form.append("file", file);
$.each(['key', 'token'], function(iter, item) {
form.append(item, opts.data[item] || 'ERROR! Missing "' + item + '"');
});
$.extend(opts, {
method: "POST",
data: form,
cache: false,
contentType: false,
processData: false
});
return $.ajax(opts);
};
fileReader.readAsArrayBuffer(xhr.response); // Use filereader on blob to get content
};
xhr.send();
I have submitted a new coffeescript for Trello developer support to consider uploading to replace Client.js. It adds a "Trello.upload(url)" that does this work.
I've also attached here for convenience in JS form.
// Generated by CoffeeScript 1.12.4
(function() {
var opts={"version":1,"apiEndpoint":"https://api.trello.com","authEndpoint":"https://trello.com"};
var deferred, isFunction, isReady, ready, waitUntil, wrapper,
slice = [].slice;
wrapper = function(window, jQuery, opts) {
var $, Trello, apiEndpoint, authEndpoint, authorizeURL, baseURL, collection, fn, fn1, i, intentEndpoint, j, key, len, len1, localStorage, location, parseRestArgs, readStorage, ref, ref1, storagePrefix, token, type, version, writeStorage;
$ = jQuery;
key = opts.key, token = opts.token, apiEndpoint = opts.apiEndpoint, authEndpoint = opts.authEndpoint, intentEndpoint = opts.intentEndpoint, version = opts.version;
baseURL = apiEndpoint + "/" + version + "/";
location = window.location;
Trello = {
version: function() {
return version;
},
key: function() {
return key;
},
setKey: function(newKey) {
key = newKey;
},
token: function() {
return token;
},
setToken: function(newToken) {
token = newToken;
},
rest: function() {
var args, error, method, params, path, ref, success;
method = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
ref = parseRestArgs(args), path = ref[0], params = ref[1], success = ref[2], error = ref[3];
opts = {
url: "" + baseURL + path,
type: method,
data: {},
dataType: "json",
success: success,
error: error
};
if (!$.support.cors) {
opts.dataType = "jsonp";
if (method !== "GET") {
opts.type = "GET";
$.extend(opts.data, {
_method: method
});
}
}
if (key) {
opts.data.key = key;
}
if (token) {
opts.data.token = token;
}
if (params != null) {
$.extend(opts.data, params);
}
if (method === 'UPLOAD' && typeof (params) === "string" && params.length > 5) {
var xhr = new XMLHttpRequest();
xhr.open('get', params);
xhr.responseType = 'blob'; // Use blob to get the mimetype
xhr.onload = function() {
var fileReader = new FileReader();
fileReader.onload = function() {
var filename = (params.split('/').pop().split('#')[0].split('?')[0]) || params || '?'; // Removes # or ? after filename
var file = new File([this.result], filename);
var form = new FormData();
form.append("file", file);
$.each(['key', 'token'], function(iter, item) {
form.append(item, opts.data[item] || 'ERROR! Missing "' + item + '"');
});
$.extend(opts, {
method: "POST",
data: form,
cache: false,
contentType: false,
processData: false
});
return $.ajax(opts);
};
fileReader.readAsArrayBuffer(xhr.response); // Use filereader on blob to get content
};
xhr.send();
} else {
return $.ajax(opts);
}
},
authorized: function() {
return token != null;
},
deauthorize: function() {
token = null;
writeStorage("token", token);
},
authorize: function(userOpts) {
var k, persistToken, ref, regexToken, scope, v;
opts = $.extend(true, {
type: "redirect",
persist: true,
interactive: true,
scope: {
read: true,
write: false,
account: false
},
expiration: "30days"
}, userOpts);
regexToken = /[&#]?token=([0-9a-f]{64})/;
persistToken = function() {
if (opts.persist && (token != null)) {
return writeStorage("token", token);
}
};
if (opts.persist) {
if (token == null) {
token = readStorage("token");
}
}
if (token == null) {
token = (ref = regexToken.exec(location.hash)) != null ? ref[1] : void 0;
}
if (this.authorized()) {
persistToken();
location.hash = location.hash.replace(regexToken, "");
return typeof opts.success === "function" ? opts.success() : void 0;
}
if (!opts.interactive) {
return typeof opts.error === "function" ? opts.error() : void 0;
}
scope = ((function() {
var ref1, results;
ref1 = opts.scope;
results = [];
for (k in ref1) {
v = ref1[k];
if (v) {
results.push(k);
}
}
return results;
})()).join(",");
switch (opts.type) {
case "popup":
(function() {
var authWindow, height, left, origin, receiveMessage, ref1, top, width;
waitUntil("authorized", (function(_this) {
return function(isAuthorized) {
if (isAuthorized) {
persistToken();
return typeof opts.success === "function" ? opts.success() : void 0;
} else {
return typeof opts.error === "function" ? opts.error() : void 0;
}
};
})(this));
width = 420;
height = 470;
left = window.screenX + (window.innerWidth - width) / 2;
top = window.screenY + (window.innerHeight - height) / 2;
origin = (ref1 = /^[a-z]+:\/\/[^\/]*/.exec(location)) != null ? ref1[0] : void 0;
authWindow = window.open(authorizeURL({
return_url: origin,
callback_method: "postMessage",
scope: scope,
expiration: opts.expiration,
name: opts.name
}), "trello", "width=" + width + ",height=" + height + ",left=" + left + ",top=" + top);
receiveMessage = function(event) {
var ref2;
if (event.origin !== authEndpoint || event.source !== authWindow) {
return;
}
if ((ref2 = event.source) != null) {
ref2.close();
}
if ((event.data != null) && /[0-9a-f]{64}/.test(event.data)) {
token = event.data;
} else {
token = null;
}
if (typeof window.removeEventListener === "function") {
window.removeEventListener("message", receiveMessage, false);
}
isReady("authorized", Trello.authorized());
};
return typeof window.addEventListener === "function" ? window.addEventListener("message", receiveMessage, false) : void 0;
})();
break;
default:
window.location = authorizeURL({
redirect_uri: location.href,
callback_method: "fragment",
scope: scope,
expiration: opts.expiration,
name: opts.name
});
}
},
addCard: function(options, next) {
var baseArgs, getCard;
baseArgs = {
mode: 'popup',
source: key || window.location.host
};
getCard = function(callback) {
var height, left, returnUrl, top, width;
returnUrl = function(e) {
var data;
window.removeEventListener('message', returnUrl);
try {
data = JSON.parse(e.data);
if (data.success) {
return callback(null, data.card);
} else {
return callback(new Error(data.error));
}
} catch (error1) {}
};
if (typeof window.addEventListener === "function") {
window.addEventListener('message', returnUrl, false);
}
width = 500;
height = 600;
left = window.screenX + (window.outerWidth - width) / 2;
top = window.screenY + (window.outerHeight - height) / 2;
return window.open(intentEndpoint + "/add-card?" + $.param($.extend(baseArgs, options)), "trello", "width=" + width + ",height=" + height + ",left=" + left + ",top=" + top);
};
if (next != null) {
return getCard(next);
} else if (window.Promise) {
return new Promise(function(resolve, reject) {
return getCard(function(err, card) {
if (err) {
return reject(err);
} else {
return resolve(card);
}
});
});
} else {
return getCard(function() {});
}
}
};
ref = ["GET", "PUT", "POST", "DELETE", "UPLOAD"];
fn = function(type) {
return Trello[type.toLowerCase()] = function() {
return this.rest.apply(this, [type].concat(slice.call(arguments)));
};
};
for (i = 0, len = ref.length; i < len; i++) {
type = ref[i];
fn(type);
}
Trello.del = Trello["delete"];
ref1 = ["actions", "cards", "checklists", "boards", "lists", "members", "organizations", "lists"];
fn1 = function(collection) {
return Trello[collection] = {
get: function(id, params, success, error) {
return Trello.get(collection + "/" + id, params, success, error);
}
};
};
for (j = 0, len1 = ref1.length; j < len1; j++) {
collection = ref1[j];
fn1(collection);
}
window.Trello = Trello;
authorizeURL = function(args) {
var baseArgs;
baseArgs = {
response_type: "token",
key: key
};
return authEndpoint + "/" + version + "/authorize?" + $.param($.extend(baseArgs, args));
};
parseRestArgs = function(arg) {
var error, params, path, success;
path = arg[0], params = arg[1], success = arg[2], error = arg[3];
if (isFunction(params)) {
error = success;
success = params;
params = {};
}
path = path.replace(/^\/*/, "");
return [path, params, success, error];
};
localStorage = window.localStorage;
if (localStorage != null) {
storagePrefix = "trello_";
readStorage = function(key) {
return localStorage[storagePrefix + key];
};
writeStorage = function(key, value) {
if (value === null) {
return delete localStorage[storagePrefix + key];
} else {
return localStorage[storagePrefix + key] = value;
}
};
} else {
readStorage = writeStorage = function() {};
}
};
deferred = {};
ready = {};
waitUntil = function(name, fx) {
if (ready[name] != null) {
return fx(ready[name]);
} else {
return (deferred[name] != null ? deferred[name] : deferred[name] = []).push(fx);
}
};
isReady = function(name, value) {
var fx, fxs, i, len;
ready[name] = value;
if (deferred[name]) {
fxs = deferred[name];
delete deferred[name];
for (i = 0, len = fxs.length; i < len; i++) {
fx = fxs[i];
fx(value);
}
}
};
isFunction = function(val) {
return typeof val === "function";
};
wrapper(window, jQuery, opts);
}).call(this);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>
Related
Bings Ads Script - Calling Google Services not working
I'm trying to run a script in Bing Ads that will get the performance data and write it in Google Sheets. I based my code on Microsoft example: https://learn.microsoft.com/en-us/advertising/scripts/examples/calling-google-services However, my code won't work. function main() { // Set these fields based on the option you chose for getting an access token. const credentials = { accessToken: '', clientId: '', clientSecret: '', refreshToken: '' }; // To get a fileId or spreadsheetId, please see // https://developers.google.com/sheets/api/guides/concepts#spreadsheet_id. // The file must contain a single bid multiplier value (for example,1.1), // which is used to update keyword bids. const fileId = 'INSERT FILE ID HERE'; // The spreadsheet must contain 3 valid keyword IDs in cells A2, A3, and A4. const spreadsheetId = 'INSERT SPREADSHEET ID HERE'; // The email address to send a notification email to at the end of the script. const notificationEmail = 'INSERT EMAIL HERE'; var driveApi = GoogleApis.createDriveService(credentials); // Read bid multiplier from the file. // Reference: https://developers.google.com/drive/api/v3/reference/files/export var bidMultiplier = driveApi.files.export({ fileId: fileId, mimeType: 'text/csv' }).body; Logger.log(`read bid multiplier ${bidMultiplier}`); var sheetsApi = GoogleApis.createSheetsService(credentials); // Write the old and new bid values back to the spreadsheet. // Reference: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate var campaignData = []; var campaigns = AdsApp.campaigns().withCondition('Cost > 0').forDateRange('THIS_MONTH'); var campaignIterator = campaigns.get(); var iterator = 0; while(campaignIterator.hasNext()) { var campaign = campaignIterator.next(); var campaignName = campaign.getName(); var stats = campaign.getStats(); var cost = stats.getCost(); var clicks = stats.getClicks(); var conversions = stats.getConversions(); var impressions = stats.getImpressions(); campaignData[iterator] = {"CampaignName":campaignName,"cost":cost,"clicks":clicks,"conversions":conversions,"impressions":impressions}; iterator++; } var updateResponse = sheetsApi.spreadsheets.values.batchUpdate({ spreadsheetId: spreadsheetId }, { data: [ { range: 'A2:G2', values: campaignData.map(x => [x]) }, ], valueInputOption: 'USER_ENTERED' }); Logger.log(`updated ${updateResponse.result.totalUpdatedCells} cells`); var gmailApi = GoogleApis.createGmailService(credentials); var email = [ `To: ${notificationEmail}`, 'Subject: Google Services script', '', `You script ran successfully ✓ and updated ${updateResponse.result.totalUpdatedCells} cells.` ].join('\n'); // Send the notification email. // Reference: https://developers.google.com/gmail/api/v1/reference/users/messages/send var sendResponse = gmailApi.users.messages.send({ userId: 'me' }, { raw: Base64.encode(email) }); Logger.log(`sent email thread ${sendResponse.result.threadId}`); } var GoogleApis; (function (GoogleApis) { function createSheetsService(credentials) { return createService("https://sheets.googleapis.com/$discovery/rest?version=v4", credentials); } GoogleApis.createSheetsService = createSheetsService; function createDriveService(credentials) { return createService("https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", credentials); } GoogleApis.createDriveService = createDriveService; function createGmailService(credentials) { return createService("https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest", credentials); } GoogleApis.createGmailService = createGmailService; // Creation logic based on https://developers.google.com/discovery/v1/using#usage-simple function createService(url, credentials) { var content = UrlFetchApp.fetch(url).getContentText(); var discovery = JSON.parse(content); var baseUrl = discovery['rootUrl'] + discovery['servicePath']; var accessToken = getAccessToken(credentials); var service = build(discovery, {}, baseUrl, accessToken); return service; } function createNewMethod(method, baseUrl, accessToken) { return (urlParams, body) => { var urlPath = method.path; var queryArguments = []; for (var name in urlParams) { var paramConfg = method.parameters[name]; if (!paramConfg) { throw `Unexpected url parameter ${name}`; } switch (paramConfg.location) { case 'path': urlPath = urlPath.replace('{' + name + '}', urlParams[name]); break; case 'query': queryArguments.push(`${name}=${urlParams[name]}`); break; default: throw `Unknown location ${paramConfg.location} for url parameter ${name}`; } } var url = baseUrl + urlPath; if (queryArguments.length > 0) { url += '?' + queryArguments.join('&'); } var httpResponse = UrlFetchApp.fetch(url, { contentType: 'application/json', method: method.httpMethod, payload: JSON.stringify(body), headers: { Authorization: `Bearer ${accessToken}` }, muteHttpExceptions: true }); var responseContent = httpResponse.getContentText(); var responseCode = httpResponse.getResponseCode(); var parsedResult; try { parsedResult = JSON.parse(responseContent); } catch (e) { parsedResult = false; } var response = new Response(parsedResult, responseContent, responseCode); if (responseCode >= 200 && responseCode <= 299) { return response; } throw response; } } function Response(result, body, status) { this.result = result; this.body = body; this.status = status; } Response.prototype.toString = function () { return this.body; } function build(discovery, collection, baseUrl, accessToken) { for (var name in discovery.resources) { var resource = discovery.resources[name]; collection[name] = build(resource, {}, baseUrl, accessToken); } for (var name in discovery.methods) { var method = discovery.methods[name]; collection[name] = createNewMethod(method, baseUrl, accessToken); } return collection; } function getAccessToken(credentials) { if (credentials.accessToken) { return credentials.accessToken; } var tokenResponse = UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v4/token', { method: 'post', contentType: 'application/x-www-form-urlencoded', muteHttpExceptions: true, payload: { client_id: credentials.clientId, client_secret: credentials.clientSecret, refresh_token: credentials.refreshToken, grant_type: 'refresh_token' } }); var responseCode = tokenResponse.getResponseCode(); var responseText = tokenResponse.getContentText(); if (responseCode >= 200 && responseCode <= 299) { var accessToken = JSON.parse(responseText)['access_token']; return accessToken; } throw responseText; } })(GoogleApis || (GoogleApis = {})); // Base64 implementation from https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/master/lib/msal-core/src/Utils.ts class Base64 { static encode(input) { const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; let output = ""; let chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; input = this.utf8Encode(input); while (i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); } return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } static utf8Encode(input) { input = input.replace(/\r\n/g, "\n"); var utftext = ""; for (var n = 0; n < input.length; n++) { var c = input.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if ((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; } } The first part of the code which brings back values from the spreadsheet works, but when it gets to writing the data in Google Sheets, I get an error and I don't know what it means. { "error": { "code": 404, "message": "Requested entity was not found.", "status": "NOT_FOUND" } } at (script code:142:7)
Webrtc video chat not working in different networks
I am trying to implement video chat with webrtc using code from google code labs . It works fine on same network but does not work on different network. i have installed coturn on my server. I am not sure if it is working correctly. Any one used google codelabs code and made it working? This is the modified main.js file 'use strict'; var isChannelReady = false; var isInitiator = false; var isStarted = false; var localStream; var pc; var remoteStream; var turnReady; var pcConfig = { "iceServers":[ {'urls': 'stun:stun.l.google.com:19302'}, {"urls":["turn:78.129.167.90"],"username":"wyzturner1","credential":"wyzturnp2ss"}] }; // Set up audio and video regardless of what devices are present. var sdpConstraints = { offerToReceiveAudio: true, offerToReceiveVideo: true }; ///////////////////////////////////////////// var room = 'foo'; // Could prompt for room name: // room = prompt('Enter room name:'); var socket = io.connect('https://www.samplesite.com:8080'); if (room !== '') { socket.emit('create or join', room); console.log('Attempted to create or join room', room); } socket.on('created', function(room) { console.log('Created room ' + room); isInitiator = true; }); socket.on('full', function(room) { console.log('Room ' + room + ' is full'); }); socket.on('join', function (room){ console.log('Another peer made a request to join room ' + room); console.log('This peer is the initiator of room ' + room + '!'); isChannelReady = true; }); socket.on('joined', function(room) { console.log('joined: ' + room); isChannelReady = true; }); socket.on('log', function(array) { console.log.apply(console, array); }); //////////////////////////////////////////////// function sendMessage(message) { console.log('Client sending message: ', message); socket.emit('message', message); } // This client receives a message socket.on('message', function(message) { console.log('Client received message:', message); if (message === 'got user media') { maybeStart(); } else if (message.type === 'offer') { if (!isInitiator && !isStarted) { maybeStart(); } pc.setRemoteDescription(new RTCSessionDescription(message)); doAnswer(); } else if (message.type === 'answer' && isStarted) { pc.setRemoteDescription(new RTCSessionDescription(message)); } else if (message.type === 'candidate' && isStarted) { var candidate = new RTCIceCandidate({ sdpMLineIndex: message.label, candidate: message.candidate }); pc.addIceCandidate(candidate); } else if (message === 'bye' && isStarted) { handleRemoteHangup(); } }); //////////////////////////////////////////////////// var localVideo = document.querySelector('#localVideo'); var remoteVideo = document.querySelector('#remoteVideo'); navigator.mediaDevices.getUserMedia({ audio: false, video: true }) .then(gotStream) .catch(function(e) { alert('getUserMedia() error: ' + e.name); }); function gotStream(stream) { console.log('Adding local stream.'); localVideo.src = window.URL.createObjectURL(stream); localStream = stream; sendMessage('got user media'); if (isInitiator) { maybeStart(); } } var constraints = { video: true }; console.log('Getting user media with constraints', constraints); //if (location.hostname !== 'localhost') { // requestTurn( // 'https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913' // ); //} function maybeStart() { console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady); if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) { console.log('>>>>>> creating peer connection'); createPeerConnection(); isStarted = true; console.log('isInitiator', isInitiator); if (isInitiator) { doCall(); } } } window.onbeforeunload = function() { sendMessage('bye'); }; ///////////////////////////////////////////////////////// function createPeerConnection() { try { pc = new RTCPeerConnection(pcConfig); pc.addStream(localStream); pc.onicecandidate = handleIceCandidate; pc.onaddstream = handleRemoteStreamAdded; pc.oniceconnectionstatechange = function(){ console.log('ICE state: ',pc.iceConnectionState); } pc.onremovestream = handleRemoteStreamRemoved; console.log('Created RTCPeerConnnection'); } catch (e) { console.log('Failed to create PeerConnection, exception: ' + e.message); alert('Cannot create RTCPeerConnection object.'); return; } } function handleIceCandidate(event) { console.log('icecandidate event: ', event); if (event.candidate) { sendMessage({ type: 'candidate', label: event.candidate.sdpMLineIndex, id: event.candidate.sdpMid, candidate: event.candidate.candidate }); } else { console.log('End of candidates.'); } } function handleRemoteStreamAdded(event) { console.log('Remote stream added.'); remoteVideo.src = window.URL.createObjectURL(event.stream); remoteStream = event.stream; } function handleCreateOfferError(event) { console.log('createOffer() error: ', event); } function doCall() { console.log('Sending offer to peer'); pc.createOffer(setLocalAndSendMessage, handleCreateOfferError); } function doAnswer() { console.log('Sending answer to peer.'); pc.createAnswer().then( setLocalAndSendMessage, onCreateSessionDescriptionError ); } function setLocalAndSendMessage(sessionDescription) { // Set Opus as the preferred codec in SDP if Opus is present. // sessionDescription.sdp = preferOpus(sessionDescription.sdp); pc.setLocalDescription(sessionDescription); console.log('setLocalAndSendMessage sending message', sessionDescription); sendMessage(sessionDescription); } function onCreateSessionDescriptionError(error) { trace('Failed to create session description: ' + error.toString()); } function requestTurn(turnURL) { var turnExists = false; for (var i in pcConfig.iceServers) { if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') { turnExists = true; turnReady = true; break; } } // if (!turnExists) { // console.log('Getting TURN server from ', turnURL); // // No TURN server. Get one from computeengineondemand.appspot.com: // var xhr = new XMLHttpRequest(); // xhr.onreadystatechange = function() { // if (xhr.readyState === 4 && xhr.status === 200) { // var turnServer = JSON.parse(xhr.responseText); // console.log('Got TURN server: ', turnServer); // pcConfig.iceServers.push({ // 'url': 'turn:' + turnServer.username + '#' + turnServer.turn, // 'credential': turnServer.password // }); // turnReady = true; // } // }; // xhr.open('GET', turnURL, true); // xhr.send(); // } } function handleRemoteStreamAdded(event) { console.log('Remote stream added.'); remoteVideo.src = window.URL.createObjectURL(event.stream); remoteStream = event.stream; } function handleRemoteStreamRemoved(event) { console.log('Remote stream removed. Event: ', event); } function hangup() { console.log('Hanging up.'); stop(); sendMessage('bye'); } function handleRemoteHangup() { console.log('Session terminated.'); stop(); isInitiator = false; } function stop() { isStarted = false; // isAudioMuted = false; // isVideoMuted = false; pc.close(); pc = null; } /////////////////////////////////////////// // Set Opus as the default audio codec if it's present. function preferOpus(sdp) { var sdpLines = sdp.split('\r\n'); var mLineIndex; // Search for m line. for (var i = 0; i < sdpLines.length; i++) { if (sdpLines[i].search('m=audio') !== -1) { mLineIndex = i; break; } } if (mLineIndex === null) { return sdp; } // If Opus is available, set it as the default in m line. for (i = 0; i < sdpLines.length; i++) { if (sdpLines[i].search('opus/48000') !== -1) { var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i); if (opusPayload) { sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload); } break; } } // Remove CN in m line and sdp. sdpLines = removeCN(sdpLines, mLineIndex); sdp = sdpLines.join('\r\n'); return sdp; } function extractSdp(sdpLine, pattern) { var result = sdpLine.match(pattern); return result && result.length === 2 ? result[1] : null; } // Set the selected codec to the first in m line. function setDefaultCodec(mLine, payload) { var elements = mLine.split(' '); var newLine = []; var index = 0; for (var i = 0; i < elements.length; i++) { if (index === 3) { // Format of media starts from the fourth. newLine[index++] = payload; // Put target payload to the first. } if (elements[i] !== payload) { newLine[index++] = elements[i]; } } return newLine.join(' '); } // Strip CN from sdp before CN constraints is ready. function removeCN(sdpLines, mLineIndex) { var mLineElements = sdpLines[mLineIndex].split(' '); // Scan from end for the convenience of removing an item. for (var i = sdpLines.length - 1; i >= 0; i--) { var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i); if (payload) { var cnPos = mLineElements.indexOf(payload); if (cnPos !== -1) { // Remove CN payload from m line. mLineElements.splice(cnPos, 1); } // Remove CN line in sdp sdpLines.splice(i, 1); } } sdpLines[mLineIndex] = mLineElements.join(' '); return sdpLines; }
Knockout JS - foreach: data-bind not get refreshed
SPEC : Building a customer support application. First time, when a user send a message able to save to DB and show in the view without any issues. And from my end, if i send I am able to push the data to the UI as well DB and the user is able to get the message. But the issue is when the user send back response again, its not showing up refreshing the UI. Below is my knockout code, var csendpoints = { "Active": "/cs/oc/", "ActiveClientData": "/cs/gnm/", "Archive": "/cw/archive/", "Dequeue": function (id) { return "/cw/dequeue/" + id; }, "GetQueue": "/cs/q/", "GetArchive": "/cs/h/", "GetActive": "/cs/active/", "RecentlyQueued": "/cs/rq/", "Send": "/cs/send", "SitRep": "/cw/sitrep", }; var customerSupportViewModel; function PagedViewModels(model) { var self = this; self.Items = ko.observableArray(model.Items || []); self.MetaData = ko.observable(model.MetaData || ""); } function PagedListMetaDataViewModel(model) { var self = this; self.FirstItemOnPage = ko.observable(model.FirstItemOnPage || ""); self.HasNextPage = ko.observable(model.HasNextPage || ""); self.HasPreviousPage = ko.observable(model.HasPreviousPage || ""); self.IsFirstPage = ko.observable(model.IsFirstPage || ""); self.IsLastPage = ko.observable(model.IsLastPage || ""); self.LastItemOnPage = ko.observable(model.LastItemOnPage || ""); self.PageCount = ko.observable(model.PageCount || ""); self.PageNumber = ko.observable(model.PageNumber || ""); self.PageSize = ko.observable(model.PageSize || ""); self.TotalItemCount = ko.observable(model.TotalItemCount || ""); } function ConversationMessageViewModels(model) { var self = this; self.ClientId = ko.observable(model.ClientId || ""); self.ClientName = ko.observable(model.ClientName || ""); self.ClientSex = ko.observable(model.ClientSex || ""); self.ClientLanguage = ko.observable(model.ClientLanguage || ""); self.ClientProvince = ko.observable(model.ClientProvince || ""); self.ClientCountry = ko.observable(model.ClientCountry || ""); self.ClientCity = ko.observable(model.ClientCity || ""); self.ClientProfileImage = ko.observable(model.ClientProfileImage || ""); self.ClientContent = ko.observable(model.ClientContent || ""); self.Content = ko.observable(model.Content || ""); self.ConversationMessageId = ko.observable(model.ConversationMessageId || ""); self.dummy = ko.observable(); self.Sender = ko.observable(model.Sender || ""); self.SentOn = ko.observable(model.SentOn || ""); self.SentOnUniversalSortable = ko.observable(model.SentOnUniversalSortable || ""); self.SentTimeFromNow = ko.computed(function () { self.dummy(); return moment.utc(self.SentOnUniversalSortable()).fromNow(); }); function updateTimeFromNow() { setTimeout(function () { self.dummy.notifySubscribers(); updateTimeFromNow(); }, 60 * 1000); } updateTimeFromNow(); } function ConversationViewModels(model) { var self = this; self.ClientId = ko.observable(model.ClientId || ""); self.ClientName = ko.observable(model.ClientName || ""); self.ClientProfileImage = ko.observable(model.ClientProfileImage || ""); self.ClientSex = ko.observable(model.ClientSex || ""); self.ClientLanguage = ko.observable(model.ClientLanguage || ""); self.ClientProvince = ko.observable(model.ClientProvince || ""); self.ClientCountry = ko.observable(model.ClientCountry || ""); self.ClientCity = ko.observable(model.ClientCity || ""); self.ClientNameInitial = ko.observable(model.ClientName.substring(0, 1).toUpperCase()); self.ClientContent = ko.observable(model.ClientContent || ""); self.ConversationId = ko.observable(model.ConversationId || ""); self.CreatedOn = ko.observable(model.CreatedOn || ""); self.MessagePreview = ko.observable(model.MessagePreview || ""); self.Messages = ko.observableArray([]); self.ProfileImageUrl = ko.observable(model.ProfileImageUrl || ""); self.Sender = ko.observable(model.Sender || ""); self.SupportName = ko.observable(customerSupportViewModel.CustomerSupportName()); self.TextBoxContent = ko.observable(""); self.TextBoxEnabled = ko.observable(true); self.MakeActive = function () { customerSupportViewModel.WaitingList.remove(self); customerSupportViewModel.ArchivedConversations.remove(self); customerSupportViewModel.ActiveConversation(self); customerSupportViewModel.CurrentConversations.push(self); $.ajax(csendpoints.Active + self.ConversationId(), { success: console.log("Debug: Now Active") }); }; self.MakeRemoveMultiActive = function () { customerSupportViewModel.WaitingList.remove(self); customerSupportViewModel.ArchivedConversations.remove(self); customerSupportViewModel.ActiveConversation(self); //customerSupportViewModel.CurrentConversations.push(self); //customerSupportViewModel.CurrentConversations.remove(self); $.ajax(csendpoints.Active + self.ConversationId(), { success: console.log("Debug: Now Active") }); $("#chat-wrap").mCustomScrollbar("destroy"); $("#chat-wrap").height($(window).height() - 314); $("#chat-wrap") .mCustomScrollbar({ axis: "y", scrollEasing: "linear", scrollButtons: { enable: true }, theme: "dark-thick", scrollbarPosition: "inside", mouseWheelPixels: 400 }); initMap() }; self.Archive = function () { customerSupportViewModel.ArchivedConversations.push(self); customerSupportViewModel.ActiveConversation(''); customerSupportViewModel.CurrentConversations.remove(self); customerSupportViewModel.WaitingList.remove(self); customerSupportViewModel.RefreshWaiting(); customerSupportViewModel.RefreshActive(); customerSupportViewModel.RefreshArchive(); $.ajax(csendpoints.Archive + self.ConversationId(), { success: console.log("Debug: Now Archived") }); }; self.SendMessage = function (data) { self.TextBoxEnabled(false); var msj = { "SentOn": moment.utc().format(), "SentOnUniversalSortable": moment().format(), "Sender": 1, "SenderName": customerSupportViewModel.CustomerSupportName(), "Content": self.TextBoxContent(), "ClientId": self.ClientId(), "ClientName": self.ClientName() }; self.Messages.push(new ConversationMessageViewModels(msj)); $.ajax({ type: "POST", url: csendpoints.Send, data: $(data).serialize(), success: function () { }, complete: function () { self.TextBoxContent(""); self.TextBoxEnabled(true); } }); }; _.each(model.Messages, function (item) { self.Messages.push(new ConversationMessageViewModels(item)); }); var sortedMessages = _.sortBy(self.Messages(), function (iteratedItem) { return iteratedItem.SentOnUniversalSortable(); }); self.Messages(sortedMessages); } function CustomerSupportViewModel(model) { var self = this; customerSupportViewModel = self; function getWaitingList(page) { $.ajax(csendpoints.GetQueue + page, { success: function (data) { self.WaitingList.removeAll(); _.each(data.Items, function (item) { self.WaitingList.push(new ConversationViewModels(item)); }); self.WaitingListPaging(data.MetaData); } }); } function getActiveList(page) { $.ajax(csendpoints.GetActive + page, { success: function (data) { self.CurrentConversations.removeAll(); _.each(data.Items, function (item) { self.CurrentConversations.push(new ConversationViewModels(item)); }); self.CurrentConversationsPaging(data.MetaData); } }); } function getArchiveList(page) { $.ajax(csendpoints.GetArchive + page, { success: function (data) { self.ArchivedConversations.removeAll(); _.each(data.Items, function (item) { self.ArchivedConversations.push(new ConversationViewModels(item)); }); self.ArchivedConversations(data.MetaData); } }); } // Conversations in Queue(i.e. waiting), Current(i.e. active) and Archive (i.e. history) self.WaitingList = ko.observableArray([]); self.ArchivedConversations = ko.observableArray([]); self.CurrentConversations = ko.observableArray([]); // Paging models self.WaitingListPaging = ko.observable(model.ConversationsInQueue.MetaData); self.ArchiveListPaging = ko.observable(model.ArchivedConversations.MetaData); self.CurrentConversationsPaging = ko.observable(model.CurrentConversations.MetaData); // The conversation currently active. self.ActiveConversation = ko.observable(); //setInterval(self.ActiveConversation, 500); // The name of the WeChatAccount serving the client self.CustomerSupportName = ko.observable(model.CustomerSupportName); self.RefreshWaiting = function () { var page = self.WaitingListPaging().PageNumber; getWaitingList(page); }; self.RefreshActive = function () { var page = self.CurrentConversationsPaging().PageNumber; getActiveList(page); //getWaitingList(page); }; self.RefreshArchive = function () { var page = self.ArchiveListPaging().PageNumber; //getArchiveList(page); getWaitingList(page); }; //self.RefreshChatActive = function () { // getActiveList() //}; // ###### PAGING for Waiting / Queue ###### // Forward paging for the queue / waiting list self.NextPageWaitingList = function () { if (self.WaitingListPaging().IsLastPage) return; var page = self.WaitingListPaging().PageNumber + 1; getWaitingList(page); }; self.PreviousPageWaitingList = function (data, e) { //console.log($(data)) //console.log((e.target) ? e.target : e.srcElement) if (self.WaitingListPaging().IsFirstPage) return; var page = self.WaitingListPaging().PageNumber - 1; getWaitingList(page); }; // ###### PAGING for Active / Current conversations ###### self.NextPageActiveList = function () { if (self.CurrentConversations().IsLastPage) return; var page = self.CurrentConversations().PageNumber + 1; getActiveList(page); }; self.PreviousPageActiveList = function () { if (self.CurrentConversations().IsFirstPage) return; var page = self.CurrentConversations().PageNumber - 1; getActiveList(page); }; // ###### PAGING for History / Archived conversations ###### self.NextPageArchiveList = function () { if (self.ArchivedConversations().IsLastPage) return; var page = self.ArchivedConversations().PageNumber + 1; getArchiveList(page); }; self.PreviousPageArchiveList = function (data, e) { if (self.ArchivedConversations().IsFirstPage) return; var page = self.ArchivedConversations().PageNumber - 1; getArchiveList(page); }; _.each(model.ConversationsInQueue.Items, function (item) { self.WaitingList.push(new ConversationViewModels(item)); }); _.each(model.CurrentConversations.Items, function (item) { self.CurrentConversations.push(new ConversationViewModels(item)); }); _.each(model.ArchivedConversations.Items, function (item) { self.ArchivedConversations.push(new ConversationViewModels(item)); }); function getNewQueue(page) { setTimeout(function () { $.ajax(csendpoints.GetQueue + page, { success: function (data, odata) { getNewQueue(); } }); }, 15 * 1000); } } var customerSupportViewModel = new CustomerSupportViewModel(jsonViewModel); window.setInterval(customerSupportViewModel.RefreshWaiting, 1000); window.setInterval(customerSupportViewModel.RefreshActive, 200); window.setInterval(customerSupportViewModel.Messages, 200); ko.applyBindings(customerSupportViewModel); var conversationMessageViewModels = new ConversationMessageViewModels(jsonViewModel); Here MakeRemoveMultiActive is used as a click function, and in the view, it load the data what user sent as well the response sent from my end. Note, this happens only on click event. I want to update the UI after the click event so the view is open with the data and autoupdates. Request experts to shed some light.
Parse multiple pages with phantomjs
I have made a code that parses all URL-s from a page. Next, I would like to get a href from every parsed URL <div class="holder"></div> and output it to a file and sepparate with a comma. So far I have made this code. It is able to find all the URL-s need to be parsed and collects them to a comma sepparated file called output2.txt. var resourceWait = 300, maxRenderWait = 10000, url = 'URL TO PARSE HREF-s FROM'; var page = require('webpage').create(), count = 0, forcedRenderTimeout, renderTimeout; page.viewportSize = { width: 1280, height : 1024 }; function doRender() { var fs = require('fs'); var path = 'output2.txt'; page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function() { fs.write(path,page.evaluate(function() { return $('.urlDIV').find('a') .map(function() { return this.href;}) .get() .join(','); }), 'w'); phantom.exit() }); } page.onResourceRequested = function (req) { count += 1; clearTimeout(renderTimeout); }; page.onResourceReceived = function (res) { if (!res.stage || res.stage === 'end') { count -= 1; if (count === 0) { renderTimeout = setTimeout(doRender, resourceWait); } } }; page.open(url, function (status) { if (status !== "success") { phantom.exit(); } else { forcedRenderTimeout = setTimeout(function () { console.log(count); doRender(); }, maxRenderWait); } }); Thanks in advance, Martti
WebRTC + JSEP + Google Channel API - cannot receive remoteStream
I've been trying to get this works, but I don't know what's wrong, can you guys help me? I tried WebRTC code with modification like this. Both caller and callee enter the web Web will create channel based on their username Caller clicks the call button and sends message to spesific channel to some user's username. When caller clicks the call button, he then creates peerConnection and adds localStream Callee will receive message, and the process goes on like WebRTC sample code. When callee receives an offer, he then creates peerConnection and adds localStream, then creates and sends answer My code goes like this var my_username = '{{ current_username }}'; var friend; var localVideo; var remoteVideo; var localStream; var remoteStream; var channel; var channelReady = false; var pc; var socket; var started = false; // Set up audio and video regardless of what devices are present. var mediaConstraints = {'mandatory': { 'OfferToReceiveAudio':true, 'OfferToReceiveVideo':true }}; var isVideoMuted = false; var isAudioMuted = false; function choiceFriendInitialize() { var choice_visible = false; $('ul#friendlist > li').click(function(e) { $(this).css('background-color', '#808080'); friend = $(this).text(); var choice = $('ul#choice'); choice_visible = true; choice.css('display', 'inline-block'); choice.css('top', e.pageY); choice.css('left', e.pageX); }); // trigger call from here $('li#call').click(function() { $('ul#friendlist > li').css('background-color', 'transparent'); $('ul#choice').css('display', 'none'); choice_visible = false; // call maybeStart(); doCall(); }); $('li#unfriend').click(function() { $('ul#friendlist > li').css('background-color', 'transparent'); $('ul#choice').css('display', 'none'); choice_visible = false; }); $("body").mouseup(function(){ if (choice_visible) { $('ul#friendlist > li').css('background-color', 'transparent'); $('ul#choice').css('display', 'none'); choice_visible = false; } }); } function initialize() { console.log("Initializing.."); choiceFriendInitialize(); localVideo = document.getElementById("localVideo"); remoteVideo = document.getElementById("remoteVideo"); openChannel(); doGetUserMedia(); } function openChannel() { console.log("Opening channel."); var channel = new goog.appengine.Channel('{{ token }}'); var handler = { 'onopen': onChannelOpened, 'onmessage': onChannelMessage, 'onerror': onChannelError, 'onclose': onChannelClosed }; socket = channel.open(handler); } function doGetUserMedia() { // Call into getUserMedia via the polyfill (adapter.js). var constraints = {"mandatory": {}, "optional": []}; try { getUserMedia({'audio':true, 'video':constraints}, onUserMediaSuccess, onUserMediaError); console.log("Requested access to local media with mediaConstraints:\n" + " \"" + JSON.stringify(constraints) + "\""); } catch (e) { alert("getUserMedia() failed. Is this a WebRTC capable browser?"); console.log("getUserMedia failed with exception: " + e.message); } } function createPeerConnection() { var pc_config = {"iceServers": [{"url": "stun:stun.l.google.com:19302"}]}; try { // Create an RTCPeerConnection via the polyfill (adapter.js). pc = new RTCPeerConnection(pc_config); pc.onicecandidate = onIceCandidate; console.log("Created RTCPeerConnnection with config:\n" + " \"" + JSON.stringify(pc_config) + "\"."); } catch (e) { console.log("Failed to create PeerConnection, exception: " + e.message); alert("Cannot create RTCPeerConnection object; WebRTC is not supported by this browser."); return; } pc.onconnecting = onSessionConnecting; pc.onopen = onSessionOpened; pc.onaddstream = onRemoteStreamAdded; pc.onremovestream = onRemoteStreamRemoved; } function maybeStart() { if (!started && localStream && channelReady) { console.log("Creating PeerConnection."); createPeerConnection(); console.log("Adding local stream."); pc.addStream(localStream); started = true; // Caller initiates offer to peer. //if (initiator) //doCall(); } } function doCall() { console.log("Sending offer to peer."); pc.createOffer(setLocalAndSendMessage, null, mediaConstraints); } function doAnswer() { console.log("Sending answer to peer."); pc.createAnswer(setLocalAndSendMessage, null, mediaConstraints); } function setLocalAndSendMessage(sessionDescription) { // Set Opus as the preferred codec in SDP if Opus is present. sessionDescription.sdp = preferOpus(sessionDescription.sdp); pc.setLocalDescription(sessionDescription); sendMessage({from: my_username, to: friend}, sessionDescription); } function sendMessage(client, message) { console.log('C->S: ' + JSON.stringify(message)); var xhr = new XMLHttpRequest(); xhr.open('POST', '/send', true); xhr.setRequestHeader("Content-type","application/json"); var msgString = {send_info: client, data_message: message}; xhr.send(JSON.stringify(msgString)); } function processSignalingMessage(message) { var msg = JSON.parse(message); var data_message = msg.data_message; var send_info = msg.send_info; if (data_message.type === 'offer') { // Callee creates PeerConnection if (!started) maybeStart(); pc.setRemoteDescription(new RTCSessionDescription(data_message)); friend = send_info.from; doAnswer(); } else if (data_message.type === 'answer' && started) { pc.setRemoteDescription(new RTCSessionDescription(data_message)); } else if (data_message.type === 'candidate' && started) { var candidate = new RTCIceCandidate({sdpMLineIndex:data_message.label, candidate:data_message.candidate}); pc.addIceCandidate(candidate); } else if (data_message.type === 'bye' && started) { onRemoteHangup(); } } function onChannelOpened() { console.log('Channel opened.'); channelReady = true; } function onChannelMessage(message) { console.log('S->C: ' + message.data); processSignalingMessage(message.data); } function onChannelError() { console.log('Channel error.'); } function onChannelClosed() { console.log('Channel closed.'); } function onUserMediaSuccess(stream) { console.log("User has granted access to local media."); // Call the polyfill wrapper to attach the media stream to this element. attachMediaStream(localVideo, stream); localVideo.style.opacity = 1; localStream = stream; // Caller creates PeerConnection. //if (initiator) maybeStart(); } function onUserMediaError(error) { console.log("Failed to get access to local media. Error code was " + error.code); alert("Failed to get access to local media. Error code was " + error.code + "."); } function onIceCandidate(event) { if (event.candidate) { sendMessage({from: my_username, to: friend}, {type: 'candidate', label: event.candidate.sdpMLineIndex, id: event.candidate.sdpMid, candidate: event.candidate.candidate}); } else { console.log("End of candidates."); } } function onSessionConnecting(message) { console.log("Session connecting."); } function onSessionOpened(message) { console.log("Session opened."); } function onRemoteStreamAdded(event) { console.log("Remote stream added."); attachMediaStream(remoteVideo, event.stream); remoteStream = event.stream; waitForRemoteVideo(); } function onRemoteStreamRemoved(event) { console.log("Remote stream removed."); } function onHangup() { console.log("Hanging up."); transitionToDone(); stop(); // will trigger BYE from server socket.close(); } function onRemoteHangup() { console.log('Session terminated.'); transitionToWaiting(); stop(); } function stop() { started = false; isAudioMuted = false; isVideoMuted = false; pc.close(); pc = null; } function waitForRemoteVideo() { if (remoteStream.videoTracks.length === 0 || remoteVideo.currentTime > 0) { console.log('ada remote stream'); transitionToActive(); } else { console.log('ga ada remote stream'); setTimeout(waitForRemoteVideo, 100); } } function transitionToActive() { remoteVideo.style.opacity = 1; } function transitionToWaiting() { remoteVideo.style.opacity = 0; } function transitionToDone() { localVideo.style.opacity = 0; remoteVideo.style.opacity = 0; } function toggleVideoMute() { if (localStream.videoTracks.length === 0) { console.log("No local video available."); return; } if (isVideoMuted) { for (i = 0; i < localStream.videoTracks.length; i++) { localStream.videoTracks[i].enabled = true; } console.log("Video unmuted."); } else { for (i = 0; i < localStream.videoTracks.length; i++) { localStream.videoTracks[i].enabled = false; } console.log("Video muted."); } isVideoMuted = !isVideoMuted; } function toggleAudioMute() { if (localStream.audioTracks.length === 0) { console.log("No local audio available."); return; } if (isAudioMuted) { for (i = 0; i < localStream.audioTracks.length; i++) { localStream.audioTracks[i].enabled = true; } console.log("Audio unmuted."); } else { for (i = 0; i < localStream.audioTracks.length; i++){ localStream.audioTracks[i].enabled = false; } console.log("Audio muted."); } isAudioMuted = !isAudioMuted; } setTimeout(initialize, 1); // Send BYE on refreshing(or leaving) a demo page // to ensure the room is cleaned for next session. window.onbeforeunload = function() { sendMessage({from: my_username, to: friend}, {type: 'bye'}); //Delay 100ms to ensure 'bye' arrives first. setTimeout(function(){}, 100); } // Ctrl-D: toggle audio mute; Ctrl-E: toggle video mute. // On Mac, Command key is instead of Ctrl. // Return false to screen out original Chrome shortcuts. document.onkeydown = function() { if (navigator.appVersion.indexOf("Mac") != -1) { if (event.metaKey && event.keyCode === 68) { toggleAudioMute(); return false; } if (event.metaKey && event.keyCode === 69) { toggleVideoMute(); return false; } } else { if (event.ctrlKey && event.keyCode === 68) { toggleAudioMute(); return false; } if (event.ctrlKey && event.keyCode === 69) { toggleVideoMute(); return false; } } } // Set Opus as the default audio codec if it's present. function preferOpus(sdp) { var sdpLines = sdp.split('\r\n'); // Search for m line. for (var i = 0; i < sdpLines.length; i++) { if (sdpLines[i].search('m=audio') !== -1) { var mLineIndex = i; break; } } if (mLineIndex === null) return sdp; // If Opus is available, set it as the default in m line. for (var i = 0; i < sdpLines.length; i++) { if (sdpLines[i].search('opus/48000') !== -1) { var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i); if (opusPayload) sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload); break; } } // Remove CN in m line and sdp. sdpLines = removeCN(sdpLines, mLineIndex); sdp = sdpLines.join('\r\n'); return sdp; } function extractSdp(sdpLine, pattern) { var result = sdpLine.match(pattern); return (result && result.length == 2)? result[1]: null; } // Set the selected codec to the first in m line. function setDefaultCodec(mLine, payload) { var elements = mLine.split(' '); var newLine = new Array(); var index = 0; for (var i = 0; i < elements.length; i++) { if (index === 3) // Format of media starts from the fourth. newLine[index++] = payload; // Put target payload to the first. if (elements[i] !== payload) newLine[index++] = elements[i]; } return newLine.join(' '); } // Strip CN from sdp before CN constraints is ready. function removeCN(sdpLines, mLineIndex) { var mLineElements = sdpLines[mLineIndex].split(' '); // Scan from end for the convenience of removing an item. for (var i = sdpLines.length-1; i >= 0; i--) { var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i); if (payload) { var cnPos = mLineElements.indexOf(payload); if (cnPos !== -1) { // Remove CN payload from m line. mLineElements.splice(cnPos, 1); } // Remove CN line in sdp sdpLines.splice(i, 1); } } sdpLines[mLineIndex] = mLineElements.join(' '); return sdpLines; } When it comes to waitForRemoteVideo, the function calls the else condition. But the blob url for remoteVideo exists.
It's funny that I've been searching for the error and re-writing the code for like three times, and suddenly after posted my question here, I realized my mistake. Here in the function processSignallingMessage.. if (data_message.type === 'offer') { // Callee creates PeerConnection if (!started) maybeStart(); pc.setRemoteDescription(new RTCSessionDescription(data_message)); friend = send_info.from; doAnswer(); } else .... Should be like this: if (data_message.type === 'offer') { friend = send_info.from; // Callee creates PeerConnection if (!started) maybeStart(); pc.setRemoteDescription(new RTCSessionDescription(data_message)); doAnswer(); } else .... because callee need variable friend to be filled before sending candidate message.