Skip to content

feat(player): add AetherEngine as an opt-in fifth playback engine - #363

Merged
bilipp merged 7 commits into
mainfrom
feature/add-support-for-aetherengine-https
Nov 2, 2026
Merged

bilipp merged 7 commits into
mainfrom
feature/add-support-for-aetherengine-https

Conversation

@bilipp

@bilipp bilipp commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds AetherEngine as a fifth playback engine alongside KSPlayer, VLCKit, AVPlayer and LumeEngine. It is opt-in and always last in the priority list, so nothing changes for existing users. Users who move it up get Aether's FFmpeg 8.1 + native-AVPlayer playback and a curated set of engine options.

Implementation

Importing AetherEngine directly into Lume fails: its AetherLib* C modules redefine KSPlayer/FFmpegKit's Libav* types. So Lume talks to it only through a new in-repo dynamic SPM package, LumeAetherBridge/. That package pins AetherEngine exactly at 7.53.1, uses library evolution and internal import AetherEngine, and exposes a small API (LumeAetherSession, LumeAetherLoadRequest). nm -m shows each engine binding to its own FFmpeg. App-side wiring follows the LumeEngine template:

  • AetherEngineCoordinator (+Failures/+Volume/+PictureInPicture)
  • AetherEngineEngineView and FullScreenPlayerView+Aether
  • MultiViewAetherTile and the PreviewPlayerHandle case
  • AetherEngineOptions (player.aether.* → AetherLoadPlan)
  • AetherEngineSettingsViews (an iOS/macOS form and a tvOS detail)

A simplify pass replaced per-engine copies with shared helpers used by all five hosts: subtitle cue overlay, invisible button style, menu/play-pause presses, rate label, macOS hover reveal, Now Playing transport, EngineFailurePolicy and tile reconnect binding.

