Mini-project: “On Air” During Zoom Calls
If you’re like me, you’re taking a lot of video calls from home these days. And as a New Yorker living in a small space with a partner who is also on a lot of calls, a constant challenge is knowing when the other person is occupied or to not make an unwanted cameo as you walk through someone’s “camera zone” to get something.
My desired solution is for a simple visual indicator — an “On Air” light — that turns on when a camera is in use.
I decided to time-box this effort to one evening. While I did create a working proof-of-concept, (a) I ran into a lot of dead ends along the way so the night went rather late, and (b) it’s still rather hacky. That being said, I figured it was worth sharing what does work to help the next person.
My Setup
I’m on MacOS, and I have a Phillips Hue home bridge. There is a Hue bulb assigned the name “OnAir”.
Detecting if Video is Turned On/Off
Rather than hook into APIs or dig too deeply, it seemed that a simple (or at least, lazy) way to know if a video camera is being used on MacOS is to look at the system log. Whenever a camera turns on, there is a log entry for kCameraStreamStart; and then when a camera stops, there’s an entry kCameraStreamStop.
For example, I turned my camera on, then off, then opened the Terminal and ran:
% log show — last 2m — predicate ‘(sender == “VDCAssistant”)’ | grep kCameraStream2021–10–07 10:40:54.892531–0400 0xe7dcf2 Default 0x0 48943 0 VDCAssistant: [com.apple.VDCAssistant:device] [guid:0x1130000046d0892] Post event kCameraStreamStart2021–10–07 10:40:54.892540–0400 0xe7dcf2 Default 0x0 48943 0 VDCAssistant: [com.apple.VDCAssistant:device] [guid:0x1130000046d0892] Init -> Streaming on event kCameraStreamStart2021–10–07 10:40:57.851867–0400 0xe7dcf2 Default 0x1f8015d 48943 0 VDCAssistant: [com.apple.VDCAssistant:device] [guid:0x1130000046d0892] Post event kCameraStreamStop2021–10–07 10:40:57.851874–0400 0xe7dcf2 Default 0x1f8015d 48943 0 VDCAssistant: [com.apple.VDCAssistant:device] [guid:0x1130000046d0892] Streaming -> Init on event kCameraStreamStop
We can just keep an eye on this event stream to see when the camera turns off or on.
Controlling Lights From a Script
phue is a nice Python package for controlling your Hue lights.
% pip install phue
You will need to know the IP address of your Philips Hue bridge on your local network. You can find this in the Hue app (Settings > Hue Bridges > Your Bridge). For me, it’s 10.0.0.2. And the first time you connect to the bridge from your MacOS computer, you will need to press the physical button on your bridge to allow the connection.
% python>>> from phue import Bridge
>>> b = Bridge(‘10.0.0.2’)
>>> b.connect()
From Python, you can toggle a named light (in my case, the bulb I had already named “OnAir”) as:
# Turn on
b.set_light('OnAir', 'on', True)# Turn off
b.set_light('OnAir', 'on', False)
Putting it Together
Originally, I just thought I’d fire up a Python script that would poll the MacOS log file every few seconds. However, when process forked from Python checks the log, it was coming up empty (as opposed to when the command to check logs was being invoked from the terminal). I’m still not sure why — I searched for a while, but I’m sure there’s some MacOS arcane knowledge about process trees having different permissions — but I decided to stop beating my head against this problem, and instead have a shell script that does the polling and invokes Python code.
(This is, of course, not a very robust setup For The Real World— but this is just a proof of concept!)
My initial shell script is an infinite loop that sleeps for 5 seconds between operations. It simply dumps the last few minutes of the camera on/off log events to a file, then calls a Python script to decide what the light should do.
#/bin/zshwhile true
do
log show --last 5m --predicate '(sender == "VDCAssistant")' | grep kCameraStream > camera.log
python on-air.py
sleep 5
done
The Python script reads the log file to decide whether to turn on or off the light. It will simply look for the last event, to see if the camera started (turn the light on) or stopped (turn the light off); no activity will be interpreted as “the light should be off.”
import subprocess
from phue import Bridge
import time# Find your IP from your Hue app
BRIDGE_IP = '10.0.0.2'
# Light is named using the Hue app ahead of time
LIGHT_NAME = 'OnAir'
# Log file dumped from the shell script
LOG = "./camera.log"# isCameraOn returns True if we think the camera is currently
# on, False if not. We're trying to find if the last line was
# kCameraStreamStart (True) or kCameraStreamStop (False); no match
# is False.
def isCameraOn():
camera_is_on = False
with open(LOG, "r") as a_file:
for line in a_file:
if 'kCameraStreamStart' in line:
camera_is_on = True
elif 'kCameraStreamStop' in line:
camera_is_on = False
return camera_is_ondef setLight(bridge, is_on):
bridge.set_light(LIGHT_NAME, 'on', is_on)# Note that the first time you run this, you'll
# need to first press the button on the hue bridge
# before running this application
b = Bridge(BRIDGE_IP)
b.connect()
camera_on = isCameraOn()
setLight(b, camera_on)
While this solution isn’t pretty, it does the job. I can fire up the script and have a light turn on and off as I drift through my scheduled calls!