Skip to content

Commit

Permalink
feat: introduce new default HLS stream mode (chrisbenincasa#780)
Browse files Browse the repository at this point in the history
This commit introduces a new HLS stream mode, modeled off of the great
"HLS segmenter" in ErsatzTV. It also introduces the concept of "channel
stream modes" which are customizable in the UI.

Overall this commit performs a ton of refactoring and cleanup around the
streaming pipeline, consolidating a lot of logic and moving things
around to make all parts of it more flexible, understandable, and
extensible in the future.
  • Loading branch information
chrisbenincasa authored Sep 24, 2024
1 parent 31abb94 commit 6e66cfe
Show file tree
Hide file tree
Showing 82 changed files with 3,362 additions and 2,033 deletions.
Binary file added docs/assets/watermark_form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
69 changes: 69 additions & 0 deletions docs/configure/channels/transcoding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Channel Trancoding Settings

## Stream Mode

Tunarr supports several different stream modes that can be set at the channel level.

!!! info

No matter which stream mode you choose for a channel, clients which require an MPEG-TS stream will still work.

### HLS (recommended)

HLS is the default streaming mode for a channel. In our testing, it is generally the most reliable and efficient. It is akin to [ErsatzTV's](https://ersatztv.org/) "HLS Segmenter" mode.

#### How does it work?

This mode creates a single FFMPEG process, per-program. The process applies all transcoding configuration necessary. Tunarr manages interleaving these processes to create seamless m3u8 playlists for playback.

#### Things to consider

In our testing, we've found this mode to be both efficient and reliable. That said, it is also the newest mode introduced to Tunarr, so there might be some kinks to work out.

### HLS alt

HLS alt (name pending!) is another HLS streaming mode, which operates a little differently. This mode is akin to [ErsatzTV's](https://ersatztv.org/) "HLS Segmenter V2" mode.

#### How does it work?

This mode creates two FFMPEG processes. The first runs per-program and applies scaling/cropping, watermarks, frame rate changes, etc, but outputs a rawvideo stream. The second process concatenates all of these together while also applying bit rate limits and codec changes.

#### Things to consider

The downside to this mode is that the one of the steps (the per-program process) _requires_ software encoding. This can put a lot of stress on certain systems. The stream setup can also lead to quality loss, due to generation loss. However, it does have the potential to create a more reliable / robust stream.

### MPEG-TS

This mode is the closest to the DizqueTV experience.

#### How does it work?

It consists of two FFMPEG processes, one which performs the per-program transcode, outputting an mpeg-ts stream and one which concatenates this raw stream together and outputs it.

#### Things to consider

If this mode was "good enough" we probably wouldn't have spent time implementing the other modes! There are a lot of potential issues with this mode; too many to list here.

## Watermarks

Channels can have watermarks to aid in recreating a classic TV experience.

![](/assets/watermark_form.png)

There are many ways to customize watermarks for a channel. Here are some details on specific options:

### Watermark Period

This value can be used to fade a channel's watermark in/out every N minutes.

### Watermark on leading edge

When using intermittent watermarks, use this option to control whether the watermark begins in a visible (true) or hidden (false) state.

### Total watermark duration

This option controls the absolute duration the watermark can be displayed for a given program segment of a channel. Its value takes precedence over the 'watermark period' but does not disable it. For instance, you could configure a watermark period of 5 minutes with total duration of 45 mins. On a show that is one hour, the watermark will fade in/out for the first 45 minutes and then be hidden for the final 15 minutes.

## Overrides

Global settings, such as target resolution, bit rate, and buffer size can be overridden per-channel.
File renamed without changes.
1 change: 1 addition & 0 deletions docs/configure/scheduling/random-slots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Scheduling
1 change: 1 addition & 0 deletions docs/configure/scheduling/time-slots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Scheduling
1 change: 1 addition & 0 deletions docs/configure/scheduling/tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Scheduling Tools
10 changes: 8 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@ nav:
- Install: getting-started/installation.md
- Setup: getting-started/setup.md
- Configure:
- Channels: configure/channels.md
- Channels:
- configure/channels/index.md
- Transcoding: configure/channels/transcoding.md
- Programming: configure/programming.md
- Scheduling: configure/scheduling.md
- Scheduling:
- configure/scheduling/index.md
- Tools: configure/scheduling/tools.md
- Time Slots: configure/scheduling/time-slots.md
- Random Slots: configure/scheduling/random-slots.md
- Flex: configure/flex.md
- System: configure/system.md
- Clients:
Expand Down
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions server/mikro-orm.base.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Migration20240603204638 } from './src/migrations/Migration2024060320463
import { Migration20240618005544 } from './src/migrations/Migration20240618005544.js';
import { Migration20240719145409 } from './src/migrations/Migration20240719145409.js';
import { Migration20240805185042 } from './src/migrations/Migration20240805185042.js';
import { Migration20240917191535 } from './src/migrations/Migration20240917191535.js';
import { DATABASE_LOCATION_ENV_VAR } from './src/util/constants.js';
import { getDefaultDatabaseDirectory } from './src/util/defaults.js';
import { LoggerFactory } from './src/util/logging/LoggerFactory.js';
Expand Down Expand Up @@ -138,6 +139,10 @@ export default defineConfig({
name: 'add_jellyfin_sources',
class: Migration20240805185042,
},
{
name: 'add_channel_stream_mode',
class: Migration20240917191535,
},
],
},
extensions: [Migrator],
Expand Down
1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"dayjs": "^1.11.10",
"fast-xml-parser": "^4.3.5",
"fastify": "^4.26.0",
"fastify-graceful-shutdown": "^4.0.1",
"fastify-plugin": "^4.5.1",
"fastify-print-routes": "^3.2.0",
"fastify-type-provider-zod": "^1.1.9",
Expand Down
101 changes: 101 additions & 0 deletions server/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{
"version": 1,
"migration": {
"legacyMigration": false,
"isFreshSettings": true
},
"settings": {
"clientId": "332e9a98-63a7-44d0-8add-854ccc5d5cee",
"hdhr": {
"autoDiscoveryEnabled": true,
"tunerCount": 2
},
"xmltv": {
"programmingHours": 12,
"refreshHours": 4,
"outputPath": "/Users/christianbenincasa/Code/projects/tunarr/server/xmltv.xml",
"enableImageCache": false
},
"plexStream": {
"streamPath": "network",
"enableDebugLogging": false,
"directStreamBitrate": 20000,
"transcodeBitrate": 2000,
"mediaBufferSize": 1000,
"transcodeMediaBufferSize": 20000,
"maxPlayableResolution": {
"widthPx": 1920,
"heightPx": 1080
},
"maxTranscodeResolution": {
"widthPx": 1920,
"heightPx": 1080
},
"videoCodecs": [
"h264",
"hevc",
"mpeg2video",
"av1"
],
"audioCodecs": [
"ac3"
],
"maxAudioChannels": "2.0",
"audioBoost": 100,
"enableSubtitles": false,
"subtitleSize": 100,
"updatePlayStatus": false,
"streamProtocol": "http",
"forceDirectPlay": false,
"pathReplace": "",
"pathReplaceWith": ""
},
"ffmpeg": {
"configVersion": 5,
"ffmpegExecutablePath": "/usr/bin/ffmpeg",
"numThreads": 4,
"concatMuxDelay": 0,
"enableLogging": false,
"enableTranscoding": true,
"audioVolumePercent": 100,
"videoEncoder": "libx264",
"hardwareAccelerationMode": "none",
"videoFormat": "h264",
"audioEncoder": "aac",
"targetResolution": {
"widthPx": 1920,
"heightPx": 1080
},
"videoBitrate": 10000,
"videoBufferSize": 1000,
"audioBitrate": 192,
"audioBufferSize": 50,
"audioSampleRate": 48,
"audioChannels": 2,
"errorScreen": "pic",
"errorAudio": "silent",
"normalizeVideoCodec": true,
"normalizeAudioCodec": true,
"normalizeResolution": true,
"normalizeAudio": true,
"maxFPS": 60,
"scalingAlgorithm": "bicubic",
"deinterlaceFilter": "none",
"disableChannelOverlay": false,
"disableChannelPrelude": false
}
},
"system": {
"backup": {
"configurations": []
},
"logging": {
"logLevel": "debug",
"logsDirectory": "/Users/christianbenincasa/Library/Preferences/tunarr/logs",
"useEnvVarLevel": true
},
"cache": {
"enablePlexRequestCache": false
}
}
}
1 change: 0 additions & 1 deletion server/src/api/debugApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export const debugApi: RouterPluginAsyncCallback = async (fastify) => {
startTime: new Date().getTime(),
channelId: req.query.channelId,
allowSkip: true,
session: 0,
});

return res.send(result);
Expand Down
Loading

0 comments on commit 6e66cfe

Please sign in to comment.