Decisions

  • Curated options only: decode path + software fallback, deinterlace mode/rate, live join profile, forward buffer, probe size / analyze duration, native HLS, audio output mode, audio delay, HDR handling, low-res upscaling, system subtitle tracks. Diagnostic flags stay hidden.
  • Package traits stay off (TrueHD/DTS:X object audio, dual-layer DV, DV still reshaping). The options they gate are never set or shown. Technical labels only, no Dolby word marks.
  • Runs everywhere: full screen, Multiview tiles and guide previews. Embedded use is muted, a secondary output, has no display criteria and uses the tile's startup window. No preview→full-screen handoff.
  • iOS/iPadOS, macOS and tvOS only. visionOS is excluded (#if !os(visionOS), platformFilters, PlayerEngineKind.isAvailable).
  • macOS gets com.apple.security.network.server for Aether's loopback HLS server. iOS/tvOS reuse the existing NSLocalNetworkUsageDescription.
  • Display name "AetherEngine", no Beta label.
  • Full PiP: native route and sample-buffer route, with MacPictureInPictureScaler on macOS.
  • Live TV: decode/render only, no timeshift (dvrWindowSeconds stays nil).
  • tvOS Match Content is Aether's own toggle (default on); the system setting is not read.
  • Options are device-local UserDefaults, not synced. No SwiftData or CloudKit change.
  • Licences: AetherEngine LGPL-3 + App Store exception; FFmpegBuild LGPL 2.1, dynamically linked, no --enable-gpl/--enable-nonfree; libdovi MIT. Notices are in CreditsInfo. The existing fix-ksplayer-frameworks.sh min-OS rewrite also covers the AetherLib* frameworks (ITMS-90208). AetherEngine's privacy manifest bundle ships inside Lume.app.

⚠️ Merge dependency

Needs bilipp/LumeEngine#61. LibDovi's Dovi.xcframework and LumeEngine's FFmpeg.xcframework both put module.modulemap at the Headers/ root, and the app build fails with "Multiple commands produce …/include/module.modulemap". #61 nests LumeEngine's module map. Until a LumeEngine release attaches the rebuilt FFmpeg.xcframework.zip and its Package.swift URL/checksum move to it, CI and sideload builds of this branch will fail. Local builds work with a restructured BinaryDependencies/.

Testing

  • Acceptance criteria met: settings, priority list and Aether movie playback verified on the iOS simulator
  • Builds clean: iOS sim (26.5), tvOS sim (27.0) and macOS, all built from this branch's worktree
  • Tests pass: 126 tests in 8 suites (PlayerSettings, AetherEngineSettings, AetherEngineFailure, PreviewPlayerHandle, RecordingPlayback, GuidePreviewPolicy, PlaybackQoE, FullScreenPreviewAdoption) on iPhone 17 Pro / iOS 26.5. The full LumeTests suite was not run.
  • SwiftLint clean (pre-commit)
  • Tests added: AetherEngineSettingsTests, AetherEngineFailureTests, plus priority/QoE/recording/preview cases
  • UI verified on simulator (iOS): Engines list, AetherEngine options, controls overlay. That run also found and fixed the bug where the coordinator's session wasn't @Published, which left the player view uninstalled.
  • Aether's own pipeline verified on the iOS simulator after b985032 (local test servers), no fallback in any run:
    • raw MPEG-TS live: join 7.4 s
    • MP4 movie behind a redirect: join 3.3 s
    • live 1080i HLS with 5.1 AC-3: live ingest with deinterlacing, join 0.5 s
    • live HLS with data-only streams: stream analysis 25 ms, join 0.9 s
  • Owner confirmed on a real provider: raw .ts and HLS channels that failed or showed no picture now play on Aether.
    • The first movie run (join 9.5 s) went through AVPlayer, not Aether, because nativeRemoteHLS was forced on. Fixed in b985032.

Not yet verified:

  • Multiview/preview tiles and PiP, checked by hand
  • tvOS UI with real remote presses
  • A sandboxed macOS playback run
  • Archive codesign / upload validation
  • libdovi's licence file, beyond its presence

AetherEngine's own logs include the redirected media URL (credentials redacted). That logging is inside the engine, not Lume.

Platforms

  • iOS / iPadOS
  • tvOS (builds; not manually verified)
  • macOS (builds; not manually verified)
  • visionOS (excluded by design)

Related

Paired with bilipp/LumeEngine#61.

bilipp and others added 7 commits November 1, 2026 18:25
AetherEngine (superuser404notfound/AetherEngine, exact 7.53.1) joins KSPlayer,
VLCKit, AVPlayer and LumeEngine. It is declared last, so existing priority
lists append it and KSPlayer stays the default. iOS, macOS and tvOS only;
visionOS never links it.

Lume reaches it only through the in-repo dynamic package LumeAetherBridge:
importing it directly clashes with KSPlayer's FFmpegKit Libav* modules.
Package traits stay off. Licences: LGPL-3 + App Store exception, FFmpegBuild
LGPL 2.1 dynamic, libdovi MIT; notices in CreditsInfo.

- Curated device-local options under player.aether.*, with iOS/macOS and
  tvOS settings screens, including Aether's own tvOS Match Content toggle
- Full-screen playback, Multiview tiles and guide previews (embedded: muted,
  secondary output, no display criteria), full PiP, no timeshift
- macOS gets com.apple.security.network.server for Aether's loopback HLS
  server
- Shared player helpers replace per-engine copies: subtitle cue overlay,
  invisible button style, menu/play-pause presses, rate label, macOS hover
  reveal, Now Playing transport, engine failure policy, tile reconnects
- 39 strings in all 10 languages; tests for options, load mapping,
  failure classification and engine priority

Needs bilipp/LumeEngine#61 (CFFmpeg module map nested under
Headers/CFFmpeg/), or LibDovi's module.modulemap collides with LumeEngine's.
Lume asked AetherEngine to hand every stream straight to AVPlayer
(nativeRemoteHLS on), so Aether's own FFmpeg pipeline never ran:

- Raw MPEG-TS live channels failed outright (AVPlayer: -12939, byte
  range and no content length) and fell back to the next engine.
- Some live HLS channels played sound but never showed a frame.

Native remote HLS now defaults to off, like the engine, and when turned
on only applies to live sources whose URL is an HLS playlist. Live HLS
goes through Aether's live-ingest reader instead, request headers
included.

Live loads also start reliably now:

