Proxmox-for-Channels: Step-by-Step for Creating a Chrome-Capture-for-Channels (cc4c) LXC Container

EDIT (05 September 25): This post has been reworked again to support the latest stable versions of Chrome (140.0.7339.80 as of this writing), along with Node v22.19.0. The git repo being cloned is back to @fancybits chrome-capture-for-channels, as that's been updated to include work from @dravenst and @bnhf (me). bun in now used in place of node. If you already have cc4c running on Proxmox, you could probably update your LXC, but it seems faster to me to delete and recreate -- following the updated instructions below.

EDIT (10 April 25): I've reworked this post to reflect my latest recommendations for creating a cc4c LXC container in Proxmox. This now uses a Debian 12 template and incorporates the @dravenst cc4c repo. This repo features better error handling, more descriptive console output, command line switches for configurable elements, and some specific support for Sling. I've also added suggested changes to ~/.bashrc to run cc4c on login in an additional post.

I've tried, as have others, to get the Docker version of Chrome-Capture-for-Channels working better. Although, a few have reported acceptable results, most have not. Being able to containerize it, has appeal though -- so, I thought I'd take a shot at installing cc4c directly in a Proxmox LXC container.

It appears to work well, so I thought I'd document the steps for creating a cc4c LXC here:

Stepping through the usual series of windows for creating a new LXC, here are the values I used:

Debian 12 (Bookworm) is the choice here:

8GB should be adequate for disk space, though 16GB is nice if you have the space available:

Cores are important here, with 6 being a nice generous number if you have the vCPUs (which are OK to over allocate). 4 would probably be fine too -- below that I wouldn't recommend:

4096 is generous, 2048 should be fine too. Keep in mind you're running Chrome:

vmbr1 is my 10GbE bridge, so I'm using that. No firewall, and DHCP on IPv4, but use whatever is typical for you:

I'm passing through my iGPU (which can be shared with other LXCs), using the Proxmox 8 method:

And then, here's what needs to be done from the new LXC's Console. These steps are essentially duplicating what's done in the cc4c Dockerfile:

# Add the non-free and non-free-firmware repos to sources.list
sed -i 's/contrib$/contrib non-free non-free-firmware/' /etc/apt/sources.list

# Add va drivers
apt update

apt install intel-media-va-driver-non-free vainfo libva-drm2 libva-x11-2 mesa-va-drivers

# Upgrade the container's OS
apt upgrade

# Install some basic system packages
apt install curl gnupg git

# Add the Node 18 repo via script and install nodejs
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -

apt install nodejs

# Install needed system libraries and utilities
apt install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils x11vnc x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable x11-apps xvfb xserver-xorg-core x11-xserver-utils xauth

# Add the Chrome signing key and install Chrome
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -

echo "deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list

apt update

apt install google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 --no-install-recommends

# Clone the cc4c repo and install it
cd /opt

git clone https://github.com/fancybits/chrome-capture-for-channels

# Add required environment variables needed by cc4c and Chrome
echo -e "DISPLAY=:99\nCHROME_BIN=/usr/bin/google-chrome\nDOCKER=true\nLIBGL_ALWAYS_INDIRECT=1\nLIBVA_DRIVER_NAME=iHD\nPUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true" | tee /etc/environment > /dev/null

# Exit followed by a fresh login to pick up the new env vars
exit

cd /opt/chrome-capture-for-channels

npm install -g bun # There will be some warnings here about vulnerabilities that can be safely ignored

bun install

The last step will run Xvbf, a VNC server and cc4c itself:

Xvfb :99 -screen 0 1920x1080x16 & x11vnc -quiet -nopw -display :99 -forever & bun main.js -v 9500000 -a 256000 

cc4c is now running in the foreground, with the other two running in the background, and you should see something like this on your Console:

[1] 16268
[2] 16269
Xlib:  extension "DPMS" missing on display ":99".

The VNC desktop is:      cc4c2:0
PORT=5900
[2025/02/08 09:09:32.750] Chrome Capture server listening on port 5589

If you kill the foreground cc4c process for any reason. Be sure to kill the two background processes as well. Their PIDs are shown as [1] and [2] at this stage, or you can get them with a ps -ef command.

