Help us shape the future of Go! Take the 2026 Go Developer Survey

livetv

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: GPL-3.0 Imports: 31 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 Provider = "tv.plex.providers.epg.xmltv:1"

Variables

This section is empty.

Functions

This section is empty.

Types

type Block

type Block struct {
	// Start and End are UTC minutes after midnight. End may be 1440.
	Start   int    `json:"start"`
	End     int    `json:"end"`
	Items   []Item `json:"items"`
	Shuffle bool   `json:"shuffle"`
}

type Channel

type Channel struct {
	Transcode     bool           `json:"transcode"` // IPTV: normalize codecs through the transcoder
	ID            string         `json:"id"`
	Number        string         `json:"number"`
	Name          string         `json:"name"`
	Kind          string         `json:"kind"` // virtual or iptv
	Enabled       bool           `json:"enabled"`
	Favorite      bool           `json:"favorite"`
	Rating        string         `json:"rating,omitempty"`
	AllowedUsers  []int64        `json:"allowedUsers,omitempty"`
	SourceID      string         `json:"sourceId,omitempty"`
	GuideID       string         `json:"guideId,omitempty"`
	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"`
	Epoch         time.Time      `json:"epoch"`
	Seed          int64          `json:"seed"`
	Shuffle       bool           `json:"shuffle"`
	Items         []Item         `json:"items"`
	Blocks        []Block        `json:"blocks,omitempty"`
	// Collection/Filter are expanded into a stable item snapshot when saved.
	Collection core.RatingKey `json:"collection,omitempty"`
	Filter     *core.Filter   `json:"filter,omitempty"`

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

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 GuideChannel

type GuideChannel struct {
	SourceID   string     `json:"sourceId"`
	SourceName string     `json:"sourceName"`
	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" or "name"
}

type GuideMatchRequest

type GuideMatchRequest struct {
	ChannelIDs []string `json:"channelIds,omitempty"`
	Overwrite  bool     `json:"overwrite"`
	DryRun     bool     `json:"dryRun"`
}

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
	DryRun    bool               `json:"dryRun"`
	Changes   []GuideMatchChange `json:"changes"`
}

type Item

type Item struct {
	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"`
}

type Options

type Options struct {
	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
	Now         func() time.Time
	// 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
}

type Policy

type Policy struct {
	UserID    int64    `json:"userId"`
	Disabled  bool     `json:"disabled"`
	Channels  []string `json:"channels,omitempty"` // empty permits all
	Favorites []string `json:"favorites,omitempty"`
	MaxRating string   `json:"maxRating,omitempty"`
}

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 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. Blocks start at an exact UTC wall-clock boundary; the last item is clipped to avoid guide gaps.

func (Programme) Offset

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

type Service

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

func New

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

func (*Service) AllowedChannels

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

func (*Service) Channel

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

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.

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 by case-insensitive substring of name, any display name, or guide id.

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) ImportSource

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

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

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) 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) SaveChannel

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

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)

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) 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) 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) (SessionInfo, error)

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

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.

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 {
	ID          string `json:"id"`
	Name        string `json:"name"`
	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"`
	// 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"`
}

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 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"`
}

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 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
}

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