livetv

package
v0.3.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 5, 2026 License: GPL-3.0 Imports: 47 Imported by: 0

Documentation

Overview

Package livetv implements owner-defined virtual channels and IPTV playout. Ether supplies neither programming nor IPTV accounts.

Index

Constants

View Source
const AddonInputPrefix = "stremio-"

Add-on channel sources (Stremio `tv` add-ons, installed as plugins) own their channels' inputs, which carry AddonInputPrefix in their ids:

  • a refresh replaces them, keeping the owner's enabled flags and any input the owner added to the channel;
  • channels new to the source are matched to guides after the import;
  • their URLs and request headers can repeat the add-on's configuration (often a debrid key), so the owner API never returns them.
View Source
const MaxChannelInputs = 16

MaxChannelInputs bounds one channel's stream inputs.

View Source
const Provider = "tv.plex.providers.epg.xmltv:1"

Variables

View Source
var ErrProviderBusy = fmt.Errorf("every connection this IPTV provider allows is in use: %w", core.ErrStreamLimit)

ErrProviderBusy reports that every connection the provider allows is in use by other channels.

View Source
var ErrProviderPaced = fmt.Errorf("this IPTV source has started as many streams this hour as its owner allows: %w", core.ErrStreamLimit)

ErrProviderPaced reports that the source's hourly allowance of new provider streams is spent.

Functions

func AddonInputID added in v0.3.3

func AddonInputID(channelID string, n int) string

AddonInputID is the id of a channel's n-th add-on input (from 0).

func BuiltinDescriptors added in v0.3.3

func BuiltinDescriptors() []extend.Descriptor

BuiltinDescriptors returns descriptors for the built-in kinds.

func CleanInputHeaders added in v0.3.3

func CleanInputHeaders(in map[string]string) map[string]string

CleanInputHeaders keeps the request headers an input may carry, with canonical names: no headers the HTTP stack or the proxy manages, no line breaks, at most 16. It returns nil when none remain.

func WithChannelMemo added in v0.3.0

func WithChannelMemo(ctx context.Context) context.Context

WithChannelMemo shares one channel decision per id for the rest of this request. Row admission checks the same channel many times.

Types

type Block

type Block struct {
	// Start and End are minutes after midnight in Timezone, or UTC when
	// the channel has no timezone. End may be 1440. Days empty means every
	// day, which is how channels saved before the weekly grid behave.
	// Days are Go weekdays: 0 Sunday … 6 Saturday, in that same zone.
	Start int   `json:"start"`
	End   int   `json:"end"`
	Days  []int `json:"days,omitempty"`
	// Name is a short grid label such as "Cartoons". It does not change
	// what plays. Known preset names are translated in the console.
	Name        string          `json:"name,omitempty"`
	Items       []Item          `json:"items"`
	Shuffle     bool            `json:"shuffle"`
	Mode        string          `json:"mode,omitempty"`  // order, shuffle, random, marathon, newest
	Align       string          `json:"align,omitempty"` // hour, half
	Fit         string          `json:"fit,omitempty"`   // cut, pad
	RepeatDays  int             `json:"repeatDays,omitempty"`
	Filler      []Item          `json:"filler,omitempty"`
	MarathonKey core.RatingKey  `json:"marathonKey,omitempty"`
	Collection  core.RatingKey  `json:"collection,omitempty"`
	Filter      *core.Filter    `json:"filter,omitempty"`
	Sources     []ProgramSource `json:"sources,omitempty"`
	Watched     string          `json:"watched,omitempty"`
}

type Channel

type Channel struct {
	Health    string `json:"health,omitempty"`
	Transcode bool   `json:"transcode"` // IPTV: normalize codecs through the transcoder
	ID        string `json:"id"`
	// Private channels are hidden from every viewer whose policy does not
	// grant them: a restricted viewer needs it in Channels, an unrestricted
	// one in Granted. Owners always see them.
	Private       bool           `json:"private,omitempty"`
	Number        string         `json:"number"`
	Name          string         `json:"name"`
	Kind          string         `json:"kind"` // virtual or iptv
	Enabled       bool           `json:"enabled"`
	Favorite      bool           `json:"favorite"`
	LogoMatched   bool           `json:"logoMatched,omitempty"`
	Rating        string         `json:"rating,omitempty"`
	SourceID      string         `json:"sourceId,omitempty"`
	GuideID       string         `json:"guideId,omitempty"`
	TvgName       string         `json:"tvgName,omitempty"`       // provider's label; a guide-matching hint
	StreamURL     string         `json:"streamUrl,omitempty"`     // owner API only
	StreamSources []StreamSource `json:"streamSources,omitempty"` // ordered inputs; independent of channel/guide identity
	Group         string         `json:"group,omitempty"`
	CustomGroup   bool           `json:"customGroup,omitempty"` // preserve owner grouping on provider refresh
	Epoch         time.Time      `json:"epoch"`
	Seed          int64          `json:"seed"`
	Shuffle       bool           `json:"shuffle"`
	Items         []Item         `json:"items"`
	Blocks        []Block        `json:"blocks,omitempty"`
	// Collection/Filter/Sources are expanded into a stable item snapshot when saved.
	Collection core.RatingKey  `json:"collection,omitempty"`
	Filter     *core.Filter    `json:"filter,omitempty"`
	Sources    []ProgramSource `json:"sources,omitempty"`
	Watched    string          `json:"watched,omitempty"` // household: watched, unwatched
	// Mode, Align, Fit, RepeatDays and Filler are the base playout. Empty
	// Mode keeps Shuffle as the original engine: false resumes in order from
	// Epoch, true is one stable shuffle. Timezone empty means block minutes
	// are UTC, every day.
	Mode        string         `json:"mode,omitempty"`
	Align       string         `json:"align,omitempty"`
	Fit         string         `json:"fit,omitempty"`
	RepeatDays  int            `json:"repeatDays,omitempty"`
	Filler      []Item         `json:"filler,omitempty"`
	Fallback    []Item         `json:"fallback,omitempty"`
	OffAir      bool           `json:"offAir,omitempty"`
	MarathonKey core.RatingKey `json:"marathonKey,omitempty"`
	Timezone    string         `json:"timezone,omitempty"`

	// GuideSourceID names the source whose XMLTV supplies GuideID's
	// programmes. Empty means SourceID. Virtual channels ignore it.
	GuideSourceID string `json:"guideSourceId,omitempty"`

	// ProviderNumber is the number the source gave this channel at the last
	// import (Xtream num, M3U tvg-chno, or the position in the owner's group
	// order). While Number still equals it the channel follows the
	// provider's renumbering; an owner edit makes Number differ and sticks.
	ProviderNumber string `json:"providerNumber,omitempty"`
	// contains filtered or unexported fields
}

func ParseM3U

func ParseM3U(r io.Reader, sourceID string) ([]Channel, error)

ParseM3U preserves only explicitly provided HTTP(S) streams. Executable, file and media-tool pseudo protocols are rejected.

type ChannelBatch added in v0.3.4

type ChannelBatch struct {
	IDs      []string `json:"ids"`
	Enabled  *bool    `json:"enabled,omitempty"`
	Favorite *bool    `json:"favorite,omitempty"`
	Group    *string  `json:"group,omitempty"`
}

ChannelBatch changes only the named fields, preserving stream credentials, programming and concurrent edits to unrelated fields. All IDs commit together.

type ChannelCounts added in v0.3.4

type ChannelCounts struct {
	All       int `json:"all"`
	Live      int `json:"live"`
	Favorites int `json:"favorites"`
	Virtual   int `json:"virtual"`
	IPTV      int `json:"iptv"`
	Off       int `json:"off"`
	Missing   int `json:"missing"`
}

type ChannelGroup added in v0.3.4

type ChannelGroup struct {
	Key      string `json:"key"`
	SourceID string `json:"sourceId,omitempty"`
	Kind     string `json:"kind"`
	Count    int    `json:"count"`
	Start    int    `json:"start"`
}

type ChannelPage added in v0.3.4

type ChannelPage struct {
	Channels   []Channel       `json:"channels"`
	Total      int             `json:"total"`
	Counts     ChannelCounts   `json:"counts"`
	Groups     []ChannelGroup  `json:"groups"`
	Coverage   map[string]bool `json:"coverage"`
	NextNumber string          `json:"nextNumber"`
}

type ChannelPageQuery added in v0.3.4

type ChannelPageQuery struct {
	Limit  int
	Offset int
	Q      string
	Filter string
	Source string
}

type ChannelSummary added in v0.3.4

type ChannelSummary struct {
	ID            string `json:"id"`
	Number        string `json:"number"`
	Name          string `json:"name"`
	Kind          string `json:"kind"`
	Private       bool   `json:"private,omitempty"`
	Enabled       bool   `json:"enabled"`
	Favorite      bool   `json:"favorite"`
	Rating        string `json:"rating,omitempty"`
	SourceID      string `json:"sourceId,omitempty"`
	GuideID       string `json:"guideId,omitempty"`
	GuideSourceID string `json:"guideSourceId,omitempty"`
	Group         string `json:"group,omitempty"`
	// contains filtered or unexported fields
}

ChannelSummary is the console lineup identity: everything the list, search and guide rows need, without stream URLs, items or programming.

type ChannelUsage added in v0.3.4

type ChannelUsage struct {
	ChannelID string `json:"channelId"`
	Viewers   int    `json:"viewers"`
}

ChannelUsage is one channel being watched.

type ChannelViewers added in v0.3.2

type ChannelViewers struct {
	Allow []int64 `json:"allow,omitempty"`
	Deny  []int64 `json:"deny,omitempty"`
}

ChannelViewers changes which viewers may watch one channel by editing their policies. Allowing a restricted viewer adds the channel to their list; allowing an unrestricted one grants a private channel (a public one they already see). Denying a private channel removes it from the list or the grants. Denying a public channel to an unrestricted viewer restricts them to every other channel they can see now, so they stop receiving new channels automatically. A viewer with Live TV disabled keeps it disabled.

type Fetched added in v0.3.3

type Fetched struct {
	Channels []Channel
	Guide    XMLGuide
	// AddonInputs marks channels whose inputs an add-on owns (ids carry
	// AddonInputPrefix); see addon_streams.go.
	AddonInputs bool
	// ProviderNumbers marks a lineup whose every number comes from the
	// provider. Channels imported before provider numbers were recorded
	// then take the provider's number instead of keeping a generated one.
	ProviderNumbers bool
}

Fetched is what a source kind yields on import or refresh: channels (with their ordered stream inputs) and guide data. ImportSource persists it atomically, preserving owner mapping, numbering and access rules.

type GuideChannel

type GuideChannel struct {
	SourceID   string     `json:"sourceId"`
	SourceName string     `json:"sourceName"`
	SourceKind string     `json:"sourceKind,omitempty"`
	GuideID    string     `json:"guideId"`
	Name       string     `json:"name"`
	Programmes int        `json:"programmes"`
	Until      *time.Time `json:"until,omitempty"`
}

GuideChannel is an XMLTV <channel> (or programme channel id) stored for a source. Programmes counts programmes that have not yet ended; Until is the end of the last stored programme.

type GuideCoverage

type GuideCoverage struct {
	ChannelID  string     `json:"channelId"`
	Programmes int        `json:"programmes"`
	Until      *time.Time `json:"until,omitempty"`
}

type GuideMatchChange

type GuideMatchChange struct {
	ChannelID       string `json:"channelId"`
	ChannelName     string `json:"channelName"`
	GuideSourceID   string `json:"guideSourceId"`
	GuideSourceName string `json:"guideSourceName"`
	GuideID         string `json:"guideId"`
	GuideName       string `json:"guideName"`
	Method          string `json:"method"` // "id", "name" or "alias"
	ReviewRegion    bool   `json:"reviewRegion,omitempty"`
}

