mirror of
https://github.com/streamwall/streamwall.git
synced 2026-04-03 20:32:08 -04:00
Add support for blurring streams
This commit is contained in:
committed by
Max Goodhart
parent
3759b05915
commit
e861071599
@@ -31,10 +31,14 @@ function Overlay({ views, streams, customStreams }) {
|
|||||||
const data = [...streams, ...customStreams].find(
|
const data = [...streams, ...customStreams].find(
|
||||||
(d) => content.url === d.Link,
|
(d) => content.url === d.Link,
|
||||||
)
|
)
|
||||||
const isListening = viewState.matches('displaying.running.listening')
|
const isListening = viewState.matches(
|
||||||
|
'displaying.running.audio.listening',
|
||||||
|
)
|
||||||
|
const isBlurred = viewState.matches('displaying.running.video.blurred')
|
||||||
const isLoading = viewState.matches('displaying.loading')
|
const isLoading = viewState.matches('displaying.loading')
|
||||||
return (
|
return (
|
||||||
<SpaceBorder pos={pos} isListening={isListening}>
|
<SpaceBorder pos={pos} isListening={isListening}>
|
||||||
|
<BlurCover isBlurred={isBlurred} />
|
||||||
{data && (
|
{data && (
|
||||||
<StreamTitle isListening={isListening}>
|
<StreamTitle isListening={isListening}>
|
||||||
<StreamIcon url={content.url} />
|
<StreamIcon url={content.url} />
|
||||||
@@ -186,4 +190,13 @@ const ListeningIndicator = styled(SoundIcon)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const BlurCover = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
backdrop-filter: ${({ isBlurred }) => (isBlurred ? 'blur(30px)' : 'blur(0)')};
|
||||||
|
`
|
||||||
|
|
||||||
render(<App />, document.body)
|
render(<App />, document.body)
|
||||||
|
|||||||
@@ -223,16 +223,23 @@ export default class StreamWindow extends EventEmitter {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadView(viewIdx) {
|
sendViewEvent(viewIdx, event) {
|
||||||
const view = this.findViewByIdx(viewIdx)
|
const view = this.findViewByIdx(viewIdx)
|
||||||
if (view) {
|
if (view) {
|
||||||
view.send('RELOAD')
|
view.send(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setViewBlurred(viewIdx, blurred) {
|
||||||
|
this.sendViewEvent(viewIdx, blurred ? 'BLUR' : 'UNBLUR')
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadView(viewIdx) {
|
||||||
|
this.sendViewEvent(viewIdx, 'RELOAD')
|
||||||
|
}
|
||||||
|
|
||||||
openDevTools(viewIdx, inWebContents) {
|
openDevTools(viewIdx, inWebContents) {
|
||||||
const view = this.findViewByIdx(viewIdx)
|
this.sendViewEvent(viewIdx, { type: 'DEVTOOLS', inWebContents })
|
||||||
view.send({ type: 'DEVTOOLS', inWebContents })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
send(...args) {
|
send(...args) {
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ async function main() {
|
|||||||
streamWindow.setViews(new Map(msg.views))
|
streamWindow.setViews(new Map(msg.views))
|
||||||
} else if (msg.type === 'set-listening-view') {
|
} else if (msg.type === 'set-listening-view') {
|
||||||
streamWindow.setListeningView(msg.viewIdx)
|
streamWindow.setListeningView(msg.viewIdx)
|
||||||
|
} else if (msg.type === 'set-view-blurred') {
|
||||||
|
streamWindow.setViewBlurred(msg.viewIdx, msg.blurred)
|
||||||
} else if (msg.type === 'set-custom-streams') {
|
} else if (msg.type === 'set-custom-streams') {
|
||||||
const customIDGen = new StreamIDGenerator(idGen)
|
const customIDGen = new StreamIDGenerator(idGen)
|
||||||
clientState.customStreams = customIDGen.process(msg.streams)
|
clientState.customStreams = customIDGen.process(msg.streams)
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ const viewStateMachine = Machine(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
running: {
|
running: {
|
||||||
initial: 'muted',
|
type: 'parallel',
|
||||||
entry: 'positionView',
|
entry: 'positionView',
|
||||||
on: {
|
on: {
|
||||||
DISPLAY: {
|
DISPLAY: {
|
||||||
@@ -114,6 +114,11 @@ const viewStateMachine = Machine(
|
|||||||
],
|
],
|
||||||
cond: 'contentUnchanged',
|
cond: 'contentUnchanged',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
states: {
|
||||||
|
audio: {
|
||||||
|
initial: 'muted',
|
||||||
|
on: {
|
||||||
MUTE: '.muted',
|
MUTE: '.muted',
|
||||||
UNMUTE: '.listening',
|
UNMUTE: '.listening',
|
||||||
},
|
},
|
||||||
@@ -126,6 +131,19 @@ const viewStateMachine = Machine(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
video: {
|
||||||
|
initial: 'normal',
|
||||||
|
on: {
|
||||||
|
BLUR: '.blurred',
|
||||||
|
UNBLUR: '.normal',
|
||||||
|
},
|
||||||
|
states: {
|
||||||
|
normal: {},
|
||||||
|
blurred: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
error: {
|
error: {
|
||||||
entry: 'logError',
|
entry: 'logError',
|
||||||
},
|
},
|
||||||
|
|||||||
1
src/static/video-slash-solid.svg
Normal file
1
src/static/video-slash-solid.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="video-slash" class="svg-inline--fa fa-video-slash fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M633.8 458.1l-55-42.5c15.4-1.4 29.2-13.7 29.2-31.1v-257c0-25.5-29.1-40.4-50.4-25.8L448 177.3v137.2l-32-24.7v-178c0-26.4-21.4-47.8-47.8-47.8H123.9L45.5 3.4C38.5-2 28.5-.8 23 6.2L3.4 31.4c-5.4 7-4.2 17 2.8 22.4L42.7 82 416 370.6l178.5 138c7 5.4 17 4.2 22.5-2.8l19.6-25.3c5.5-6.9 4.2-17-2.8-22.4zM32 400.2c0 26.4 21.4 47.8 47.8 47.8h288.4c11.2 0 21.4-4 29.6-10.5L32 154.7v245.5z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 617 B |
@@ -8,6 +8,7 @@ import styled, { css } from 'styled-components'
|
|||||||
import '../index.css'
|
import '../index.css'
|
||||||
import { GRID_COUNT } from '../constants'
|
import { GRID_COUNT } from '../constants'
|
||||||
import SoundIcon from '../static/volume-up-solid.svg'
|
import SoundIcon from '../static/volume-up-solid.svg'
|
||||||
|
import NoVideoIcon from '../static/video-slash-solid.svg'
|
||||||
import ReloadIcon from '../static/redo-alt-solid.svg'
|
import ReloadIcon from '../static/redo-alt-solid.svg'
|
||||||
import LifeRingIcon from '../static/life-ring-regular.svg'
|
import LifeRingIcon from '../static/life-ring-regular.svg'
|
||||||
import WindowIcon from '../static/window-maximize-regular.svg'
|
import WindowIcon from '../static/window-maximize-regular.svg'
|
||||||
@@ -42,7 +43,10 @@ function App({ wsEndpoint }) {
|
|||||||
const stream = allStreams.find((d) => d.Link === content.url)
|
const stream = allStreams.find((d) => d.Link === content.url)
|
||||||
const streamId = stream?._id
|
const streamId = stream?._id
|
||||||
const state = State.from(viewState.state)
|
const state = State.from(viewState.state)
|
||||||
const isListening = state.matches('displaying.running.listening')
|
const isListening = state.matches(
|
||||||
|
'displaying.running.audio.listening',
|
||||||
|
)
|
||||||
|
const isBlurred = state.matches('displaying.running.video.blurred')
|
||||||
for (const space of pos.spaces) {
|
for (const space of pos.spaces) {
|
||||||
if (!newStateIdxMap.has(space)) {
|
if (!newStateIdxMap.has(space)) {
|
||||||
newStateIdxMap.set(space, {})
|
newStateIdxMap.set(space, {})
|
||||||
@@ -52,6 +56,7 @@ function App({ wsEndpoint }) {
|
|||||||
content,
|
content,
|
||||||
state,
|
state,
|
||||||
isListening,
|
isListening,
|
||||||
|
isBlurred,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,6 +107,16 @@ function App({ wsEndpoint }) {
|
|||||||
)
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleSetBlurred = useCallback((idx, blurred) => {
|
||||||
|
wsRef.current.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: 'set-view-blurred',
|
||||||
|
viewIdx: idx,
|
||||||
|
blurred: blurred,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleReloadView = useCallback((idx) => {
|
const handleReloadView = useCallback((idx) => {
|
||||||
wsRef.current.send(
|
wsRef.current.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -166,6 +181,7 @@ function App({ wsEndpoint }) {
|
|||||||
const {
|
const {
|
||||||
streamId = '',
|
streamId = '',
|
||||||
isListening = false,
|
isListening = false,
|
||||||
|
isBlurred = false,
|
||||||
content = { url: '' },
|
content = { url: '' },
|
||||||
state,
|
state,
|
||||||
} = stateIdxMap.get(idx) || {}
|
} = stateIdxMap.get(idx) || {}
|
||||||
@@ -177,8 +193,10 @@ function App({ wsEndpoint }) {
|
|||||||
isError={state && state.matches('displaying.error')}
|
isError={state && state.matches('displaying.error')}
|
||||||
isDisplaying={state && state.matches('displaying')}
|
isDisplaying={state && state.matches('displaying')}
|
||||||
isListening={isListening}
|
isListening={isListening}
|
||||||
|
isBlurred={isBlurred}
|
||||||
onChangeSpace={handleSetView}
|
onChangeSpace={handleSetView}
|
||||||
onSetListening={handleSetListening}
|
onSetListening={handleSetListening}
|
||||||
|
onSetBlurred={handleSetBlurred}
|
||||||
onReloadView={handleReloadView}
|
onReloadView={handleReloadView}
|
||||||
onBrowse={handleBrowse}
|
onBrowse={handleBrowse}
|
||||||
onDevTools={handleDevTools}
|
onDevTools={handleDevTools}
|
||||||
@@ -255,7 +273,9 @@ function GridInput({
|
|||||||
isDisplaying,
|
isDisplaying,
|
||||||
isError,
|
isError,
|
||||||
isListening,
|
isListening,
|
||||||
|
isBlurred,
|
||||||
onSetListening,
|
onSetListening,
|
||||||
|
onSetBlurred,
|
||||||
onReloadView,
|
onReloadView,
|
||||||
onBrowse,
|
onBrowse,
|
||||||
onDevTools,
|
onDevTools,
|
||||||
@@ -279,6 +299,11 @@ function GridInput({
|
|||||||
() => onSetListening(idx, !isListening),
|
() => onSetListening(idx, !isListening),
|
||||||
[idx, onSetListening, isListening],
|
[idx, onSetListening, isListening],
|
||||||
)
|
)
|
||||||
|
const handleBlurClick = useCallback(() => onSetBlurred(idx, !isBlurred), [
|
||||||
|
idx,
|
||||||
|
onSetBlurred,
|
||||||
|
isBlurred,
|
||||||
|
])
|
||||||
const handleReloadClick = useCallback(() => onReloadView(idx), [
|
const handleReloadClick = useCallback(() => onReloadView(idx), [
|
||||||
idx,
|
idx,
|
||||||
onReloadView,
|
onReloadView,
|
||||||
@@ -295,23 +320,32 @@ function GridInput({
|
|||||||
<StyledGridContainer>
|
<StyledGridContainer>
|
||||||
{isDisplaying && (
|
{isDisplaying && (
|
||||||
<StyledGridButtons side="left">
|
<StyledGridButtons side="left">
|
||||||
<StyledButton onClick={handleReloadClick}>
|
<StyledSmallButton onClick={handleReloadClick} tabIndex={1}>
|
||||||
<ReloadIcon />
|
<ReloadIcon />
|
||||||
</StyledButton>
|
</StyledSmallButton>
|
||||||
<StyledButton onClick={handleBrowseClick}>
|
<StyledSmallButton onClick={handleBrowseClick} tabIndex={1}>
|
||||||
<WindowIcon />
|
<WindowIcon />
|
||||||
</StyledButton>
|
</StyledSmallButton>
|
||||||
<StyledButton onClick={handleDevToolsClick}>
|
<StyledSmallButton onClick={handleDevToolsClick} tabIndex={1}>
|
||||||
<LifeRingIcon />
|
<LifeRingIcon />
|
||||||
</StyledButton>
|
</StyledSmallButton>
|
||||||
</StyledGridButtons>
|
</StyledGridButtons>
|
||||||
)}
|
)}
|
||||||
<StyledGridButtons side="right">
|
<StyledGridButtons side="right">
|
||||||
<ListeningButton
|
<StyledToggleButton
|
||||||
isListening={isListening}
|
isActive={isBlurred}
|
||||||
|
onClick={handleBlurClick}
|
||||||
|
tabIndex={1}
|
||||||
|
>
|
||||||
|
<NoVideoIcon />
|
||||||
|
</StyledToggleButton>
|
||||||
|
<StyledToggleButton
|
||||||
|
isActive={isListening}
|
||||||
onClick={handleListeningClick}
|
onClick={handleListeningClick}
|
||||||
tabIndex={1}
|
tabIndex={1}
|
||||||
/>
|
>
|
||||||
|
<SoundIcon />
|
||||||
|
</StyledToggleButton>
|
||||||
</StyledGridButtons>
|
</StyledGridButtons>
|
||||||
<StyledGridInput
|
<StyledGridInput
|
||||||
name={idx}
|
name={idx}
|
||||||
@@ -365,14 +399,6 @@ function CustomStreamInput({ idx, onChange, ...props }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListeningButton(props) {
|
|
||||||
return (
|
|
||||||
<StyledListeningButton {...props}>
|
|
||||||
<SoundIcon />
|
|
||||||
</StyledListeningButton>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyledDataContainer = styled.div`
|
const StyledDataContainer = styled.div`
|
||||||
opacity: ${({ isConnected }) => (isConnected ? 1 : 0.5)};
|
opacity: ${({ isConnected }) => (isConnected ? 1 : 0.5)};
|
||||||
`
|
`
|
||||||
@@ -400,9 +426,16 @@ const StyledButton = styled.button`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledListeningButton = styled(StyledButton)`
|
const StyledSmallButton = styled(StyledButton)`
|
||||||
${({ isListening }) =>
|
svg {
|
||||||
isListening &&
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const StyledToggleButton = styled(StyledButton)`
|
||||||
|
${({ isActive }) =>
|
||||||
|
isActive &&
|
||||||
`
|
`
|
||||||
border-color: red;
|
border-color: red;
|
||||||
background: #c77;
|
background: #c77;
|
||||||
@@ -426,7 +459,7 @@ const StyledGridButtons = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const StyledGridInput = styled.input`
|
const StyledGridInput = styled.input`
|
||||||
width: 150px;
|
width: 160px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 2px solid ${({ isError }) => (isError ? 'red' : 'black')};
|
border: 2px solid ${({ isError }) => (isError ? 'red' : 'black')};
|
||||||
|
|||||||
Reference in New Issue
Block a user