mirror of
https://github.com/streamwall/streamwall.git
synced 2025-12-06 01:45:37 -05:00
Add Streamdelay integration
This commit is contained in:
3
package-lock.json
generated
3
package-lock.json
generated
@@ -12167,8 +12167,7 @@
|
||||
"ws": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz",
|
||||
"integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w=="
|
||||
},
|
||||
"xml-name-validator": {
|
||||
"version": "3.0.0",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"styled-components": "^5.1.1",
|
||||
"svg-loaders-react": "^2.2.1",
|
||||
"ws": "^7.3.0",
|
||||
"xstate": "^4.10.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
|
||||
48
src/node/StreamdelayClient.js
Normal file
48
src/node/StreamdelayClient.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import EventEmitter from 'events'
|
||||
import * as url from 'url'
|
||||
import WebSocket from 'ws'
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
|
||||
export default class StreamdelayClient extends EventEmitter {
|
||||
constructor({ endpoint, key }) {
|
||||
super()
|
||||
this.endpoint = endpoint
|
||||
this.key = key
|
||||
this.ws = null
|
||||
this.status = null
|
||||
}
|
||||
|
||||
connect() {
|
||||
const wsURL = url.resolve(this.endpoint, `ws?key=${this.key}`)
|
||||
const ws = (this.ws = new ReconnectingWebSocket(wsURL, [], {
|
||||
WebSocket,
|
||||
maxReconnectionDelay: 5000,
|
||||
minReconnectionDelay: 1000 + Math.random() * 500,
|
||||
reconnectionDelayGrowFactor: 1.1,
|
||||
}))
|
||||
ws.addEventListener('open', () => this.emitState())
|
||||
ws.addEventListener('close', () => this.emitState())
|
||||
ws.addEventListener('message', (ev) => {
|
||||
let data
|
||||
try {
|
||||
data = JSON.parse(ev.data)
|
||||
} catch (err) {
|
||||
console.error('invalid JSON from streamdelay:', ev.data)
|
||||
return
|
||||
}
|
||||
this.status = data.status
|
||||
this.emitState()
|
||||
})
|
||||
}
|
||||
|
||||
emitState() {
|
||||
this.emit('state', {
|
||||
isConnected: this.ws.readyState === WebSocket.OPEN,
|
||||
...this.status,
|
||||
})
|
||||
}
|
||||
|
||||
setCensored(isCensored) {
|
||||
this.ws.send(JSON.stringify({ isCensored }))
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { app, shell, session, BrowserWindow } from 'electron'
|
||||
import { ensureValidURL } from '../util'
|
||||
import { pollPublicData, StreamIDGenerator } from './data'
|
||||
import StreamWindow from './StreamWindow'
|
||||
import StreamdelayClient from './StreamdelayClient'
|
||||
import initWebServer from './server'
|
||||
|
||||
async function main() {
|
||||
@@ -52,6 +53,13 @@ async function main() {
|
||||
describe: 'Background color of wall (useful for chroma-keying)',
|
||||
default: '#000',
|
||||
})
|
||||
.option('streamdelay-endpoint', {
|
||||
describe: 'URL of Streamdelay endpoint',
|
||||
default: 'http://localhost:8404',
|
||||
})
|
||||
.option('streamdelay-key', {
|
||||
describe: 'Streamdelay API key',
|
||||
})
|
||||
.help().argv
|
||||
|
||||
// Reject all permission requests from web content.
|
||||
@@ -69,8 +77,14 @@ async function main() {
|
||||
streamWindow.init()
|
||||
|
||||
let browseWindow = null
|
||||
let streamdelayClient = null
|
||||
|
||||
const clientState = { streams: [], customStreams: [], views: [] }
|
||||
const clientState = {
|
||||
streams: [],
|
||||
customStreams: [],
|
||||
views: [],
|
||||
streamdelay: false,
|
||||
}
|
||||
const getInitialState = () => clientState
|
||||
let broadcastState = () => {}
|
||||
const onMessage = (msg) => {
|
||||
@@ -113,6 +127,8 @@ async function main() {
|
||||
} else if (msg.type === 'dev-tools') {
|
||||
streamWindow.openDevTools(msg.viewIdx, browseWindow.webContents)
|
||||
}
|
||||
} else if (msg.type === 'set-stream-censored' && streamdelayClient) {
|
||||
streamdelayClient.setCensored(msg.isCensored)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +150,18 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
if (argv.streamdelayKey) {
|
||||
streamdelayClient = new StreamdelayClient({
|
||||
endpoint: argv.streamdelayEndpoint,
|
||||
key: argv.streamdelayKey,
|
||||
})
|
||||
streamdelayClient.on('state', (state) => {
|
||||
clientState.streamdelay = state
|
||||
broadcastState(clientState)
|
||||
})
|
||||
streamdelayClient.connect()
|
||||
}
|
||||
|
||||
streamWindow.on('state', (viewStates) => {
|
||||
clientState.views = viewStates
|
||||
streamWindow.send('state', clientState)
|
||||
|
||||
@@ -21,6 +21,7 @@ function App({ wsEndpoint }) {
|
||||
const [streams, setStreams] = useState([])
|
||||
const [customStreams, setCustomStreams] = useState([])
|
||||
const [stateIdxMap, setStateIdxMap] = useState(new Map())
|
||||
const [delayState, setDelayState] = useState(false)
|
||||
const allStreams = sortBy([...streams, ...customStreams], ['_id'])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -38,6 +39,7 @@ function App({ wsEndpoint }) {
|
||||
streams: newStreams,
|
||||
views,
|
||||
customStreams: newCustomStreams,
|
||||
streamdelay,
|
||||
} = msg.state
|
||||
const newStateIdxMap = new Map()
|
||||
const allStreams = [...newStreams, ...newCustomStreams]
|
||||
@@ -66,6 +68,12 @@ function App({ wsEndpoint }) {
|
||||
setStateIdxMap(newStateIdxMap)
|
||||
setStreams(newStreams)
|
||||
setCustomStreams(newCustomStreams)
|
||||
setDelayState(
|
||||
streamdelay && {
|
||||
...streamdelay,
|
||||
state: State.from(streamdelay.state),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
console.warn('unexpected ws message', msg)
|
||||
}
|
||||
@@ -169,6 +177,15 @@ function App({ wsEndpoint }) {
|
||||
)
|
||||
})
|
||||
|
||||
const setStreamCensored = useCallback((isCensored) => {
|
||||
wsRef.current.send(
|
||||
JSON.stringify({
|
||||
type: 'set-stream-censored',
|
||||
isCensored,
|
||||
}),
|
||||
)
|
||||
}, [])
|
||||
|
||||
// Set up keyboard shortcuts.
|
||||
// Note: if GRID_COUNT > 3, there will not be keys for view indices > 9.
|
||||
for (const idx of range(GRID_COUNT * GRID_COUNT)) {
|
||||
@@ -189,6 +206,20 @@ function App({ wsEndpoint }) {
|
||||
[stateIdxMap],
|
||||
)
|
||||
}
|
||||
useHotkeys(
|
||||
`alt+c`,
|
||||
() => {
|
||||
setStreamCensored(true)
|
||||
},
|
||||
[setStreamCensored],
|
||||
)
|
||||
useHotkeys(
|
||||
`alt+shift+c`,
|
||||
() => {
|
||||
setStreamCensored(false)
|
||||
},
|
||||
[setStreamCensored],
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -196,6 +227,12 @@ function App({ wsEndpoint }) {
|
||||
<div>
|
||||
connection status: {isConnected ? 'connected' : 'connecting...'}
|
||||
</div>
|
||||
{delayState !== false && (
|
||||
<StreamDelayBox
|
||||
delayState={delayState}
|
||||
setStreamCensored={setStreamCensored}
|
||||
/>
|
||||
)}
|
||||
<StyledDataContainer isConnected={isConnected}>
|
||||
<div>
|
||||
{range(0, 3).map((y) => (
|
||||
@@ -261,6 +298,38 @@ function App({ wsEndpoint }) {
|
||||
)
|
||||
}
|
||||
|
||||
function StreamDelayBox({ delayState, setStreamCensored }) {
|
||||
const handleToggleStreamCensored = useCallback(() => {
|
||||
setStreamCensored(!delayState.isCensored)
|
||||
}, [delayState.isCensored, setStreamCensored])
|
||||
let buttonText
|
||||
if (delayState.state.matches('censorship.censored.deactivating')) {
|
||||
buttonText = 'Deactivating...'
|
||||
} else if (delayState.isCensored) {
|
||||
buttonText = 'Uncensor stream'
|
||||
} else {
|
||||
buttonText = 'Censor stream'
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<StyledStreamDelayBox>
|
||||
<strong>Streamdelay</strong>
|
||||
<span>{delayState.isConnected ? 'connected' : 'connecting...'}</span>
|
||||
{delayState.isConnected && (
|
||||
<span>delay: {delayState.delaySeconds}s</span>
|
||||
)}
|
||||
<StyledToggleButton
|
||||
isActive={delayState.isCensored}
|
||||
onClick={handleToggleStreamCensored}
|
||||
tabIndex={1}
|
||||
>
|
||||
{buttonText}
|
||||
</StyledToggleButton>
|
||||
</StyledStreamDelayBox>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StreamLine({
|
||||
id,
|
||||
row: { label, source, title, link, notes },
|
||||
@@ -423,6 +492,17 @@ function CustomStreamInput({ idx, onChange, ...props }) {
|
||||
)
|
||||
}
|
||||
|
||||
const StyledStreamDelayBox = styled.div`
|
||||
display: inline-flex;
|
||||
margin: 5px 0;
|
||||
padding: 10px;
|
||||
background: #fdd;
|
||||
|
||||
& > * {
|
||||
margin-right: 1em;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledDataContainer = styled.div`
|
||||
opacity: ${({ isConnected }) => (isConnected ? 1 : 0.5)};
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user