type GuideMatchRequest

type GuideMatchRequest struct {
	ChannelIDs []string `json:"channelIds,omitempty"`
	Overwrite  bool     `json:"overwrite"`
	DryRun     bool     `json:"dryRun"`
	// Reviewed is set by the owner route for explicitly accepted proposals, never by imports.
	Reviewed bool `json:"-"`
}

type GuideMatchResult

type GuideMatchResult struct {
	Matched   int `json:"matched"`
	Unmatched int `json:"unmatched"`
	Skipped   int `json:"skipped"` // already had guide data and overwrite was false
	// Ambiguous and RegionRejected count name matches the matcher refused:
	// several guide ids or playlist rows share the name, or the declared
	// regions contradict.
	Ambiguous      int                `json:"ambiguous"`
	RegionRejected int                `json:"regionRejected"`
	DryRun         bool               `json:"dryRun"`
	Changes        []GuideMatchChange `json:"changes"`
}

type Item

type Item struct {
	EpisodeTitle string         `json:"episodeTitle,omitempty"`
	ShowTitle    string         `json:"showTitle,omitempty"`
	Art          string         `json:"art,omitempty"`
	Genres       []string       `json:"genres,omitempty"`
	GUID         string         `json:"guid,omitempty"`
	ShowGUID     string         `json:"showGuid,omitempty"`
	MediaType    string         `json:"mediaType,omitempty"`
	Premiere     bool           `json:"premiere,omitempty"`
	Live         bool           `json:"live,omitempty"`
	Key          core.RatingKey `json:"key"`
	Title        string         `json:"title"`
	DurationMS   int64          `json:"durationMs"`
	Summary      string         `json:"summary,omitempty"`
	Rating       string         `json:"rating,omitempty"`
	Thumb        string         `json:"thumb,omitempty"`
	// Show metadata keeps episodes in season order inside a shuffle,
	// marathon or newest-first playout. Zero values are movies and items
	// saved before weekly scheduling, which stay in their stored order.
	ShowKey    core.RatingKey `json:"showKey,omitempty"`
	Season     int            `json:"season,omitempty"`
	Episode    int            `json:"episode,omitempty"`
	ReleasedMS int64          `json:"releasedMs,omitempty"`
	// Slate is "offair" for a guide entry that fills a gap. It is not a
	// catalogue title and is never stored on a channel.
	Slate string `json:"slate,omitempty"`
}

type KindRegistry added in v0.3.3

type KindRegistry struct {
	// contains filtered or unexported fields
}

KindRegistry holds the Live TV source kinds. Safe for concurrent use.

func NewKindRegistry added in v0.3.3

func NewKindRegistry() *KindRegistry

NewKindRegistry returns a registry holding the built-in kinds.

func (*KindRegistry) Classify added in v0.3.3

func (r *KindRegistry) Classify(src Source) (SourceKind, bool)

Classify returns the first registered kind that recognises src, in registration order (xtream and plugin kinds before m3u, so an Xtream login or plugin source is never mistaken for a plain playlist URL).

func (*KindRegistry) Descriptors added in v0.3.3

func (r *KindRegistry) Descriptors() []extend.Descriptor

Descriptors returns the liveTVSource descriptors for every kind.

func (*KindRegistry) Kinds added in v0.3.3

func (r *KindRegistry) Kinds() []SourceKind

func (*KindRegistry) Lookup added in v0.3.3

func (r *KindRegistry) Lookup(id string) (SourceKind, bool)

func (*KindRegistry) Register added in v0.3.3

func (r *KindRegistry) Register(k SourceKind) error

func (*KindRegistry) Unregister added in v0.3.3

func (r *KindRegistry) Unregister(id string)

Unregister removes a runtime source kind by id.

type Options

type Options struct {
	PlexGuideToken func() string
	PlexGuideURL   string // test endpoint

	CanPlay            func(*core.User, bool, time.Time) bool
	StreamLimit        func(*core.User) int
	DB                 *sql.DB
	Catalog            core.Catalog
	Collections        core.Collections
	Playback           core.Playback
	Transcoder         core.Transcoder
	HTTPClient         *http.Client
	LogoHTTP           *http.Client // optional test transport for the logo index; nil uses the guarded client
	LogoIndexURL       func() string
	HealthSweepMinutes func() int // 0 defaults to 15; negative disables
	Now                func() time.Time
	// Logger receives credential-safe import, playback and delivery diagnostics.
	Logger *slog.Logger
	// Allowed applies additional owner title/genre policies to every surface.
	Allowed func(*core.User, *core.Title) bool
	// Warm resolves/primes a title without opening a viewer stream.
	Warm func(context.Context, core.RatingKey) error
	// Availability is the resolver's cached playability. Known-unavailable
	// titles are left out of the guide; unknown titles stay in place.
	Availability core.AvailabilitySource
	// Sealer protects source credentials at rest (secrets.go); nil stores
	// them as given.
	Sealer config.FieldSealer
	// Kinds overrides the source-kind registry; nil uses the built-ins.
	Kinds *KindRegistry
}

type PlexLineup added in v0.3.4

type PlexLineup struct {
	ID    string `json:"id"`
	Title string `json:"title"`
	Type  string `json:"type"`
}

type Policy

type Policy struct {
	UserID   int64 `json:"userId"`
	Disabled bool  `json:"disabled"`
	// Restricted limits the viewer to Channels (none when empty), private
	// ones included. Unrestricted viewers see every public channel, including
	// ones added later, plus the private channels in Granted; Channels is
	// empty.
	Restricted bool     `json:"restricted"`
	Channels   []string `json:"channels,omitempty"`
	Granted    []string `json:"granted,omitempty"`
	Favorites  []string `json:"favorites,omitempty"`
	MaxRating  string   `json:"maxRating,omitempty"`
	// contains filtered or unexported fields
}

Policy is one viewer's Live TV access. It is the only channel allow-list: the owner edits it from the viewer's page and from each channel.

func (Policy) Permits added in v0.3.2

func (p Policy) Permits(id string, private bool) bool

Permits reports whether the policy lets its viewer watch channel id, which is private when only granted viewers may watch it. Rating and library limits are checked separately.

type PreviewSession

type PreviewSession struct {
	ID               string     `json:"id"`
	Playlist         string     `json:"playlist"`
	ActiveSourceID   string     `json:"activeSourceId,omitempty"`
	ActiveSourceName string     `json:"activeSourceName,omitempty"`
	Programme        *Programme `json:"programme,omitempty"`
}

PreviewSession is what the owner UI needs to play a tuned channel. Stream URLs never leave the server; the playlist is relative to the livetv API.

type ProgramSource added in v0.3.0

type ProgramSource struct {
	Name       string         `json:"name,omitempty"`
	Weight     int            `json:"weight,omitempty"`
	Items      []Item         `json:"items,omitempty"`
	Collection core.RatingKey `json:"collection,omitempty"`
	Filter     *core.Filter   `json:"filter,omitempty"`
	Watched    string         `json:"watched,omitempty"`
}

ProgramSource is one weighted part of a mix. Weight 0 means 1. Watched is "", "watched" or "unwatched" for the whole household.

type Programme

type Programme struct {
	ID        string    `json:"id"`
	ChannelID string    `json:"channelId"`
	Start     time.Time `json:"start"`
	End       time.Time `json:"end"`
	Item      Item      `json:"item"`
}

func Schedule

func Schedule(c Channel, from, until time.Time) []Programme

Schedule is deterministic across restarts and query windows. A shuffle is a stable hash ordering, not process-global randomness.

Channels saved before weekly blocks and playout modes take legacySchedule, which is the original day-parting loop: blocks are UTC minutes every day, a shuffle is one stable permutation, and the last item in a block is clipped. That path is what keeps an existing channel's guide identical.

func (Programme) Offset

func (p Programme) Offset(at time.Time) time.Duration

type ProviderAccount added in v0.3.4

type ProviderAccount struct {
	Status            string     `json:"status,omitempty"`
	Trial             bool       `json:"trial,omitempty"`
	ExpiresAt         *time.Time `json:"expiresAt,omitempty"`
	MaxConnections    int        `json:"maxConnections,omitempty"`
	ActiveConnections int        `json:"activeConnections"`
	Formats           []string   `json:"formats,omitempty"`
	CheckedAt         time.Time  `json:"checkedAt"`
}

ProviderAccount is what an IPTV panel reports about the owner's login.

type Service

type Service struct {
	// contains filtered or unexported fields
}

func New

func New(o Options) (*Service, error)

func (*Service) AdoptAddonSource added in v0.3.3

func (s *Service) AdoptAddonSource(ctx context.Context, id, kind string) error

AdoptAddonSource moves a legacy Stremio source onto an add-on's channel source kind, in place. The source keeps its id, name and refresh setting; the add-on link is dropped (the plugin record holds it now). Channels are untouched: the add-on channel source mints the same channel and input ids, so numbers, enabled flags, favourites, ratings, guide matches and transcode settings carry over. A source that already moved is left alone.

func (*Service) AllowedChannel added in v0.3.4

func (s *Service) AllowedChannel(ctx context.Context, u *core.User, key string) (Channel, error)

AllowedChannel resolves one channel by id or number and applies the same policy check as AllowedChannels without loading the lineup. Plex tune used the whole list only to find the requested channel.

func (*Service) AllowedChannelCached added in v0.3.0

func (s *Service) AllowedChannelCached(ctx context.Context, u *core.User, id string, cached func(context.Context, core.RatingKey) (*core.Title, error)) error

AllowedChannelCached checks one channel and its current programme using local title facts only. Missing programme title or inherited show facts fail closed. The supplied lookup must not perform network or release resolution.

func (*Service) AllowedChannels

func (s *Service) AllowedChannels(ctx context.Context, u *core.User) ([]Channel, error)

func (*Service) AuthenticateMediaGrant added in v0.3.0

func (s *Service) AuthenticateMediaGrant(r *http.Request, active func(context.Context, int64) *core.User) (*core.User, bool)

AuthenticateMediaGrant authorizes GET and HEAD media for the session that minted the stream grant. It does not accept account tokens and it does not authorize session control.

func (*Service) Channel

func (s *Service) Channel(ctx context.Context, id string) (Channel, error)

func (*Service) ChannelPage added in v0.3.4

func (s *Service) ChannelPage(ctx context.Context, q ChannelPageQuery) (ChannelPage, error)

ChannelPage returns one page of full channel records for the console list and guide, plus the totals and groups the page needs to lay out the rest.

func (*Service) ChannelPermitted added in v0.3.0

func (s *Service) ChannelPermitted(ctx context.Context, u *core.User, id string) (bool, error)

ChannelPermitted decides one channel. A memo on ctx, when present, answers repeat checks for the same channel without another guide read. Callers that are not looking at a live channel must not call this.

func (*Service) ChannelSummaries added in v0.3.4

func (s *Service) ChannelSummaries(ctx context.Context) ([]ChannelSummary, error)

ChannelSummaries returns the whole lineup in display order. The result is shared and must be treated as read-only.

func (*Service) Channels

func (s *Service) Channels(ctx context.Context) ([]Channel, error)

func (*Service) Close

func (s *Service) Close() error

func (*Service) Coverage

func (s *Service) Coverage(ctx context.Context) ([]GuideCoverage, error)

Coverage reports, for every channel, how many programmes the guide has in the next 24 hours and when its guide data runs out.

func (*Service) DeleteChannel

func (s *Service) DeleteChannel(ctx context.Context, id string) error

func (*Service) DeleteSource

func (s *Service) DeleteSource(ctx context.Context, id string) error

func (*Service) Failover

func (s *Service) Failover(ctx context.Context, id string, u *core.User) error

Failover keeps the channel timestamp when Playback resolves another release.

