diff --git a/src/lib/audio/voice.svelte.ts b/src/lib/audio/voice.svelte.ts index b8548ec..4d621b8 100644 --- a/src/lib/audio/voice.svelte.ts +++ b/src/lib/audio/voice.svelte.ts @@ -140,7 +140,7 @@ class VoiceEngine { this.error = ''; try { if (typeof AudioEncoder === 'undefined' || !(await AudioEncoder.isConfigSupported(this.encoderConfig())).supported) { - throw new Error('This system cannot encode Opus audio'); + throw new Error('This browser cannot encode Opus audio (WebCodecs is missing), so you cannot talk or hear others here. Chromium-based browsers and the desktop app can.'); } const ctx = new AudioContext({ sampleRate: SAMPLE_RATE, latencyHint: 'interactive' }); this.ctx = ctx; diff --git a/src/ui/ShareDialog.svelte b/src/ui/ShareDialog.svelte index 82afe17..f056d09 100644 --- a/src/ui/ShareDialog.svelte +++ b/src/ui/ShareDialog.svelte @@ -76,15 +76,27 @@ async function showCamera(deviceId = camera) { error = ''; dropPreview(); + const wanted: MediaTrackConstraints = { width: { ideal: 1280 }, height: { ideal: 720 }, frameRate: { ideal: fps() } }; + if (deviceId) wanted.deviceId = { exact: deviceId }; + else wanted.facingMode = 'user'; try { - preview = await navigator.mediaDevices.getUserMedia({ - video: { deviceId: deviceId ? { exact: deviceId } : undefined, width: { ideal: 1280 }, height: { ideal: 720 }, frameRate: fps() } - }); + try { + preview = await navigator.mediaDevices.getUserMedia({ video: wanted }); + } catch (e) { + // A refusal or a missing camera is final; anything else may be a phone camera that + // dislikes the wishes above, so ask once more for whatever it has + if (['NotAllowedError', 'SecurityError', 'NotFoundError'].includes((e as Error).name)) throw e; + preview = await navigator.mediaDevices.getUserMedia({ video: deviceId ? { deviceId: { exact: deviceId } } : true }); + } // Names are only given out once a camera was allowed cameras = (await navigator.mediaDevices.enumerateDevices()).filter(d => d.kind === 'videoinput'); camera = preview.getVideoTracks()[0]?.getSettings().deviceId ?? deviceId; } catch (e) { - error = (e as Error).name === 'NotFoundError' ? 'No camera was found.' : `Could not open the camera: ${(e as Error).message}`; + const { name, message } = e as Error; + error = name === 'NotFoundError' ? 'No camera was found.' + : name === 'NotAllowedError' || name === 'SecurityError' + ? 'The camera was refused. If no question appeared, access is blocked: allow the camera for this site (the icon next to the address), and for the browser app in the phone\'s settings, then try again.' + : `Could not open the camera (${name}): ${message}`; } } @@ -241,6 +253,7 @@ {#if error}

{error}

{/if} + {#if kind === 'camera' && error}
{/if} {#snippet footer()} diff --git a/src/ui/VoiceStage.svelte b/src/ui/VoiceStage.svelte index 046b44d..01d948d 100644 --- a/src/ui/VoiceStage.svelte +++ b/src/ui/VoiceStage.svelte @@ -234,6 +234,7 @@ $effect(() => () => popup?.close()); +{#if voice.error && session.status === 'connected'}{/if} {#if share.error}{/if} {#snippet spot()}