Improve WebRTC connection for Safari browsers
This commit is contained in:
parent
b8b53c65f3
commit
f094213a34
92
src/App.vue
92
src/App.vue
|
@ -37,6 +37,7 @@ const configuration = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const FileUploadMaxSize = 1024 * 1024 * 8; // 8 MB
|
const FileUploadMaxSize = 1024 * 1024 * 8; // 8 MB
|
||||||
|
const DebugChannelID = "barertc-debug";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BareRTC',
|
name: 'BareRTC',
|
||||||
|
@ -156,6 +157,7 @@ export default {
|
||||||
muteSounds: false, // mute all sound effects
|
muteSounds: false, // mute all sound effects
|
||||||
theme: "auto", // auto, light, dark theme
|
theme: "auto", // auto, light, dark theme
|
||||||
appleCompat: isAppleWebkit(), // Apple browser compatibility mode
|
appleCompat: isAppleWebkit(), // Apple browser compatibility mode
|
||||||
|
debug: false, // enable debugging features
|
||||||
},
|
},
|
||||||
|
|
||||||
// My video feed.
|
// My video feed.
|
||||||
|
@ -523,6 +525,9 @@ export default {
|
||||||
"prefs.appleCompat": function () {
|
"prefs.appleCompat": function () {
|
||||||
LocalStorage.set('appleCompat', this.prefs.appleCompat);
|
LocalStorage.set('appleCompat', this.prefs.appleCompat);
|
||||||
},
|
},
|
||||||
|
"prefs.debug": function () {
|
||||||
|
LocalStorage.set('debug', this.prefs.debug);
|
||||||
|
},
|
||||||
"prefs.theme": function() {
|
"prefs.theme": function() {
|
||||||
LocalStorage.set('theme', this.prefs.theme);
|
LocalStorage.set('theme', this.prefs.theme);
|
||||||
},
|
},
|
||||||
|
@ -900,6 +905,9 @@ export default {
|
||||||
if (this.prefs.appleCompat != undefined) {
|
if (this.prefs.appleCompat != undefined) {
|
||||||
this.prefs.appleCompat = settings.appleCompat === true;
|
this.prefs.appleCompat = settings.appleCompat === true;
|
||||||
}
|
}
|
||||||
|
if (this.prefs.debug != undefined) {
|
||||||
|
this.prefs.debug = settings.debug === true;
|
||||||
|
}
|
||||||
if (settings.whoSort != undefined) {
|
if (settings.whoSort != undefined) {
|
||||||
this.whoSort = settings.whoSort;
|
this.whoSort = settings.whoSort;
|
||||||
}
|
}
|
||||||
|
@ -993,6 +1001,16 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DEBUGGING: enable the 'debug' pref and see the debug channel.
|
||||||
|
if (this.message.toLowerCase().indexOf("/toggle-debug-settings") === 0) {
|
||||||
|
this.prefs.debug = !this.prefs.debug;
|
||||||
|
this.ChatClient(
|
||||||
|
`Debug tools have been turned: <strong>${this.prefs.debug ? 'on' : 'off'}.</strong>`,
|
||||||
|
);
|
||||||
|
this.message = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// DEBUGGING: fake set the freeze indicator.
|
// DEBUGGING: fake set the freeze indicator.
|
||||||
let match = this.message.match(/^\/freeze (.+?)$/i);
|
let match = this.message.match(/^\/freeze (.+?)$/i);
|
||||||
if (match) {
|
if (match) {
|
||||||
|
@ -1006,7 +1024,9 @@ export default {
|
||||||
// DEBUGGING: test whether the page thinks you're Apple Webkit.
|
// DEBUGGING: test whether the page thinks you're Apple Webkit.
|
||||||
if (this.message.toLowerCase().indexOf("/ipad") === 0) {
|
if (this.message.toLowerCase().indexOf("/ipad") === 0) {
|
||||||
if (this.isAppleWebkit) {
|
if (this.isAppleWebkit) {
|
||||||
this.ChatClient("I have detected that you are probably an iPad or iPhone browser.");
|
this.ChatClient("I have detected that you are probably an iPad or iPhone browser.<br><br>" +
|
||||||
|
`* Auto-detection: ${this.isAppleWebkit}<br>` +
|
||||||
|
`* Manual setting: ${this.prefs.appleCompat}`);
|
||||||
} else {
|
} else {
|
||||||
this.ChatClient("I have detected that you <strong>are not</strong> an iPad or iPhone browser.");
|
this.ChatClient("I have detected that you <strong>are not</strong> an iPad or iPhone browser.");
|
||||||
}
|
}
|
||||||
|
@ -1322,6 +1342,7 @@ export default {
|
||||||
|
|
||||||
// Send a video request to access a user's camera.
|
// Send a video request to access a user's camera.
|
||||||
sendOpen(username) {
|
sendOpen(username) {
|
||||||
|
this.DebugChannel(`[WebRTC] Sending "open" message to ask to connect to: ${username}`);
|
||||||
this.client.send({
|
this.client.send({
|
||||||
action: "open",
|
action: "open",
|
||||||
username: username,
|
username: username,
|
||||||
|
@ -1341,10 +1362,12 @@ export default {
|
||||||
},
|
},
|
||||||
onOpen(msg) {
|
onOpen(msg) {
|
||||||
// Response for the opener to begin WebRTC connection.
|
// Response for the opener to begin WebRTC connection.
|
||||||
|
this.DebugChannel(`[WebRTC] Received "open" echo from chat server to connect to: ${msg.username}`);
|
||||||
this.startWebRTC(msg.username, true);
|
this.startWebRTC(msg.username, true);
|
||||||
},
|
},
|
||||||
onRing(msg) {
|
onRing(msg) {
|
||||||
// Request from a viewer to see our broadcast.
|
// Request from a viewer to see our broadcast.
|
||||||
|
this.DebugChannel(`[WebRTC] Received "ring" message from chat server to share my video with: ${msg.username}`);
|
||||||
this.startWebRTC(msg.username, false);
|
this.startWebRTC(msg.username, false);
|
||||||
},
|
},
|
||||||
onUserExited(msg) {
|
onUserExited(msg) {
|
||||||
|
@ -1518,17 +1541,11 @@ export default {
|
||||||
this.WebRTC.pc[username].answerer = pc;
|
this.WebRTC.pc[username].answerer = pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.DebugChannel(`[WebRTC] Starting WebRTC with: ${username} (I am the: ${isOfferer ? 'offerer' : 'answerer'})`);
|
||||||
|
|
||||||
// Keep a pointer to the current channel being established (for candidate/SDP).
|
// Keep a pointer to the current channel being established (for candidate/SDP).
|
||||||
this.WebRTC.pc[username].connecting = pc;
|
this.WebRTC.pc[username].connecting = pc;
|
||||||
|
|
||||||
// Create a data channel so we have something to connect over even if
|
|
||||||
// the local user is not broadcasting their own camera.
|
|
||||||
// TODO: adding a dummy data channel might allow iPad to open single directional video
|
|
||||||
let dataChannel = pc.createDataChannel("data");
|
|
||||||
dataChannel.addEventListener("open", event => {
|
|
||||||
// beginTransmission(dataChannel);
|
|
||||||
})
|
|
||||||
|
|
||||||
// 'onicecandidate' notifies us whenever an ICE agent needs to deliver a
|
// 'onicecandidate' notifies us whenever an ICE agent needs to deliver a
|
||||||
// message to the other peer through the signaling server.
|
// message to the other peer through the signaling server.
|
||||||
pc.onicecandidate = event => {
|
pc.onicecandidate = event => {
|
||||||
|
@ -1554,7 +1571,7 @@ export default {
|
||||||
// cam and sending their video on the offer, but we don't want to auto-open their
|
// cam and sending their video on the offer, but we don't want to auto-open their
|
||||||
// video, so don't use it)
|
// video, so don't use it)
|
||||||
if (!isOfferer && !this.webcam.mutualOpen) {
|
if (!isOfferer && !this.webcam.mutualOpen) {
|
||||||
console.log(`The offerer ${username} gave us a video, but we don't auto-open their video.`);
|
this.DebugChannel(`[WebRTC] The offerer ${username} gave us a video, but we don't auto-open their video.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1606,7 +1623,7 @@ export default {
|
||||||
// Set a mute video handler to detect freezes.
|
// Set a mute video handler to detect freezes.
|
||||||
stream.getVideoTracks().forEach(videoTrack => {
|
stream.getVideoTracks().forEach(videoTrack => {
|
||||||
let freezeDetected = () => {
|
let freezeDetected = () => {
|
||||||
console.log("FREEZE DETECTED:", username);
|
this.DebugChannel("[WebRTC] A video freeze was detected from:", username);
|
||||||
// Wait some seconds to see if the stream has recovered on its own
|
// Wait some seconds to see if the stream has recovered on its own
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Flag it as likely frozen.
|
// Flag it as likely frozen.
|
||||||
|
@ -1616,7 +1633,6 @@ export default {
|
||||||
}, 7500); // 7.5s
|
}, 7500); // 7.5s
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Apply onmute handler for", username);
|
|
||||||
videoTrack.onmute = freezeDetected;
|
videoTrack.onmute = freezeDetected;
|
||||||
|
|
||||||
// Double check for frozen streams on an interval.
|
// Double check for frozen streams on an interval.
|
||||||
|
@ -1634,6 +1650,7 @@ export default {
|
||||||
// ANSWERER: add our video to the connection so that the offerer (the one who
|
// ANSWERER: add our video to the connection so that the offerer (the one who
|
||||||
// clicked on our video icon to watch us) can receive it.
|
// clicked on our video icon to watch us) can receive it.
|
||||||
if (!isOfferer && this.webcam.active) {
|
if (!isOfferer && this.webcam.active) {
|
||||||
|
this.DebugChannel(`[WebRTC] Answerer: attaching my video to the connection with: ${username}`);
|
||||||
let stream = this.webcam.stream;
|
let stream = this.webcam.stream;
|
||||||
stream.getTracks().forEach(track => {
|
stream.getTracks().forEach(track => {
|
||||||
pc.addTrack(track, stream)
|
pc.addTrack(track, stream)
|
||||||
|
@ -1665,6 +1682,7 @@ export default {
|
||||||
// NOTE: on Apple devices, always send your video to satisfy the two-way video call
|
// NOTE: on Apple devices, always send your video to satisfy the two-way video call
|
||||||
// constraint imposed by Safari's WebRTC implementation.
|
// constraint imposed by Safari's WebRTC implementation.
|
||||||
if (shouldOfferVideo || this.isAppleWebkit) {
|
if (shouldOfferVideo || this.isAppleWebkit) {
|
||||||
|
this.DebugChannel(`[WebRTC] Offerer: I am attaching my video to the connection with: ${username}`)
|
||||||
let stream = this.webcam.stream;
|
let stream = this.webcam.stream;
|
||||||
stream.getTracks().forEach(track => {
|
stream.getTracks().forEach(track => {
|
||||||
pc.addTrack(track, stream)
|
pc.addTrack(track, stream)
|
||||||
|
@ -1674,6 +1692,7 @@ export default {
|
||||||
|
|
||||||
// If we are the offerer, begin the connection.
|
// If we are the offerer, begin the connection.
|
||||||
if (isOfferer) {
|
if (isOfferer) {
|
||||||
|
this.DebugChannel(`[WebRTC] Offerer: create the offer and send it to ${username}`);
|
||||||
pc.createOffer({
|
pc.createOffer({
|
||||||
offerToReceiveVideo: true,
|
offerToReceiveVideo: true,
|
||||||
offerToReceiveAudio: true,
|
offerToReceiveAudio: true,
|
||||||
|
@ -1685,6 +1704,7 @@ export default {
|
||||||
localDescCreated(pc, username) {
|
localDescCreated(pc, username) {
|
||||||
return (desc) => {
|
return (desc) => {
|
||||||
pc.setLocalDescription(desc).then(() => {
|
pc.setLocalDescription(desc).then(() => {
|
||||||
|
this.DebugChannel(`[WebRTC] Local description created; sending SDP message to ${username}:<br><br>${JSON.stringify(pc.localDescription)}`);
|
||||||
this.client.send({
|
this.client.send({
|
||||||
action: "sdp",
|
action: "sdp",
|
||||||
username: username,
|
username: username,
|
||||||
|
@ -1709,6 +1729,8 @@ export default {
|
||||||
// about at all). Re-parse the JSON stringified object here.
|
// about at all). Re-parse the JSON stringified object here.
|
||||||
let candidate = JSON.parse(msg.candidate);
|
let candidate = JSON.parse(msg.candidate);
|
||||||
|
|
||||||
|
this.DebugChannel(`[WebRTC] ICE candidate from ${msg.username}:<br><br>${msg.candidate}`);
|
||||||
|
|
||||||
// Add the new ICE candidate.
|
// Add the new ICE candidate.
|
||||||
pc.addIceCandidate(candidate).catch(e => {
|
pc.addIceCandidate(candidate).catch(e => {
|
||||||
console.error(`addIceCandidate: ${e}`);
|
console.error(`addIceCandidate: ${e}`);
|
||||||
|
@ -1720,6 +1742,8 @@ export default {
|
||||||
}
|
}
|
||||||
let pc = this.WebRTC.pc[msg.username].connecting;
|
let pc = this.WebRTC.pc[msg.username].connecting;
|
||||||
|
|
||||||
|
this.DebugChannel(`[WebRTC] Received SDP message from ${msg.username}:<br><br>${msg.description}`);
|
||||||
|
|
||||||
// XX: WebRTC candidate/SDP messages JSON stringify their inner payload so that the
|
// XX: WebRTC candidate/SDP messages JSON stringify their inner payload so that the
|
||||||
// Go back-end server won't re-order their json keys (Safari on Mac OS is very sensitive
|
// Go back-end server won't re-order their json keys (Safari on Mac OS is very sensitive
|
||||||
// to the keys being re-ordered during the handshake, in ways that NO OTHER BROWSER cares
|
// to the keys being re-ordered during the handshake, in ways that NO OTHER BROWSER cares
|
||||||
|
@ -1727,13 +1751,16 @@ export default {
|
||||||
let message = JSON.parse(msg.description);
|
let message = JSON.parse(msg.description);
|
||||||
|
|
||||||
// Add the new ICE candidate.
|
// Add the new ICE candidate.
|
||||||
// this.ChatClient(`Received a Remote Description from ${msg.username}: ${JSON.stringify(msg.description)}.`);
|
pc.setRemoteDescription(new RTCSessionDescription(message)).then(() => {
|
||||||
pc.setRemoteDescription(new RTCSessionDescription(message), () => {
|
this.DebugChannel(`[WebRTC] <strong>setRemoteDescription</strong> called back OK!<br>Our pc.remoteDescription.type is: ${pc.remoteDescription.type}`);
|
||||||
// When receiving an offer let's answer it.
|
// When receiving an offer let's answer it.
|
||||||
if (pc.remoteDescription.type === 'offer') {
|
if (pc.remoteDescription.type === 'offer') {
|
||||||
|
this.DebugChannel(`[WebRTC] Answerer: create SDP answer message for ${msg.username}`);
|
||||||
pc.createAnswer().then(this.localDescCreated(pc, msg.username)).catch(this.ChatClient);
|
pc.createAnswer().then(this.localDescCreated(pc, msg.username)).catch(this.ChatClient);
|
||||||
|
} else {
|
||||||
|
this.DebugChannel(`[WebRTC] pc.remoteDescription.type was not 'offer', we do not need to create an SDP Answer message.`);
|
||||||
}
|
}
|
||||||
}, console.error);
|
}).catch(this.DebugChannel);
|
||||||
},
|
},
|
||||||
onWatch(msg) {
|
onWatch(msg) {
|
||||||
// The user has our video feed open now.
|
// The user has our video feed open now.
|
||||||
|
@ -1783,7 +1810,9 @@ export default {
|
||||||
setChannel(channel) {
|
setChannel(channel) {
|
||||||
this.channel = typeof (channel) === "string" ? channel : channel.ID;
|
this.channel = typeof (channel) === "string" ? channel : channel.ID;
|
||||||
this.scrollHistory(this.channel, true);
|
this.scrollHistory(this.channel, true);
|
||||||
|
if (this.channels[this.channel]) {
|
||||||
this.channels[this.channel].unread = 0;
|
this.channels[this.channel].unread = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Responsive CSS: switch back to chat panel upon selecting a channel.
|
// Responsive CSS: switch back to chat panel upon selecting a channel.
|
||||||
this.openChatPanel();
|
this.openChatPanel();
|
||||||
|
@ -1977,6 +2006,14 @@ export default {
|
||||||
}
|
}
|
||||||
result.push(data);
|
result.push(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is the debug channel enabled?
|
||||||
|
if (this.prefs.debug) {
|
||||||
|
result.push({
|
||||||
|
ID: DebugChannelID,
|
||||||
|
Name: "Debug Log",
|
||||||
|
});
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2242,6 +2279,7 @@ export default {
|
||||||
|
|
||||||
// Camera is already open? Then disconnect the connection.
|
// Camera is already open? Then disconnect the connection.
|
||||||
if (this.WebRTC.pc[user.username] != undefined && this.WebRTC.pc[user.username].offerer != undefined) {
|
if (this.WebRTC.pc[user.username] != undefined && this.WebRTC.pc[user.username].offerer != undefined) {
|
||||||
|
this.DebugChannel(`OpenVideo(${user.username}): already had a connection open, closing it first.`);
|
||||||
this.closeVideo(user.username, "offerer");
|
this.closeVideo(user.username, "offerer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2914,6 +2952,14 @@ export default {
|
||||||
isChatClient: true,
|
isChatClient: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
DebugChannel(message) {
|
||||||
|
this.pushHistory({
|
||||||
|
channel: DebugChannelID,
|
||||||
|
username: "ChatClient",
|
||||||
|
message: message,
|
||||||
|
isChatClient: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// CSS classes for the profile button (color coded genders)
|
// CSS classes for the profile button (color coded genders)
|
||||||
profileButtonClass(user) {
|
profileButtonClass(user) {
|
||||||
|
@ -3912,6 +3958,22 @@ export default {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field" v-if="isOp || prefs.debug">
|
||||||
|
<label class="label mb-0">
|
||||||
|
Stats for nerds
|
||||||
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
v-model="prefs.debug"
|
||||||
|
:value="true">
|
||||||
|
</label>
|
||||||
|
Enable the "Debug Log" channel.
|
||||||
|
<p class="help">
|
||||||
|
This enables a channel where under-the-hood debug messages may be posted,
|
||||||
|
e.g. to debug WebRTC connection problems.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,6 +27,7 @@ const keys = {
|
||||||
'muteSounds': Boolean,
|
'muteSounds': Boolean,
|
||||||
'closeDMs': Boolean, // close unsolicited DMs
|
'closeDMs': Boolean, // close unsolicited DMs
|
||||||
'appleCompat': Boolean, // Apple browser compatibility mode
|
'appleCompat': Boolean, // Apple browser compatibility mode
|
||||||
|
'debug': Boolean, // Debug views enabled (admin only)
|
||||||
|
|
||||||
// Don't Show Again on NSFW modals.
|
// Don't Show Again on NSFW modals.
|
||||||
'skip-nsfw-modal': Boolean,
|
'skip-nsfw-modal': Boolean,
|
||||||
|
|
|
@ -2,8 +2,16 @@
|
||||||
// special nuances in their WebRTC video sharing support. This is intended to
|
// special nuances in their WebRTC video sharing support. This is intended to
|
||||||
// detect: iPads, iPhones, and Safari on macOS.
|
// detect: iPads, iPhones, and Safari on macOS.
|
||||||
function isAppleWebkit() {
|
function isAppleWebkit() {
|
||||||
// By User-Agent.
|
const ua = navigator.userAgent;
|
||||||
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
|
|
||||||
|
// By User-Agent: Apple mobiles.
|
||||||
|
if (/iPad|iPhone|iPod/.test(ua)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safari browser: claims to be Safari but not Chrome
|
||||||
|
// (Google Chrome claims to be both)
|
||||||
|
if (/Safari/i.test(ua) && !/Chrome/i.test(ua)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user