Playback.Failover and HLSSession.SwapInput do I/O, so they run without ss.mu (getSession/ServeSegment/close stay responsive): the current track is snapshotted under mu and the swap is only kept if, once it finishes, the session is still alive and still carries that same track. A stale swap is discarded — the replacement playback session (when it is a new one) is stopped and the swapped HLS is closed — and reported as ErrNotFound, matching a session/track that is already gone.

func (*Service) Guide

func (s *Service) Guide(ctx context.Context, u *core.User, from, until time.Time) ([]Programme, error)

func (*Service) GuideChannels

func (s *Service) GuideChannels(ctx context.Context, q, source string, limit int) ([]GuideChannel, error)

GuideChannels searches stored XMLTV channels. Every word of q must appear, in any order, in the name, a display name or the guide id, so "fox phoenix" finds "FOX LOCAL Phoenix". Exact matches rank first, then channels that still have listings ahead, then by name.

func (*Service) GuideForChannels added in v0.3.4

func (s *Service) GuideForChannels(ctx context.Context, u *core.User, from, until time.Time, ids []string) ([]Programme, error)

GuideForChannels builds the guide for the requested channels only. The console sends the rows it can see; nil means the whole lineup (XMLTV).

func (*Service) GuideWindow added in v0.3.3

func (s *Service) GuideWindow() (from, until time.Time)

GuideWindow is the span of listings an import keeps: a day back and two weeks ahead of now.

func (*Service) Handler

func (s *Service) Handler() http.Handler

Handler is the owner management API. Mount behind the application's owner authentication and CSRF middleware; it deliberately cannot authenticate Plex tokens on its own. Streaming uses the separately authenticated Plex adapter.

func (*Service) HomeHub

func (s *Service) HomeHub(ctx context.Context, u *core.User, limit int) (core.Hub, error)

HomeHub is the domain-level row used by the owner's final-layout preview. The Plex adapter preserves its own channel/tune metadata for this row ID; these synthetic keys are never sent to catalogue playback.

func (*Service) ImportProgress added in v0.3.4

func (s *Service) ImportProgress(id string) (SourceProgress, bool)

ImportProgress returns a live snapshot for the owner endpoint.

func (*Service) ImportSource

func (s *Service) ImportSource(ctx context.Context, src Source) (Source, error)

ImportSource atomically replaces source programming and logs the outcome. Owner changes to mapping, enabled state, channel numbering, favourites and restrictions survive refresh.

func (*Service) ImportUpload added in v0.2.49

func (s *Service) ImportUpload(ctx context.Context, id, part string, body io.Reader) (Source, error)

ImportUpload streams a raw playlist or guide body (gzip is decompressed while reading) into an existing source.

func (*Service) KindRegistry added in v0.3.3

func (s *Service) KindRegistry() *KindRegistry

KindRegistry exposes this service's source-kind registry.

func (*Service) LegacyStremioSources added in v0.3.3

func (s *Service) LegacyStremioSources(ctx context.Context) ([]Source, error)

LegacyStremioSources returns the sources still on the built-in Stremio kind, with their add-on links opened, for the start-up migration.

func (*Service) LineupChanged added in v0.3.0

func (s *Service) LineupChanged() time.Time

LineupChanged is the DVR refreshedAt clients poll. It moves whenever the channel set changes, including twice in the same wall-clock second.

func (*Service) LiveUsage added in v0.3.4

func (s *Service) LiveUsage(_ context.Context) (Usage, error)

LiveUsage reports current viewing and provider usage. It reads only in-memory state. Source limits come from the console’s existing source snapshot; this poll must never deserialize stored playlists or guides.

func (*Service) MatchGuides

func (s *Service) MatchGuides(ctx context.Context, req GuideMatchRequest) (GuideMatchResult, error)

MatchGuides maps IPTV channels to guide channels from any source: an exact tvg-id wins, then a case-insensitive id, then a normalized display name. Only guide channels with upcoming programmes are considered, and disabled channels only when listed in ChannelIDs. Without Overwrite, channels that already have programmes in the next 24 hours are left alone.

func (*Service) MigrateSecrets added in v0.2.4

func (s *Service) MigrateSecrets(ctx context.Context) (int, error)

MigrateSecrets seals source secrets stored in plaintext and returns how many rows it rewrote. A row is replaced only if unchanged since read (safe to rerun on any node); a row that does not open is left untouched and reported in the error.

func (*Service) Now added in v0.3.3

func (s *Service) Now() time.Time

Now returns the service's clock reading.

func (*Service) OnNow added in v0.3.0

func (s *Service) OnNow(ctx context.Context, u *core.User, now, until time.Time) ([]Channel, []Programme, error)

OnNow is the bounded guide used to build a live row: one policy read, then at most the current and next programme for each enabled channel.

func (*Service) OnNowN added in v0.3.0

func (s *Service) OnNowN(ctx context.Context, u *core.User, now, until time.Time, limit int) ([]Channel, []Programme, error)

OnNowN stops after limit allowed channels. Home uses it so a large guide does not query a programme window for every channel on the request.

func (*Service) ParseSourceGuide added in v0.3.3

func (s *Service) ParseSourceGuide(ctx context.Context, src Source, channels []Channel, r io.Reader) (XMLGuide, error)

ParseSourceGuide reads an XMLTV guide (plain or gzip) for a source kind's Fetch: listings outside the guide window are dropped, as are channels that are neither among channels nor mapped to src by the owner.

func (*Service) PlexLineups added in v0.3.4

func (s *Service) PlexLineups(ctx context.Context, country, postal string) ([]PlexLineup, error)

func (*Service) Policies added in v0.3.2

func (s *Service) Policies(ctx context.Context) ([]Policy, error)

Policies lists every stored viewer policy. Viewers without one see every channel.

func (*Service) Policy

func (s *Service) Policy(ctx context.Context, userID int64) (Policy, error)

func (*Service) RefreshSource

func (s *Service) RefreshSource(ctx context.Context, id string) (Source, error)

