mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-18 13:52:20 +02:00
@@ -23,6 +23,8 @@
|
||||
- [Metadata Updater & FFprobe](./configuration/metadata-update.md)
|
||||
- [Local Media Library](./configuration/local-library.md)
|
||||
- [source.yml (Inputs, Panel API & Targets)](./configuration/source.md)
|
||||
- [Channel Groups & Stream Failover](./configuration/channel-groups.md)
|
||||
- [Channel Groups: Settings Reference](./configuration/channel-groups-settings.md)
|
||||
- [api-proxy.yml (Server, Users & RBAC)](./configuration/api-proxy.md)
|
||||
- [template.yml (Macros & Regex)](./configuration/template.md)
|
||||
- [mapping.yml (Mapper DSL & Logic)](./configuration/mapping-dsl.md)
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
# Channel Groups: Settings Reference
|
||||
|
||||
This page explains every channel group setting: what it does, which values it takes, its default, when to change it,
|
||||
and an example. For how channel groups work as a whole, see [Channel Groups & Stream Failover](./channel-groups.md).
|
||||
|
||||
## Where the settings live
|
||||
|
||||
| Where | What |
|
||||
|:--------------------------------------------------------------|:--------------------------------------------------------------------|
|
||||
| `config.yml` → `channel_groups` | All settings below. They apply to every target with channel groups. |
|
||||
| Web UI: **Settings → Channel groups** | Edits the same block. |
|
||||
| `source.yml` → `targets[].options.channel_groups` | `enabled` and a few [overrides](#target-overrides) for one target. |
|
||||
| Web UI: **Source editor → Targets → target → Channel groups** | Edits the same target block. |
|
||||
|
||||
Nothing happens until a target sets `enabled: true`. The global block is optional; without it every setting has
|
||||
its default.
|
||||
|
||||
A complete global block with all defaults:
|
||||
|
||||
```yaml
|
||||
channel_groups:
|
||||
match_by: caption
|
||||
match_as_ascii: false
|
||||
match_normalize:
|
||||
strip_regex: null
|
||||
ignore_tokens: []
|
||||
aliases: {}
|
||||
epg_conflict: split
|
||||
name_strip_quality: false
|
||||
policy: auto
|
||||
allow_provider_grace: true
|
||||
start:
|
||||
timeout_secs: 15
|
||||
max_attempts: 4
|
||||
failover:
|
||||
enabled: true
|
||||
no_data_timeout_secs: 8
|
||||
slow_source_pct: 90
|
||||
slow_source_secs: 15
|
||||
max_outage_secs: 20
|
||||
same_source_retries: 1
|
||||
stable_after_secs: 30
|
||||
max_switches: 6
|
||||
max_switches_window_secs: 600
|
||||
audio_language: prefer
|
||||
circuit:
|
||||
source_failures: 2
|
||||
cooldown_secs: 60
|
||||
cooldown_max_secs: 900
|
||||
input_failures: 3
|
||||
input_window_secs: 120
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Grouping
|
||||
|
||||
Grouping decides which channels become one channel. It runs during the playlist update, after the target's filters,
|
||||
renames and mappings, so it sees the channel names as the target outputs them. Changes take effect with the next
|
||||
playlist update.
|
||||
|
||||
### How two channels are found equal
|
||||
|
||||
For every live channel Tuliprox builds a **match key**. Channels with the same key form a group (a key with a single
|
||||
channel leaves that channel alone). The key is built in this order:
|
||||
|
||||
1. Take the field chosen by [`match_by`](#match_by).
|
||||
2. Remove everything [`strip_regex`](#match_normalizestrip_regex) matches.
|
||||
3. With [`match_as_ascii`](#match_as_ascii), turn accented and special letters into plain letters.
|
||||
4. Split into words at every character that is not a letter or digit (spaces, `:`, `|`, `-`, brackets, ...).
|
||||
5. Drop quality words (`SD`, `HD`, `FHD`, `UHD`, `QHD`, `4K`, `480p`, `576p`, `720p`, `1080p`, `1440p`, `2160p`) and
|
||||
the words of [`ignore_tokens`](#match_normalizeignore_tokens).
|
||||
6. Join the rest in lower case with single spaces.
|
||||
7. Replace the key if [`aliases`](#match_normalizealiases) maps it to another key.
|
||||
|
||||
Example with `strip_regex: "^[A-Z]{2}:\\s*"`, `ignore_tokens: [backup]` and `aliases: {"das erste": "ard"}`:
|
||||
|
||||
| Channel name | After step 2 | Words kept (step 5) | Key after step 6 | Final key |
|
||||
|:--------------------|:----------------|:--------------------|:-----------------|:----------|
|
||||
| `DE: Das Erste HD` | `Das Erste HD` | `Das`, `Erste` | `das erste` | `ard` |
|
||||
| `ARD [FHD]` | `ARD [FHD]` | `ARD` | `ard` | `ard` |
|
||||
| `DE: ARD HD backup` | `ARD HD backup` | `ARD` | `ard` | `ard` |
|
||||
| `ZDF-HD` | `ZDF-HD` | `ZDF` | `zdf` | `zdf` |
|
||||
|
||||
The first three channels become one group `ard`. `ZDF-HD` stays a normal channel unless another input has a ZDF
|
||||
channel too.
|
||||
|
||||
### `match_by`
|
||||
|
||||
Which channel field the match key is built from.
|
||||
|
||||
| Value | Uses |
|
||||
|:-----------------|:-----------------------------------------------------------------------------------------------------------------------|
|
||||
| `caption` | The title, or the name when the title is empty. Right for most setups. |
|
||||
| `name` | The `name` field only. |
|
||||
| `title` | The `title` field only. |
|
||||
| `epg_channel_id` | The EPG channel id, compared without case. Steps 2 to 7 above do not apply. Channels without EPG id are never grouped. |
|
||||
|
||||
**Default:** `caption`. **Can be overridden per target.**
|
||||
|
||||
Use `epg_channel_id` when your providers name channels differently but carry clean, identical EPG ids – for example
|
||||
`ZDF HD` at one provider and `ZDF Deutschland` at the other, both with `ZDF.de`.
|
||||
|
||||
```yaml
|
||||
match_by: epg_channel_id
|
||||
```
|
||||
|
||||
### `match_as_ascii`
|
||||
|
||||
Compares names after turning accented and special letters into plain letters: `Télé 5` and `Tele 5` match, as do
|
||||
`Ö3` and `O3`.
|
||||
|
||||
**Default:** `false`. **Can be overridden per target.**
|
||||
|
||||
Turn it on when one provider writes names with accents and another without.
|
||||
|
||||
### `match_normalize.strip_regex`
|
||||
|
||||
A [regular expression](https://docs.rs/regex/latest/regex/#syntax); every match is removed from the name before the
|
||||
key is built. Use it for prefixes and suffixes that differ between providers.
|
||||
|
||||
**Default:** none.
|
||||
|
||||
| Goal | `strip_regex` | Name | Becomes |
|
||||
|:------------------------------------|:--------------------------|:-------------------|:----------|
|
||||
| Drop a country prefix like `DE:` | `"^[A-Z]{2}:\\s*"` | `DE: ZDF HD` | `ZDF HD` |
|
||||
| Drop a prefix like `DE -` or `DE /` | `"^[A-Z]{2}\\s*[-/]\\s*"` | `DE - ZDF HD` | `ZDF HD` |
|
||||
| Drop anything in round brackets | `"\\(.*?\\)"` | `ZDF (Germany) HD` | `ZDF HD` |
|
||||
|
||||
For a prefix with a pipe (`DE | ZDF`) use `"^[A-Z]{2}\\s*\\|\\s*"`. Without stripping, the country code stays a word
|
||||
of the key: `DE: ZDF` becomes `de zdf` and does not match `ZDF` of a provider without prefix.
|
||||
|
||||
In YAML, write the expression in double quotes and double every backslash (`\\s`). In the Web UI, enter it once
|
||||
(`^[A-Z]{2}:\s*`).
|
||||
|
||||
### `match_normalize.ignore_tokens`
|
||||
|
||||
Words that are dropped when names are compared, in addition to the quality words. Case does not matter. A token is a
|
||||
whole word: `backup` drops `ZDF backup` → `zdf`, but not `ZDFbackup`.
|
||||
|
||||
**Default:** empty.
|
||||
|
||||
```yaml
|
||||
match_normalize:
|
||||
ignore_tokens: [backup, hevc, h265, raw, "+1"]
|
||||
```
|
||||
|
||||
Careful with words that tell channels apart: ignoring `plus` would join `Sky Sport` and `Sky Sport Plus`.
|
||||
|
||||
### `match_normalize.aliases`
|
||||
|
||||
Maps one match key to another, for channels that have different names at different providers. Both sides are
|
||||
normalized like a channel name first, so write them as you would read them: `"Das Erste"` and `"das erste"` are the
|
||||
same.
|
||||
|
||||
**Default:** empty.
|
||||
|
||||
```yaml
|
||||
match_normalize:
|
||||
aliases:
|
||||
"das erste": "ard"
|
||||
"zdf deutschland": "zdf"
|
||||
"rtl television": "rtl"
|
||||
```
|
||||
|
||||
In the Web UI, enter the name on the left and the name it should be compared as on the right.
|
||||
|
||||
### `epg_conflict`
|
||||
|
||||
What happens when channels with the same key carry **different** EPG ids – often a sign that they are different
|
||||
channels with the same name, for example two regional `WDR` channels.
|
||||
|
||||
| Value | Effect |
|
||||
|:---------|:---------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `split` | One group per EPG id. Channels without EPG id in such a key stay normal channels. A split part with a single channel stays a normal channel. |
|
||||
| `ignore` | All channels with the key form one group, whatever their EPG ids. |
|
||||
|
||||
**Default:** `split`. **Can be overridden per target.**
|
||||
|
||||
Example: `WDR HD` (EPG `wdr.koeln`), `WDR FHD` (EPG `wdr.koeln`) and `WDR` (EPG `wdr.dortmund`). With `split`,
|
||||
the two Köln channels form a group and the Dortmund channel stays alone. With `ignore`, all three are one group.
|
||||
|
||||
Use `ignore` only when your providers use different EPG ids for the same channel and you are sure the names are
|
||||
unique.
|
||||
|
||||
### `name_strip_quality`
|
||||
|
||||
Removes quality words from the name of the joined channel, so the group is shown as `ZDF` instead of `ZDF HD`. Only
|
||||
whole, space-separated words are removed: `ZDF [FHD]` becomes `ZDF`, `ZDF-HD` stays `ZDF-HD`.
|
||||
|
||||
**Default:** `false`. **Can be overridden per target.**
|
||||
|
||||
The group takes name, logo and EPG id of its first channel; this setting changes only the name.
|
||||
|
||||
### `policy`
|
||||
|
||||
The order in which the channels of a group ("members") are tried.
|
||||
|
||||
| Value | Order |
|
||||
|:-----------|:--------------------------------------------------------------------------------------------------------------|
|
||||
| `auto` | By score: 60 % reliability history, 30 % picture quality, 10 % position in the playlist. Paused members last. |
|
||||
| `priority` | By position in the playlist (or the order in the overrides file). Paused members last. |
|
||||
|
||||
**Default:** `auto`. **Can be overridden per target.**
|
||||
|
||||
- **Reliability history** comes from the stream history: how often a member started and played without problems in
|
||||
the last 24 hours and 7 days. It needs `reverse_proxy.stream_history` and `reverse_proxy.qos_aggregation` in
|
||||
`config.yml`; without them every member counts as average.
|
||||
- **Picture quality** comes from the metadata prober (resolution and bitrate); without a probe the quality word in
|
||||
the name is used (`FHD` beats `HD`).
|
||||
- **Position:** the channel that appears first in the target playlist ranks highest.
|
||||
|
||||
Use `priority` when you want a fixed order, for example "always the paid provider first, the second provider only
|
||||
as backup". Sort your playlist or the overrides file accordingly.
|
||||
|
||||
---
|
||||
|
||||
## Start
|
||||
|
||||
These settings control what happens when a client starts a group. They apply to the next stream start.
|
||||
|
||||
### `allow_provider_grace`
|
||||
|
||||
Providers can have a grace connection: one connection above `max_connections` that is allowed for a short time while
|
||||
an old stream is closing. With this setting a start may use it when no regular connection is free.
|
||||
|
||||
**Default:** `true`. **Can be overridden per target.**
|
||||
|
||||
Switches while watching never use the grace connection. Turn it off for targets with many users on few provider
|
||||
connections, so a group start does not hold the slot other users need while they zap.
|
||||
|
||||
### `start.timeout_secs`
|
||||
|
||||
The longest time a start may try members – selecting, opening and checking that the provider sends a video stream.
|
||||
When it runs out, the client gets the "channel unavailable" video.
|
||||
|
||||
**Default:** `15`. `0` means no limit.
|
||||
|
||||
Example: member 1 does not answer (given up after 4 seconds), member 2 answers with HTTP 403, member 3 plays after
|
||||
2 seconds. The start takes about 6 seconds and stays well inside 15.
|
||||
|
||||
Lower it (for example to `8`) when your players give up early; raise it when your providers are slow to answer.
|
||||
|
||||
### `start.max_attempts`
|
||||
|
||||
The most members a start tries.
|
||||
|
||||
**Default:** `4`. Must be greater than 0.
|
||||
|
||||
With 6 members and `max_attempts: 4`, at most the 4 best-ranked members are tried before the start gives up.
|
||||
|
||||
---
|
||||
|
||||
## Failover while watching
|
||||
|
||||
When the playing member fails, Tuliprox opens the next member and continues the **same** response, so the player
|
||||
does not notice more than a short freeze. This works for MPEG-TS streams (direct and shared); HLS streams change the
|
||||
member only when a new stream starts. Changes apply to the next stream start.
|
||||
|
||||
### `failover.enabled`
|
||||
|
||||
Turns the switch while watching on or off. With `false` a group still starts on the best member, but the stream ends
|
||||
when its member fails, as for a normal channel.
|
||||
|
||||
**Default:** `true`. **Can be overridden per target** (as `failover: false`).
|
||||
|
||||
Turn it off for a target whose players show artefacts or stop when the stream changes – some built-in TV players
|
||||
do. VLC, Kodi, ffmpeg-based players and most IPTV apps handle the switch.
|
||||
|
||||
### `failover.no_data_timeout_secs`
|
||||
|
||||
A member that sends no picture and no sound for this long is replaced.
|
||||
|
||||
**Default:** `8`. `0` turns the check off.
|
||||
|
||||
Keep it above the keyframe interval of your channels: after a switch, Tuliprox waits up to 4 seconds for a keyframe
|
||||
of the new member, and some sports channels send a keyframe only every 5 to 10 seconds. Too short a value replaces
|
||||
members that are merely slow to start. Raise it to `12`–`15` for providers that pause for a few seconds now and then
|
||||
without really failing.
|
||||
|
||||
### `failover.slow_source_pct` and `failover.slow_source_secs`
|
||||
|
||||
A member must deliver video at least as fast as it is played. A member that is slower than this percentage of
|
||||
real-time speed for `slow_source_secs` seconds is replaced.
|
||||
|
||||
**Defaults:** `slow_source_pct: 90`, `slow_source_secs: 15`. `slow_source_pct: 0` turns the check off; at most `100`.
|
||||
|
||||
What the numbers mean: with `90`, the member must deliver at least 9 seconds of video every 10 seconds. A member
|
||||
that delivers only 7 seconds every 10 seconds drains the player's buffer – after a while the picture stutters or
|
||||
stops to buffer. If that lasts for 15 seconds, Tuliprox switches before the viewer sees it.
|
||||
|
||||
The speed is measured with the clock inside the stream (PCR), not by counting bytes, so channels with a low bitrate
|
||||
are not mistaken for slow ones.
|
||||
|
||||
| Situation | Suggestion |
|
||||
|:-----------------------------------------------------|:---------------------------------------------------------------------|
|
||||
| Switches happen although the picture is fine | Lower `slow_source_pct` to `80` or raise `slow_source_secs` to `30`. |
|
||||
| The picture stutters for a long time before a switch | Lower `slow_source_secs` to `8`. |
|
||||
| You do not want this check | `slow_source_pct: 0`. |
|
||||
|
||||
### `failover.max_outage_secs`
|
||||
|
||||
The longest time without picture while Tuliprox looks for a replacement. When no member plays within this time, the
|
||||
stream ends (with the "channel unavailable" video, if configured).
|
||||
|
||||
**Default:** `20`. Must be greater than 0.
|
||||
|
||||
During the search, the player receives empty packets every half second so it keeps the connection. A member that
|
||||
played for 5 seconds (or half of `max_outage_secs`, if that is shorter) counts as working; a later failure starts a
|
||||
new outage with the full time.
|
||||
|
||||
### `failover.same_source_retries`
|
||||
|
||||
A member that played well (for at least [`stable_after_secs`](#failoverstable_after_secs)) and then fails is first
|
||||
tried again this many times before another member is used. Short hiccups of a good provider are common; reopening it
|
||||
is often the best choice.
|
||||
|
||||
**Default:** `1`. `0` switches to another member at once.
|
||||
|
||||
### `failover.stable_after_secs`
|
||||
|
||||
After this much smooth playback a member counts as stable: the current incident is over, earlier failed members may
|
||||
be tried again, and a new failure first retries the same member (see above).
|
||||
|
||||
**Default:** `30`.
|
||||
|
||||
### `failover.max_switches` and `failover.max_switches_window_secs`
|
||||
|
||||
The switch limit. When more than `max_switches` switches happen within `max_switches_window_secs`, the stream ends
|
||||
instead of jumping between members that all fail.
|
||||
|
||||
**Defaults:** `max_switches: 6`, `max_switches_window_secs: 600` (10 minutes). `max_switches: 0` turns the limit off.
|
||||
|
||||
Keep the limit on: a provider that answers every request with a short error video would otherwise keep a stream
|
||||
switching forever.
|
||||
|
||||
### `failover.audio_language`
|
||||
|
||||
Whether a switch must keep the audio language of the member that played (as found by the metadata prober).
|
||||
|
||||
| Value | Effect |
|
||||
|:----------|:-------------------------------------------------------------------------------------|
|
||||
| `prefer` | Members with the same language rank higher; others are used when nothing else works. |
|
||||
| `require` | Only members with the same language (or without a known language) are used. |
|
||||
|
||||
**Default:** `prefer`.
|
||||
|
||||
Use `require` when a group mixes language versions, for example an English and a German feed of the same sports
|
||||
channel.
|
||||
|
||||
---
|
||||
|
||||
## Pausing failing members (`circuit`)
|
||||
|
||||
A member that keeps failing is paused, so starts and switches try the others first. Pauses belong to the member
|
||||
(input and stream), so they are shared by all targets. Answers like "too many connections" (HTTP 429/509) never
|
||||
count as a failure – the member is fine, the account is just full.
|
||||
|
||||
### `circuit.source_failures`
|
||||
|
||||
How many failures in a row pause a member. A successful start resets the count.
|
||||
|
||||
**Default:** `2`. Must be greater than 0.
|
||||
|
||||
### `circuit.cooldown_secs` and `circuit.cooldown_max_secs`
|
||||
|
||||
How long a member stays paused. After the pause it is tried once; if that fails, the next pause is twice as long, up
|
||||
to `cooldown_max_secs`. A success ends the pausing.
|
||||
|
||||
**Defaults:** `cooldown_secs: 60`, `cooldown_max_secs: 900`. A `cooldown_max_secs` below `cooldown_secs` is raised to
|
||||
it.
|
||||
|
||||
Example with the defaults: pauses of 1, 2, 4, 8 minutes, then 15 minutes for every further failure.
|
||||
|
||||
A paused member is not removed: when every member of a group is paused, the best of them is still tried.
|
||||
|
||||
### `circuit.input_failures` and `circuit.input_window_secs`
|
||||
|
||||
When **several different** channels of one input fail within a short time, the provider itself is probably down. Then
|
||||
the whole input is paused and its members are tried last.
|
||||
|
||||
**Defaults:** `input_failures: 3`, `input_window_secs: 120`. `input_failures` must be at least 2; the failures must
|
||||
come from at least two different members.
|
||||
|
||||
Example: within 2 minutes, `ZDF` of provider A fails twice and `ARD` of provider A once – 3 failures from 2 members.
|
||||
All channels of provider A now rank behind provider B until the pause ends.
|
||||
|
||||
---
|
||||
|
||||
## Target overrides
|
||||
|
||||
A target turns channel groups on with `enabled` and can override these settings. Every override is optional; a
|
||||
missing one uses the global value. In the Web UI each override offers "Global (*value*)".
|
||||
|
||||
| Key | Overrides |
|
||||
|:-----------------------|:------------------------------------------------|
|
||||
| `enabled` | – (target only, default `false`) |
|
||||
| `match_by` | [`match_by`](#match_by) |
|
||||
| `match_as_ascii` | [`match_as_ascii`](#match_as_ascii) |
|
||||
| `epg_conflict` | [`epg_conflict`](#epg_conflict) |
|
||||
| `name_strip_quality` | [`name_strip_quality`](#name_strip_quality) |
|
||||
| `policy` | [`policy`](#policy) |
|
||||
| `allow_provider_grace` | [`allow_provider_grace`](#allow_provider_grace) |
|
||||
| `failover` | [`failover.enabled`](#failoverenabled) |
|
||||
|
||||
`match_normalize`, `start`, the failover timing and `circuit` exist only globally: the pause state belongs to the
|
||||
member and is shared by all targets, and the timing describes your providers, not a target.
|
||||
|
||||
---
|
||||
|
||||
## Recipes
|
||||
|
||||
### Two providers with the same channels
|
||||
|
||||
```yaml
|
||||
# config.yml – nothing needed, the defaults fit
|
||||
# source.yml
|
||||
targets:
|
||||
- name: tv
|
||||
options:
|
||||
channel_groups:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
### Providers with country prefixes and different spellings
|
||||
|
||||
```yaml
|
||||
# config.yml
|
||||
channel_groups:
|
||||
match_as_ascii: true
|
||||
name_strip_quality: true
|
||||
match_normalize:
|
||||
strip_regex: "^[A-Z]{2}\\s*[:|-]\\s*"
|
||||
ignore_tokens: [backup, hevc]
|
||||
aliases:
|
||||
"das erste": "ard"
|
||||
```
|
||||
|
||||
### A paid provider first, a cheap one only as backup
|
||||
|
||||
```yaml
|
||||
# config.yml
|
||||
channel_groups:
|
||||
policy: priority
|
||||
```
|
||||
|
||||
Put the paid provider's input first in the source, so its channels come first in the playlist.
|
||||
|
||||
### One target for a TV box that cannot handle a switch
|
||||
|
||||
```yaml
|
||||
# source.yml
|
||||
targets:
|
||||
- name: living-room
|
||||
options:
|
||||
channel_groups:
|
||||
enabled: true
|
||||
failover: false
|
||||
```
|
||||
|
||||
### Many users on few provider connections
|
||||
|
||||
```yaml
|
||||
# source.yml
|
||||
targets:
|
||||
- name: family
|
||||
options:
|
||||
channel_groups:
|
||||
enabled: true
|
||||
allow_provider_grace: false
|
||||
share_live_streams:
|
||||
mpeg_ts: true # viewers of the same group share one provider connection
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Check |
|
||||
|:---------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Channels that should be one are not grouped | Compare their names after the target's renames; add `strip_regex`, `ignore_tokens` or `aliases`; check that the EPG ids are equal or set `epg_conflict: ignore`; run a playlist update. |
|
||||
| Different channels were joined | Keep `epg_conflict: split`; remove too broad `ignore_tokens`; use the mapping field `channel_group: ""` to keep a channel out. |
|
||||
| A switch shows artefacts for a moment | Normal when the members differ in codec or resolution; members with the same codec and resolution are preferred. Turn `failover` off for players that cannot handle it. |
|
||||
| Streams switch although the picture was fine | Raise `no_data_timeout_secs`; lower `slow_source_pct` or raise `slow_source_secs`. |
|
||||
| Streams end after many switches | All members fail; check the providers. `max_switches` ended the stream as intended. |
|
||||
| A good provider is always tried last | It is paused after failures; wait for the pause to end or check the events `channel_group.*`. |
|
||||
@@ -0,0 +1,226 @@
|
||||
# Channel Groups and Stream Failover
|
||||
|
||||
Many setups carry the same live channel from several inputs: two providers, two accounts of one provider, or an
|
||||
HD and an FHD variant. **Channel groups** collapse these equal channels into **one channel** in the target. When a
|
||||
client plays it, Tuliprox picks the best reachable source ("member") and, if that source fails while the client is
|
||||
watching, switches to another member **underneath the open response** – the player keeps its connection and does
|
||||
not have to reconnect.
|
||||
|
||||
Channel groups are **turned on per target** and are **off by default**. How channels are matched and how sources are
|
||||
selected and switched is set **once in `config.yml`** under `channel_groups`; a target can override a few of these
|
||||
settings where it needs to.
|
||||
|
||||
## Quick start
|
||||
|
||||
```yaml
|
||||
# source.yml
|
||||
sources:
|
||||
- inputs: [provider_a, provider_b]
|
||||
targets:
|
||||
- name: tv
|
||||
output:
|
||||
- type: xtream
|
||||
options:
|
||||
channel_groups:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
With this, live channels of `provider_a` and `provider_b` whose caption matches (quality words such as `HD`, `FHD`,
|
||||
`4K` are ignored) appear once in the `tv` target. All other settings use the defaults of `config.yml`.
|
||||
|
||||
In the Web UI:
|
||||
|
||||
- **Settings → Channel groups** edits the global block of `config.yml`.
|
||||
- **Source editor → Targets → *target* → Channel groups** turns the feature on for a target and holds its overrides.
|
||||
Every override offers "Global (*value*)" to keep the global setting.
|
||||
|
||||
## How channels are grouped
|
||||
|
||||
Grouping runs at the end of the target processing, after filters, renames and mappings.
|
||||
|
||||
1. Only live channels take part (`XtreamCluster::Live`, no local library items).
|
||||
2. Each channel gets a **match key** from the field chosen by `match_by` (`caption`, `name`, `title` or
|
||||
`epg_channel_id`), normalized with `match_as_ascii` and `match_normalize`.
|
||||
3. Channels with the same key form a group; a key with a single channel leaves that channel as it is.
|
||||
4. With `epg_conflict: split`, a group whose members carry **different EPG ids** is split into one group per EPG id,
|
||||
so channels with different programmes are never mixed. `ignore` keeps such groups together.
|
||||
5. The group appears as one playlist entry. Its name, logo and EPG id come from its first member; with
|
||||
`name_strip_quality: true` the quality words are removed from the name. The members disappear from the
|
||||
playlist but keep their stable virtual ids, so old favourites and direct member requests still play (a request
|
||||
for a member id plays the group).
|
||||
|
||||
The mapping DSL can steer grouping per channel with the header field `channel_group`: a value forces the channel
|
||||
into the group with that key, an empty string (`""`) keeps the channel out of any group.
|
||||
|
||||
### Manual groups and corrections
|
||||
|
||||
Per target, an optional overrides file under `<config_path>/channel_groups/<target>--<hash>.yml` holds manual groups
|
||||
(`groups`), corrections of automatic groups (`auto_overrides`: order, added/removed members, name, logo, EPG id,
|
||||
policy) and excluded channels (`excluded`). Members are referenced by input name and provider stream id. A manual
|
||||
group with fewer than two resolvable members is dropped; members that vanished from the provider are
|
||||
skipped with a warning. There is no Web UI editor for this file yet.
|
||||
|
||||
## How a source is selected
|
||||
|
||||
When a client starts a group, Tuliprox ranks the members and opens the best one that has provider capacity.
|
||||
|
||||
- **Ranking (`policy: auto`).** Members are scored from their QoS history (24 h / 7 d success, needs
|
||||
`stream_history` and `qos_aggregation` in `config.yml`), their probed quality (resolution, codec, bitrate from the
|
||||
metadata prober) and their position in the group. Members with an open circuit or a long failure streak go last.
|
||||
`priority` keeps the configured member order and only moves failing members back.
|
||||
- **Capacity.** A member with a free provider connection wins over a better member that would need the grace slot
|
||||
(`allow_provider_grace`) or the preemption of a lower-priority stream. Inputs in maintenance are skipped.
|
||||
- **Start attempts.** If a member cannot be opened (HTTP error, provider refuses the account, no MPEG-TS/playlist in
|
||||
the first bytes, no answer), the next member is tried, up to `start.max_attempts` and bounded by
|
||||
`start.timeout_secs`. A provider that does not answer at all is given up after 4 seconds per member.
|
||||
- **Pausing failing sources.** `circuit.source_failures` consecutive failures of one member pause it for
|
||||
`circuit.cooldown_secs`, doubling up to `circuit.cooldown_max_secs` while it keeps failing. Failures of at least two
|
||||
different members of one input within `circuit.input_window_secs` (`circuit.input_failures` in total) put the whole
|
||||
input last. This state is shared by all targets, because it belongs to the source, not to the target.
|
||||
"Too many connections" answers (`429`/`509`) never open a circuit.
|
||||
|
||||
HLS entries use the same start selection; with the shared HLS cache enabled, viewers of the same member share one
|
||||
origin session.
|
||||
|
||||
## Failover while watching (MPEG-TS)
|
||||
|
||||
With `failover.enabled` (default on), MPEG-TS playback – direct or [shared](./source.md#326-options) – replaces a
|
||||
running member when
|
||||
|
||||
- its provider body ends or fails (`upstream_eof`, `upstream_error`),
|
||||
- it sends no media for `failover.no_data_timeout_secs` (`stall`),
|
||||
- it is too slow (`underrun`): it delivers less than `failover.slow_source_pct` percent of real-time speed for
|
||||
`failover.slow_source_secs`. With 90 % a source must deliver at least 9 seconds of video every 10 seconds;
|
||||
a slower source drains the player's buffer and the picture stutters. The speed is measured from the stream's PCR.
|
||||
|
||||
With `failover.enabled: false` a group still starts on the best source, but the stream ends with its member.
|
||||
|
||||
The switch keeps the client response open. While the next member opens, null packets are sent every 500 ms so players
|
||||
and proxies do not time out. The new member is spliced in at a clean point: the output continues with its PAT and
|
||||
PMT (version numbers continue the previous member's, CRC recomputed) followed by a keyframe, and the first packet of
|
||||
each stream carries the MPEG-TS `discontinuity_indicator`. Players such as VLC, ffmpeg-based players and Kodi handle
|
||||
this well; some built-in TV players may stutter when codec or resolution change between members, so the ranking
|
||||
prefers members with the same codec, resolution and audio as the one that played.
|
||||
|
||||
Rules of a switch:
|
||||
|
||||
- A member that played for at least `failover.stable_after_secs` is retried `failover.same_source_retries` times before
|
||||
others.
|
||||
- Runtime switches never use the provider grace slot and never preempt other streams.
|
||||
- A member whose slot was preempted by a higher-priority stream is not failed over; the stream ends as before.
|
||||
- Members that refuse the account or have no capacity, or that deliver no MPEG-TS, are not asked again within the
|
||||
same switch. Others are retried in rounds with a growing pause (0.5 s up to 4 s).
|
||||
- **Outage limit.** The time without playback while looking for a replacement is bounded by `failover.max_outage_secs`.
|
||||
A member counts as playing once it has sent media for 5 seconds (or half of `max_outage_secs`, if that is shorter);
|
||||
then a later failure starts a fresh outage limit. A provider that answers every request with an error clip longer
|
||||
than that therefore keeps the stream alive between switches – `failover.max_switches` is the outer bound for that
|
||||
case.
|
||||
- **Switch limit.** More than `failover.max_switches` switches within `failover.max_switches_window_secs` end the stream.
|
||||
- When the outage limit or the switch limit ends the stream, the `channel_unavailable` custom video is shown if
|
||||
configured.
|
||||
|
||||
Each switch emits the event `channel_group.member_switched` (with the reason); start selections emit
|
||||
`channel_group.member_selected`, exhausted groups `channel_group.exhausted`.
|
||||
|
||||
HLS playback switches members only when a new entry is started; HLS runtime failover is not implemented.
|
||||
|
||||
## Global settings (`config.yml` → `channel_groups`)
|
||||
|
||||
All keys are optional; the block itself can be left out. Every setting is explained with examples in the
|
||||
[Settings Reference](./channel-groups-settings.md).
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|:-----------------------|:-----|:----------|:-----------------------------------------------------------------------------------------------------------------|
|
||||
| `match_by` | Enum | `caption` | Field that decides which channels are equal: `caption`, `name`, `title`, `epg_channel_id`. |
|
||||
| `match_as_ascii` | Bool | `false` | Compares match keys after transliteration to ASCII ("Télé" matches "Tele"). |
|
||||
| `match_normalize` | Map | empty | Extra normalization: `strip_regex` (removed from the key), `ignore_tokens` (words ignored), `aliases` (key map). |
|
||||
| `epg_conflict` | Enum | `split` | `split` separates members with different EPG ids into own groups, `ignore` keeps them together. |
|
||||
| `name_strip_quality` | Bool | `false` | Removes quality words (`HD`, `FHD`, `4K`, ...) from the group's name. |
|
||||
| `policy` | Enum | `auto` | Member order: `auto` (ranked by QoS, quality, order) or `priority` (configured order, failing members last). |
|
||||
| `allow_provider_grace` | Bool | `true` | A start may use the provider grace slot when no free connection is left. Switches while watching never do. |
|
||||
| `start` | Map | see below | How a start picks a member. |
|
||||
| `failover` | Map | see below | Switching members while watching. |
|
||||
| `circuit` | Map | see below | Pausing failing members and inputs. |
|
||||
|
||||
### `start`
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|:---------------|:-----|:--------|:-------------------------------------------------------------------------------------|
|
||||
| `timeout_secs` | Int | `15` | Longest time a start may try members before the client gets an error. `0`: no limit. |
|
||||
| `max_attempts` | Int | `4` | Most members tried at start. Must be greater than 0. |
|
||||
|
||||
### `failover`
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|:---------------------------|:-----|:---------|:--------------------------------------------------------------------------------------------------|
|
||||
| `enabled` | Bool | `true` | Switches to another member while a client watches an MPEG-TS stream. |
|
||||
| `no_data_timeout_secs` | Int | `8` | Seconds without media before a member is replaced. Keep it above the keyframe interval. `0`: off. |
|
||||
| `slow_source_pct` | Int | `90` | Minimum share of real-time speed a member must deliver. `0`: off; at most 100. |
|
||||
| `slow_source_secs` | Int | `15` | How long a member may stay below `slow_source_pct` before it is replaced. |
|
||||
| `max_outage_secs` | Int | `20` | Longest time without playback while looking for a replacement. Must be greater than 0. |
|
||||
| `same_source_retries` | Int | `1` | Retries of a member that played stably before other members are tried. |
|
||||
| `stable_after_secs` | Int | `30` | Playback after which a member counts as stable and an incident is over. |
|
||||
| `max_switches` | Int | `6` | Most switches within `max_switches_window_secs`; then the stream ends. `0`: no limit. |
|
||||
| `max_switches_window_secs` | Int | `600` | Window of `max_switches`. |
|
||||
| `audio_language` | Enum | `prefer` | Switches `prefer` members with the same audio language, or `require` it. |
|
||||
|
||||
### `circuit`
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|:--------------------|:-----|:--------|:--------------------------------------------------------------------------------------------------|
|
||||
| `source_failures` | Int | `2` | Consecutive failures that pause a member. Must be greater than 0. |
|
||||
| `cooldown_secs` | Int | `60` | First pause of a failing member; doubles while it keeps failing. |
|
||||
| `cooldown_max_secs` | Int | `900` | Longest pause. Raised to `cooldown_secs` when lower. |
|
||||
| `input_failures` | Int | `3` | Failures of one input (from at least two members) within the window that put the input last. ≥ 2. |
|
||||
| `input_window_secs` | Int | `120` | Window of `input_failures`. |
|
||||
|
||||
Changes to grouping settings take effect with the next playlist update; selection and failover settings apply to the
|
||||
next stream start.
|
||||
|
||||
## Target settings (`source.yml` → `targets[].options.channel_groups`)
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|:-----------------------|:-----|:---------|:---------------------------------------------------------------------------------|
|
||||
| `enabled` | Bool | `false` | Turns channel groups on for this target. |
|
||||
| `match_by` | Enum | (global) | Overrides `match_by`, e.g. `epg_channel_id` for a target with clean EPG ids. |
|
||||
| `match_as_ascii` | Bool | (global) | Overrides `match_as_ascii`. |
|
||||
| `epg_conflict` | Enum | (global) | Overrides `epg_conflict`. |
|
||||
| `name_strip_quality` | Bool | (global) | Overrides `name_strip_quality`. |
|
||||
| `policy` | Enum | (global) | Overrides `policy`. |
|
||||
| `allow_provider_grace` | Bool | (global) | Overrides `allow_provider_grace`, e.g. `false` for a target with many users. |
|
||||
| `failover` | Bool | (global) | Overrides `failover.enabled`, e.g. `false` for players that stutter on a switch. |
|
||||
|
||||
Timing, `start`, `circuit` and `match_normalize` exist only globally: the pause state of a source is shared by all
|
||||
targets, and the timing describes the providers, not the target.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
# config.yml
|
||||
channel_groups:
|
||||
name_strip_quality: true
|
||||
match_normalize:
|
||||
strip_regex: "^[A-Z]{2}:\\s*" # drop country prefixes like "DE: "
|
||||
ignore_tokens: [hevc, backup]
|
||||
aliases:
|
||||
"das erste": "ard"
|
||||
failover:
|
||||
no_data_timeout_secs: 10
|
||||
max_outage_secs: 30
|
||||
```
|
||||
|
||||
```yaml
|
||||
# source.yml
|
||||
targets:
|
||||
- name: family
|
||||
options:
|
||||
channel_groups:
|
||||
enabled: true
|
||||
share_live_streams:
|
||||
mpeg_ts: true # optional: one provider connection per group for all viewers
|
||||
- name: tv-box
|
||||
options:
|
||||
channel_groups:
|
||||
enabled: true
|
||||
failover: false # this player stutters on a switch; keep the start selection only
|
||||
```
|
||||
@@ -40,6 +40,7 @@ hdhomerun:
|
||||
library:
|
||||
reverse_proxy:
|
||||
metadata_update:
|
||||
channel_groups:
|
||||
```
|
||||
|
||||
### Global System & Storage Settings (Flat Keys)
|
||||
@@ -87,20 +88,21 @@ instead of your mount point.
|
||||
|
||||
### Subsections (Object Keys)
|
||||
|
||||
| Block | Description | Link |
|
||||
|:------------------|:----------------------------------------------------------------------------------|:------------------------------------------------|
|
||||
| `api` | Internal web server binding settings. | [See section](#1-api-server-api) |
|
||||
| `web_ui` | Web Dashboard, RBAC, and Authentication. | [See section](#2-web-ui--administration-web_ui) |
|
||||
| `log` | Console output verbosity and sanitization. | [See section](#3-logging-log) |
|
||||
| `schedules` | Automated background tasks (Cronjobs). | [See section](#4-schedules-schedules) |
|
||||
| `messaging` | Webhooks & Push-Notifications (Telegram, Discord, etc.). | [See section](#5-messaging-messaging) |
|
||||
| `video` | Extension mapping and Web UI download behavior. | [See section](#6-video--web-search-video) |
|
||||
| `proxy` | SOCKS5/HTTP proxy settings for outgoing requests. | [See section](#7-outgoing-proxy-proxy) |
|
||||
| `ipcheck` | IP detection to verify in the Web UI which public IP Tuliprox is currently using. | [See section](#8-ip-check-ipcheck) |
|
||||
| `hdhomerun` | Virtual DVB-C/T network tuner emulation. | [See section](#9-hdhomerun-emulation-hdhomerun) |
|
||||
| `library` | Local Media Library integration. | [See Local Library](./local-library.md) |
|
||||
| `reverse_proxy` | Streaming buffers, rate limits, caching. | [See Reverse Proxy](./reverse-proxy.md) |
|
||||
| `metadata_update` | TMDB matching, FFprobe processing, Job Queues. | [See Metadata Update](./metadata-update.md) |
|
||||
| Block | Description | Link |
|
||||
|:------------------|:----------------------------------------------------------------------------------|:---------------------------------------------------|
|
||||
| `api` | Internal web server binding settings. | [See section](#1-api-server-api) |
|
||||
| `web_ui` | Web Dashboard, RBAC, and Authentication. | [See section](#2-web-ui--administration-web_ui) |
|
||||
| `log` | Console output verbosity and sanitization. | [See section](#3-logging-log) |
|
||||
| `schedules` | Automated background tasks (Cronjobs). | [See section](#4-schedules-schedules) |
|
||||
| `messaging` | Webhooks & Push-Notifications (Telegram, Discord, etc.). | [See section](#5-messaging-messaging) |
|
||||
| `video` | Extension mapping and Web UI download behavior. | [See section](#6-video--web-search-video) |
|
||||
| `proxy` | SOCKS5/HTTP proxy settings for outgoing requests. | [See section](#7-outgoing-proxy-proxy) |
|
||||
| `ipcheck` | IP detection to verify in the Web UI which public IP Tuliprox is currently using. | [See section](#8-ip-check-ipcheck) |
|
||||
| `hdhomerun` | Virtual DVB-C/T network tuner emulation. | [See section](#9-hdhomerun-emulation-hdhomerun) |
|
||||
| `library` | Local Media Library integration. | [See Local Library](./local-library.md) |
|
||||
| `reverse_proxy` | Streaming buffers, rate limits, caching. | [See Reverse Proxy](./reverse-proxy.md) |
|
||||
| `metadata_update` | TMDB matching, FFprobe processing, Job Queues. | [See Metadata Update](./metadata-update.md) |
|
||||
| `channel_groups` | Matching, source selection and failover of channel groups. | [See Channel Groups](./channel-groups-settings.md) |
|
||||
|
||||
*(Note: The advanced topics **Local Library**, **Reverse Proxy** and **Metadata Update** are extremely extensive and
|
||||
have their own
|
||||
|
||||
@@ -1620,6 +1620,8 @@ targets:
|
||||
match_by: caption
|
||||
keep: best_quality
|
||||
match_as_ascii: false
|
||||
channel_groups:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
#### Target Option Parameters
|
||||
@@ -1635,6 +1637,7 @@ targets:
|
||||
| `epg_output.adopt_guide_id_case` | Bool | No | `true` | Rewrites a live channel EPG id that matches a guide channel only case-insensitively (for example `usa4k.us` against `<channel id="USA4K.us">`) to the guide's exact spelling, so the M3U `tvg-id`, Xtream `epg_channel_id`, XMLTV `<channel id>` and EPG API lookups use the same id. Set `false` to keep the playlist's original spelling. Changing this option requires a full target refresh. |
|
||||
| `epg_output.lowercase_ids` | Bool | No | `false` | Canonicalizes visible technical EPG IDs with ASCII lowercase across M3U `tvg-id`, Xtream `epg_channel_id`, XMLTV channel/programme references, and EPG API responses. Changing this option requires a full target refresh. |
|
||||
| `epg_output.lowercase_xmltv_display_names` | Bool | No | `false` | Applies Unicode lowercase exclusively to XMLTV `<display-name>` values during serialization. Playlist names, Xtream names, programme titles, and programme descriptions remain unchanged; persisted target data does not require rebuilding. |
|
||||
| `channel_groups` | Map | No | - | Turns channel groups on for this target (`enabled`) and overrides single global settings of `config.yml` `channel_groups`. See [Channel Groups & Stream Failover](./channel-groups.md). |
|
||||
| `force_redirect` | Bool | No | `false` | Optional redirect-related behavior switch. This influences how Tuliprox serves final stream delivery where redirect-style output handling is required by the deployment model. |
|
||||
|
||||
> **Shared HLS:** `share_live_streams.hls` requires `reverse_proxy.hls_cache` in `config.yml`.
|
||||
|
||||
@@ -40,6 +40,8 @@ That makes it usable both for IPTV players and for media-server-oriented workflo
|
||||
- put inputs into maintenance mode: new streams get a maintenance video, the provider is not contacted
|
||||
- persist optional stream history for per-variant connect/disconnect, startup-failure, and reconnect telemetry
|
||||
- aggregate optional QoS snapshots from stream history to rank equivalent channel variants by reliability
|
||||
- group equal live channels from several inputs into one channel with source selection and runtime failover
|
||||
([Channel Groups](./configuration/channel-groups.md))
|
||||
|
||||
## Metadata and library
|
||||
|
||||
|
||||
Reference in New Issue
Block a user