Repository navigation
Send new photos from iPhone with the app off screen - #1
Merged
Merged
Conversation
On iPhone photos only ever reached the PC while PepoConnect was in front. iOS suspends the process on the way out, and there is no API that wakes a suspended app when a photo is taken: PHPhotoLibraryChangeObserver only delivers to a running process, the BGTaskScheduler tasks run minutes to hours later, and BGContinuedProcessingTask needs a foreground tap. The only way a photo leaves on its own is for the process never to be suspended. PepoBackground.swift keeps it alive with the audio background mode: one second of silence on a loop, session .playback + .mixWithOthers, no now-playing info. Nothing is audible, other apps keep their audio and no indicator shows up, but the Dart engine keeps running exactly as it does in the foreground. A watchdog and the interruption, route-change and media-reset notifications win the session back when a call or another app takes it. Transfers are wrapped in a beginBackgroundTask assertion, and BGAppRefresh/BGProcessing are registered as the safety net for when the engine is not running at all. None of those Info.plist keys needs an entitlement, so they survive re-signing with a free Apple ID. Staying alive is not enough on its own, because automatic sending had three holes of its own: - MediaServer sent only to whoever happened to be connected at that instant, so a photo taken with the PC asleep was dropped and never retried. - engine.setAutoSend was called only from the settings switch, so after any restart the switch read on while nothing was ever sent. - MediaSource takes a fresh snapshot when it starts, so whatever was photographed while the process was dead was never reported as new. AutoSendQueue now writes every pending item to disk, hands it over as soon as the device is reachable, and remembers what already went. AppServices re-applies the setting at every start, and a catch-up pass picks up what appeared while nothing was listening. Caps on age, backlog size and retries keep a phone that was offline for a week from flooding the PC. HEIC conversion is finally wired in, and the copies the platform exports to send an original are cleaned up once nothing is queued or in flight. Bonjour/mDNS discovery goes with it: the UDP beacon cannot run on iPhone (broadcast needs an entitlement Apple does not hand out), so without it a PC that reboots onto a new DHCP lease disappeared for good. Both ends announce now. The Flatpak manifest needed the Avahi name on the system bus, not the session one. Settings tell the truth on iPhone now, including a background-state row that shows how long the process has been alive, and a warning when photo access is limited to a hand-picked selection, which hides every later photo. ci.yml grows an iOS job: the Swift is only ever compiled on a macOS runner, and it also checks the background keys survive into the built bundle.
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.
En iPhone las fotos solo llegaban al PC con PepoConnect en pantalla. iOS suspende el proceso al salir, y no existe ninguna API que despierte una app suspendida al hacer una foto:
PHPhotoLibraryChangeObserversolo entrega a un proceso vivo, lasBGTaskson oportunistas (minutos u horas) yBGContinuedProcessingTaskde iOS 26 exige que el usuario toque algo en primer plano. La única salida es que el proceso no llegue a suspenderse.Motor de segundo plano
ios/Runner/PepoBackground.swift:KeepAlive— un bucle de 1 s de silencio con la sesión de audio en.playback+.mixWithOthersy sin now-playing. No se oye nada, no interrumpe la música de otras apps, no aparece indicador, y el motor Dart sigue corriendo igual que en primer plano: observador de fototeca, socket TLS de control y transferencias. Vigilante de 30 s más observadores de interrupción, cambio de ruta, reinicio de los servicios de medios y ciclo de vida (app y escena) para recuperar la sesión cuando una llamada u otra app se la lleva.BackgroundHold—beginBackgroundTaskmientras hay algo en vuelo, para que una transferencia que arranca justo al salir de pantalla termine.PepoTasks— las dosBGTaskregistradas como red de seguridad para cuando el motor no corre, con timeout de 25 s si Dart no contesta.UIBackgroundModes: audio, fetch, processingyBGTaskSchedulerPermittedIdentifiers. Ninguna de esas claves necesita entitlements, así que sobreviven a la refirma de AltStore o Sideloadly con un Apple ID gratuito.Envío automático que no pierde nada
Mantener el proceso vivo no bastaba: el envío automático tenía tres agujeros propios.
MediaServerenviaba solo a quien estuviera conectado en ese instante, así que una foto con el PC dormido se descartaba y no se reintentaba nunca.engine.setAutoSendse llamaba solo desde el interruptor de ajustes: tras reiniciar el proceso el interruptor decía «sí» y no se enviaba nada.MediaSourcetoma una instantánea al arrancar, así que lo fotografiado con el proceso muerto no se notificaba jamás.Ahora
AutoSendQueueescribe cada pendiente a disco, lo entrega en cuanto el dispositivo es alcanzable y recuerda lo que ya salió;AppServicesreaplica el ajuste en cada arranque yMediaServer.catchUprecoge lo que apareció mientras nadie escuchaba. Topes de edad, tamaño de cola y reintentos para que un móvil una semana sin red no inunde el PC. De paso queda conectado el gancho HEIC→JPEG (declarado pero nunca asignado) y se limpian las copias temporales que iOS exporta para poder enviar un original.Bonjour/mDNS
El beacon UDP no puede correr en iPhone (broadcast necesita una autorización que Apple no da a cuentas gratuitas), así que un PC que se reiniciaba con otra IP desaparecía para siempre.
BonjourDiscoveryusabonsoir, que ya estaba enpubspec.yamlsin usar, y anuncian las cinco plataformas. El manifiesto Flatpak necesitaba el nombre de Avahi en el bus de sistema, no en el de sesión.Interfaz y CI
Los ajustes dicen la verdad en iPhone, con una fila de estado que muestra cuánto lleva vivo el proceso, y un aviso cuando el acceso a fotos está limitado a una selección (que oculta todas las fotos posteriores y dejaba el envío automático sin nada que enviar).
ci.ymlgana un jobiosenmacos-15: el Swift no se compila en ningún otro sitio, y el job comprueba además que las claves de segundo plano siguen en el bundle construido.release.ymllo llama conios: falseporque ya compila iOS por su cuenta.Comprobado
flutter analyze --fatal-infos,dart format, 191 tests de la app y 78 depepo_core(cola de envío,MediaServer, motor iOS por canal simulado, mapeo de Bonjour, sección de ajustes), más un caso nuevo enintegration_test/e2e_test.dart: foto con el PC desconectado → queda en cola → sale al reconectar.El Swift solo lo compila este PR: no hay Mac en el equipo de desarrollo. La prueba en un iPhone real sigue pendiente (foto con el móvil bloqueado, vídeo largo, 30-60 min bloqueado, PC apagado y encendido, IP del PC cambiada, cerrar desde el selector de apps).
Lo que iOS sigue sin permitir
Cerrar la app desde el selector de apps mata el motor: las fotos se acumulan en la cola y salen al reabrirla, o antes si iOS concede una
BGProcessingTask. Tras reiniciar el iPhone hay que abrir PepoConnect una vez. Y el motor gasta batería mientras está activo, igual que el servicio de Android.