Add receive-only transceivers to remove Apple compat mode
* WebRTC functionality is now 100% working as intended for Safari and iPad browsers! * The legacy WebRTC API had properties like offerToReceiveVideo available on createOffer(), to set up a receive-only channel, but the modern WebRTC API had removed these and Safari only supports the modern API. * The modern solution for the same feature is to add a recvonly transceiver to the connection in place of offering a local video/audio stream to share.
This commit is contained in:
parent
f094213a34
commit
f36c83dbcc
106
src/App.vue
106
src/App.vue
|
@ -18,7 +18,6 @@ import LocalStorage from './lib/LocalStorage';
|
||||||
import VideoFlag from './lib/VideoFlag';
|
import VideoFlag from './lib/VideoFlag';
|
||||||
import StatusMessage from './lib/StatusMessage';
|
import StatusMessage from './lib/StatusMessage';
|
||||||
import { SoundEffects, DefaultSounds } from './lib/sounds';
|
import { SoundEffects, DefaultSounds } from './lib/sounds';
|
||||||
import { isAppleWebkit } from './lib/browsers';
|
|
||||||
|
|
||||||
// WebRTC configuration.
|
// WebRTC configuration.
|
||||||
const configuration = {
|
const configuration = {
|
||||||
|
@ -156,7 +155,6 @@ export default {
|
||||||
closeDMs: false, // ignore unsolicited DMs
|
closeDMs: false, // ignore unsolicited DMs
|
||||||
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
|
|
||||||
debug: false, // enable debugging features
|
debug: false, // enable debugging features
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -522,9 +520,6 @@ 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);
|
|
||||||
},
|
|
||||||
"prefs.debug": function () {
|
"prefs.debug": function () {
|
||||||
LocalStorage.set('debug', this.prefs.debug);
|
LocalStorage.set('debug', this.prefs.debug);
|
||||||
},
|
},
|
||||||
|
@ -819,11 +814,6 @@ 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
|
||||||
|
@ -902,9 +892,6 @@ 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 (this.prefs.debug != undefined) {
|
if (this.prefs.debug != undefined) {
|
||||||
this.prefs.debug = settings.debug === true;
|
this.prefs.debug = settings.debug === true;
|
||||||
}
|
}
|
||||||
|
@ -1021,19 +1008,6 @@ export default {
|
||||||
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.<br><br>" +
|
|
||||||
`* Auto-detection: ${this.isAppleWebkit}<br>` +
|
|
||||||
`* Manual setting: ${this.prefs.appleCompat}`);
|
|
||||||
} else {
|
|
||||||
this.ChatClient("I have detected that you <strong>are not</strong> an iPad or iPhone browser.");
|
|
||||||
}
|
|
||||||
this.message = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEBUGGING: print WebRTC statistics
|
// DEBUGGING: print WebRTC statistics
|
||||||
if (this.message.toLowerCase().indexOf("/debug-webrtc") === 0) {
|
if (this.message.toLowerCase().indexOf("/debug-webrtc") === 0) {
|
||||||
let lines = [
|
let lines = [
|
||||||
|
@ -1660,11 +1634,6 @@ export default {
|
||||||
// OFFERER: If we were already broadcasting our own video, and the answerer
|
// OFFERER: If we were already broadcasting our own video, and the answerer
|
||||||
// has the "auto-open your video" setting enabled, attach our video to the initial
|
// has the "auto-open your video" setting enabled, attach our video to the initial
|
||||||
// offer right now.
|
// 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) {
|
if (isOfferer) {
|
||||||
let shouldOfferVideo = (
|
let shouldOfferVideo = (
|
||||||
(this.whoMap[username].video & this.VideoFlag.MutualOpen) // They auto-open us
|
(this.whoMap[username].video & this.VideoFlag.MutualOpen) // They auto-open us
|
||||||
|
@ -1679,14 +1648,21 @@ export default {
|
||||||
|
|
||||||
// Attach our video on the outgoing offer, so that on the answerer's side our
|
// Attach our video on the outgoing offer, so that on the answerer's side our
|
||||||
// local video pops up on their screen.
|
// local video pops up on their screen.
|
||||||
// NOTE: on Apple devices, always send your video to satisfy the two-way video call
|
if (shouldOfferVideo) {
|
||||||
// constraint imposed by Safari's WebRTC implementation.
|
|
||||||
if (shouldOfferVideo || this.isAppleWebkit) {
|
|
||||||
this.DebugChannel(`[WebRTC] Offerer: I am attaching my video to the connection with: ${username}`)
|
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)
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// We aren't offering video, but still want to receive audio/video. Add a receive-only
|
||||||
|
// transceiver to this offer. NOTE: in the legacy WebRTC API we could put offerToReceiveVideo
|
||||||
|
// and offerToReceiveAudio in the createOffer() call later, but the modern WebRTC has removed
|
||||||
|
// those options and Safari only supports the modern way. Adding a receive-only transceiver
|
||||||
|
// here is the modern way to do it that Safari will be happy with.
|
||||||
|
this.DebugChannel(`[WebRTC] Offer: I am attaching a receive-only video/audio transceiver to the connection with: ${username}`);
|
||||||
|
pc.addTransceiver('video', { direction: 'recvonly' });
|
||||||
|
pc.addTransceiver('audio', { direction: 'recvonly' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2299,20 +2275,9 @@ export default {
|
||||||
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. 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);
|
||||||
|
|
||||||
|
@ -2500,15 +2465,6 @@ export default {
|
||||||
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.isVideoNotAllowed(user)) return 'fa-video-slash';
|
if (this.isVideoNotAllowed(user)) return 'fa-video-slash';
|
||||||
return 'fa-video';
|
return 'fa-video';
|
||||||
},
|
},
|
||||||
|
@ -3641,11 +3597,19 @@ export default {
|
||||||
<!-- Sound settings -->
|
<!-- Sound settings -->
|
||||||
<div v-else-if="settingsModal.tab === 'sounds'">
|
<div v-else-if="settingsModal.tab === 'sounds'">
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="columns mb-4">
|
||||||
<label class="checkbox">
|
<div class="column">
|
||||||
<input type="checkbox" v-model="prefs.muteSounds" :value="true">
|
<label class="checkbox">
|
||||||
Mute all sound effects
|
<input type="checkbox" v-model="prefs.muteSounds" :value="true">
|
||||||
</label>
|
Mute all sound effects
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" v-model="webcam.autoMuteWebcams" :value="true">
|
||||||
|
Automatically mute webcams
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile">
|
||||||
|
@ -3940,24 +3904,6 @@ export default {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label mb-0">
|
|
||||||
Apple compatibility mode
|
|
||||||
</label>
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox"
|
|
||||||
v-model="prefs.appleCompat"
|
|
||||||
:value="true">
|
|
||||||
Check this box if you are on an iPad, iPhone, or Safari browser
|
|
||||||
</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 class="field" v-if="isOp || prefs.debug">
|
<div class="field" v-if="isOp || prefs.debug">
|
||||||
<label class="label mb-0">
|
<label class="label mb-0">
|
||||||
Stats for nerds
|
Stats for nerds
|
||||||
|
|
|
@ -26,7 +26,6 @@ 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
|
|
||||||
'debug': Boolean, // Debug views enabled (admin only)
|
'debug': Boolean, // Debug views enabled (admin only)
|
||||||
|
|
||||||
// Don't Show Again on NSFW modals.
|
// Don't Show Again on NSFW modals.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user