mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 04:20:30 +00:00
* Change default UPLOAD_IPA value to 'false' * Part 1 of the great debloat * Remove Mini Tools, adopt native alerts in StikJITApp, and update HomeView UI * Refactor views to use native SwiftUI alerts instead of CustomErrorView * Update ProfileView to use minimal List UI style with native alerts * Fix up homeview * Squash: Redesign HomeView, remove Feature Flags, enable all tabs, refactor alerts, and updates - Redesign HomeView to a simpler style with 'Enable JIT' action. - Remove 'Help & Info' and 'Quick Launch' from Home. - Fix DDI false positive by starting heartbeat on HomeView appear. - Restore robust DDI checks and update app name to 'StikDebug'. - Remove FeatureFlags and Beta tags; enable 'profiles', 'processes', 'location' tabs by default. - Remove 'Devices' tab; add 'Target Device IP' field in Settings. - Hardcode 'Settings' tab to 4th position. - Refactor 'SettingsView' tab customization to be reorder-only. - Remove 'Appearance' theme section from Settings. - Refactor custom error cards to use native SwiftUI/UIKit alerts across the app (HomeView, ConsoleLogsView, ProfileView, etc). - Restore JS callback support and script execution logic in HomeView. - Add necessary Color extensions. - Update project version to 3.0.0. * Clean up: Remove In-App Purchases, update checker, and unused IPA installer code * delete more bloat * fix build issue * Remove widget to save on App IDs * Attempt heartbeat restart when Enable JIT button is pressed * Get rid of PIP * Update Tab Naming * remove more bloat * final changes for today * Remove unused packages * UX Improvements * revert bundle id * Update README.md * remove old idevice * add updated idevice * Fix ddi false positive errors and add templates for GitHub stuff * Remove more unused stuff. * Fix links and update README content * Update compatibility table in README.md * Improve performance of console logs * Remove old code * Change default UPLOAD_IPA value to 'true' * remove the welcome sheet * import support for .mobiledevicepair * Add back LC JIT stuff * Fix heartbeat issues with LC JIT * update the build yml for nightly stuff * Update README.md * Update README.md * Update GitHub Actions workflow for IPA build * Update GitHub Actions workflow for nightly builds * Add URL scheme to kill tasks stikjit://kill-process?pid=1234 * fix build issues * Added App Intents * Unify the error handling style across the app
114 lines
3.7 KiB
Swift
114 lines
3.7 KiB
Swift
//
|
|
// BackgroundAudioManager.swift
|
|
// StikJIT
|
|
//
|
|
|
|
import AVFoundation
|
|
|
|
final class BackgroundAudioManager {
|
|
static let shared = BackgroundAudioManager()
|
|
|
|
private var engine = AVAudioEngine()
|
|
private var player = AVAudioPlayerNode()
|
|
private var isRunning = false
|
|
private var healthCheckTimer: Timer?
|
|
|
|
private init() {
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(handleInterruption),
|
|
name: AVAudioSession.interruptionNotification,
|
|
object: nil
|
|
)
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(handleMediaServicesReset),
|
|
name: AVAudioSession.mediaServicesWereResetNotification,
|
|
object: nil
|
|
)
|
|
}
|
|
|
|
func start() {
|
|
isRunning = true
|
|
startEngine()
|
|
startHealthCheck()
|
|
}
|
|
|
|
func stop() {
|
|
isRunning = false
|
|
healthCheckTimer?.invalidate()
|
|
healthCheckTimer = nil
|
|
player.stop()
|
|
engine.stop()
|
|
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
|
}
|
|
|
|
private func startEngine() {
|
|
do {
|
|
let session = AVAudioSession.sharedInstance()
|
|
try session.setCategory(.playback, options: .mixWithOthers)
|
|
try session.setActive(true)
|
|
|
|
engine.attach(player)
|
|
let format = engine.mainMixerNode.outputFormat(forBus: 0)
|
|
engine.connect(player, to: engine.mainMixerNode, format: format)
|
|
|
|
scheduleSilence()
|
|
try engine.start()
|
|
player.play()
|
|
} catch {
|
|
LogManager.shared.addErrorLog("BackgroundAudioManager: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
private func scheduleSilence() {
|
|
let format = engine.mainMixerNode.outputFormat(forBus: 0)
|
|
let frameCount = AVAudioFrameCount(format.sampleRate)
|
|
guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount) else { return }
|
|
buffer.frameLength = frameCount
|
|
// PCM buffer is zero-initialized — pure silence
|
|
player.scheduleBuffer(buffer, at: nil, options: .loops)
|
|
}
|
|
|
|
// Runs every 2 seconds to reclaim the session if continuous game audio
|
|
// holds it and the interruption-ended notification never fires.
|
|
private func startHealthCheck() {
|
|
let timer = Timer(timeInterval: 2, repeats: true) { [weak self] _ in
|
|
self?.recoverIfNeeded()
|
|
}
|
|
RunLoop.main.add(timer, forMode: .common)
|
|
healthCheckTimer = timer
|
|
}
|
|
|
|
private func recoverIfNeeded() {
|
|
guard isRunning, !engine.isRunning || !player.isPlaying else { return }
|
|
do {
|
|
try AVAudioSession.sharedInstance().setActive(true)
|
|
if !engine.isRunning {
|
|
try engine.start()
|
|
}
|
|
player.play()
|
|
} catch {
|
|
// Session still held by the game — will retry next tick
|
|
}
|
|
}
|
|
|
|
@objc private func handleInterruption(_ notification: Notification) {
|
|
guard let typeValue = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
let type = AVAudioSession.InterruptionType(rawValue: typeValue),
|
|
type == .ended,
|
|
isRunning else { return }
|
|
|
|
// Best-effort immediate resume; health check will cover failures.
|
|
try? AVAudioSession.sharedInstance().setActive(true)
|
|
if !engine.isRunning { try? engine.start() }
|
|
player.play()
|
|
}
|
|
|
|
@objc private func handleMediaServicesReset() {
|
|
guard isRunning else { return }
|
|
engine = AVAudioEngine()
|
|
player = AVAudioPlayerNode()
|
|
startEngine()
|
|
}
|
|
}
|