Repository navigation
Conversation
Opening an avfoundation camera the user has not allowed blocks forever in libavdevice, which waits for a first frame that AVFoundation never delivers -- on the GUI thread, since devices start when playback starts. CameraInput now checks the authorization first: denied fails right away with a hint, and undetermined asks the user and opens the device once they agree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o.plist create-app-macos.sh replaced the bundle's main executable with a bash script exec'ing a renamed score. macOS only shows privacy prompts (camera, microphone) for the bundle's main executable, so custom apps had camera access refused without a prompt, and LivePose hung waiting for frames. score now stays the main executable. What the script set comes from the bundle's Info.plist, read at the start of main(): - LSEnvironment, the standard key for a bundle's environment. macOS applies it only when the app is opened through Launch Services and caches it, so score sets it again, expanding $RESOURCES_DIR to Contents/Resources; - ScoreArguments, inserted before the command-line arguments; --debug still turns --ui into --ui-debug. The environment file is converted at packaging time, in the syntax the Windows launcher already reads (KEY=VALUE, export, quotes, comments); anything a shell would have expanded is an error. --app-info-plist merges the app's own entries, such as a usage description worded for it, over score's: binary-related and option-set keys are refused, and so is emptying a usage description. score's Info.plist declares ScoreCustomAppVersion = 1; the script refuses a score without it instead of producing an app that starts as plain score. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jcelerier
force-pushed
the
fix/macos-camera-permission
branch
from
October 21, 2026 19:54
8b3fad6 to
e0d2120
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Custom apps built with
tools/create-app.sh, LivePose among them, hang on macOS as soon as they start a camera, and the user is never asked for camera access. Two commits fix the two causes.1.
camera: ask for camera access on macOS instead of hangingScore.play()→DocumentPlugin::reload()→start_execution()→CameraInput::start()→avformat_open_input("avfoundation"), on the GUI thread. Without camera access, libavdevice waits inget_video_config()for a first frame that AVFoundation never delivers. That loop ignores the interrupt callback, soCameraInput's 2 s timeout can't fire. (FFmpeg side: ossia/sdk#50, merged.)CameraInput::start()now checksAVCaptureDevice authorizationStatusForMediaType:first (Video/CameraPermission.{hpp,mm}, with a stub elsewhere):requestAccessForMediaType:and returns. If the user agrees, the answer (delivered on the main queue) starts the camera. Astop()or the input's destruction while the prompt is up cancels that start.If the bundle has no
NSCameraUsageDescription, the old behaviour is kept: asking would get the process killed.2.
macOS custom apps: keep score as the main executable, settings in Info.plistWhy custom apps never got the prompt.
create-app-macos.shrenamed score toapp-binand madeCFBundleExecutablea bash script thatexec'd it. A bundle's signature, with its identifier andInfo.plist, belongs to its main executable, here the script.app-binwas signed as a separate helper (ad-hoc identifierapp-bin-<hash>). When it asked for the camera, TCC logged:app-bin's signature does contain that entitlement. Access is refused silently, and then the wait from part 1 never ends.Now: score itself is the bundle's main executable, renamed to the app's name. What the script did comes from the bundle's
Info.plist, read at the very start ofmain()(custom_app::setup):LSEnvironment, the standard key for a bundle's environment. macOS applies it only when the app is opened through Launch Services (Finder, Dock,open), and caches it. So score sets the variables again in every case, replacing$RESOURCES_DIR/${RESOURCES_DIR}withContents/Resources. It holds theSCORE_CUSTOM_APP_*variables,QML2_IMPORT_PATHand the app's environment file.ScoreArguments, an array put before the command-line arguments:[--autoplay] --ui $RESOURCES_DIR/qml/<main>.qml [$RESOURCES_DIR/<doc>.score].--debugon the command line still turns--uiinto--ui-debug.create-app-macos.sh:Info.plistlacks the newScoreCustomAppVersion = 1(added tosrc/app/Info.plist.in), instead of producing an app that starts as plain score. All our apps packagecontinuousor a freshmasterbuild.plutil. The environment file is converted at packaging time, in the syntax the Windows launcher already reads:[export] KEY=VALUE, optional quotes,#/;comments. Nothing runs it through a shell any more, so anything a shell would have expanded ($HOME, backticks), or a line that isn't an assignment, is an error with its line number. LivePose's, DomeportPro's, koaia's and Spatial Protocol Mapper's files all convert unchanged.app-bin.New option
--app-info-plist FILE(inputapp-info-plistin ossia/actions#32): the app's ownInfo.plistentries, e.g. a camera description worded for the app.LSEnvironment, which is merged variable by variable.CFBundleExecutable,LSMinimumSystemVersion,NSPrincipalClass…) or that other options set (name, identifier, versions, icon). Also refused: emptying any usage description, since macOS kills an app that asks for a permission without one.Tested on macmini-m1 (macOS 26.4.1, Apple Silicon)
score built from this branch against
/opt/ossia-sdk-aarch64. Test apps packaged withcreate-app.sh --local-installer, using LivePose's environment files plus a$RESOURCES_DIRvariable, signed ad hoc through the script (MAC_CODESIGN_IDENTITY=-). The script ran under the system/bin/bash3.2. The app name contains spaces ("Cam Test Plist"). The test UI does what LivePose's Start button does:Score.createDevice()on the OBS Virtual Camera, thenScore.play(), with a 500 ms heartbeat. It also logsQt.application.argumentsand imports a QML module found only throughQML2_IMPORT_PATH.openAUTHREQ_PROMPTING … subject=Sub:{ca.qc.sat.camtestplist} Resp:{identifier=ca.qc.sat.camtestplist}: prompt shown--ui …/Contents/Resources/qml/Main.qmlSCORE_CUSTOM_APP_*open --args --debug--ui-debug …/Main.qmlopen, app with a fragmentLSEnvironment)SCORE_CUSTOM_APP_*: set by score itselfIn every case
play()returns ("Camera: waiting for the user to allow camera access") and the heartbeat continues.Packaging:
codesign -dvshowsIdentifier=ca.qc.sat.camtestplistandcodesign --verify --strictpasses.--score … --autoplay:["--autoplay","--ui","$RESOURCES_DIR/qml/Main.qml","$RESOURCES_DIR/app.score"].ScoreCustomAppVersion(master'sInfo.plist.in),export PATH=$HOME/bin:$PATHin the environment file, a fragment settingCFBundleIdentifier, a fragment emptyingNSMicrophoneUsageDescription.Not tested:
🤖 Generated with Claude Code