Introducing PrismCast: Browser-based Live TV Capture for Channels DVR and Plex

Thanks for elaborating - I have no use/utility for FruitDeepLinks or similar solutions...so bear with me while I get my head around the ask. :smile:

What're you passing to PrismCast currently to try to get this to work? Is it a URL that hits FDL and then redirects to espn.com in your example?

Edit: it never fails to amaze me the lengths people go to for sports programming...color me amused, but I shouldn't really be surprised that FDL-type things exist. It's got to be a bear to maintain with how fragile scraping Apple's sites has to be. :smile:

Yes.

However it's also possible for PrismCast to query an FDL URL to get the real URL, and then use that. Which is what we're doing in ADBTuner, and may be a better solution here as well. That way the correct PrismCast Profile will be used.

Given this, would it make more sense for me to take this on and submit a PR?

I'm unlikely to add, nor accept into PrismCast, any special handling just for FDL - too fragile and too coupled to a singular external solution.

I'm going to add the ability for PrismCast to match against the redirect to determine profile, which should address your use case.

In general - I don't tend to look favorably upon special handling for other integration points unless there's general utility / use cases. Everyone should talk using the same standardized endpoints and folks can script around them. At the end of the day, I'm the one who has to maintain it all, so I'm a bit picky in this area. :smile:

1 Like

That'll work.

Hey- FDL has been a hobby of mine. Yes, it’s been a bear to maintain/build, but this is the Playground!

Curious of what standardized endpoint would be easier on your end?

A sincere hat tip to you! :smile: Always appreciate someone who takes time and energy to put into their passion projects, especially ones so dependent on all the weirdness that is our favorite fruit company!

I think for me the redirect answers the "standardized endpoint" question...that's something all manner of sites do, and it's a standardized protocol behavior, etc. I just prefer not to add special handling for other integration endpoints unless it's essential to the core functionality I'm trying to provide. If you change your endpoints/API down the line, PrismCast would have to track it or break, etc.

Redirects are a well-defined interface that I can safely develop against and not worry about how you may choose, for example, to evolve your JSON format or do something differently, since fundamentally you're a redirection endpoint to other sites.

Fair enough- I’ll fully admit I’m a novice at this and learning every single day. Love to take tips from actual developers that know what the heck they’re doing :slight_smile: . Your project seems very cool and a good match for FDL users so hope some integration could work out - even with API. Happy to take any constructive feedback from the community.

1 Like

A couple of followups before I roll out an update...so I'm assuming you're programmatically adding channels to PrismCast that point to FDL...why aren't you also just telling PrismCast which profiles you want it to use, since presumably you know where most of these sites ultimately lead to?