RefreshSource re-imports a stored source. On failure the existing channels and guide are kept and the error is recorded on the source.

func (*Service) RegisterSourceKind added in v0.3.3

func (s *Service) RegisterSourceKind(k SourceKind) error

RegisterSourceKind adds a source kind to this service.

func (*Service) ResolveStreamSource added in v0.3.3

func (s *Service) ResolveStreamSource(ctx context.Context, c Channel, src StreamSource) (string, http.Header, error)

ResolveStreamSource resolves a channel's stream input at tune time. HTTP(S) URLs return directly; plugin:// inputs delegate to the installed stream opener under its Open deadline.

func (*Service) SaveChannel

func (s *Service) SaveChannel(ctx context.Context, c Channel) (Channel, error)

func (*Service) SavePendingSource added in v0.2.49

func (s *Service) SavePendingSource(ctx context.Context, src Source) (Source, error)

SavePendingSource stores a named source that will receive a file upload next. A body that already has a playlist or guide is imported instead.

func (*Service) SavePolicy

func (s *Service) SavePolicy(ctx context.Context, p Policy) error

func (*Service) ServeDASH

func (s *Service) ServeDASH(w http.ResponseWriter, r *http.Request, id string, u *core.User) error

func (*Service) ServeDASHPart

func (s *Service) ServeDASHPart(w http.ResponseWriter, r *http.Request, id string, u *core.User, rep, n int, init bool)

func (*Service) ServeHTTP added in v0.3.0

func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request, id string, u *core.User) error

ServeHTTP writes Plex for Mac's protocol=http Matroska stream for a live session, when the decision can convert the channel.

func (*Service) ServePlaylist

func (s *Service) ServePlaylist(w http.ResponseWriter, r *http.Request, id string, u *core.User)

ServePlaylist creates a sliding live playlist. Each programme has its own transcoder, with a discontinuity at the exact programme boundary. Offsets are measured in programme time and never in release byte positions.

func (*Service) ServeRelaySegment

func (s *Service) ServeRelaySegment(w http.ResponseWriter, r *http.Request, id string, n int, u *core.User)

func (*Service) ServeResource

func (s *Service) ServeResource(w http.ResponseWriter, r *http.Request, id, key string, u *core.User)

func (*Service) ServeSegment

func (s *Service) ServeSegment(w http.ResponseWriter, r *http.Request, id string, start int64, n int, u *core.User)

func (*Service) ServeXMLTV

func (s *Service) ServeXMLTV(w http.ResponseWriter, r *http.Request, u *core.User)

func (*Service) Session

func (s *Service) Session(ctx context.Context, id string, u *core.User) (SessionInfo, error)

func (*Service) Sessions

func (s *Service) Sessions(ctx context.Context, u *core.User) []SessionInfo

func (*Service) SetChannelViewers added in v0.3.2

func (s *Service) SetChannelViewers(ctx context.Context, channelID string, change ChannelViewers) error

func (*Service) SetOnChange added in v0.3.0

func (s *Service) SetOnChange(fn func())

SetOnChange registers the Plex lineup refresh. Save, delete and source import call it after the database commit.

func (*Service) SetStreamOpener added in v0.3.3

func (s *Service) SetStreamOpener(fn func(ctx context.Context, c Channel, src StreamSource) (string, http.Header, error))

SetStreamOpener installs a lazy stream resolver for plugin:// stream inputs.

func (*Service) SourceChannels added in v0.3.3

func (s *Service) SourceChannels(ctx context.Context, sourceID string) ([]Channel, error)