When you setup your CDVR Custom Channels Source, be sure to include chrome://gpu as a channel so you can check Chrome's stats regarding GPU use:

#EXTM3U

#EXTINF:-1 channel-id="weatherscan",Weatherscan
chrome://cc4c:5589/stream?url=https://v2.weatherscan.net/

#EXTINF:-1 channel-id="chrome.gpu",Chrome GPU
chrome://cc4c:5589/stream?url=chrome://gpu

Use the cc4c hostname in the above M3U if it resolves on your LAN, or substitute the IP address of the LXC container.

Use a VNC client to connect to the container, when a cc4c stream is active, if you need to login to a streaming service, or to go fullscreen. Logins and fullscreen player settings should be maintained by Chrome just like when you're using Chrome locally.

I'll add a script that'll autorun at container start, once I put this into production, and I'll update this step-by-step accordingly.

4 Likes

As usual, your contributions are greatly appreciated!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.

Here are my recommended additions to ~/.bashrc to run cc4c at login with whatever command line switches you'd like to use:

# Usage: node main.js [options]
#
# Options:
#   -v, --videoBitrate  Video bitrate in bits per second  [number] [default: 6000000]
#   -a, --audioBitrate  Audio bitrate in bits per second  [number] [default: 256000]
#   -f, --frameRate     Minimum frame rate  [number] [default: 30]
#   -p, --port          Port number for the server  [number] [default: 5589]
#   -w, --width         Video width in pixels (e.g., 1920 for 1080p)  [number] [default: 1920]
#   -h, --height        Video height in pixels (e.g., 1080 for 1080p)  [number] [default: 1080]
#   -i, --videoCodec    Video codec (e.g., h264_nvenc, h264_qsv, h264_amf, h264_vaapi)  [string] [default: "h264_nvenc"]
#   -u, --audioCodec    Audio codec (e.g., aac, opus)  [string] [default: "aac"]
#   -?, --help          Show help  [boolean]
#
# Examples:
#   node main.js -v 6000000 -a 192000 -f 30 -w 1920 -h 1080
#   node main.js --videoBitrate 8000000 --frameRate 60 --width 1920 --height 1080

cd /opt/chrome-capture-for-channels
Xvfb :99 -screen 0 1920x1080x16 & x11vnc -quiet -nopw -display :99 -forever & node main.js -v 8500000 -f 60

Put the whole block in there, including the commented out lines, in case you want to modify any of the command line switches in the future.

Here's a sample of what you'd see when logging in using this approach, including one channel tune:

Linux cc4c 6.8.12-2-pve #1 SMP PREEMPT_DYNAMIC PMX 6.8.12-2 (2024-09-05T10:03Z) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Apr 10 16:16:52 UTC 2025 on tty1
Xlib:  extension "DPMS" missing on display ":99".

The VNC desktop is:      cc4c:0
PORT=5900
[2025/04/10 16:18:09.869] Selected settings:
[2025/04/10 16:18:09.870] Video Bitrate: 8500000 bps (8.5Mbps)
[2025/04/10 16:18:09.870] Audio Bitrate: 256000 bps (256kbps)
[2025/04/10 16:18:09.870] Minimum Frame Rate: 60 fps
[2025/04/10 16:18:09.870] Port: 5589
[2025/04/10 16:18:09.870] Resolution: 1920x1080
[2025/04/10 16:18:09.870] Video Codec: h264_nvenc
[2025/04/10 16:18:09.870] Audio Codec: aac
[2025/04/10 16:18:09.874] Chrome Capture server listening on port 5589
[2025/04/10 16:22:04.313] Launching Browser, Opts {
  executablePath: '/usr/bin/google-chrome',
  pipe: true,
  headless: false,
  defaultViewport: null,
  userDataDir: '/opt/chrome-capture-for-channels/chromedata',
  args: [
    '--no-first-run',
    '--hide-crash-restore-bubble',
    '--allow-running-insecure-content',
    '--autoplay-policy=no-user-gesture-required',
    '--disable-blink-features=AutomationControlled',
    '--hide-scrollbars',
    '--window-size=1920,1080',
    '--disable-notifications',
    '--disable-background-networking',
    '--disable-background-timer-throttling',
    '--disable-background-media-suspend',
    '--disable-backgrounding-occluded-windows',
    '--load-extension=/opt/chrome-capture-for-channels/node_modules/puppeteer-stream/extension',
    '--disable-extensions-except=/opt/chrome-capture-for-channels/node_modules/puppeteer-stream/extension',
    '--allowlisted-extension-id=jjndjgheafjngoipoacpjgeicjeomjli',
    '--auto-accept-this-tab-capture',
    '--use-gl=angle',
    '--use-angle=gl-egl',
    '--enable-features=VaapiVideoDecoder,VaapiVideoEncoder',
    '--ignore-gpu-blocklist',
    '--enable-zero-copy',
    '--enable-drdc',
    '--no-sandbox'
  ],
  ignoreDefaultArgs: [
    '--enable-automation',
    '--disable-extensions',
    '--disable-default-apps',
    '--disable-component-update',
    '--disable-component-extensions-with-background-pages',
    '--enable-blink-features=IdleDetection'
  ],
  extensionPath: '/opt/chrome-capture-for-channels/node_modules/puppeteer-stream/extension'
}
[2025/04/10 16:22:04.676] New target page created: about:blank
[2025/04/10 16:22:04.699] Target page changed: chrome-extension://jjndjgheafjngoipoacpjgeicjeomjli/options.html#55200
[2025/04/10 16:22:05.690] Need to initialize stream capabilities
[2025/04/10 16:22:05.759] streaming https://watch.sling.com/1/channel/d96b102ac5c54967bf56b8b57e20c84e/watch
[2025/04/10 16:22:05.936] Target page changed: https://watch.sling.com/1/channel/d96b102ac5c54967bf56b8b57e20c84e/watch
[2025/04/10 16:22:05.954] URL contains watch.sling.com
[2025/04/10 16:22:08.140] New target page created: blob:https://watch.sling.com/f479666f-6ca1-4602-b020-edf65675ca88
[2025/04/10 16:22:08.215] Browser page closed: blob:https://watch.sling.com/f479666f-6ca1-4602-b020-edf65675ca88
[2025/04/10 16:22:11.057] Set Sling to Full Screen and Volume to max

Use the console anytime you want to see what's happening with cc4c. Use WinSCP and/or putty if you need to make a second connection via ssh. Use a VNC client to connect to the container for entering streaming credentials, or for any other interaction with Chrome.

Just in case this helps with anyone else, I updated the resolution in both main.js and the x session to 720p on my humble proxmox server and it really smoothed out playback (at the expense of picture quality obviously). Not quite perfect but definitely watchable.

Does this work on 139+ builds?

I've futzed about with this, but cannot get past this crash every time I try to tune something with Channels:

Error: net::ERR_BLOCKED_BY_CLIENT at chrome-extension://jjndjgheafjngoipoacpjgeicjeomjli/options.html#55200
at
navigate (/opt/chrome-capture-for-channels/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/Frame.js:183:27)
at
process.processTicksAndRejections (node:internal/process/task queues:95:5)
at
async Deferred.race (/opt/chrome-capture-for-channels/node_modules/puppeteer-core/lib/cjs/puppeteer/util/Deferred.js:36:20)
at
async CdpFrame.goto
at async CapPage. goto
(/opt/chrome-capture-for-channels/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/Frame.js:149:25)
(/opt/chrome-capture-for-channels/node_modules/puppeteer-core/1ib/cjs/puppeteer/api/Page.js:574:20)

My research seems to indicate this is from a Chrome security setting that is supposed to be disable-able. But I tried adding the disable flag (HttpsFirstBalancedModeAutoEnable) to main.js and still I'm met with this error.

Interestingly through the Docker method it works just fine. Curious if you had any ideas of other workarounds? I'd prefer to run it the LXC way!

Thanks!

The problem atm is the same as what's being reported in the cc4c thread -- which is that Chrome 139 doesn't work with cc4c. Mine works, as I'm on an older version of Chrome and have avoided updating.

