diff --git a/src/capturer.tsx b/src/capturer.tsx index b192986c..9b7e6a96 100644 --- a/src/capturer.tsx +++ b/src/capturer.tsx @@ -55,7 +55,7 @@ export async function startDisplayCapture( ...videoConstraints, ...height, }, - audio: false, + audio: true, }; try { diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index e4a344b6..c301ad34 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -55,7 +55,9 @@ "aspect-ratio-auto": "auto", "quality": "Qualität", "quality-auto": "auto", - "preferences-note": "<0>Hinweis: Dies sind lediglich Präferenzen. Es ist nicht garantiert, dass alle Einstellungen von Ihrem Gerät unterstützt werden. Im Zweifelsfall 'auto' wählen." + "preferences-note": "<0>Hinweis: Dies sind lediglich Präferenzen. Es ist nicht garantiert, dass alle Einstellungen von Ihrem Gerät unterstützt werden. Im Zweifelsfall 'auto' wählen.", + "display-audio-shared": "Bildschirmton wird aufgezeichnet.", + "display-audio-not-shared": "Bildschirmton wird nicht aufgezeichnet. <0>Hinweis: Nicht alle Browser und Betriebssysteme unterstützen die Aufnahme des Bildschirmtons." }, "audio": { "label": "Audioquelle auswählen", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 8568f961..d1a0774a 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -55,7 +55,9 @@ "aspect-ratio-auto": "auto", "quality": "Quality", "quality-auto": "auto", - "preferences-note": "<0>Note: these are merely preferences and it cannot be guaranteed that all options are actually supported on your device. If in doubt, choose 'auto'." + "preferences-note": "<0>Note: these are merely preferences and it cannot be guaranteed that all options are actually supported on your device. If in doubt, choose 'auto'.", + "display-audio-shared": "Display audio will be recorded.", + "display-audio-not-shared": "Display audio will not be recorded. <0>Note: Not all browsers and operating systems support display audio capture." }, "audio": { "label": "Select audio source", diff --git a/src/steps/recording/index.tsx b/src/steps/recording/index.tsx index 4b00b7be..78b619bb 100644 --- a/src/steps/recording/index.tsx +++ b/src/steps/recording/index.tsx @@ -36,12 +36,29 @@ const addRecordOnStop = ( }; }; -const mixAudioIntoVideo = (audioStream: MediaStream | null, videoStream: MediaStream) => { - if (!(audioStream?.getAudioTracks().length)) { - return videoStream; - } - return new MediaStream([...videoStream.getVideoTracks(), ...audioStream.getAudioTracks()]); -}; +const mixAudioIntoVideo = (audioStreams: (MediaStream | null)[], videoStream: MediaStream) => ( + audioStreams.reduce( + (stream, audioStream) => audioStream?.getAudioTracks().length + ? new MediaStream([ + ...stream.getVideoTracks(), + ...( + stream.getAudioTracks().length + ? (() => { + const audioContext = new AudioContext(); + const accumulatedAudio = audioContext.createMediaStreamSource(stream); + const currentAudio = audioContext.createMediaStreamSource(audioStream); + const resultAudio = audioContext.createMediaStreamDestination(); + accumulatedAudio.connect(resultAudio); + currentAudio.connect(resultAudio); + return resultAudio.stream; + })() + : audioStream + ).getAudioTracks(), + ]) + : stream, + videoStream, + ) +); export const Recording: React.FC = ({ goToNextStep, goToPrevStep }) => { @@ -70,14 +87,13 @@ export const Recording: React.FC = ({ goToNextStep, goToPrevStep }) = if (displayStream) { const onStop = addRecordOnStop(dispatch, "desktop"); - const stream = mixAudioIntoVideo(state.audioStream, displayStream); + const stream = mixAudioIntoVideo([state.audioStream], displayStream); desktopRecorder.current = new Recorder(stream, settings.recording, onStop); desktopRecorder.current.start(); } - if (userStream) { const onStop = addRecordOnStop(dispatch, "video"); - const stream = mixAudioIntoVideo(state.audioStream, userStream); + const stream = mixAudioIntoVideo([state.audioStream, displayStream], userStream); videoRecorder.current = new Recorder(stream, settings.recording, onStop); videoRecorder.current.start(); } diff --git a/src/steps/video-setup/prefs.tsx b/src/steps/video-setup/prefs.tsx index b4796b54..01c4043d 100644 --- a/src/steps/video-setup/prefs.tsx +++ b/src/steps/video-setup/prefs.tsx @@ -18,6 +18,7 @@ import { stopUserCapture, } from "../../capturer"; import { Select } from "../../ui/Select"; +import { OVERLAY_STYLE } from "./preview"; /** @@ -218,29 +219,14 @@ export const StreamSettings: React.FC = ({ isDesktop, strea onClick={() => setIsExpanded(old => !old)} aria-label={label} css={{ - border: "none", - display: "inline-block", - backgroundColor: "rgba(0, 0, 0, 0.3)", - color: "white", - padding: 8, + ...OVERLAY_STYLE, fontSize: 26, - backdropFilter: "invert(0.3) blur(4px)", - lineHeight: 0, - borderRadius: "10px", - cursor: "pointer", - "&:hover, &:focus-visible": { - backgroundColor: "rgba(0, 0, 0, 0.5)", - }, "> svg": { transition: "transform 0.2s", }, "&:hover > svg, &:focus > svg": { transform: isExpanded ? "none" : "rotate(45deg)", }, - "&:focus-visible": { - outline: "5px dashed white", - outlineOffset: -2.5, - }, }} > {isExpanded ? : } diff --git a/src/steps/video-setup/preview.tsx b/src/steps/video-setup/preview.tsx index 2922db49..1eed5fca 100644 --- a/src/steps/video-setup/preview.tsx +++ b/src/steps/video-setup/preview.tsx @@ -1,12 +1,13 @@ import { useEffect, useRef } from "react"; -import { Spinner, match, unreachable, useColorScheme } from "@opencast/appkit"; -import { useTranslation } from "react-i18next"; +import { Spinner, WithTooltip, match, unreachable, useColorScheme } from "@opencast/appkit"; +import { Trans, useTranslation } from "react-i18next"; +import { LuInfo, LuVolume2, LuVolumeX } from "react-icons/lu"; import { COLORS, dimensionsOf } from "../../util"; -import { StreamSettings } from "./prefs"; -import { Input } from "."; import { VideoBox, useVideoBoxResize } from "../../ui/VideoBox"; import { ErrorBox } from "../../ui/ErrorBox"; +import { StreamSettings } from "./prefs"; +import { Input } from "."; @@ -63,7 +64,10 @@ const StreamPreview: React.FC<{ input: Input }> = ({ input }) => { }, }}> - {input.stream && } + {input.stream && <> + {input.isDesktop && } + + } ); }; @@ -145,3 +149,49 @@ const PreviewVideo: React.FC<{ input: Input }> = ({ input }) => { ); }; + +export const DisplayAudioInfo: React.FC<{ stream: MediaStream }> = ({ stream }) => { + const hasAudio = stream.getAudioTracks().length; + + return ( +
+ + Note: Explanation. + + } + > +
+ {hasAudio ? : } +
+
+
+ ); +}; + +export const OVERLAY_STYLE = { + border: "none", + display: "inline-block", + backgroundColor: "rgba(0, 0, 0, 0.3)", + color: "white", + padding: 8, + backdropFilter: "invert(0.3) blur(4px)", + lineHeight: 0, + borderRadius: 10, + cursor: "pointer", + "&:hover, &:focus-visible": { + backgroundColor: "rgba(0, 0, 0, 0.5)", + }, + "&:focus-visible": { + outline: "5px dashed white", + outlineOffset: -2.5, + }, +};