SourceChannels returns the stored channels of one source, inputs unredacted (for a source kind's Fetch).

func (*Service) SourceKindOf added in v0.3.3

func (s *Service) SourceKindOf(src Source) string

SourceKindOf classifies a saved source ("xtream", "guideOnly", "m3u").

func (*Service) Sources

func (s *Service) Sources(ctx context.Context) ([]Source, error)

func (*Service) Stop

func (s *Service) Stop(ctx context.Context, id string, u *core.User) error

func (*Service) StreamSourceStatuses

func (s *Service) StreamSourceStatuses(ctx context.Context, channelID string) ([]StreamSourceStatus, error)

StreamSourceStatuses is owner-only health, without source URLs or upstream errors that might contain credentials. Transient failures never ban a source.

func (*Service) TranscodeFor

func (s *Service) TranscodeFor(playbackID string) (core.HLSSession, bool)

TranscodeFor lets the playback manager's existing release failover locate programme sessions, preserving the same media timestamp after a source swap.

func (*Service) Tune

func (s *Service) Tune(ctx context.Context, u *core.User, channelID string, caps core.ClientCaps) (SessionInfo, error)

func (*Service) TuneWithSession

func (s *Service) TuneWithSession(ctx context.Context, u *core.User, channelID string, caps core.ClientCaps, clientSessionID string) (result SessionInfo, resultErr error)

TuneWithSession preserves the client's playback-session identity independently of the server's stream, subscription and activity identities.

func (*Service) UnregisterSourceKind added in v0.3.3

func (s *Service) UnregisterSourceKind(id string)

UnregisterSourceKind removes a runtime source kind from this service.

func (*Service) UpdateChannels added in v0.3.4

func (s *Service) UpdateChannels(ctx context.Context, change ChannelBatch) (int, error)

func (*Service) ViewingSessions

func (s *Service) ViewingSessions() []*core.PlaySession

ViewingSessions reports one trusted reservation unit per live viewer. It is used by ordinary playback's limit accounting, including HLS passthrough viewers that do not own an ffmpeg/source session.

func (*Service) VisibleTo added in v0.3.4

func (s *Service) VisibleTo(ctx context.Context, u *core.User) bool

VisibleTo reports whether Live TV should appear in u's apps at all: the server has at least one channel and the owner has not turned Live TV off for this viewer. The owner's own switch applies to the owner's apps too. It is two indexed lookups, cheap enough for the provider list apps fetch on every launch.

type SessionInfo

type SessionInfo struct {
	ActiveSourceID string `json:"activeSourceId,omitempty"`
	// Plex's rolling subscription identity is numeric; the stream UUID remains
	// separate. These are ephemeral identities, scoped to this service lifetime.
	SubscriptionID  int64           `json:"-"`
	ActivityID      string          `json:"-"`
	ClientSessionID string          `json:"-"`
	MediaInfo       *core.MediaInfo `json:"-"` // Actual source analysis for native clients.
	ID              string          `json:"id"`
	UserID          int64           `json:"userId"`
	Channel         Channel         `json:"channel"`
	Programme       *Programme      `json:"programme,omitempty"`
	StartedAt       time.Time       `json:"startedAt"`
}

type Source

type Source struct {
	LineupID    string `json:"lineupId,omitempty"`
	Country     string `json:"country,omitempty"`
	PostalCode  string `json:"postalCode,omitempty"`
	PlexCursor  int    `json:"plexCursor,omitempty"`
	ID          string `json:"id"`
	Name        string `json:"name"`
	Kind        string `json:"kind,omitempty"` // a source kind id, e.g. plugin:<id>/channels
	PlaylistURL string `json:"playlistUrl,omitempty"`
	GuideURL    string `json:"guideUrl,omitempty"`
	// Inline uploads are supplied on import and are not returned by GET.
	Playlist  string    `json:"playlist,omitempty"`
	Guide     string    `json:"guide,omitempty"`
	UpdatedAt time.Time `json:"updatedAt"`
	// Auth authenticates playlist, guide and stream requests to the provider.
	// Secrets are redacted by the owner API (see auth.go).
	Auth *SourceAuth `json:"auth,omitempty"`
	// RefreshHours caps the time between automatic refreshes of URL-backed
	// sources: 0 uses the 6 hour default, a negative value means manual
	// refresh only. Sources also refresh before mapped listings run out.
	// Inline uploads never refresh automatically.
	RefreshHours int `json:"refreshHours,omitempty"`
	// LastError/LastErrorAt/RefreshFailures record failed refreshes; a
	// successful import clears them. Existing data is kept on failure.
	LastError       string     `json:"lastError,omitempty"`
	LastErrorAt     *time.Time `json:"lastErrorAt,omitempty"`
	RefreshFailures int        `json:"refreshFailures,omitempty"`
	// SkippedVOD counts VOD entries (Xtream /movie/ and /series/ streams)
	// the last playlist import left out. It is persisted with the source so
	// the console can report what the import dropped.
	SkippedVOD int `json:"skippedVod,omitempty"`
	// Read-only statistics computed by Sources(); ignored on import.
	HasPlaylist       bool       `json:"hasPlaylist"`
	HasGuide          bool       `json:"hasGuide"`
	ChannelCount      int        `json:"channelCount"`
	GuideChannelCount int        `json:"guideChannelCount"`
	ProgrammeCount    int        `json:"programmeCount"`
	GuideFrom         *time.Time `json:"guideFrom,omitempty"`
	GuideUntil        *time.Time `json:"guideUntil,omitempty"`
	NextRefresh       *time.Time `json:"nextRefresh,omitempty"`

	// Account is the provider's report on this login (Xtream panels),
	// refreshed with the lineup. Read-only.
	Account *ProviderAccount `json:"account,omitempty"`
	// Groups are the provider's channel groups from the last refresh, in
	// provider order, including the ones the owner excludes. Read-only.
	Groups []SourceGroup `json:"groups,omitempty"`
	// ExcludeGroups are group names the owner leaves out; their channels
	// are not imported at all. NewGroups "exclude" adds each group that
	// first appears on a refresh to ExcludeGroups; "" includes it.
	// HideAdult leaves out every group the provider flags as adult.
	ExcludeGroups []string `json:"excludeGroups,omitempty"`
	NewGroups     string   `json:"newGroups,omitempty"`
	HideAdult     bool     `json:"hideAdult,omitempty"`
	// GroupOrder is the owner's group order. Empty keeps the provider's
	// order and numbers; otherwise channels are numbered 1… down the
	// listed groups (unlisted groups follow in provider order).
	GroupOrder []string `json:"groupOrder,omitempty"`

	// Provider etiquette for the source's streams. MaxConnections caps
	// simultaneous provider connections below what the panel allows (0 uses
	// the panel's limit; it can never raise it). TuneGapSeconds is the least
	// time between two new provider streams: 0 is the default (2 s on a
	// connection-limited account, none otherwise), negative turns it off.
	// TunesPerHour caps new provider streams per hour; 0 is no cap.
	MaxConnections int `json:"maxConnections,omitempty"`
	TuneGapSeconds int `json:"tuneGapSeconds,omitempty"`
	TunesPerHour   int `json:"tunesPerHour,omitempty"`
}

type SourceAuth

type SourceAuth struct {
	// Type: "" (none), "basic", "bearer", "headers" or "xtream".
	Type     string `json:"type,omitempty"`
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"` // secret
	Token    string `json:"token,omitempty"`    // secret (bearer)
	// UserAgent and Referer apply to every type; many providers reject
	// unknown clients.
	UserAgent string `json:"userAgent,omitempty"`
	Referer   string `json:"referer,omitempty"`
	// Headers are extra request headers (cookies, API keys); values are secret.
	Headers map[string]string `json:"headers,omitempty"`
	// Xtream Codes: the panel URL. Playlist and guide URLs are derived from
	// it and the credentials, and never returned by the API.
	Server string `json:"server,omitempty"`
	// Output selects the Xtream stream format: "ts" (default) or "m3u8".
	Output string `json:"output,omitempty"`
}

SourceAuth is how Ether authenticates to an IPTV or guide provider. It is used for the source's playlist and guide fetches and, through upstreamHeaders, for the streams of channel inputs that belong to it.

type SourceDescribe added in v0.3.3

type SourceDescribe struct {
	Label          extend.Text
	Hint           extend.Text
	Icon           string
	Order          int
	Editor         string
	Params         []extend.ParamField
	YieldsChannels bool
	YieldsGuide    bool
}

SourceDescribe carries the console-facing description of a source kind.

type SourceGroup added in v0.3.4

type SourceGroup struct {
	Name     string `json:"name"`
	Channels int    `json:"channels"`
	Adult    bool   `json:"adult,omitempty"`
	// Excluded reports whether the owner's rules leave this group out.
	Excluded bool `json:"excluded,omitempty"`
}

SourceGroup is one provider channel group.

type SourceKind added in v0.3.3

type SourceKind interface {
	ID() string
	Describe() SourceDescribe
	Matches(src Source) bool
	// Fetch fills src.Playlist/src.Guide from URLs as needed (so they are
	// stored for the next refresh) and returns the parsed result.
	Fetch(ctx context.Context, s *Service, src *Source) (Fetched, error)
}

SourceKind is one Live TV source kind: how the console describes it, how a saved Source is recognised as this kind, and how it yields channels, inputs and guide data. Fetch runs on import/refresh only, never on a request path; playback reads stored channels.

To add a kind: implement SourceKind and register it (built-ins in builtinSourceKinds; runtime kinds such as plugin channel sources, which include Stremio `tv` add-ons, via Service.RegisterSourceKind). Then serve the result of KindRegistry.Descriptors through the extend registry. A kind that needs its own Source fields beyond playlist/guide/auth must extend Source additively and classify in Matches before "m3u".

type SourceProgress added in v0.3.4

type SourceProgress struct {
	Phase           string    `json:"phase"` // starting, playlist, guide, saving
	Read            int64     `json:"read"`
	Total           int64     `json:"total,omitempty"`
	Channels        int       `json:"channels,omitempty"`
	Programmes      int       `json:"programmes,omitempty"`
	SavedChannels   int       `json:"savedChannels,omitempty"`
	SavedProgrammes int       `json:"savedProgrammes,omitempty"`
	Started         time.Time `json:"started"`
	Updated         time.Time `json:"updated"`
}

SourceProgress is the owner-visible progress of one manual source import. Read counts bytes the panel has sent, so a multi-gigabyte guide can be watched while it downloads.

type SourceUsage added in v0.3.4

type SourceUsage struct {
	SourceID string `json:"sourceId"`
	// Open is how many provider connections Ether holds now.
	Open int `json:"open"`
	// StartsLastHour counts stream starts reserved in the last hour.
	StartsLastHour int `json:"startsLastHour"`
}

SourceUsage is one source's provider connections and stream starts.

type StreamSource

type StreamSource struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	URL      string `json:"url"`
	Enabled  bool   `json:"enabled"`
	Priority int    `json:"priority"` // smaller first; equal priorities retain list order
	// AuthSourceID names the source whose provider credentials this input
	// uses; empty means the channel's own source.
	AuthSourceID string `json:"authSourceId,omitempty"`
	// Headers are request headers this input needs (a Stremio stream's
	// proxy headers), sent after the source's own.
	Headers map[string]string `json:"headers,omitempty"`
}