- The live join profile defaults to fast zap. Standard cuts 4 s
  segments and holds the first frame until ~18 s of a 1x origin has
  arrived, past the 15 s fallback watchdog.
- A live load on the standard profile gets at least 35 s before the
  watchdog fires, above Aether's own 30 s first-serve deadline.
- Probe size and analyze duration on Default cap live loads at 5 MiB /
  5 s. The engine's 50 MiB / 60 s spent 16 s in find_stream_info on a
  channel carrying data-only streams. VOD keeps the engine default, and
  an explicit choice always wins.
…om:bilipp/Lume into feature/add-support-for-aetherengine-https
…iable

An end-to-end run on an Apple TV 4K (tvOS 27.2) across live channels,
VOD and every Aether setting turned up three Lume-side bugs.

- Match Content off still switched the TV. The toggle fed the engine's
  matchContentEnabled, which only mirrors the system flag. It now picks
  who owns the display criteria (displayCriteriaOwner .engine / .host),
  so off leaves the TV in the format set in tvOS Settings.
- Every zap stopped the session before loading the next channel. stop()
  resets the criteria to the menu's mode, so a UHD-to-UHD zap re-synced
  HDMI twice (~2.9 s each). A zap or reload now replaces the item in
  place (LumeAetherSession.prepareForReplacement(), then load).
- A live source that goes quiet longer than the buffer (Sky Sport UHD on
  an Xtream panel: 13-25 s gaps) runs AVPlayer dry. AVPlayer pauses
  itself, the engine reports .paused, and Lume showed a frozen paused
  player with nothing to resume it. A live pause the viewer didn't ask
  for is now a stall: buffering UI, play re-asserted every 3 s, and a
  reconnect after 20 s. Recovered ~3 s after the source came back.

The tvOS settings footer now says Native HLS only switches the frame
rate for HDR video (the engine writes no criteria for SDR on that path),
in all 10 languages. Also fixes the Arabic "%lld segments" entry, which
failed ArabicLocalizationTests.
AetherEngine's live HLS ingest fetches four segments in parallel and
hands the demuxer a segment only once it has arrived whole, so on a
join the first segment shares the link with the next three. With an
Xtream panel's 6-50 MB segments, that costs a second or more per zap.

Live HLS loads now set maxConcurrentSourceRequests = 1. On an Apple TV
4K, 2 x 7 zaps: median open 1.65 -> 1.14 s, zap to playing 2.40 ->
1.94 s.

The engine keeps the ceiling per origin for the rest of the process, so
VOD from the same provider then also runs on one request. Measured on
that provider (MKV, MP4, AVI), movie starts were no slower: 0.6-1.6 s
capped vs 0.9-1.8 s uncapped.

AGENTS.md no longer lists max concurrent requests among the options
Lume never sets; it names this exception.
Bumps the exact pin in LumeAetherBridge and the credits tag URLs
together. Transitive dependencies are unchanged.

Measured on Apple TV 4K (tvOS 27.2) against LumeEngine: 7.54.0 now
waits for the display's switch-end on rate-only mode changes (AE#950),
which this TV never reports, so zaps that change frame rate hit the
2 s cap (median 3.34 s vs LumeEngine 1.14 s). DV profiles 20 and 4
still fail on the native path and fall back to LumeEngine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…subtitles, scrub log secrets

- Live source reset: retry the engine in place (backoff 1/2/4 s, at most
  3 within 60 s) before retuning, keeping the cache and the picture. Retune
  only when the engine declines, the budget is spent, or the playhead hasn't
  moved 2 s within 20 s; one 10 s extension while the engine is still
  rebuffering after it reconnected.
- PGS, DVB and DVD subtitles reach SubtitleCueModel.bitmaps and are drawn
  on the video rect at their authored position.
- A rejected TLS certificate maps to .unsupported so playback falls back
  instead of reconnecting to the same refusal.
- Register the credentials only Lume can name with EngineLog per load
  (URL userinfo, short and long Xtream layouts, header values and their
  tokens) and release them at the next load or teardown.
@bilipp
bilipp merged commit 0ec7ef9 into main Nov 2, 2026
@bilipp
bilipp deleted the feature/add-support-for-aetherengine-https branch November 2, 2026 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant