Apple compatibility mode for WebRTC

* Try a new strategy to get Apple (iPad/iPhone) webcams to connect.
* "Apple compatibility mode" setting: on by default if iPad/iPhone is
  detected or can be opted into in the chat settings Misc tab.
* In Apple compat mode: when you open someone else's webcam, you always
  attach your local video on the WebRTC offer. This would normally make
  your video auto-open on the remote side, but the previous commit
  updates the chat page to ignore offered video if you did not opt-in to
  auto-open your viewer's camera.
* This should satisfy the two-way video call limitation in Safari: the
  iPad always shares its video and gets video from the person they are
  watching.
* If the person they are watching did not auto-open your video: they
  ignore the attached video on your offer and don't display it.
This commit is contained in:
Noah 2024-01-11 20:33:57 -08:00
parent bf59a7b6c9
commit 8e87c377e8
2 changed files with 60 additions and 22 deletions

View File

@ -151,6 +151,7 @@ export default {
watchNotif: true, // notify in chat about cameras being watched watchNotif: true, // notify in chat about cameras being watched
closeDMs: false, // ignore unsolicited DMs closeDMs: false, // ignore unsolicited DMs
muteSounds: false, // mute all sound effects muteSounds: false, // mute all sound effects
appleCompat: isAppleWebkit(), // Apple browser compatibility mode
}, },
// My video feed. // My video feed.
@ -490,6 +491,9 @@ export default {
// Tell ChatServer if we have gone to/from DND. // Tell ChatServer if we have gone to/from DND.
this.sendMe(); this.sendMe();
}, },
"prefs.appleCompat": function () {
LocalStorage.set('appleCompat', this.prefs.appleCompat);
},
}, },
computed: { computed: {
connected() { connected() {
@ -765,6 +769,11 @@ export default {
// Default ordering from ChatServer = a-z // Default ordering from ChatServer = a-z
return result; return result;
}, },
isAppleWebkit() {
// Return whether we are detected to be an iPad or iPhone,
// or if the appleCompat seting is enabled.
return isAppleWebkit() || this.prefs.appleCompat;
},
}, },
methods: { methods: {
// Load user prefs from localStorage, called on startup // Load user prefs from localStorage, called on startup
@ -843,6 +852,9 @@ export default {
if (settings.closeDMs != undefined) { if (settings.closeDMs != undefined) {
this.prefs.closeDMs = settings.closeDMs === true; this.prefs.closeDMs = settings.closeDMs === true;
} }
if (this.prefs.appleCompat != undefined) {
this.prefs.appleCompat = settings.appleCompat === true;
}
if (settings.whoSort != undefined) { if (settings.whoSort != undefined) {
this.whoSort = settings.whoSort; this.whoSort = settings.whoSort;
} }
@ -945,7 +957,7 @@ 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 (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.");
} 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.");
@ -1579,7 +1591,8 @@ export default {
// is also the only way that iPads/iPhones/Safari browsers can make a call // 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 // (two-way video is the only option for them; send-only/receive-only channels seem
// not to work in Safari). // not to work in Safari).
if (isOfferer && if (isOfferer) {
let shouldOfferVideo = (
(this.whoMap[username].video & this.VideoFlag.MutualOpen) // They auto-open us (this.whoMap[username].video & this.VideoFlag.MutualOpen) // They auto-open us
&& this.webcam.active // Our camera is active (to add it) && this.webcam.active // Our camera is active (to add it)
&& !this.isBooted(username) // We had not booted them off ours before && !this.isBooted(username) // We had not booted them off ours before
@ -1588,12 +1601,19 @@ export default {
// If our webcam is NSFW and the viewer prefers not to see explicit, // If our webcam is NSFW and the viewer prefers not to see explicit,
// do not send our camera on this offer. // do not send our camera on this offer.
&& (!this.webcam.nsfw || !(this.whoMap[username].video & this.VideoFlag.NonExplicit)) && (!this.webcam.nsfw || !(this.whoMap[username].video & this.VideoFlag.NonExplicit))
) { );
// Attach our video on the outgoing offer, so that on the answerer's side our
// local video pops up on their screen.
// NOTE: on Apple devices, always send your video to satisfy the two-way video call
// constraint imposed by Safari's WebRTC implementation.
if (shouldOfferVideo || this.isAppleWebkit) {
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, begin the connection. // If we are the offerer, begin the connection.
if (isOfferer) { if (isOfferer) {
@ -2184,7 +2204,7 @@ export default {
this.WebRTC.openTimeouts[user.username] = setTimeout(() => { this.WebRTC.openTimeouts[user.username] = setTimeout(() => {
// It timed out. If they are on an iPad, offer additional hints on // It timed out. If they are on an iPad, offer additional hints on
// how to have better luck connecting their cameras. // how to have better luck connecting their cameras.
if (isAppleWebkit()) { if (this.isAppleWebkit) {
this.ChatClient( this.ChatClient(
`There was an error opening <strong>${user.username}</strong>'s camera.<br><br>` + `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 " + "<strong>Advice:</strong> You appear to be on an iPad-style browser. Webcam sharing " +
@ -2386,16 +2406,10 @@ export default {
// iPad test: they will have very limited luck opening videos unless // iPad test: they will have very limited luck opening videos unless
// A) the iPad camera is already on, and // A) the iPad camera is already on, and
// B) the person they want to watch has mutual auto-open enabled. // B) the person they want to watch has mutual auto-open enabled.
if (isAppleWebkit()) { if (this.isAppleWebkit) {
if (!this.webcam.active) { if (!this.webcam.active) {
return 'fa-video-slash'; // can not open any cam w/o local video on 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';
@ -3584,6 +3598,24 @@ export default {
</p> </p>
</div> </div>
<div class="field">
<label class="label mb-0">
Advanced
</label>
<label class="checkbox">
<input type="checkbox"
v-model="prefs.appleCompat"
:value="true">
Apple compatibility mode (iPad, iPhone, Safari)
</label>
<p class="help">
If you experience difficulty opening cameras and you are on an Apple device (iPad,
iPhone, or the Safari browser on macOS) try enabling this option and see if it will
help. <strong>Note:</strong> You will need to share your webcam first before you can
open successfully open others', due to limitations in Apple's WebRTC implementation.
</p>
</div>
</div> </div>
</div> </div>
@ -3931,6 +3963,11 @@ export default {
<!-- Easy video zoom buttons --> <!-- Easy video zoom buttons -->
<div class="column is-narrow is-hidden-mobile" v-if="anyVideosOpen"> <div class="column is-narrow is-hidden-mobile" v-if="anyVideosOpen">
<button type="button" class="button is-small is-outlined mr-1" :disabled="webcam.videoScale === 'x4'"
@click="settingsModal.tab='webcam'; showSettings()">
<i class="fa fa-gear"></i>
</button>
<button type="button" class="button is-small is-outlined" :disabled="webcam.videoScale === 'x4'" <button type="button" class="button is-small is-outlined" :disabled="webcam.videoScale === 'x4'"
@click="scaleVideoSize(true)"> @click="scaleVideoSize(true)">
<i class="fa fa-magnifying-glass-plus"></i> <i class="fa fa-magnifying-glass-plus"></i>

View File

@ -25,6 +25,7 @@ const keys = {
'watchNotif': Boolean, 'watchNotif': Boolean,
'muteSounds': Boolean, 'muteSounds': Boolean,
'closeDMs': Boolean, // close unsolicited DMs 'closeDMs': Boolean, // close unsolicited DMs
'appleCompat': Boolean, // Apple browser compatibility mode
// Don't Show Again on NSFW modals. // Don't Show Again on NSFW modals.
'skip-nsfw-modal': Boolean, 'skip-nsfw-modal': Boolean,