diff --git a/src/browser/overlay.js b/src/browser/overlay.js
index 9f4d5dd..be17828 100644
--- a/src/browser/overlay.js
+++ b/src/browser/overlay.js
@@ -29,6 +29,9 @@ function Overlay({ config, views, streams }) {
const isListening = viewState.matches(
'displaying.running.audio.listening',
)
+ const isBackgroundListening = viewState.matches(
+ 'displaying.running.audio.background',
+ )
const isBlurred = viewState.matches('displaying.running.video.blurred')
const isLoading = viewState.matches('displaying.loading')
return (
@@ -51,7 +54,7 @@ function Overlay({ config, views, streams }) {
>
)}
- {isListening && }
+ {(isListening || isBackgroundListening) && }
)}
{isLoading && }
diff --git a/src/node/StreamWindow.js b/src/node/StreamWindow.js
index 7b6a3f6..8beccce 100644
--- a/src/node/StreamWindow.js
+++ b/src/node/StreamWindow.js
@@ -257,6 +257,10 @@ export default class StreamWindow extends EventEmitter {
}
}
+ setViewBackgroundListening(viewIdx, listening) {
+ this.sendViewEvent(viewIdx, listening ? 'BACKGROUND' : 'UNBACKGROUND')
+ }
+
setViewBlurred(viewIdx, blurred) {
this.sendViewEvent(viewIdx, blurred ? 'BLUR' : 'UNBLUR')
}
diff --git a/src/node/index.js b/src/node/index.js
index 1e9ad66..191b2cc 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -270,6 +270,8 @@ async function main() {
const onMessage = async (msg, respond) => {
if (msg.type === 'set-listening-view') {
streamWindow.setListeningView(msg.viewIdx)
+ } else if (msg.type === 'set-view-background-listening') {
+ streamWindow.setViewBackgroundListening(msg.viewIdx, msg.listening)
} else if (msg.type === 'set-view-blurred') {
streamWindow.setViewBlurred(msg.viewIdx, msg.blurred)
} else if (msg.type === 'set-custom-streams') {
diff --git a/src/node/viewStateMachine.js b/src/node/viewStateMachine.js
index 0ff2731..5caddf5 100644
--- a/src/node/viewStateMachine.js
+++ b/src/node/viewStateMachine.js
@@ -141,6 +141,8 @@ const viewStateMachine = Machine(
on: {
MUTE: '.muted',
UNMUTE: '.listening',
+ BACKGROUND: '.background',
+ UNBACKGROUND: '.muted',
},
states: {
muted: {
@@ -149,6 +151,13 @@ const viewStateMachine = Machine(
listening: {
entry: 'unmuteAudio',
},
+ background: {
+ on: {
+ // Ignore normal audio swapping.
+ MUTE: {},
+ },
+ entry: 'unmuteAudio',
+ },
},
},
video: {
diff --git a/src/roles.js b/src/roles.js
index 1c5567e..5651d14 100644
--- a/src/roles.js
+++ b/src/roles.js
@@ -1,5 +1,6 @@
const operatorActions = new Set([
'set-listening-view',
+ 'set-view-background-listening',
'set-view-blurred',
'set-custom-streams',
'reload-view',
diff --git a/src/web/control.js b/src/web/control.js
index 5e3a5ec..95a7f6d 100644
--- a/src/web/control.js
+++ b/src/web/control.js
@@ -15,6 +15,7 @@ import {
import { State } from 'xstate'
import styled, { createGlobalStyle } from 'styled-components'
import { useHotkeys } from 'react-hotkeys-hook'
+import Color from 'color'
import '../index.css'
import { idxInBox } from '../geometry'
@@ -139,6 +140,9 @@ function useStreamwallConnection(wsEndpoint) {
const isListening = state.matches(
'displaying.running.audio.listening',
)
+ const isBackgroundListening = state.matches(
+ 'displaying.running.audio.background',
+ )
const isBlurred = state.matches('displaying.running.video.blurred')
for (const space of pos.spaces) {
if (!newStateIdxMap.has(space)) {
@@ -147,6 +151,7 @@ function useStreamwallConnection(wsEndpoint) {
Object.assign(newStateIdxMap.get(space), {
state,
isListening,
+ isBackgroundListening,
isBlurred,
spaces: pos.spaces,
})
@@ -332,6 +337,14 @@ function App({ wsEndpoint, role }) {
})
}, [])
+ const handleSetBackgroundListening = useCallback((viewIdx, listening) => {
+ send({
+ type: 'set-view-background-listening',
+ viewIdx,
+ listening,
+ })
+ }, [])
+
const handleSetBlurred = useCallback((idx, blurred) => {
send({
type: 'set-view-blurred',
@@ -504,8 +517,12 @@ function App({ wsEndpoint, role }) {
{range(0, gridCount).map((x) => {
const idx = gridCount * y + x
- const { isListening = false, isBlurred = false, state } =
- stateIdxMap.get(idx) || {}
+ const {
+ isListening = false,
+ isBackgroundListening = false,
+ isBlurred = false,
+ state,
+ } = stateIdxMap.get(idx) || {}
const { streamId } = sharedState.views?.[idx] || ''
const isDragHighlighted =
dragStart !== undefined &&
@@ -517,6 +534,7 @@ function App({ wsEndpoint, role }) {
isError={state && state.matches('displaying.error')}
isDisplaying={state && state.matches('displaying')}
isListening={isListening}
+ isBackgroundListening={isBackgroundListening}
isBlurred={isBlurred}
isHighlighted={isDragHighlighted}
isSwapping={idx === swapStartIdx}
@@ -528,6 +546,7 @@ function App({ wsEndpoint, role }) {
onBlur={handleBlurInput}
onChangeSpace={handleSetView}
onSetListening={handleSetListening}
+ onSetBackgroundListening={handleSetBackgroundListening}
onSetBlurred={handleSetBlurred}
onReloadView={handleReloadView}
onSwapView={handleSwapView}
@@ -722,6 +741,7 @@ function GridInput({
isDisplaying,
isError,
isListening,
+ isBackgroundListening,
isBlurred,
isHighlighted,
isSwapping,
@@ -732,6 +752,7 @@ function GridInput({
onFocus,
onBlur,
onSetListening,
+ onSetBackgroundListening,
onSetBlurred,
onReloadView,
onSwapView,
@@ -762,8 +783,17 @@ function GridInput({
[idx, onChangeSpace],
)
const handleListeningClick = useCallback(
- () => onSetListening(idx, !isListening),
- [idx, onSetListening, isListening],
+ (ev) =>
+ ev.shiftKey || isBackgroundListening
+ ? onSetBackgroundListening(idx, !isBackgroundListening)
+ : onSetListening(idx, !isListening),
+ [
+ idx,
+ onSetListening,
+ onSetBackgroundListening,
+ isListening,
+ isBackgroundListening,
+ ],
)
const handleBlurClick = useCallback(() => onSetBlurred(idx, !isBlurred), [
idx,
@@ -839,7 +869,8 @@ function GridInput({
)}
{roleCan(role, 'set-listening-view') && (
@@ -931,11 +962,11 @@ const StyledButton = styled.button`
background: #ccc;
border-radius: 5px;
- ${({ isActive }) =>
+ ${({ isActive, activeColor }) =>
isActive &&
`
- border-color: red;
- background: #c77;
+ border-color: ${activeColor};
+ background: ${Color(activeColor).desaturate(0.5).lighten(0.5)};
`};
&:focus {