I would imagine you can specify a previous version of Chrome (the last 138 should be fine), but I'm not sure how that's done since we're getting Chrome from the Google repo. You might need to download it as a deb file and install it that way.

1 Like

Awesome, thanks! I shall dabble with a downgrade.

Update:
Downgrading to 138 worked like a charm. Thanks again!

There is a new workaround.

@Ryboflavins I just updated the instructions in Post #1 to incorporate the latest stable version of Chrome and have switched the chrome-capture-for-channels repo to clone back to @fancybits -- as @tmm1 has made numerous changes and updates to it:

1 Like

Also, for those using this Proxmox implementation of cc4c, here's my recommended approach for starting cc4c at login -- along with an auto restart similar to Docker's restart: unless-stopped:

Add the following to the bottom of /root/.bashrc

nano /root/.bashrc
./cc4c.sh

Then create the following shell script and make it executable:

nano /root/cc4c.sh
#!/bin/bash
# cc4c.sh
# 2025.09.11

cd /opt/chrome-capture-for-channels
pkill -TERM -x Xvfb   2>/dev/null || true
pkill -TERM -x x11vnc 2>/dev/null || true
[[ -f /tmp/.X99-lock ]] && rm /tmp/.X99-lock

while true; do
  google-chrome --version
  echo "Starting xvfb, x11vnc, and npm..."

  # Start Xvfb
  Xvfb :99 -screen 0 1920x1080x16 &
  XVFB_PID=$!

  # Start x11vnc
  x11vnc -quiet -nopw -display :99 -forever >x11vnc.log 2>&1 &
  X11VNC_PID=$!

  sleep 1
  grep "The VNC desktop is:" x11vnc.log
  echo "The VNC port is:         $HOST_VNC_PORT"
  sleep 1

  # Start your npm app
  DISPLAY=:99 bun main.js -v $VIDEO_BITRATE -a $AUDIO_BITRATE -f $FRAMERATE -p $CC4C_PORT -w $VIDEO_WIDTH -h $VIDEO_HEIGHT
  NODE_EXIT_CODE=$?

  echo "node exited with code $NODE_EXIT_CODE"

  # Clean up processes
  echo "Killing xvfb ($XVFB_PID) and x11vnc ($X11VNC_PID)..."
  kill $XVFB_PID $X11VNC_PID 2>/dev/null

  wait $XVFB_PID $X11VNC_PID 2>/dev/null

  [[ -f /tmp/.X99-lock ]] && rm /tmp/.X99-lock

  echo "Restarting everything in 2 seconds..."
  sleep 2
done
chmod +x /root/cc4c.sh

Also, you need to add some additional env var values to /etc/environment. Which when done, should be along these lines:

nano /etc/environment
DISPLAY=:99
CHROME_BIN=/usr/bin/google-chrome
DOCKER=true
LIBGL_ALWAYS_INDIRECT=1
LIBVA_DRIVER_NAME=iHD
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
HOST_PORT=5589
CC4C_PORT=5589
HOST_VNC_PORT=5900
VIDEO_BITRATE=9500000
AUDIO_BITRATE=256000
FRAMERATE=60
VIDEO_WIDTH=1920
VIDEO_HEIGHT=1080
VIDEO_CODEC=h264_vaapi
AUDIO_CODEC=aac
TZ=US/Mountain

Finally, logout and log back in for this all to take effect:

exit

When you log back in cc4c will start automatically, with error recovery.

Thanks so much! I setup a fresh container this way and it worked immediately!

Now if only I could get device passthrough to work for hardware encoding.

1 Like

Intel GPU?

1 Like

Yup! i7-11700. It works on my Channels LXC, but for some reason Device Passthrough is greyed out now on my containers.

I improved the cc4c.sh script above to be more robust in terms of cleaning up after trapped or untrapped errors. Plus, there are additions to the /etc/environment file that need to be made. Check this post:

1 Like

Also, for anybody that's made the jump to v2.01, and you want to pull the few minor changes that have been made since:

cd /opt/chrome-capture-for-channels
git pull --ff-only

Updated! Thanks for this.

1 Like