StreamSource is one independently replaceable input to a channel. EPG, numbering, favourites and viewer policy belong to Channel, never to an input.

type StreamSourceStatus

type StreamSourceStatus struct {
	ID          string    `json:"id"`
	State       string    `json:"state"`
	Failures    int       `json:"failures"`
	LastChecked time.Time `json:"lastChecked,omitempty"`
	LastPlayed  time.Time `json:"lastPlayed,omitempty"`
	RetryAfter  time.Time `json:"retryAfter,omitempty"`
	LastError   string    `json:"lastError,omitempty"`
}

type TuneError added in v0.3.4

type TuneError struct {
	Code           string
	Stage          string
	UpstreamStatus int
	RetryAfter     time.Duration
	// contains filtered or unexported fields
}

TuneError retains machine-readable evidence without retaining a provider URL, response body or headers in its printable message. Unwrap preserves existing cancellation, availability and stream-limit handling.

func Diagnose added in v0.3.4

func Diagnose(stage string, err error) *TuneError

Diagnose never copies an upstream error's text: net/url and ffmpeg errors commonly include credentials in path components, not just query parameters.

func (*TuneError) Error added in v0.3.4

func (e *TuneError) Error() string

func (*TuneError) LogAttrs added in v0.3.4

func (e *TuneError) LogAttrs() []any

func (*TuneError) Unwrap added in v0.3.4

func (e *TuneError) Unwrap() error

type Usage added in v0.3.4

type Usage struct {
	// Sessions counts live tunes; Viewers the distinct viewers behind them.
	Sessions int            `json:"sessions"`
	Viewers  int            `json:"viewers"`
	Watching []ChannelUsage `json:"watching"`
	Sources  []SourceUsage  `json:"sources"`
}

Usage is the Live TV console's live picture: who is watching which channel, and how much of each provider's allowance Ether is using.

type VirtualDASHError added in v0.3.0

type VirtualDASHError struct {
	Err error
	// Message is the client-facing reason when it is more specific than Err.
	Message string
}

VirtualDASHError is a virtual channel that could not produce DASH. Plex turns it into a decision document. A bare 503 is what Plex Web reports as s1001 (Network), which hides the reason and blocks fallback.

func (*VirtualDASHError) Error added in v0.3.0

func (e *VirtualDASHError) Error() string

func (*VirtualDASHError) Unwrap added in v0.3.0

func (e *VirtualDASHError) Unwrap() error

type XMLGuide

type XMLGuide struct {
	Channels   map[string]Channel
	Programmes []Programme
	// Names holds every display-name of each XMLTV channel, in order.
	Names map[string][]string
	// LateChannels counts <channel> elements that appeared after the first
	// <programme>. A valid guide has none; they are skipped rather than
	// buffered, so name candidates they declare are missed.
	LateChannels int
}

func ParseXMLTV

func ParseXMLTV(r io.Reader) (XMLGuide, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL