fix: preserve app identity through FFmpeg preflight

This commit is contained in:
2026-10-11 02:10:43 +02:00
parent cf24a0fc4f
commit 5f0287f489
7 changed files with 155 additions and 17 deletions
+59
View File
@@ -0,0 +1,59 @@
import json
import os
from pathlib import Path
import plistlib
import selectors
import shutil
import subprocess
import sys
import tempfile
import uuid
app_source, library, fixtures = map(lambda path: Path(path).resolve(), sys.argv[1:])
with tempfile.TemporaryDirectory(prefix="terebi-startup-") as directory:
root = Path(directory)
app = root / "terebi.app"
shutil.copytree(app_source, app)
identifier = "local.personal.terebi.startup-check." + uuid.uuid4().hex
info_path = app / "Contents/Info.plist"
info = plistlib.loads(info_path.read_bytes())
info["CFBundleIdentifier"] = identifier
info_path.write_bytes(plistlib.dumps(info))
shutil.copy2(library, app / "Contents/Frameworks/libterebi-player.dylib")
pipe = root / "result.fifo"
os.mkfifo(pipe)
descriptor = os.open(pipe, os.O_RDONLY | os.O_NONBLOCK)
launch = None
try:
with selectors.DefaultSelector() as selector:
selector.register(descriptor, selectors.EVENT_READ)
launch = subprocess.Popen(
["open", "-g", "-n", "-W", str(app), "--args", str(pipe), str(fixtures)],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
)
if not selector.select(timeout=30):
raise RuntimeError("packaged app did not report its startup state")
result = json.loads(os.read(descriptor, 4096))
stdout, stderr = launch.communicate(timeout=10)
if launch.returncode != 0 or not result["passed"] or result["bundleIdentifier"] != identifier:
raise RuntimeError(f"packaged startup failed: {result}; {stdout}{stderr}")
print("PASS packaged bootstrap: registered app PID/identity, in-process player load, actual playback and Now Playing")
finally:
os.close(descriptor)
subprocess.run(
["xcrun", "swift", "-e",
'import AppKit; for app in NSWorkspace.shared.runningApplications { '
'if app.bundleIdentifier == CommandLine.arguments[1] { app.forceTerminate() } }',
identifier],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False,
)
if launch is not None and launch.poll() is None:
launch.terminate()
launch.communicate(timeout=5)
subprocess.run(
["/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", "-u", str(app)],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False,
)
for domain in [identifier, identifier + ".history"]:
subprocess.run(["defaults", "delete", domain], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
+59
View File
@@ -0,0 +1,59 @@
import AppKit
import MediaPlayer
@_cdecl("terebi_app_main")
@MainActor
public func checkAppStartup() {
AppStartupCheck.run()
}
@MainActor
private enum AppStartupCheck {
static var model: PlayerModel?
static var timer: Timer?
static let deadline = ContinuousClock.now.advanced(by: .seconds(15))
static let historyDomain = Bundle.main.bundleIdentifier! + ".history"
static func run() {
_ = NSApplication.shared
NSApp.setActivationPolicy(.regular)
let defaults = UserDefaults(suiteName: historyDomain)!
model = PlayerModel(defaults: defaults)
model!.setVolume(0)
model!.open(URL(fileURLWithPath: CommandLine.arguments[2]).appendingPathComponent("av.mp4"))
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in
MainActor.assumeIsolated { finishWhenReady() }
}
NSApp.run()
}
static func finishWhenReady() {
guard let model else { return }
let ready = model.canTogglePlayback && model.state.status == PLAYER_PLAYING
guard ready || model.state.status == PLAYER_ERROR || ContinuousClock.now >= deadline else { return }
timer?.invalidate()
let pid = ProcessInfo.processInfo.processIdentifier
let application = NSRunningApplication.current
let recognized = application.processIdentifier == pid &&
NSWorkspace.shared.runningApplications.contains { $0.processIdentifier == pid && $0.bundleURL == Bundle.main.bundleURL }
let result: [String: Any] = [
"passed": ready && recognized && MPNowPlayingInfoCenter.default().playbackState == .playing,
"pid": pid,
"registeredPID": application.processIdentifier,
"bundleIdentifier": application.bundleIdentifier ?? "nil",
"playing": ready,
]
do {
let pipe = try FileHandle(forWritingTo: URL(fileURLWithPath: CommandLine.arguments[1]))
try pipe.write(contentsOf: JSONSerialization.data(withJSONObject: result))
try pipe.write(contentsOf: Data([10]))
try pipe.close()
} catch {
FileHandle.standardError.write(Data("Startup check could not report: \(error)\n".utf8))
}
model.shutdown()
UserDefaults(suiteName: historyDomain)!.removePersistentDomain(forName: historyDomain)
self.model = nil
NSApp.terminate(nil)
}
}