feat(apple): tvOS client — third app target, first-lit in the Apple TV simulator
ci / rust (push) Has been cancelled
ci / rust (push) Has been cancelled
The same app now runs on tvOS (target Punktfunk-tvOS, bundle io.unom.punktfunk.tvos), validated live against the box: vkcube at 1280x720@60, 60 fps in the Apple TV 4K simulator, glass HUD with a focusable Disconnect button. - PunktfunkCore.xcframework grows tvOS device + universal-simulator slices. These are TIER-3 Rust targets (no prebuilt std): BUILD_TVOS=1 builds them with nightly and -Zbuild-std from rust-src — the full quic stack (quinn/rustls-ring/tokio) compiles for tvOS unchanged. - The UIKit stream view covers iOS AND tvOS, with pointer interaction, pointer lock, touch forwarding and InputCapture gated to iOS — tvOS is view-only until gamepad capture lands (the natural tvOS input). - SessionAudio on tvOS: .playback session, no mic (no app-accessible microphone). - App chrome gates: keyboardShortcut/textSelection/controlSize/statusBarHidden are iOS/macOS-only; host cards use the focus-native .card button style on tvOS; the Audio settings section hides (system-routed); mode seeding works from the TV screen (1920x1080@60). - Package platforms += .tvOS(.v17); new Xcode target + shared scheme (TARGETED_DEVICE_FAMILY 3, local-network usage description included). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -161,8 +161,18 @@ public final class SessionAudio {
|
||||
} catch {
|
||||
log.warning("AVAudioSession setup failed: \(error.localizedDescription)")
|
||||
}
|
||||
#elseif os(tvOS)
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
} catch {
|
||||
log.warning("AVAudioSession setup failed: \(error.localizedDescription)")
|
||||
}
|
||||
#endif
|
||||
startPlayback(speakerUID: speakerUID)
|
||||
#if os(tvOS)
|
||||
// No app-accessible microphone input on tvOS — playback only.
|
||||
#else
|
||||
guard micEnabled else { return }
|
||||
switch AVCaptureDevice.authorizationStatus(for: .audio) {
|
||||
case .authorized:
|
||||
@@ -177,6 +187,7 @@ public final class SessionAudio {
|
||||
default:
|
||||
log.warning("microphone access denied — mic uplink disabled (System Settings → Privacy)")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Stop both directions. Safe from any thread; waits the drain thread out (≤ its
|
||||
@@ -199,7 +210,7 @@ public final class SessionAudio {
|
||||
if wasDraining {
|
||||
_ = drainDone.wait(timeout: .now() + .milliseconds(400))
|
||||
}
|
||||
#if os(iOS)
|
||||
#if !os(macOS)
|
||||
// Release the session so audio we interrupted (Music, podcasts) gets its
|
||||
// resume cue.
|
||||
do {
|
||||
@@ -310,6 +321,7 @@ public final class SessionAudio {
|
||||
|
||||
// MARK: - Mic (mic → host)
|
||||
|
||||
#if !os(tvOS)
|
||||
private func startCapture(micUID: String) {
|
||||
let engine = AVAudioEngine()
|
||||
let input = engine.inputNode
|
||||
@@ -408,6 +420,7 @@ public final class SessionAudio {
|
||||
stateLock.unlock()
|
||||
log.info("mic uplink started (\(micUID.isEmpty ? "default input" : micUID))")
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
private static func setDevice(_ id: AudioDeviceID, on unit: AudioUnit) -> Bool {
|
||||
|
||||
Reference in New Issue
Block a user