Skip to content

About

An FFmpeg 9-based media player engine for Apple platforms (iOS, tvOS, macOS, visionOS), designed from scratch for stability on long-running IPTV streams. The engine behind Lume.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

9 stars

Watchers

0 watching

Forks

Repository files navigation

LumeEngine

An FFmpeg 9-based media player engine for Apple platforms, designed from scratch for stability on long-running IPTV streams. Built for Lume.

CI Platforms Swift FFmpeg License

Status: pre-1.0, beta. The engine plays real IPTV content and ships as an opt-in beta engine in Lume, but the public API is not yet frozen — expect breaking changes between 0.x releases.

An original architecture, not a fork of an existing player. See PLAN.md for the full design document, including the catalog of legacy-engine failure modes this engine makes structurally impossible (§3), the architecture decisions (§4), and the roadmap (§9).

Highlights

  • FFmpeg 9.0.x, LGPL configuration, built by an in-repo pipeline (build/) into a single FFmpeg.xcframework with App Store-clean slices for all platforms — no bundle-ID patching scripts.
  • System-quality A/V sync: decoded frames feed AVSampleBufferRenderSynchronizer — Apple's clock owns sync, rate, HDR tone mapping, and PiP. No hand-rolled drop-frame heuristics.
  • One hardware decode path (FFmpeg-managed VideoToolbox) with a single, tested software-fallback policy.
  • Honest Swift 6 concurrency: actors for control, dedicated threads + bounded PTS-accounted channels for data, RAII wrappers around FFmpeg objects, session epochs instead of rebuild-in-place.
  • MPEG-TS 33-bit wraparound handled at the demux boundary — engine time is monotonic 64-bit, verified by tests against a real wrap-seam fixture.
  • Watchdogs built in: stall detection from playhead ground truth, supervised decode threads, typed PlayerEvents. Silence is never a failure mode.
  • Subtitles (embedded + external files), runtime audio-track switching, Now Playing / remote commands, Picture-in-Picture, audio session lifecycle.

Scope

The engine decodes and renders, and reports what happened. It does not retry on its own schedule: reconnect policy, backoff, engine fallback, and UI belong to the consuming app. Failures surface as typed EngineErrors and PlayerEvents on the session's AsyncStream — never as a log line you have to parse, and never as silence. See PLAN.md §2.

Installation

Add the package to your Package.swift:

dependencies: [
    .package(url: "https://github.com/bilipp/LumeEngine.git", from: "0.1.3")
],
targets: [
    .target(name: "YourApp", dependencies: ["LumeEngine"])
]

Or in Xcode: File ▸ Add Package Dependencies…, paste https://github.com/bilipp/LumeEngine.git, and add the LumeEngine library to your app target. Xcode embeds and signs it for you; if you wire it up by hand, make sure LumeEngine is in Embed Frameworks with Embed & Sign — the product is a dynamic library (on purpose).

You do not need the FFmpeg build pipeline to consume the package. SwiftPM downloads a prebuilt, checksum-pinned FFmpeg.xcframework from the release attached to this repository — a 117 MB download that expands to ~270 MB, covering all 10 platform slices, cached across builds. The build/ directory matters only if you are working on the engine itself, or want to build FFmpeg differently — see CONTRIBUTING.md.

Requires Xcode 26+, and iOS 18+ / tvOS 18+ / macOS 15+ / visionOS 2+.

If your app also links another FFmpeg-based player (KSPlayer/FFmpegKit, VLCKit, …), add LumeEngine as a path or submodule dependency rather than by URL. The engine normally builds with library evolution, which keeps its FFmpeg C module out of your compile — but SwiftPM forbids the flag that enables it in version-resolved dependencies, so a URL dependency drops it and your build hits conflicting definitions of FFmpeg's C types (enum AVPixelFormat from CFFmpeg vs Libavutil). A path dependency keeps evolution on and compiles cleanly. This is how Lume itself integrates.

Usage

import LumeEngine

let player = LumePlayer()                    // @MainActor @Observable
try await player.load(url: "https://example.com/stream.m3u8")
player.play()

// SwiftUI
LumePlayerView(player: player)

// Lower-level control plane
let session = PlayerSession(configuration: .init())
let info = try await session.open(url: url)
await session.play()
for await event in session.events { ... }

Building from source

Only needed to work on the engine — consumers get the FFmpeg binary from the release (see Installation). Cloning the repo and running swift build works the same way: with no BinaryDependencies/FFmpeg.xcframework present, the manifest falls back to the released artifact and downloads it.

To build FFmpeg yourself instead — required if you change build/versions.json, the configure flags, or the patch set — put the result where the manifest prefers it, and it takes precedence over the download:

# FFmpeg xcframework (10-20 min for one slice)
brew install meson ninja nasm                    # dav1d's build; nasm only for x86_64 slices
build/scripts/fetch-sources.sh                   # FFmpeg + dav1d tarballs, SHA-256 verified
build/scripts/build-ffmpeg.sh macos-arm64        # one slice is enough for local dev
build/scripts/make-xcframework.sh                # -> BinaryDependencies/FFmpeg.xcframework

swift build
swift test          # 64 tests: unit, fixture-based decode, playback, torture server
swift run LumeEngineDemo                         # macOS demo with diagnostics HUD

Testing needs a host ffmpeg CLI (brew install ffmpeg) to generate fixtures on first run — a build-time tool, unrelated to the FFmpeg the engine links. The full 10-slice matrix (build/scripts/platforms.sh) is only needed for release binaries, which CI builds on tags.

Debugging streams with lumectl

lumectl (macOS) plays a URL headlessly through the engine and prints what it reports, so a stream problem — a slow start, stalls, a reconnect that replays media, timestamp seams, a hardware decoder falling back, an HTTP error — can be reproduced from a terminal without the app:

swift run lumectl probe 'http://provider.example:8080/live/user/pass/1234.ts'
swift run lumectl play  'http://provider.example:8080/live/user/pass/1234.ts' --duration 60
swift run lumectl play  stream.m3u8 --json --record-events run.jsonl --interval 1

probe prints the container, duration, start time, live/seekable flags and every track (codec, language, resolution, frame rate, bit depth, HDR transfer and colour tags). play runs a muted session and prints every PlayerEvent with a timestamp, a diagnostics heartbeat (queue depths, renderer health, delivery rate, timeline seams, replayed packets), the startup timeline once the first frame shows, the stalled stage on every stall, and the typed error — with its HTTP status class — on failure. --json switches stdout to JSON Lines; --record-events FILE keeps a JSON Lines copy next to the readable output.

The exit status tells the outcome apart for scripted repro loops: 0 ok, 3 the source never opened, 4 playback failed, 5 stalled (without failing), 2 bad arguments. Ctrl-C ends a run cleanly with a summary.

URLs are printed as scheme and host only (http://provider.example:8080/[redacted].ts), because IPTV URLs carry the account; header values are never printed. Pass --show-urls to see them in full. lumectl --help lists every option: start position, mid-stream seek, software decoding, preferred audio languages, a single lane, custom headers, user agent, referer, reconnect and timeouts.

Layout

  • Sources/LumeEngineCore — demux, decode, render, session, subtitles, media integration (no UI)
  • Sources/LumeEngine — public facade: LumePlayer, LumePlayerView
  • Sources/lumectl, Sources/LumeCtlCore — the lumectl command-line tool
  • build/ — FFmpeg build pipeline (versions manifest, scripts, patches)
  • DemoApp/ — macOS demo with diagnostics HUD
  • Tests/ — including a fault-injecting HTTP torture server

Contributing

Contributions are welcome — start with CONTRIBUTING.md, and read PLAN.md §3–§4 before touching the data plane. The invariants there (control-plane/data-plane split, session epochs, timestamp unwrapping at the demux boundary, Apple owning A/V sync) are hard requirements rather than style preferences, and they are the reason this engine exists.

Security issues: see SECURITY.md — please don't open a public issue.

License

Engine source: MIT — see LICENSE.

LumeEngine links FFmpeg under the LGPL 2.1+, and ships one patch to FFmpeg source under that same license. If you distribute an app built on this engine, THIRD-PARTY-NOTICES.md tells you exactly what that obliges you to do — and how this repository is arranged to keep it straightforward. The build is verifiably LGPL, not GPL (--disable-gpl --disable-nonfree), and the LumeEngine product is dynamic so FFmpeg stays relinkable.

About

An FFmpeg 9-based media player engine for Apple platforms (iOS, tvOS, macOS, visionOS), designed from scratch for stability on long-running IPTV streams. The engine behind Lume.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

9 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages