Skip to content

Commit

Permalink
fix: 🗿 update baseUrl and logging
Browse files Browse the repository at this point in the history
update baseUrl, added additional required reverse proxy header, and
added example nginx config in readme
  • Loading branch information
imamkhaira committed Jul 25, 2022
1 parent 6ca60a6 commit 3687662
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 38 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,21 @@ on:
- v*-release

jobs:
build:
name: 'Create build'
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
# Runs a single command using the runners shell
- run: |
npm install
npm run build
release:
needs: build
name: Create Release
runs-on: ubuntu-latest
permissions:
Expand Down
101 changes: 77 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,27 @@ How to deploy transcoder:
3. Deploy the transcoder by running the following command:

```sh
docker run --name transcoder -d -p 80:80 -v /dev/shm:/tmp imamkhaira/rtsp-to-hls:latest
docker run --name transcoder -d -p 80:80 -v /dev/shm:/tmp:delegated imamkhaira/rtsp-to-hls:latest
```

4. Configure nginx to proxy /transcode request to the server's listening port (see point D).

# Complete Description
For a more complete configuration, please follow these steps below.

## A. Description
# Project Goal

This aims to make a software to convert a RTSP stream from china CCTV to modern HLS stream.
request can be made via API calls and it includes automatic shutdown of unused streams.

## B. Running with Docker

# use below steps if you'd like to use Docker.

## B. Installation or Deployment
# Installation

This package offers two way to run, either by using Docker or bare-metal.

### B.1. Using the Docker image
## Using the Docker image

Use below steps if you'd like to use Docker.

for quick testing or deployment, just run this image using Docker with following command:
For quick testing or deployment, just run this image using Docker with following command:

```sh
# no ramdisk mounting (use this if you unsure)
Expand All @@ -51,9 +47,9 @@ Note:
- please read your operating system's documentation regarding the location of ramdisk or how to make one.
- read more on performance in section D below

### B.2. Running from source code
## Running from source code

If you have a really powerful hardware, or you just hate Docker (like a hardcore BSD dude), you can just follow steps below.
If you have a really powerful hardware, or you just hate Docker ([like BSD dudes]), you can just follow steps below.

1. Install `ffmpeg` command-line utility. Go to https://ffmpeg.org/download.html and follow instructions.
2. Copy `.env.example` to `.env`
Expand All @@ -67,9 +63,9 @@ you can stop the transcoder by `npm run deploy:stop`. Or, view the proces by `np

Also, please read more on performance in section D below.

## C. Using the Transcoder
# Using the Transcoder

### C.1. Create transcoder
## Create Transcoder task

Once the transcoder is up and running, you can start transcoding by sending HTTP POST request into `http://<your ip>:<PORT>/transcode` with the following body:

Expand Down Expand Up @@ -100,7 +96,7 @@ Below is the example if how to send request using cURL
# {"error":false,"stream":"http://<your ip>:<PORT>/output/<stream id>/index.m3u8"}
```

### A.3 Testing transcoded stream
## Testing transcoded stream

you can then play the `stream` part of the response in the browser using either:

Expand All @@ -112,7 +108,7 @@ or if you just wanted to check, simply paste the `stream` into:
- [VLC][vlc]
- QuickTime Player.

### A.4. Sample code
## Sample code

below is a sample of Typescript code:

Expand Down Expand Up @@ -146,25 +142,73 @@ async function start(): Promise<void> {
}
```

## D. Performance Optimization
# <<<<<<< HEAD

# Proxying to the Internet

When using the Transcoder via reverse proxy, you need to forward two headers from the proxy to the transcoder:

- `Host`: set this to the listening domain name, and port.
The port can be unset if it is either 80 or 443.
If not set, it will default to container name or `localhost`.
- `X-Forwarded-Proto` set this to the listening protocol (http/https).
If not set, it will default to `http`

This is necessary so that the transcoder response has the correct stream url (ex: `https://your_domain.com/output/12345/index.m3u8`).
otherwise, it will fallback to localhost (ex: `http://localhost/output/12345/index.m3u8`)

Below is a sample of working Nginx configuration:

```nginx
server {
listen 8080 ssl;
listen [::]:8080 ssl;
server_name transcoder.batmen.cc;
>>>>>>> cba1fb8 (fix: 🗿 update baseUrl and logging)
# include snippets/ssl.conf;
error_log /var/log/nginx/transcoder.error.log;
access_log /var/log/nginx/transcoder.access.log;
location / {
proxy_pass_request_headers on;
# transcoder is listening on port 3000
proxy_pass http://localhost:3000;
proxy_set_header Host $host:$server_port;
# or: proxy_set_header Host transcoder.batmen.cc:8080;
proxy_set_header X-Forwarded-Proto $scheme;
# or: proxy_set_header X-Forwarded-Proto https;
}
}
```

> Help needed! Please help us to add sample config for other web servers!
# Performance Optimization

Please bear in mind that the performance bottleneck of this software is limited by ffmpeg.

### D.1. Optimizing I/O performance.
### Optimizing I/O performance.

When a transcode process is running, ffmpeg creates a lot of small video files, called 'segments',
that will be listed in an index.m3u8 file. a HLS video player will then download the segments as the time progresses.
When a transcode process is running, `ffmpeg` creates a lot of small video files, called `segments`,
that will be listed in an `index.m3u8` file. a HLS video player will then download the segments as the time progresses.
Each transcode process can create up to 240 segments that will be cleaned up when no one is watching the steams.

Therefore, it is very good if you can make use of your free RAM as temporary storage to store these segments.
If you're using Docker, you can mount the ramdisk as a delegated volume mount.

`docker run --name transcoder -d -p 80:80 -v /dev/shm:/tmp:delegated imamkhaira/rtsp-to-hls:latest`

Read more about delegation at https://docker-docs.netlify.app/docker-for-mac/osxfs-caching/#examples

But if you are running from source code/bare-metal, you need to edit the `.env` file,
and update the value of `WORK_DIRECTORY` to the location of your ramdisk.

### D.2. Optimizing Encode/Decode performance
### Optimizing Encode/Decode performance

You can try setting up an optimized version of FFMPEG that is compiled specifically for your GPU.

Expand All @@ -174,11 +218,12 @@ You can try setting up an optimized version of FFMPEG that is compiled specifica
https://docs.nvidia.com/video-technologies/video-codec-sdk/ffmpeg-with-nvidia-gpu/
- Other GPU manufacturer, please read https://trac.ffmpeg.org/wiki/HWAccelIntro

### D.3 Network Consideration
### Network Consideration

The video stream can be very bandwidth-consuming, especially if you transcode multiple streams simultaneously.
Therefore, it is best to run the transcoder in a server with a gigabit adapter.

<<<<<<< HEAD
<<<<<<< HEAD

