remove VideoCall and its mp3s, 5 sec timeout to NetworkFirst cache

This commit is contained in:
Martti Malmi 2023-02-09 20:59:32 +02:00
parent 69c9f38feb
commit 8a8ac6718f
8 changed files with 6 additions and 521 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -8,7 +8,6 @@ import '@fontsource/lato';
import Footer from './components/Footer';
import MediaPlayer from './components/MediaPlayer';
import Menu from './components/Menu';
import VideoCall from './components/VideoCall';
import { translationLoaded } from './translations/Translation';
import About from './views/About';
import Chat from './views/chat/Chat';
@ -193,7 +192,6 @@ class Main extends Component<Props, ReactState> {
</section>
<MediaPlayer />
<Footer />
<VideoCall />
</div>
);
}

View File

@ -1,439 +0,0 @@
import { html } from 'htm/preact';
import iris from 'iris-lib';
import $ from 'jquery';
import noop from 'lodash/noop';
import { Component } from 'preact';
import { route } from 'preact-router';
import { translate as t } from '../translations/Translation';
import Button from './basic/Button';
const ringSound = new Audio('../../assets/audio/ring.mp3');
ringSound.loop = true;
const callSound = new Audio('../../assets/audio/call.mp3');
let callTimeout;
let callSoundTimeout;
let callingInterval;
let incomingCallNotification;
let userMediaStream;
let ourIceCandidates;
let localStorageIce = localStorage.getItem('rtcConfig');
let DEFAULT_RTC_CONFIG = {
iceServers: [{ urls: ['stun:turn.hepic.tel'] }, { urls: ['stun:stun.l.google.com:19302'] }],
};
let RTC_CONFIG = localStorageIce ? JSON.parse(localStorageIce) : DEFAULT_RTC_CONFIG;
function getRTCConfig() {
return RTC_CONFIG;
}
function setRTCConfig(c) {
RTC_CONFIG = c;
try {
localStorage.setItem('rtcConfig', JSON.stringify(RTC_CONFIG));
} catch (e) {
// empty
}
}
class VideoCall extends Component {
componentDidMount() {
iris.local().get('activeCall').put(null);
iris.local().get('outgoingCall').put(null);
iris.local().get('incomingCall').put(null);
/*iris.local().get('call').open(call => {
this.onCallMessage(call.pub, call.call);
});*/
iris
.local()
.get('incomingCall')
.on((incomingCall) => {
if (!incomingCall) {
clearTimeout(callTimeout);
ringSound.pause();
ringSound.currentTime = 0;
incomingCallNotification && incomingCallNotification.close();
} else {
if (this.state.activeCall) return;
ringSound.play().catch(noop);
this.notifyIfNotVisible(incomingCall, t('incoming_call'));
}
this.setState({ incomingCall });
});
iris
.local()
.get('activeCall')
.on((activeCall) => {
this.setState({ activeCall });
this.stopCalling();
});
iris
.local()
.get('outgoingCall')
.on((outgoingCall) => {
outgoingCall && this.onCallUser(outgoingCall);
this.setState({ outgoingCall });
});
}
async answerCall(pub) {
iris.local().get('incomingCall').put(null);
iris.local().get('activeCall').put(pub);
await this.initConnection(false, pub);
}
onCallMessage(pub, call) {
this.stopCalling();
if (call && call !== 'null' && call.time) {
let d = new Date(call.time);
if (new Date() - d > 5000) {
console.log('ignoring old call from', pub);
return;
}
if (call.offer) {
if (iris.private(pub).rejectedTime && new Date() - iris.private(pub).rejectedTime < 5000) {
this.rejectCall(pub);
return;
}
iris.local().get('incomingCall').put(pub);
clearTimeout(callTimeout);
callTimeout = setTimeout(() => iris.local().get('incomingCall').put(null), 5000);
}
} else {
this.callClosed(pub);
}
}
notifyIfNotVisible(pub, text) {
if (document.visibilityState !== 'visible') {
incomingCallNotification = new Notification(iris.private(pub).name, {
icon: '/assets/img/icon128.png',
body: text,
requireInteraction: true,
silent: true,
});
incomingCallNotification.onclick = function () {
route(`/chat/${pub}`);
window.focus();
};
}
}
resetCalls() {
iris.local().get('outgoingCall').put(null);
iris.local().get('activeCall').put(null);
iris.local().get('incomingCall').put(null);
}
callClosed(pub) {
this.stopCalling(pub);
this.resetCalls();
if (this.state.outgoingCall) {
this.stopUserMedia(pub);
this.notifyIfNotVisible(t('call_rejected'));
} else if (this.state.activeCall) {
this.stopUserMedia(pub);
iris.private(pub).put('call', 'null');
this.notifyIfNotVisible(t('call_ended'));
}
if (iris.private(pub)) {
iris.private(pub).pc && iris.private(pub).pc.close();
iris.private(pub).pc = null;
}
}
async addStreamToPeerConnection(pc) {
let constraints = {
audio: true,
video: true,
};
userMediaStream = await navigator.mediaDevices.getUserMedia(constraints);
userMediaStream.getTracks().forEach((track) => {
pc.addTrack(track, userMediaStream);
});
const localVideo = $('#localvideo');
localVideo[0].srcObject = userMediaStream;
localVideo[0].onloadedmetadata = function () {
localVideo[0].muted = true;
localVideo[0].play();
};
localVideo.attr('disabled', true);
}
timeoutPlayCallSound() {
callSoundTimeout = setTimeout(() => callSound.play(), 3500);
}
async onCallUser(pub, video = true) {
if (this.state.outgoingCall) {
return;
}
await this.initConnection(true, pub);
console.log('calling', pub);
let call = () =>
iris.private(pub).put('call', {
time: new Date().toISOString(),
type: video ? 'video' : 'voice',
offer: true,
});
callingInterval = setInterval(call, 1000);
call();
callSound.addEventListener('ended', () => this.timeoutPlayCallSound());
callSound.play();
iris.local().get('outgoingCall').put(pub);
}
cancelCall(pub) {
iris.local().get('outgoingCall').put(null);
this.stopCalling();
this.stopUserMedia(pub);
iris.private(pub).put('call', 'null');
iris.private(pub).pc && iris.private(pub).pc.close();
iris.private(pub).pc = null;
}
stopUserMedia() {
userMediaStream.getTracks().forEach((track) => track.stop());
}
stopCalling() {
iris.local().get('outgoingCall').put(null);
callSound.pause();
callSound.removeEventListener('ended', () => this.timeoutPlayCallSound());
clearTimeout(callSoundTimeout);
callSound.currentTime = 0;
clearInterval(callingInterval);
callingInterval = null;
}
endCall(pub) {
iris.private(pub).pc && iris.private(pub).pc.close();
this.stopUserMedia(pub);
iris.private(pub).put('call', 'null');
iris.private(pub).pc = null;
iris.local().get('activeCall').put(null);
}
rejectCall(pub) {
iris.private(pub).rejectedTime = new Date();
iris.local().get('incomingCall').put(null);
console.log('rejectCall', pub, iris.private(pub));
iris.private(pub).put('call', 'null');
}
async initConnection(createOffer, pub) {
console.log('initConnection', createOffer, pub);
ourIceCandidates = {};
const theirIceCandidateKeys = [];
const chat = iris.private(pub);
chat.pc = new RTCPeerConnection(RTC_CONFIG);
console.log(chat.pc.signalingState);
await this.addStreamToPeerConnection(chat.pc);
async function createOfferFn() {
try {
if (chat.isNegotiating) {
return;
}
chat.isNegotiating = true;
let offer = await chat.pc.createOffer();
chat.pc.setLocalDescription(offer);
console.log('sending our sdp', offer);
chat.put('sdp', { time: new Date().toISOString(), data: offer });
} finally {
chat.isNegotiating = false;
}
}
if (createOffer) {
await createOfferFn();
}
chat.onTheir('sdp', async (sdp) => {
console.log('got their sdp', sdp);
if (!chat.pc) {
console.log(1);
return;
}
if (createOffer && chat.pc.signalingState === 'stable') {
console.log(2);
return;
}
if (sdp.data && sdp.time && new Date(sdp.time).getTime() < new Date() - 5000) {
console.log(3);
return;
}
chat.pc.setRemoteDescription(new RTCSessionDescription(sdp.data));
console.log(4);
});
chat.onTheir('icecandidates', (c) => {
console.log('got their icecandidates', c);
if (!chat.pc || chat.pc.signalingState === 'closed') {
return;
}
if (c.data && c.time && new Date(c.time).getTime() < new Date() - 5000) {
return;
}
Object.keys(c.data).forEach((k) => {
if (theirIceCandidateKeys.indexOf(k) === -1) {
theirIceCandidateKeys.push(k);
chat.pc.addIceCandidate(new RTCIceCandidate(c.data[k])).then(console.log, console.error);
}
});
});
chat.pc.onicecandidate =
chat.pc.onicecandidate ||
(({ candidate }) => {
if (!candidate) return;
console.log('sending our ice candidate');
let i = btoa(Math.random().toString()).slice(0, 12);
ourIceCandidates[i] = candidate;
chat.put('icecandidates', {
time: new Date().toISOString(),
data: ourIceCandidates,
});
});
if (createOffer) {
chat.pc.onnegotiationneeded = async () => {
createOfferFn();
};
}
chat.pc.onsignalingstatechange = async () => {
if (!chat.pc) {
return;
}
console.log(`Signaling State Change:${chat.pc}`, chat.pc.signalingState);
switch (chat.pc.signalingState) {
case 'have-remote-offer': {
const answer = await chat.pc.createAnswer({
offerToReceiveAudio: 1,
offerToReceiveVideo: 1,
});
chat.pc.setLocalDescription(answer);
chat.put('sdp', { time: new Date().toISOString(), data: answer });
break;
}
case 'stable':
this.stopCalling();
console.log('call answered by', pub);
iris.local().get('activeCall').put(pub);
break;
case 'closed':
console.log("Signalling state is 'closed'");
this.callClosed(pub);
break;
}
};
chat.pc.onconnectionstatechange = () => {
console.log('iceConnectionState changed', chat.pc.iceConnectionState);
switch (chat.pc.iceConnectionState) {
case 'connected':
break;
case 'disconnected':
this.callClosed(pub);
break;
case 'new':
//this.callClosed(pub);
break;
case 'failed':
this.callClosed(pub);
break;
case 'closed':
this.callClosed(pub);
break;
default:
console.log('Change of state', chat.pc.iceConnectionState);
break;
}
};
chat.pc.ontrack = (event) => {
console.log('ontrack', event);
const remoteVideo = $('#remotevideo');
if (remoteVideo[0].srcObject !== event.streams[0]) {
remoteVideo[0].srcObject = event.streams[0];
remoteVideo[0].onloadedmetadata = function () {
console.log('metadata loaded');
remoteVideo[0].play();
};
console.log('received remote stream', event);
}
};
}
render() {
const resizeButton = html`<span
style="cursor:pointer;margin-left:15px;font-size:2em;position:absolute;left:0;top:0"
onClick=${() => this.setState({ maximized: !this.state.maximized })}
>${this.state.maximized ? '-' : '+'}</span
>`;
const width = this.state.maximized ? '100%' : '400px';
const height = this.state.maximized ? '100%' : '400px';
const bottom = this.state.maximized ? '0' : '70px';
const localVideo = html`<video
id="localvideo"
autoplay="true"
playsinline="true"
style="width:50%;max-height:60%"
/>`;
const remoteVideo = html`<video
id="remotevideo"
autoplay="true"
playsinline="true"
style="width:50%;max-height:60%"
/>`;
if (this.state.activeCall) {
return html` <div
id="active-call"
style="position: fixed; right:0; bottom: ${bottom}; height:${height}; width: ${width}; max-width: 100%; text-align: center; background: #000; color: #fff; padding: 15px 0"
>
<div style="margin-bottom:5px;position:relative;height:50px;">
${resizeButton} ${t('on_call_with')}
${iris.private(this.state.activeCall) && iris.private(this.state.activeCall).name}
</div>
${localVideo} ${remoteVideo}
<${Button}
style="display:block;margin:15px auto"
onClick=${() => this.endCall(this.state.activeCall)}
>End call<//
>
</div>`;
} else if (this.state.outgoingCall) {
return html`<div
id="outgoing-call"
style="position:fixed; right:0; bottom: ${bottom}; height:${height}; width: ${width}; text-align: center; background: #000; color: #fff; padding: 15px"
>
${t('calling')} ${iris.private(this.state.outgoingCall).name}
<${Button}
onClick=${() => this.cancelCall(this.state.outgoingCall)}
style="display:block; margin: 15px auto"
>
${t('cancel')}
<//>
${localVideo} ${remoteVideo}
</div>`;
} else if (this.state.incomingCall) {
return html`
<div
id="incoming-call"
style="position:fixed; right:0; bottom: ${bottom}; height:${height}; width: ${width}; text-align: center; background: #000; color: #fff; padding: 15px 0"
>
Incoming call from ${iris.private(this.state.incomingCall).name}
<${Button}
style="display:block; margin: 15px auto"
onClick=${() => this.answerCall(this.state.incomingCall)}
>${t('answer')}<//
>
<${Button}
style="display:block; margin: 15px auto"
onClick=${() => this.rejectCall(this.state.incomingCall)}
>${t('reject')}<//
>
</div>
`;
}
}
}
export default VideoCall;
export { RTC_CONFIG, DEFAULT_RTC_CONFIG, setRTCConfig, getRTCConfig };

View File

@ -6,7 +6,6 @@ import BetaSettings from './BetaSettings';
import BlockedSettings from './BlockedSettings';
import LanguageSettings from './LanguageSettings';
import NostrSettings from './NostrSettings';
import WebRTCSettings from './WebRTCSettings';
import WebtorrentSettings from './WebtorrentSettings';
export default class SettingsContent extends Component {
@ -17,7 +16,6 @@ export default class SettingsContent extends Component {
appearance: AppearanceSettings,
language: LanguageSettings,
webtorrent: WebtorrentSettings,
webrtc: WebRTCSettings,
beta: BetaSettings,
blocked_users: BlockedSettings,
};

View File

@ -1,77 +0,0 @@
import iris from 'iris-lib';
import $ from 'jquery';
import Component from '../../BaseComponent';
import Button from '../../components/basic/Button';
import { DEFAULT_RTC_CONFIG, getRTCConfig, setRTCConfig } from '../../components/VideoCall';
import { translate as t } from '../../translations/Translation';
export default class WebRTCSettings extends Component {
render() {
return (
<>
<div class="centered-container">
<h3>{t('webrtc_connection_options')}</h3>
<p>
<textarea
rows="4"
id="rtc-config"
placeholder={t('webrtc_connection_options')}
onChange={(e) => this.rtcConfigChanged(e)}
/>
</p>
<Button onClick={() => this.restoreDefaultRtcConfig()}>{t('restore_defaults')}</Button>
</div>
</>
);
}
componentDidMount() {
const blockedUsers = {};
$('#rtc-config').val(JSON.stringify(getRTCConfig()));
iris.electron && iris.electron.get('settings').on(this.inject('electron', 'electron'));
iris
.local()
.get('settings')
.on(
this.sub((local) => {
console.log('local settings', local);
if (local) {
this.setState({ local });
}
}),
);
iris
.public()
.get('webPushSubscriptions')
.map()
.on(
this.sub(() =>
this.setState({
webPushSubscriptions: iris.notifications.webPushSubscriptions,
}),
),
);
iris
.public()
.get('block')
.map()
.on(
this.sub((v, k) => {
blockedUsers[k] = v;
this.setState({ blockedUsers });
}),
);
}
rtcConfigChanged(e) {
setRTCConfig(JSON.parse(e.target.value));
}
restoreDefaultRtcConfig() {
setRTCConfig(DEFAULT_RTC_CONFIG);
$('#rtc-config').val(JSON.stringify(getRTCConfig()));
}
}

View File

@ -14,7 +14,12 @@ registerRoute(
plugins: [bgSyncPlugin],
}),
);
registerRoute(({ url }) => url.pathname === '/', new NetworkFirst());
registerRoute(
({ url }) => url.pathname === '/',
new NetworkFirst({
networkTimeoutSeconds: 5,
}),
);
registerRoute(
({ url }) => {
return location.host.indexOf('localhost') !== 0 && url.origin === self.location.origin;