That solves the ambiguity you're ultimately looking to close out. PrismCast doesn't know about every single linear/streaming site out there...just the major ones (and if I'm being honest, the major ones I care about and use or have access to).

What am I not seeing that you can't already do right now, without needing PrismCast to know about where things ultimately redirect/resolve to?

A given FruitDeepLinks lane can land on any one of these sites (I was just working on a list, as I thought this might come up), based on whatever event is "airing" on a given virtual channel at that point in time. A given provider can be airing many events simultaneously, spread across many different lanes.

This all assumes we use the same approach for PrismCast we've used for cc4c integration. Not mandatory, of course, but would make for the easiest transition between cc4c, ch4c and Prismcast.

I believe this is a comprehensive list (@KineticMan?):

espn.com
app.primevideo.com
peacocktv.com
nbcsports.com
kayosports.com
paramountplus.com
nba.com
vix.com
cbssports.com
foxsports.com
open.dazn.com
play.hbomax.com
tntdrama.com
trutv.com
nfl.com

Presumably, not all of these would use the same profile. Plus, I already know that "auto" doesn't fill-the-bill, based on the testing I've done so far.

app.primevideo.com - Amazon Prime Video
gothamsports.com - Gotham Sports (MSG/YES)
kayosports.com.au - Kayo Sports
open.dazn.com - DAZN
play.hbomax.com - Max (HBO Max)
tv.apple.com - Apple TV+ (MLS/MLB)
victoryplus.com - Victory+
vix.com - ViX
watch.fanatiz.com - Fanatiz Soccer
www.beinsports.com - beIN Sports
www.cbssports.com - CBS Sports
www.espn.com - ESPN
www.foxsports.com - FOX Sports
www.nba.com - NBA
www.nbcsports.com - NBC Sports
www.nfl.com - NFL
www.paramountplus.com - Paramount+
www.peacocktv.com - Peacock
www.tntdrama.com - TNT
www.trutv.com - truTV

just pulled all the URLs found in the DB (added a few new services this weekend)

So I sort of followed the instructions posted at the very top of this thread:

I opened a command prompt running as administrator and then ran the following commands:

npm install -g prismcast
  prismcast service install

Now, after running the prismcast service install command, I got a bunch of error messages. So I put all of that in Chat GPT and it gave me the following commands to run which successfully installed the service and created a task to boot up at startup of Windows 11:

1. Create a Service script
Create a file, for example:
C:\prismcast\prismcast-service.cmd

Put this inside:

@echo off
cd /d "C:\Users\stive\AppData\Roaming\npm\node_modules\prismcast"

set PRISMCAST_SERVICE=1
"C:\Program Files\nodejs\node.exe" dist\index.js

In this code above, obviously I have my own paths to the prismcast node modules and node.exe so you'll need to figure out what the paths are on your machine and put them in there.

2. Create the scheduled task manually (run CMD as administrator)

schtasks /Create ^
 /TN "PrismCast" ^
 /TR "C:\prismcast\prismcast-service.cmd" ^
 /SC ONLOGON ^
 /RL HIGHEST ^
 /F

Now I go a step further to have all of this run entirely hidden, like literally nothing pops up at startup. But if you go to your task manager and look at everything running, you'll see node running which indicates the prismcast service is successfully running. Here's how I accomplished that:

1. Go to your task in task scheduler and make sure the following are checked under the general tab:
"Run whether the user is logged on or not"
"Run with highest privelages"
DO NOT check "run only when user is logged on"

2. Select the Actions Tab and do the following:
For Program/script:
C:\Windows\System32\cmd.exe
For Arguments (this is the location of the .cmd file that you created earlier):
/c "C:\prismcast\prismcast-service.cmd"
And finally for Start in (the location of the folder that the .cmd file is located in):
C:\prismcast

And that should do it! Let me know if you have any other questions!

There's a bug related to auto that's fixed in v1.1.0. That release also adds redirect resolution — PrismCast will follow redirect URLs (like FDL links) and match against the sites it knows about, if you've set the profile to auto or don't specify one. If you specify a profile, PrismCast honors that, always.

Now to the heart of things: that's a lot of sites. I keep up with a handful that matter to me, but you're already adding these programmatically — so you can resolve which sites they are and tell PrismCast which profile to use. You don't need to rely on my curated list. That's the more sustainable answer long-term.

You can test individual sites (say nba.com) by adding a user-defined channel in PrismCast and experimenting with different profiles until you find the right one - no restart of PrismCast needed…channels can be added/removed/modified dynamically, though you may need to refresh the M3U on Channels DVR if you’re adding/removing channels since Channels polls for updates rather than receiving them dynamically. Then feed that into your own programmatic mapping.

I'm not going to sort out all these sites myself — but if you track down which profiles work for ones I don't currently support, I'm happy to add them. Grab v1.1.0 and try it - I've already added some from your list. That should get you where you want to be, at least until one of the sites I don't watch closely breaks. I'd appreciate the contributions to grow the list for the community's sake - so please, contribute away.

Also new in v1.1.0: /play. For your use case, you don't really care about predefined channels — you just want PrismCast to do its thing. /play takes an arbitrary URL (including redirect links like FDL), an optional profile, and when appropriate, a channel selector (disneyplus.com, usanetwork.com primarily). Build your own M3U, point it at /play, and PrismCast will happily serve you. Documentation under the API reference tab of PrismCast.

To summarize - two general approaches you can use to solve for your scenario:

  1. "Peek" at the redirect URLs from FDL and build your own list of site->profile mappings. Use that to either generate an M3U that uses PrismCast's ad hoc /play capabilities to serve to Channels.

  2. Your current approach of loading channels into PrismCast. For the sites PrismCast knows about, it'll resolve to the right profile. For the ones it doesn't, you'll need to add those and again build that mapping described in option 1.

I've push an updated container this morning based on v1.1.0 as bnhf/prismcast:latest (aka bnhf/prismcast:2026.02.03). The next container I build though, I'll switch to using your version numbers in the tag (along with latest of course).

So far, I'm optimistic this will provide a path for cc4c and ch4c users to use PrismCast instead. It also looks promising for FruitDeepLinks users. I'll have more of a chance to test some of the concepts you've implemented, in the coming days.

Thanks for doing this!

I've push an updated container this morning based on v1.1.0 as bnhf/prismcast:latest (aka bnhf/prismcast:2026.02.03 ). The next container I build though, I'll switch to using your version numbers in the tag (along with latest of course).

I should probably start building these and publishing them myself to relieve you of that burden and keep things more in sync on my end. I’ll take a look at that later this week or this weekend and would welcome your feedback and testing. Your work is appreciated, but it’s probably cleaner to consolidate.

So far, I'm optimistic this will provide a path for cc4c and ch4c users to use PrismCast instead. It also looks promising for FruitDeepLinks users. I'll have more of a chance to test some of the concepts you've implemented, in the coming days.

Give it a go. I think if we can get a healthy list of the 80-90% of sites people use cataloged in PrismCast, things will “just work” for most people, most of the time, by default.

Thank you for taking the time to explain the use case and what you’re trying to accomplish and why…I’m trying to build something robust, maintainable, and scalable that I use every single day (in my case for everything but sports, amusingly enough!).

Let’s see what we discover as you and others play with it…

@hjd

Hey mate,

Ended up editing the code to remove the following from channelSelection.ts in browser

  // Step 2: Wait for the "WATCH LIVE" button to appear on the entity modal. The button is an <a> element with a specific data-testid attribute. After clicking the
  // tile, the site performs a SPA navigation that renders a modal with playback options.
  const playButtonSelector = "[data-testid=\"live-modal-watch-live-action-button\"]";

  try {

    await page.waitForSelector(playButtonSelector, { timeout: CONFIG.streaming.videoTimeout });
  } catch {

    return { reason: "Play button did not appear after clicking channel tile.", success: false };
  }

  // Get the play button coordinates for clicking.
  const playTarget = await evaluateWithAbort(page, (selector: string): ClickTarget | null => {

    const button = document.querySelector(selector);

    if(!button) {

      return null;
    }

    (button as HTMLElement).scrollIntoView({ behavior: "instant", block: "center", inline: "center" });

    const rect = button.getBoundingClientRect();

    if((rect.width > 0) && (rect.height > 0)) {

      return { x: rect.x + (rect.width / 2), y: rect.y + (rect.height / 2) };
    }

    return null;
  }, [playButtonSelector]);

  if(!playTarget) {

    return { reason: "Play button found but has no dimensions.", success: false };
  }

  // Click the play button to start live playback.
  await scrollAndClick(page, playTarget);

As this seemed to be catching my channels in a loop (plays straight from the tile - no click play needed)

Removing this obviously could cause other channels to break, did you have any ideas that could be implemented to bypass the click play if it's not needed to extend compatibility? Could we check for video element if no play button is found?

Super excited to have this in a working state though! Thanks Heaps!

Fair enough- I’ll fully admit I’m a novice at this and learning every single day. Love to take tips from actual developers that know what the heck they’re doing :slight_smile: . Your project seems very cool and a good match for FDL users so hope some integration could work out - even with API. Happy to take any constructive feedback from the community.

One suggestion - given the ways people use FDL, you could conceivably serve a custom M3U depending on their preferred tuning platform (PrismCast, CC4C, etc.). That way, people like @bnhf wouldn’t need to do anything other than point Channels to your custom M3U. Given the API endpoints I’ve provided, it should be straightforward. The only gap, such that it is, will be to finish either building out the list of sites->profiles within PrismCast, or create your own mapping between the two and tell PrismCast which profile to apply for which site and have it play stuff.

Just a suggestion…but I suspect one you might find popular. I’m assuming you’re already doing something similar with your multisource lanes M3U, but you might want to simplify and abstract it to support the various solutions people like to use and either name them accordingly (e.g. prismcast.m3u) or something like tuner.m3u?type=prismcast if you wanted to maintain the same endpoint and use the type parameter to switch between supported tuner platforms.

PS: we were all novices at one point…I’m just an old man with a lot of lessons learned over the years. Keep indulging your passion projects…you learn the most from them, I’ve found. :smile:

Glad you’re up and running.

That code is essential for many sites, as you suspected…so it’s not going anywhere. If you’d like to submit a PR for a profile that addresses your use case, I’m happy to take a look at it. If/when I have time, I might poke at this…but I’m one person, with a lot of competing priorities as you can imagine. I appreciate you understanding.

1 Like

@hjd

Yeah I'm absolutely fine with running a self edited version myself.

Will look into trying to make something like a 'apiMultiVideoDirect' profile and if it tests well, I'll submit it for review.

On another note, testing the direct to Plex intergration and although ive disabled all the preset channels, I'm only getting those channels, as well as the 10 tuner default. Any way im missing to refresh the HDHR emulation channels and info?

2026/02/03 22:50:00.714512 [HLS] Session ch9010-dANY-ip192.168.4.38 stopped: segment buffer full: exceeded 10000000 bytes
2026/02/03 22:50:00.714595 [HLS] Stopping transcoder session ch9010-dANY-ip192.168.4.38 (out=0s finished=true first_seq=1 last_seq=0)
2026/02/03 22:50:00.775313 [TNR] Closed connection to M3U-PrismCast for ch9010 Fox Cricket
2026/02/03 22:50:00.775346 [HLS] Probed live stream in 680.865292ms: h264 1920x1080 progressive 29272736bps

Also getting this error, seems to be playing fine in VLC though, so I'm not sure what's causing the error.

Edit: Looks like im just gettingn huge spikes in bitrate - it should only be a 6.5mbps 1080p50 input. Any advice to get this to play nicely?

EDIT2: Seems like some of the settings weren't actually updating unless I did a full reset. Noticed this when I tried to swap from 1080p to 720p to help reduce buffer size.

I am having trouble connecting the VNC server running in the docker container using the provided Docker run command.

I am using the URL, but when click "Connect" in the NoVNC window, it is stuck on "Connecting ....". What am I missing?

You'd be better off using Portainer, and the sample prismcast.yaml file in a Portainer-Stack, but if you must use docker run the equivalent would be:

docker run -d \
  --name prismcast \
  --hostname prismcast \
  --dns-search localdomain \
  --shm-size 1gb \
  -p 5589:5589 \
  -p 5900:5900 \
  -p 6080:6080 \
  -p 5004:5004 \
  -e DISPLAY_NUM=99 \
  -e SCREEN_WIDTH=1920 \
  -e SCREEN_HEIGHT=1080 \
  -e SCREEN_DEPTH=24 \
  -v prismcast-data:/root/.prismcast \
  --restart unless-stopped \
  --health-cmd="wget -q --spider http://localhost:5589/health" \
  --health-interval=30s \
  --health-timeout=10s \
  --health-retries=3 \
  --health-start-period=30s \
  bnhf/prismcast:latest