## D. Proxying and exposing to the Internet.
Expand Down Expand Up @@ -217,10 +262,18 @@ server {
## E. Issues and bugs

Should you encounter any issues or bugs, please open a ticket here.
if you got special needs and wanted futher discussion, please email me please report to me via 1331247.duck.com
=======

## Issues and bugs

> > > > > > > cba1fb8 (fix: 🗿 update baseUrl and logging)
Should you encounter any issues or bugs, please [open a ticket here].
if you got special needs and wanted futher discussion, please email me to 1331247 at duck.com

[hls]: https://github.com/video-dev/hls.js/
[vjs]: https://videojs.com/
[vlc]: https://www.videolan.org/
[open a ticket here]: https://github.com/imamkhaira/rtsp-to-hls/issues
[(still deserve the 🖕🏻)]: https://www.reddit.com/r/linux/comments/vbvxiv/10_years_ago_today_linus_torvalds_to_nvidia_fu_you/
[like bsd dudes]: https://www.reddit.com/r/BSD/comments/r32fbi/eli5_why_does_the_freebsd_community_hate_docker/
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rtsp-to-hls",
"version": "1.2.0",
"version": "1.2.2",
"description": "Convert a RTSP stream from china CCTV to modern HLS stream",
"main": "index.ts",
"scripts": {
Expand Down
1 change: 0 additions & 1 deletion src/modules/transcoder/stream.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ export class Stream implements Manageable {
*/
public refresh(): Stream {
this.killAt = Date.now() + this.keepalive;
console.log(`refreshing ${this.sourceUrl}`);
return this;
}
}
7 changes: 3 additions & 4 deletions src/modules/transcoder/task-manager.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ export class TaskManager<T extends Manageable> {

/** find a process by processId or sourceUrl */
public getProcessById(id: string): T | undefined {
return this.processes.find((p) => p.id === id);
return this.processes.find(p => p.id === id);
}

public getProcessbyParam(param: keyof T, value: T[keyof T]) {
return this.processes.find((p) => p[param] === value);
return this.processes.find(p => p[param] === value);
}

/** add a process to taskManager */
Expand All @@ -34,8 +34,7 @@ export class TaskManager<T extends Manageable> {
private scan(): NodeJS.Timer {
console.log(`scanning garbage every ${this.scanInterval} ms`);
return setInterval(() => {
console.log(`scanning for garbage`);
this.processes = this.processes.filter((p) => {
this.processes = this.processes.filter(p => {
if (p.isActive()) return true;
p.stop();
return false;
Expand Down
26 changes: 18 additions & 8 deletions src/modules/transcoder/transcoder.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ export function createResponse(stream: string | null): string {
});
}

/**
* extract URL from request, be it proxied or not.
* @param req incoming request
* @returns URL string
*/
function getBaseUrl(req: express.Request): string {
const proto = req.header('x-forwarded-proto') || req.protocol;
const host = req.header('x-forwarded-host') || req.header('host');
return `${proto}://${host}`;
}

const moduleCors = cors({
allowedHeaders: '*',
origin: '*',
Expand All @@ -39,6 +50,7 @@ const moduleCors = cors({
export function TranscoderModule({ workDir, outputUrl, keepalive, userUid }: TranscoderConfig) {
const createStream = (sourceUrl: string): Stream =>
new Stream({ sourceUrl, workDir, keepalive, userUid });

//
const taskManager = new TaskManager<Stream>(keepalive);
const transcoder = express.Router();
Expand All @@ -50,8 +62,6 @@ export function TranscoderModule({ workDir, outputUrl, keepalive, userUid }: Tra

// prints greeting in html
transcoder.get('', (req, res) => {
const baseUrl = `${req.protocol}://${req.hostname}`;

res.end(`
<html>
<head>
Expand All @@ -68,7 +78,7 @@ export function TranscoderModule({ workDir, outputUrl, keepalive, userUid }: Tra
<h1>Transcoder is Accessible.</h1>
<p>
If you can see this, transcoder API is working and accessible from outside.
Start by sending a POST request to this endpoint (${baseUrl}/transcode)
Start by sending a POST request to this endpoint (${getBaseUrl(req)}/transcode)
and include the following body:
</p>
<pre>{ "url": "your RTSP stream" }</pre>
Expand All @@ -85,7 +95,7 @@ export function TranscoderModule({ workDir, outputUrl, keepalive, userUid }: Tra
transcoder.post('', body('url').notEmpty(), async (req, res) => {
try {
const rtspUrl = req.body['url'] as string;
if (!rtspUrl.includes('rtsp://')) throw new Error(`url is not defined`);
if (!rtspUrl.includes('rtsp://')) throw `url is not defined`;

let task = taskManager.getProcessbyParam('sourceUrl', rtspUrl);

Expand All @@ -94,10 +104,10 @@ export function TranscoderModule({ workDir, outputUrl, keepalive, userUid }: Tra
task = taskManager.addProcess(stream);
}

const baseUrl = `${req.protocol}://${req.hostname}`;
return res
.status(200)
.end(createResponse(baseUrl + path.join(outputUrl, task.refresh().getIndex())));
const response = createResponse(
getBaseUrl(<any>req) + path.join(outputUrl, task.refresh().getIndex())
);
return res.status(200).end(response);
} catch (error) {
return res.status(200).end(createResponse(null));
}
Expand Down

0 comments on commit 3687662

Please sign in to comment.