feat(tvos): fetch Top Shelf content live and show poster art

The Top Shelf extension now fetches Continue Watching directly from
Plex/Jellyfin/Emby instead of replaying a cache the app wrote on its
last foreground Discover pass. The app publishes per-profile server
descriptors on every shelf sync (`updateSources`): token-free metadata
in the app group, tokens in an app-group-shared keychain item, both
wiped by `clear`. On success the extension rewrites the cached payload
as the offline fallback; any fetch failure falls back to the previous
cache-replay behavior. Poster images are passed as remote URLs, so the
extension no longer depends on app-side artwork downloads.

Episodes now render season/series poster art (2:3, `.poster` shape)
instead of 16:9 episode stills, and labels lead with the S/E marker so
long titles no longer hide it behind the focused-item marquee. Shelf
schema v3 (Dart, Android, tvOS envelopes bumped together) discards
stale wide-art caches instead of letterboxing them into poster slots.

close #1474
close #1835
This commit is contained in:
edde746
2026-08-09 10:59:16 +02:00
parent 0b4fd9e8f3
commit 291a22a4a4
14 changed files with 1605 additions and 31 deletions
+10
View File
@@ -84,6 +84,11 @@ project.files.select { |file| file.display_name == 'Foundation.framework' }.each
end
RUNNER_TESTS_DIR = File.expand_path('../RunnerTests', __dir__)
COMPILED_TEST_EXTENSIONS = %w[.swift .m .mm].freeze
# Extension sources without a Flutter import; compiled into both the
# TopShelfExtension target and the Runner app so the hosted RunnerTests bundle
# reaches them via `@testable import Runner` — the test Sources phase itself
# must only contain files under RunnerTests/ (scripts/check_tvos_test_wiring.py).
EXTENSION_SHARED_SOURCES = %w[ShelfFetcher.swift ShelfItemMapper.swift ShelfSources.swift].freeze
runner_test_files = Dir.children(RUNNER_TESTS_DIR).reject { |name| name.start_with?('.') }.sort
runner_test_sources = runner_test_files.select { |name| COMPILED_TEST_EXTENSIONS.include?(File.extname(name)) }
raise "No RunnerTests sources found in #{RUNNER_TESTS_DIR}" if runner_test_sources.empty?
@@ -128,6 +133,11 @@ end
extension_target.product_type = 'com.apple.product-type.app-extension'
ensure_source(extension_target, top_shelf_ref)
EXTENSION_SHARED_SOURCES.each do |filename|
shared_ref = ensure_file(extension_group, filename)
ensure_source(extension_target, shared_ref)
ensure_source(runner, shared_ref)
end
removed_framework_refs = []
extension_target.frameworks_build_phase.files.delete_if do |build_file|