UPDATE 2023-06-20: While this solution can and does still work, it is unnecessary to do so because this now exists:
Please follow along there as I will no longer be updating this workaround.
Inspired by the HDMI for Channels project, this solution attempts to do the same thing. Namely, this project allows the user to stream any station, including ones protected by DRM, in Channels--so long as there is a web source to get the content from that the user has legal access to. However, this solution focuses on doing so without using a separate (expensive) HDMI capture device and setup, eliminating multiple potential physical hardware fail points, and using an existing always-on Channels DVR Server.
Important Information
Version History (Current: v1.2023.06.16)
- v1.2023.06.16 - Modified Task Scheduler directions, issue tracker update
- v1.2023.06.15 - Beta Release
- v0.2023.06.14 - Alpha Testing Complete
Please Note: This guide has been written for Windows as that is what I am most comfortable with. There is no reason that all of this could not be accomplished on a Mac or Linux with the appropriate scripting language edits and comparable add-on tools. Additionally, this could be run on a machine separate from the Channels server, but I prefer to limit complications like multiple PCs.
Prerequisites
(1) Download and install VLC
Files: Official download of VLC media player, the best Open Source player - VideoLAN
(2) Download and install the Go Programming Language
(3) Download FFmpeg
Files: Download FFmpeg
OPTIONAL - Install FFmpeg by these directions: Installing FFmpeg on Windows {Step-by-Step}
(4) Download, install, and update Screen Capture Recorder
In Windows Explorer, navigate to C:\Program Files (x86)\Screen Capturer Recorder\configuration_setup_utility\vendor\ffmpeg\bin
Copy and paste the FFmpeg files from Step #3 into this directory and replace the out-of-date ones that are included with the installation files.
(5) Download and Unzip Android HDMI for Channels
FILES: GitHub - tmm1/androidhdmi-for-channels: androidhdmi-for-channels
- Under Code, select Download Zip.
-
Save the file.
-
Create a directory where you will want the solution to run from. For instance, mine are in
C:\Users\{USERNAME}\OneDrive\04 Media\Channels Backups\~Programs\Computer Stream for Channels
-
Unzip all the files to that directory.
Preparation
(6) Change Chrome Settings | Log in to Providers | Install Extensions
-
Open up Chrome or your preferred browser (we'll be using Chrome for the base example).
-
In your Settings, go to On Startup, and change the setting to Open the New Tab page.
This will make Chrome forget its prior sessions, which will be important as we'll only want one Chrome ever running to capture the stream.
The solution will just capture whatever is on the screen; it currently does not do any automatic logging in or anything like that in the background. Therefore, you should go to the website of whatever provider you want to use and make sure you are connected and that it can play. For instance, if you want to capture NFL Network, you should...
-
Make sure anything that would stop a stream from auto-playing is turned off or disabled.
-
Go to the website where the live feed streams (
https://www.nfl.com/network/watch/nfl-network-live
). -
Click to Authenticate and follow the directions on screen to do so
-
Confirm the live stream(s) is(are) playing correctly.
-
Go to Fullscreen Anything - Chrome Web Store and install the extension.
-
Go to Auto Hide Cursor - Chrome Web Store and install the extension. Change settings to 1 second.
(7) Change VLC Settings
VLC is used to make the stream, but it does some odd things visually that need to be corrected first.
- Open up VLC
- On the menu, go to Tools and select Effects and Filters.
- Select the tab named Visual Effects and under that select Geometry.
- Here, check Transform and under the menu select Flip Vertically. Make sure nothing else is selected.
NOTE: This will make anything you play in VLC be flipped upside down. You can turn it off temporarily if you are using VLC for real, but remember to turn it back otherwise things are not going to go well for you!
(8) Modify Android HDMI for Channels
- Edit main.go and replace it with this code:
package main
import (
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/exec"
"strconv"
"sync"
"time"
"github.com/gin-gonic/gin"
)
var (
tunerLock sync.Mutex
tuners = []tuner{
{
url: "http://{IP_ADDRESS_OF_YOUR_SERVER}:7653/stream/",
pre: "C:\\Users\\{USERNAME}\\OneDrive\\04 Media\\Channels Backups\\~Programs\\Computer Stream for Channels\\prebmitune.bat",
start: "C:\\Users\\{USERNAME}\\OneDrive\\04 Media\\Channels Backups\\~Programs\\Computer Stream for Channels\\bmitune.bat",
stop: "C:\\Users\\{USERNAME}\\OneDrive\\04 Media\\Channels Backups\\~Programs\\Computer Stream for Channels\\stopbmitune.bat",
},
}
)
type tuner struct {
url string
pre, start, stop string
active bool
}
type reader struct {
io.ReadCloser
t *tuner
channel string
started bool
}
func init() {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.ResponseHeaderTimeout = 5 * time.Second
transport.DialContext = (&net.Dialer{
Timeout: 5 * time.Second,
}).DialContext
http.DefaultClient.Transport = transport
}
func (r *reader) Read(p []byte) (int, error) {
if !r.started {
r.started = true
go func() {
if err := execute(r.t.start, r.channel); err != nil {
log.Printf("[ERR] Failed to run start script: %v", err)
return
}
}()
}
return r.ReadCloser.Read(p)
}
func (r *reader) Close() error {
if err := execute(r.t.stop); err != nil {
log.Printf("[ERR] Failed to run stop script: %v", err)
}
tunerLock.Lock()
r.t.active = false
tunerLock.Unlock()
return r.ReadCloser.Close()
}
func execute(args ...string) error {
t0 := time.Now()
log.Printf("Running %v", args)
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
log.Printf("Finished running %v in %v", args[0], time.Since(t0))
return err
}
func tune(idx, channel string) (io.ReadCloser, error) {
tunerLock.Lock()
defer tunerLock.Unlock()
var t *tuner
log.Printf("tune for %v %v", idx, channel)
if idx == "" || idx == "auto" {
for i, ti := range tuners {
if ti.active {
continue
}
t = &tuners[i]
break
}
} else {
i, _ := strconv.Atoi(idx)
if i < len(tuners) && i >= 0 {
t = &tuners[i]
}
}
if t == nil {
return nil, fmt.Errorf("tuner not available")
}
execute(t.pre)
resp, err := http.Get(t.url)
if err != nil {
log.Printf("[ERR] Failed to fetch source: %v", err)
return nil, err
} else if resp.StatusCode != 200 {
log.Printf("[ERR] Failed to fetch source: %v", resp.Status)
return nil, fmt.Errorf("invalid response: %v", resp.Status)
}
t.active = true
return &reader{
ReadCloser: resp.Body,
channel: channel,
t: t,
}, nil
}
func run() error {
r := gin.Default()
r.SetTrustedProxies(nil)
r.GET("/play/tuner:tuner/:channel", func(c *gin.Context) {
tuner := c.Param("tuner")
channel := c.Param("channel")
c.Header("Transfer-Encoding", "identity")
c.Header("Content-Type", "video/mp2t")
c.Writer.WriteHeaderNow()
c.Writer.Flush()
reader, err := tune(tuner, channel)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer func() {
reader.Close()
}()
io.Copy(c.Writer, reader)
})
return r.Run(":7654")
}
func main() {
err := run()
if err != nil {
panic(err)
}
}
Basically, what has been modified is the removal of an error handler for the "pre" step and the "pre" step being forced to run before an attempt to tune into a station. Additionally, the original program was looking for an exterior connection, while this one is focused inward.
-
Be sure to update the fields like for the IP Address of your server and the directory path of where the files are. When dealing with Windows machines, your directory paths must have two slashes!
-
Rename prebmitune.sh to prebmitune.bat and edit it to have this code:
@ECHO OFF
REM ***** Start local stream *****
REM *** Send the Escape key to make sure the monitor and audio is on correctly, otherwise there are issues ***
ECHO >script.vbs set shell = CreateObject("WScript.Shell"):shell.SendKeys "{ESC}" & script.vbs
REM *** Wait 5 seconds to make sure everything is up ***
ping 127.0.0.1 -n 5
REM *** Start the stream in VLC with a number of options ***
CALL start vlc.exe dshow:// :dshow-vdev=screen-capture-recorder :dshow-adev=virtual-audio-capturer :dshow-aspect-ratio=16:9 :dshow-chroma= :dshow-fps=0 :no-dshow-config :no-dshow-tuner :dshow-tuner-channel=0 :dshow-tuner-frequency=0 :dshow-tuner-country=0 :dshow-tuner-standard=0 :dshow-tuner-input=0 :dshow-video-input=-1 :dshow-video-output=-1 :dshow-audio-input=-1 :dshow-audio-output=-1 :dshow-amtuner-mode=1 :dshow-audio-channels=0 :dshow-audio-samplerate=0 :dshow-audio-bitspersample=0 :live-caching=300 :sout=#transcode{vcodec=mp1v,vfilter=transform,fps=60,scale=Auto,width=1920,height=1080,acodec=mpga,ab=128,channels=2,samplerate=44100,afilter=remap,scodec=none}:http{mux=ts,dst=:7653/stream/} :no-sout-all :sout-keep
This changes the original function to now start the stream through VLC using the Screen Capture Recorder. This is very resource intensive, which is why we only want it running when we are actively using a station.
- Rename stopbmitune.sh to stopbmitune.bat and edit it to have this code:
@ECHO OFF
REM ***** End local stream *****
taskkill /IM vlc.exe /T /F
taskkill /IM chrome.exe /T /F
Here, when a station is closed, we forcefully kill the stream in VLC and the Chrome session. VLC and Chrome must be fully dedicated to this solution in order for this to work.
- Rename bmitune.sh to bmitune.bat and edit it to have this code:
@ECHO OFF
REM ***** Actions to run before selecting Station *****
SET ScriptLog=bmitune.log
SET "StationID=%1"
SET StationLink=""
SET HelpPlay="Y"
ECHO %DATE% - %TIME% - STARTING TUNE TO STATION >> %ScriptLog%
ECHO %DATE% - %TIME% - TUNING TO STATION WITH THE ID %StationID% >> %ScriptLog%
REM ***** Set the Link to the Station *****
IF "%StationID%"=="nfl" (
SET StationLink="https://www.nfl.com/network/watch/nfl-network-live"
SET HelpPlay=""
)
IF "%StationID%"=="nbcnewsnow" ( SET StationLink="https://www.nbc.com/live?brand=nbc-news&callsign=nbcnews" )
IF "%StationID%"=="nbclocal" ( SET StationLink="https://www.nbc.com/live?brand=nbc&callsign=nbc" )
IF "%StationID%"=="bravoe" ( SET StationLink="https://www.nbc.com/live?brand=bravo&callsign=bravo_east" )
IF "%StationID%"=="bravow" ( SET StationLink="https://www.nbc.com/live?brand=bravo&callsign=bravo_west" )
IF "%StationID%"=="cnbc" ( SET StationLink="https://www.nbc.com/live?brand=cnbc&callsign=cnbc" )
IF "%StationID%"=="ee" ( SET StationLink="https://www.nbc.com/live?brand=e&callsign=e_east" )
IF "%StationID%"=="ew" ( SET StationLink="https://www.nbc.com/live?brand=e&callsign=e_west" )
IF "%StationID%"=="golf" ( SET StationLink="https://www.nbc.com/live?brand=golf&callsign=golf" )
IF "%StationID%"=="msnbc" ( SET StationLink="https://www.nbc.com/live?brand=msnbc&callsign=msnbc" )
IF "%StationID%"=="oxygene" ( SET StationLink="https://www.nbc.com/live?brand=oxygen&callsign=oxygen_east" )
IF "%StationID%"=="oxygenw" ( SET StationLink="https://www.nbc.com/live?brand=oxygen&callsign=oxygen_west" )
IF "%StationID%"=="syfye" ( SET StationLink="https://www.nbc.com/live?brand=syfy&callsign=syfy_east" )
IF "%StationID%"=="syfyw" ( SET StationLink="https://www.nbc.com/live?brand=syfy&callsign=syfy_west" )
IF "%StationID%"=="telemundolocal" ( SET StationLink="https://www.nbc.com/live?brand=telemundo&callsign=telemundo" )
IF "%StationID%"=="usae" ( SET StationLink="https://www.nbc.com/live?brand=usa&callsign=usa_east" )
IF "%StationID%"=="usaw" ( SET StationLink="https://www.nbc.com/live?brand=usa&callsign=usa_west" )
REM ***** Error Checking *****
REM *** Station is Missing ***
IF "%StationID%"=="" (
ECHO %DATE% - %TIME% - ERROR - NO STATION ID RECEIVED >> %ScriptLog%
GOTO END
)
REM *** Link not Set ***
IF %StationLink%=="" (
ECHO %DATE% - %TIME% - ERROR - NO LINK SET >> %ScriptLog%
GOTO END
)
REM ***** Station Launch *****
ECHO %DATE% - %TIME% - SET THE LINK TO %StationLink% >> %ScriptLog%
REM *** Launch Chrome with the options: Disable Autoplay Settings | Hide Crash Report Bubble from last forced close | Full Screen | In a New Window | To this link ***
START chrome --autoplay-policy=no-user-gesture-required --hide-crash-restore-bubble --kiosk /new-window %StationLink%
REM *** Wait 30 seconds to make sure Chrome is loaded and the video has started ***
ping 127.0.0.1 -n 30
REM *** Send the keys Ctrl+Space to make fullscreen ***
ECHO >script.vbs set shell = CreateObject("WScript.Shell"):shell.SendKeys "^ " & script.vbs
REM *** Some stations do not want to autoplay and need help getting going ***
IF %HelpPlay%=="" ( GOTO SKIPHELPPLAY )
ping 127.0.0.1 -n 5
ECHO >script.vbs set shell = CreateObject("WScript.Shell"):shell.SendKeys " " & script.vbs
ECHO %DATE% - %TIME% - HELP PLAY EXECUTED >> %ScriptLog%
:SKIPHELPPLAY
ECHO %DATE% - %TIME% - SUCCESSFULLY LAUNCHED THE STATION WITH THE ID %StationID% AT THE LINK %StationLink% >> %ScriptLog%
REM ***** Exit and Finish *****
:END
ECHO %DATE% - %TIME% - FINISHED >> %ScriptLog%
EXIT /B
You can have as many stations as you have access to, or even create your own. If there is a webpage for it, then it can become a station! See Future Plans below for some other considerations and possibilities.
(9) Create an audio silencer
Once a station launches, the audio will be coming out of the PC's default speakers. The computer cannot be muted as the capture method just records whatever audio the computer is playing, too. As such, you should do something like plug in speakers or headphones and physically turn off their volume. Even just a stereo connector/convertor not connected to anything would work. I have my computer connected by a HDMI cable to a TV and have the audio play only on that TV. Therefore, so long as the TV is off, no audio is playing where I don't want it.
Of course, if you do not care, and your PC is in some part of your house that cannot be heard, then this step can be skipped entirely.
Setup
(10) Create androidhdmi-for-channels.exe
- Open up a Command Prompt (in Administrative Mode)
2, Navigate to the directory containing all of the "Android HDMI for Channels" files from above.
- Once there, type "go build".
- You will now have a file called androidhdmi-for-channels.exe, which is the actual program you need to do anything. If there were errors during the build, then something is wrong in the main.go code. The first time you run this it might take several minutes, but it will be much faster every subsequent time after that (if you need to fix it).
For an additional example of this process, see .strmlnk generator - #2 by babsonnexus.
(11) Schedule androidhdmi-for-channels.exe to run on startup
-
In the same directory, create a new file called androidhdmi-for-channels.ps1
-
Edit the file and add this code:
Start-Process -WindowStyle hidden "C:\Users\{USERNAME}\OneDrive\04 Media\Channels Backups\~Programs\Computer Stream for Channels\androidhdmi-for-channels.exe"
-
Be sure to edit the path to your directory!
-
Open Task Scheduler:
- Create a new task and follow the directions from the link below on how to setup a PowerShell task:
https://community.spiceworks.com/how_to/17736-run-powershell-scripts-from-task-scheduler
- Make sure you set it to:
- Run only if the user is logged in (critical for now in order to not hide windows)
- Run with the highest privileges
- Triggered When user logs on and make sure it is the account you will run this under
- Start the program powershell with these arguments: -ExecutionPolicy Bypass -File "C:\Users{USERNAME}\OneDrive\04 Media\Channels Backups~Programs\Computer Stream for Channels\androidhdmi-for-channels.ps1", but replacing the directory path with your own.
- Right click on the newly created task and click Run to execute it immediately and confirm it is working. If it is, you'll see it in Task Manager:
(12) Create stations in Channels
- Add a Custom Channels:
Channels Support - Add Custom Channels with M3U Playlists
- Begin by...
- Giving this a name you will love forever
- Change the Stream Format to MPEG-TS
- Prefer Channel logos from Guide Data
- Limit to 1 Stream
- Your preference for Channel Numbers (You can even put them in the m3u in the next step with a tvg-chno="###" flag. For instance, if you want to use the original TVE numbers that Channels has, you can do so!)
- Now, change the Source to Text and put this in:
#EXTM3U
#EXTINF:-1 channel-id="NFL Network",NFL Network
http://10.255.1.144:7654/play/tuner/nfl
#EXTINF:-1 channel-id="NBC News Now",NBC News Now
http://10.255.1.144:7654/play/tuner/nbcnewsnow
#EXTINF:-1 channel-id="NBC Local",NBC Local (WJAR)
http://10.255.1.144:7654/play/tuner/nbclocal
#EXTINF:-1 channel-id="Bravo (East)",Bravo (East)
http://10.255.1.144:7654/play/tuner/bravoe
#EXTINF:-1 channel-id="Bravo (West)",Bravo (West)
http://10.255.1.144:7654/play/tuner/bravow
#EXTINF:-1 channel-id="CNBC",CNBC
http://10.255.1.144:7654/play/tuner/cnbc
#EXTINF:-1 channel-id="E! (East)",E! (East)
http://10.255.1.144:7654/play/tuner/ee
#EXTINF:-1 channel-id="E! (West)",E! (West)
http://10.255.1.144:7654/play/tuner/ew
#EXTINF:-1 channel-id="Golf",Golf
http://10.255.1.144:7654/play/tuner/golf
#EXTINF:-1 channel-id="MSNBC",MSNBC
http://10.255.1.144:7654/play/tuner/msnbc
#EXTINF:-1 channel-id="Oxygen (East)",Oxygen (East)
http://10.255.1.144:7654/play/tuner/oxygene
#EXTINF:-1 channel-id="Oxygen (West)",Oxygen (West)
http://10.255.1.144:7654/play/tuner/oxygenw
#EXTINF:-1 channel-id="SyFy (East)",SyFy (East)
http://10.255.1.144:7654/play/tuner/syfye
#EXTINF:-1 channel-id="SyFy (West)",SyFy (West)
http://10.255.1.144:7654/play/tuner/syfyw
#EXTINF:-1 channel-id="Telemundo Local",Telemundo Local (WNEU)
http://10.255.1.144:7654/play/tuner/telemundolocal
#EXTINF:-1 channel-id="USA (East)",USA (East)
http://10.255.1.144:7654/play/tuner/usae
#EXTINF:-1 channel-id="USA (West)",USA (West)
http://10.255.1.144:7654/play/tuner/usaw
You can add, remove, or modify (especially local IDs) these stations to your heart's content. Most importantly is that the name/case after tuner must match what we put in the bmitune.bat above.
- Click save and you will now have the stations. However, they are not mapped to their guide data. For that, click the gear icon next to it and select Manage Lineup.
- For each station, click the + icon the right and in the dropdown change the search to Search All Lineups.
- Search for what you are looking for and select the most appropriate one. This can be tricky with some stations, so don't worry if you have to try a few different options to get it right.
- You will now end up with something like this:
- Close out with X and either let the guide data update naturally or force an update.
(13) Testing and Warnings
- If you go to the Guide and select your new source, you will see all these stations and guide data now available:
-
DO NOT UNDER ANY CIRCUMSTANCES launch the station on the same machine it will be broadcasting from. Go to another machine or a client. If you do this, you will be in a terrible loop that will blow out your eardrums and you will be forced to manually close everything.
-
Due to the various coded wait periods and the general lag, you will need a buffer on any recording. I would recommend a couple of minutes on each end.
-
Trying to quick tune between these stations is not a good idea as you may kick off multiple processes at once. I have also seen that sometimes Channels takes 10-15 seconds to disconnect, which is required to close out everything that is playing.
-
Otherwise, you should be good to go and get expected results:
-
I am finding that on the client side I have to set the decoding to "Software" in order to see the stations and recordings. I would prefer that this was unnecessary and am investigating.
-
Now, you can do the usual Favorite/Hide and add to Channel Collections to get these stations wherever you like.
Other Details
(i) Known Issues, Bugs, Requests, and Future Plans
-
Batch script logging appears to be broken while running in the background. This is not mission critical, so will launch without a solution.
-
See if something can done with the encoding so that the stations and recordings can play back with the "Hardware" setting instead of the "Software" setting.
-
Look for ways to make the quality of the stream higher with tweaking settings. UPDATE 2023-06-15: Looking into using OBS or other similar tools for better quality, faster streaming, etc...
-
Code efficiencies and brute force / hardcoded workarounds in general, especially when dealing with the "prebmitune" step in the main.go file.
-
Finding a way for all of this to happen in the background so a machine is not completely taken over when it is running. During alpha testing, I had been able to make it run in the background by changing Task Scheduler options to run whether the user is logged in or not. However, although I got the audio, the video was just a blank screen. I believe this is a limitation of the third party screen capture tool, though.
-
Finding a way to have multiple instances on one machine so that it is not limited to one tuner at a time.
-
Add a way to launch other programs instead of a web browser. For instance, if it launched the HDHomeRun desktop app and tuned to a specific ATSC 3.0 station with DRM, that image on screen could be captured.
-
In a similar vein, I am considering a Windows 11 with Android Apps version of this instead of using a browser. This would also make things much more similar to the original project that this based on.
-
ADDED 2023-06-15: If you use a headless or RDP session, solution does not appear to work. See: DEPRECATED: PC Stream for Channels (DRM Workaround) | No new hardware required! - #8 by jaidenc UPDATE 2023-06-16: See workaround for headless here: DEPRECATED: PC Stream for Channels (DRM Workaround) | No new hardware required! - #24 by marcuscthomas
-
Other bugs / issues / suggestions from the below thread to be added here...
(ii) Special Thanks and Credits
-
@tmm1 for developing HDMI for Channels
-
Everyone who helped with the same and made it possible for me to figure out how this could work, especially @Fofer, @JT-DFW, and @Absenm
-
@marcuscthomas for creating and maintaining the Screen Capture Recorder and explaining how to get it working with Channels.
-
@miibeez for developing a workaround for the DRM feeds of PBS, which inspired me to believe that capturing a stream and redirecting it must be possible. The solution they used is far superior to this one, and if I can figure out how they did it, then I believe it should be able to be applied here instead of what I have put together.
(iii) Further Reading (Not Previously Mentioned)
- An update on DRM support
- NBC, NBC Sports, NBCU Networks No Longer Available via TV Everywhere Providers
- Tv Everywhere Disappearing?
- Experimental: Stream Links from the Guide via Custom Channels
- DRM Protected ATSC 3.0 Channels
- New HDHomeRun firmware - #41 by babsonnexus
- ATSC 3.0 and DRM Transition: A Historical Perspective
Proof of Concept Demonstration Videos