Unique problems require unique solutions

OBS, Traktor, Express.js and React - What do all these have in common? Not much, to be honest! Here are a few things I learned while building this little app.
What was the problem?
A friend enjoys spending his free time DJing on twitch.tv. His setup is prety unique but his ask was seemingly simple: To be able to display the current artist and track to his audience on twitch at the press of a button. In the above gif, this app is used to display the current artist and track that's animating in the bottom left corner.
He's using Tracktor turntables which is both the hardware and software managing his media. Traktor is pretty complex and has all sorts of data available but nothing easily accessible in a way that I could use in browser. I needed to find a way to get the current artist and track from Traktor and display it on his stream through OBS. Traktor does however have a broadcasting feature that allows you to send data to a server, enter Icecast.
Icecast is server software for streaming multimedia - that's from their site directly. Out of the box, they provide runtime statistics that are accessible via a GET request. This is great! I can get the data I need from Traktor via Icecast and display it on his stream.
Right! We have the data but now what?
The next hurdle was figuring out how to handle clients that are out of date. The setup involves 2 laptops, one running OBS thats streaming a live feed of the app and the other will be primarily used to DJ and control the audio. Now he needs a way to update the data on the screen on a remote computer across the room at the press of a button.
Enter Express.JS, React and Socket.io.
// Client Side TS:
const [display, setDisplay] = useState<boolean>(false)
const [socket, setSocket] = useState<Socket>(null)
const [icecastData, setIcecastData] = useState<IcecastData>()
async function fetchData() {
const response = await fetch(iceStatsURL)
const data = await response.json()
let iceCastSource: IcecastData = data.icestats.source
if (iceCastSource) {
setIcecastData(iceCastSource)
socket.emit('updateTrack', iceCastSource)
return
}
// dataNotFound is a default object that provides error messaging
setIcecastData(dataNotFound)
}
useEffect(() => {
//Fetch data on mount
fetchData()
}, [])
const updateDisplay = () => {
// This function is to show/hide the track on the screen, sends an
// event to the server to broadcast to all connected clients
socket.emit('updateDisplay', !display)
setDisplay(!display)
}
if (socket) {
socket.on('trackDetails', (trackData: TrackData) => {
//This broadcasts the track details to all connected clients
setIcecastData(trackData)
})
socket.on('display', (showTrack: boolean) => {
// This updates the display boolean, toggling the track details
setDisplay(showTrack)
})
}
// Server Side JS:
socketIO.on('connection', (socket) => {
socket.on('updateTrack', (trackData) => {
socket.broadcast.emit('trackDetails', trackData)
})
socket.on('updateDisplay', (showTrack) => {
socket.broadcast.emit('display', showTrack)
})
})
Client Side UI
In short, the UI has two buttons for the DJ to interact wth - Update track and hide track. Hide track is easy, it just hides the track from the screen. Update track is a little more complex. When the DJ presses the button, it sends a socket event to the server with the current artist and track. The server then broadcasts that event to all connected clients. The client that is running OBS will then update the UI with the new data.
Final thoughts:
This was a simple sounding ask that had a few twists to it. I really enjoyed working with Socket.io and will be looking forward to using it in future projects.
I am sure that there will be more added to this project and will be writing more about it in the future.