Merge pull request 'WebRTC iPad Testing' (#36) from ipad-testing into master

Reviewed-on: #36
This commit is contained in:
Noah 2023-09-01 00:24:07 +00:00
commit 0607fac724

View File

@ -687,6 +687,17 @@ const app = Vue.createApp({
return; return;
} }
// DEBUGGING: test whether the page thinks you're Apple Webkit.
if (this.message.toLowerCase().indexOf("/ipad") === 0) {
if (this.isAppleWebkit()) {
this.ChatClient("I have detected that you are probably an iPad or iPhone browser.");
} else {
this.ChatClient("I have detected that you <strong>are not</strong> an iPad or iPhone browser.");
}
this.message = "";
return;
}
// console.debug("Send message: %s", this.message); // console.debug("Send message: %s", this.message);
this.ws.conn.send(JSON.stringify({ this.ws.conn.send(JSON.stringify({
action: "message", action: "message",
@ -1016,7 +1027,7 @@ const app = Vue.createApp({
this.disconnectCount++; this.disconnectCount++;
if (this.disconnectCount > this.disconnectLimit) { if (this.disconnectCount > this.disconnectLimit) {
this.ChatClient(`It seems there's a problem connecting to the server. Please try some other time. Note that iPhones and iPads currently have issues connecting to the chat room in general.`); this.ChatClient(`It seems there's a problem connecting to the server. Please try some other time.`);
return; return;
} }
@ -1153,6 +1164,7 @@ const app = Vue.createApp({
// Create a data channel so we have something to connect over even if // Create a data channel so we have something to connect over even if
// the local user is not broadcasting their own camera. // 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"); let dataChannel = pc.createDataChannel("data");
dataChannel.addEventListener("open", event => { dataChannel.addEventListener("open", event => {
// beginTransmission(dataChannel); // beginTransmission(dataChannel);
@ -1226,21 +1238,23 @@ const app = Vue.createApp({
}) })
}; };
// If we were already broadcasting video, send our stream to // ANSWERER: add our video to the connection so that the offerer (the one who
// the connecting user. // clicked on our video icon to watch us) can receive it.
// TODO: currently both users need to have video on for the connection
// to succeed - if offerer doesn't addTrack it won't request a video channel
// and so the answerer (who has video) won't actually send its
if (!isOfferer && this.webcam.active) { if (!isOfferer && this.webcam.active) {
// this.ChatClient(`Sharing our video stream to ${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)
}); });
} }
// If we are the offerer, and this member wants to auto-open our camera // OFFERER: If we were already broadcasting our own video, and the answerer
// then add our own stream to the connection. // has the "auto-open your video" setting enabled, attach our video to the initial
// offer right now.
//
// NOTE: this will force open our video on the answerer's side, and this workflow
// is also the only way that iPads/iPhones/Safari browsers can make a call
// (two-way video is the only option for them; send-only/receive-only channels seem
// not to work in Safari).
if (isOfferer && (this.whoMap[username].video & this.VideoFlag.MutualOpen) && this.webcam.active) { if (isOfferer && (this.whoMap[username].video & this.VideoFlag.MutualOpen) && this.webcam.active) {
let stream = this.webcam.stream; let stream = this.webcam.stream;
stream.getTracks().forEach(track => { stream.getTracks().forEach(track => {
@ -1260,18 +1274,15 @@ const app = Vue.createApp({
// Common handler function for // Common handler function for
localDescCreated(pc, username) { localDescCreated(pc, username) {
return (desc) => { return (desc) => {
// this.ChatClient(`setLocalDescription ${JSON.stringify(desc)}`); pc.setLocalDescription(desc).then(() => {
pc.setLocalDescription( this.ws.conn.send(JSON.stringify({
new RTCSessionDescription(desc), action: "sdp",
() => { username: username,
this.ws.conn.send(JSON.stringify({ description: JSON.stringify(pc.localDescription),
action: "sdp", }));
username: username, }).catch(e => {
description: JSON.stringify(pc.localDescription), this.ChatClient(`Error sending WebRTC negotiation message (SDP): ${e}`);
})); });
},
console.error,
)
}; };
}, },
@ -1289,13 +1300,9 @@ const app = Vue.createApp({
let candidate = JSON.parse(msg.candidate); let candidate = JSON.parse(msg.candidate);
// Add the new ICE candidate. // Add the new ICE candidate.
pc.addIceCandidate( pc.addIceCandidate(candidate).catch(e => {
new RTCIceCandidate( this.ChatClient(`addIceCandidate: ${e}`);
candidate, });
() => { },
console.error,
)
);
}, },
onSDP(msg) { onSDP(msg) {
if (this.WebRTC.pc[msg.username] == undefined || !this.WebRTC.pc[msg.username].connecting) { if (this.WebRTC.pc[msg.username] == undefined || !this.WebRTC.pc[msg.username].connecting) {
@ -1816,10 +1823,20 @@ const app = Vue.createApp({
delete(this.WebRTC.openTimeouts[user.username]); delete(this.WebRTC.openTimeouts[user.username]);
} }
this.WebRTC.openTimeouts[user.username] = setTimeout(() => { this.WebRTC.openTimeouts[user.username] = setTimeout(() => {
// It timed out. // It timed out. If they are on an iPad, offer additional hints on
this.ChatClient( // how to have better luck connecting their cameras.
`There was an error opening <strong>${user.username}</strong>'s camera.`, if (this.isAppleWebkit()) {
); this.ChatClient(
`There was an error opening <strong>${user.username}</strong>'s camera.<br><br>` +
"<strong>Advice:</strong> You appear to be on an iPad-style browser. Webcam sharing " +
"may be limited and only work if:<br>A) You are sharing your own camera first, and<br>B) "+
"The person you view has the setting to auto-open your camera in return.<br>Best of luck!",
);
} else {
this.ChatClient(
`There was an error opening <strong>${user.username}</strong>'s camera.`,
);
}
delete(this.WebRTC.openTimeouts[user.username]); delete(this.WebRTC.openTimeouts[user.username]);
}, 10000); }, 10000);
@ -1901,8 +1918,10 @@ const app = Vue.createApp({
// - May be a crossed-out video if isVideoNotAllowed // - May be a crossed-out video if isVideoNotAllowed
// - Or an eyeball for cameras already opened // - Or an eyeball for cameras already opened
// - Or a spinner if we are actively trying to open the video // - Or a spinner if we are actively trying to open the video
// Current user sees their own self camera always.
if (user.username === this.username && this.webcam.active) { if (user.username === this.username && this.webcam.active) {
return 'fa-eye'; // user sees their own self camera always return 'fa-eye';
} }
// In spinner mode? (Trying to open the video) // In spinner mode? (Trying to open the video)
@ -1915,6 +1934,21 @@ const app = Vue.createApp({
return 'fa-eye'; return 'fa-eye';
} }
// iPad test: they will have very limited luck opening videos unless
// A) the iPad camera is already on, and
// B) the person they want to watch has mutual auto-open enabled.
if (this.isAppleWebkit()) {
if (!this.webcam.active) {
return 'fa-video-slash'; // can not open any cam w/o local video on
}
if (!(this.whoMap[user.username].video & this.VideoFlag.MutualOpen)) {
// the user must have mutual auto-open on: the iPad has to offer
// their video which will force open their cam on the other side,
// and this is only if the user expects it.
return 'fa-video-slash';
}
}
if (this.isVideoNotAllowed(user)) return 'fa-video-slash'; if (this.isVideoNotAllowed(user)) return 'fa-video-slash';
return 'fa-video'; return 'fa-video';
}, },
@ -2526,6 +2560,25 @@ const app = Vue.createApp({
// Set the "reported" flag. // Set the "reported" flag.
this.reportModal.origMessage.reported = true; this.reportModal.origMessage.reported = true;
}, },
// Miscellaneous utility methods.
isAppleWebkit() {
// Try and detect whether the user is on an Apple Safari browser, which has
// special nuances in their WebRTC video sharing support. This is intended to
// detect: iPads, iPhones, and Safari on macOS.
// By User-Agent.
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
return true;
}
// By (deprecated) navigator.platform.
if (navigator.platform === 'iPad' || navigator.platform === 'iPhone' || navigator.platform === 'iPod') {
return true;
}
return false;
},
} }
}); });