@kaeawc/auto-mobile 0.0.92 → 0.0.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/dist/build-info.json +12 -12
  2. package/dist/schemas/test-plan.schema.json +370 -52
  3. package/dist/src/{chunk-jhnbfj19.js → chunk-00167wan.js} +5 -5
  4. package/dist/src/chunk-0jrrsnc9.js +6 -0
  5. package/dist/src/chunk-0n6swkwg.js +5 -0
  6. package/dist/src/chunk-0qsy1qsx.js +57 -0
  7. package/dist/src/chunk-11m8wj8j.js +237 -0
  8. package/dist/src/{chunk-4pfftt07.js → chunk-1p7xf5kb.js} +3 -3
  9. package/dist/src/chunk-23p5f095.js +4 -0
  10. package/dist/src/{chunk-a4k9rt7z.js → chunk-26k317v5.js} +3 -3
  11. package/dist/src/chunk-284ptqa4.js +7 -0
  12. package/dist/src/chunk-28re4xrg.js +4 -0
  13. package/dist/src/chunk-2f9rkmhx.js +5 -0
  14. package/dist/src/chunk-2fxdh48t.js +4 -0
  15. package/dist/src/chunk-2tbdrbt6.js +4 -0
  16. package/dist/src/chunk-2tqzq5qh.js +971 -0
  17. package/dist/src/chunk-32gvv9b4.js +21 -0
  18. package/dist/src/chunk-3f3t1x97.js +8 -0
  19. package/dist/src/chunk-3fspkbc7.js +4 -0
  20. package/dist/src/chunk-3qwrzxh0.js +11 -0
  21. package/dist/src/chunk-3sy6ahhz.js +26 -0
  22. package/dist/src/chunk-3vg455e6.js +4 -0
  23. package/dist/src/chunk-3xkpk44w.js +5 -0
  24. package/dist/src/chunk-3y6eykz9.js +4 -0
  25. package/dist/src/chunk-49bsqdx3.js +4 -0
  26. package/dist/src/chunk-4hw3665t.js +42 -0
  27. package/dist/src/chunk-58wzr3mt.js +5 -0
  28. package/dist/src/chunk-5d2hft6c.js +5 -0
  29. package/dist/src/chunk-5snsn9cf.js +4 -0
  30. package/dist/src/chunk-5tm9mn47.js +54 -0
  31. package/dist/src/chunk-5zc77a5t.js +4 -0
  32. package/dist/src/chunk-64j0majr.js +19 -0
  33. package/dist/src/chunk-65y8v4td.js +17 -0
  34. package/dist/src/chunk-6awz7vhq.js +5 -0
  35. package/dist/src/{chunk-stfzjr9y.js → chunk-6bznss4t.js} +5 -5
  36. package/dist/src/chunk-6ep7f5ge.js +4 -0
  37. package/dist/src/{chunk-0dfnyfkh.js → chunk-6wt9rgjf.js} +3 -3
  38. package/dist/src/chunk-6xmss8er.js +8 -0
  39. package/dist/src/chunk-710rxz9y.js +5 -0
  40. package/dist/src/chunk-7cs2da62.js +5 -0
  41. package/dist/src/chunk-7g103b54.js +4 -0
  42. package/dist/src/chunk-7j5vj4pa.js +4 -0
  43. package/dist/src/chunk-7rz9898k.js +5 -0
  44. package/dist/src/chunk-7tw8x05v.js +4 -0
  45. package/dist/src/chunk-7zy586cb.js +4 -0
  46. package/dist/src/{chunk-r0f1q783.js → chunk-8037tzzj.js} +3 -3
  47. package/dist/src/chunk-84nanf8s.js +5 -0
  48. package/dist/src/{chunk-950qcq1e.js → chunk-8g1922rp.js} +4 -4
  49. package/dist/src/chunk-8gvwcv2p.js +4 -0
  50. package/dist/src/{chunk-sz1a7p43.js → chunk-8ph29g99.js} +1 -1
  51. package/dist/src/chunk-8thrmfat.js +4 -0
  52. package/dist/src/chunk-8zgzzs7k.js +78 -0
  53. package/dist/src/chunk-97b1zmaq.js +4 -0
  54. package/dist/src/chunk-9fth9ayh.js +4 -0
  55. package/dist/src/chunk-9j6anhjr.js +4 -0
  56. package/dist/src/{chunk-w7zsh13a.js → chunk-aabchzxk.js} +4 -4
  57. package/dist/src/{chunk-wry6skrm.js → chunk-ayp07wbb.js} +1 -1
  58. package/dist/src/chunk-bfjwqk8q.js +4 -0
  59. package/dist/src/{chunk-jdqvcq0a.js → chunk-bfw6125s.js} +3 -3
  60. package/dist/src/chunk-bnhscm2n.js +4 -0
  61. package/dist/src/chunk-bxmsax22.js +5 -0
  62. package/dist/src/chunk-c1vs4g1n.js +4 -0
  63. package/dist/src/chunk-ct3e7yng.js +4 -0
  64. package/dist/src/{chunk-8en08r7q.js → chunk-cyy71ggq.js} +3 -3
  65. package/dist/src/chunk-ddzkz92z.js +4 -0
  66. package/dist/src/chunk-dgrbdpb7.js +9 -0
  67. package/dist/src/chunk-drdz8hee.js +5 -0
  68. package/dist/src/chunk-e98dqnc9.js +145 -0
  69. package/dist/src/chunk-eersndy7.js +5 -0
  70. package/dist/src/chunk-eggq53ks.js +4 -0
  71. package/dist/src/chunk-f2dya7cs.js +4 -0
  72. package/dist/src/chunk-fbakd6cw.js +4 -0
  73. package/dist/src/chunk-fgb98zjj.js +5 -0
  74. package/dist/src/chunk-fmpad823.js +5 -0
  75. package/dist/src/{chunk-v90es00d.js → chunk-g04bwyj3.js} +2 -2
  76. package/dist/src/chunk-g0xr8zw0.js +4 -0
  77. package/dist/src/chunk-g3rjs80x.js +4 -0
  78. package/dist/src/chunk-g78rtrfv.js +4 -0
  79. package/dist/src/{chunk-r20pe1nv.js → chunk-g7tmjbkt.js} +3 -3
  80. package/dist/src/chunk-gabgv3y5.js +4 -0
  81. package/dist/src/chunk-gmr45m8f.js +4 -0
  82. package/dist/src/chunk-gsyfebqj.js +4 -0
  83. package/dist/src/chunk-h5rpn9wr.js +247 -0
  84. package/dist/src/chunk-hb1x7v55.js +4 -0
  85. package/dist/src/chunk-hrfytkxh.js +168 -0
  86. package/dist/src/chunk-j4yjzbak.js +4 -0
  87. package/dist/src/chunk-jd6a5v6p.js +5 -0
  88. package/dist/src/chunk-jh6vhqzx.js +5 -0
  89. package/dist/src/chunk-kn5spq15.js +4 -0
  90. package/dist/src/{chunk-w2mwa0jk.js → chunk-m94qs0f0.js} +2 -2
  91. package/dist/src/chunk-mjrps0ac.js +7 -0
  92. package/dist/src/chunk-n03dmn5q.js +5 -0
  93. package/dist/src/chunk-n72qhwd9.js +4 -0
  94. package/dist/src/{chunk-km0dy5vc.js → chunk-ndt1ccxm.js} +9 -9
  95. package/dist/src/chunk-nr6y0n4y.js +5 -0
  96. package/dist/src/chunk-pcsen2vs.js +4 -0
  97. package/dist/src/chunk-ppfr0b8h.js +4 -0
  98. package/dist/src/chunk-q10bdnat.js +4 -0
  99. package/dist/src/chunk-q9xmq595.js +4 -0
  100. package/dist/src/{chunk-2v7tw1nj.js → chunk-qcxzr2my.js} +3 -3
  101. package/dist/src/chunk-qf6nynk6.js +7 -0
  102. package/dist/src/chunk-qyqypn28.js +5 -0
  103. package/dist/src/chunk-r2epb56s.js +4 -0
  104. package/dist/src/chunk-r5bxavdx.js +311 -0
  105. package/dist/src/{chunk-r14wwk37.js → chunk-r6z1eztj.js} +12 -12
  106. package/dist/src/{chunk-d57432wq.js → chunk-r7bpkgff.js} +2 -2
  107. package/dist/src/chunk-r8jf3d92.js +4 -0
  108. package/dist/src/{chunk-9rkfyt6q.js → chunk-rbpdk7m0.js} +3 -3
  109. package/dist/src/chunk-rc0dbypm.js +4 -0
  110. package/dist/src/chunk-rhcty4ms.js +4 -0
  111. package/dist/src/chunk-rpvckv3p.js +4 -0
  112. package/dist/src/{chunk-s1zq65ny.js → chunk-rqc9ap3x.js} +2 -2
  113. package/dist/src/chunk-rvvy1bte.js +7 -0
  114. package/dist/src/chunk-ry0mz41y.js +4 -0
  115. package/dist/src/{chunk-34zxqcmq.js → chunk-s3jc6cwp.js} +3 -3
  116. package/dist/src/chunk-sfv61xjb.js +6 -0
  117. package/dist/src/chunk-t6bfvfkg.js +4 -0
  118. package/dist/src/chunk-t6c8akcm.js +5 -0
  119. package/dist/src/chunk-t7w8rctc.js +4 -0
  120. package/dist/src/chunk-thbh3jew.js +5 -0
  121. package/dist/src/chunk-v2wd4n3e.js +5 -0
  122. package/dist/src/{chunk-zg67wjkh.js → chunk-v6pya0dm.js} +3 -3
  123. package/dist/src/chunk-vy3bwm1d.js +17 -0
  124. package/dist/src/chunk-w3vs79ex.js +27 -0
  125. package/dist/src/chunk-w4zga19f.js +4 -0
  126. package/dist/src/chunk-w5b2jwk3.js +14 -0
  127. package/dist/src/chunk-w7hkv422.js +5 -0
  128. package/dist/src/{chunk-qv2918jt.js → chunk-waqwcjyn.js} +2 -2
  129. package/dist/src/{chunk-b4cxdzwy.js → chunk-wbh8spm5.js} +7 -7
  130. package/dist/src/{chunk-ajsrz473.js → chunk-wyrkw6hw.js} +3 -3
  131. package/dist/src/chunk-xa73x2cx.js +5 -0
  132. package/dist/src/{chunk-eka89q3k.js → chunk-xgan0qqp.js} +1 -1
  133. package/dist/src/chunk-y2vs6jwg.js +4 -0
  134. package/dist/src/chunk-yattm52s.js +4 -0
  135. package/dist/src/{chunk-9y5bcy28.js → chunk-yb97j25b.js} +4 -4
  136. package/dist/src/{chunk-c9nk1r78.js → chunk-ybbtagbt.js} +3 -3
  137. package/dist/src/chunk-ycaj2sgf.js +9 -0
  138. package/dist/src/chunk-yf7ympyz.js +325 -0
  139. package/dist/src/chunk-yjawdzk8.js +4 -0
  140. package/dist/src/chunk-z637nqg8.js +11 -0
  141. package/dist/src/{chunk-0w1spptt.js → chunk-zgja5f3p.js} +4 -4
  142. package/dist/src/chunk-zs1sghs9.js +4 -0
  143. package/dist/src/daemon/processEntry.js +2 -2
  144. package/dist/src/index.js +4 -4
  145. package/dist/src/runtime/bunRuntimeGuard.js +2 -2
  146. package/dist/src/utils/workers/iosBundleExtractWorker.js +2 -2
  147. package/ios/CameraPosterInjection/Injection/CameraPosterInjection.m +466 -0
  148. package/package.json +3 -2
  149. package/schemas/test-plan.schema.json +370 -52
  150. package/dist/src/chunk-09442h7v.js +0 -4
  151. package/dist/src/chunk-0e4gm1kj.js +0 -5
  152. package/dist/src/chunk-0eb8576f.js +0 -4
  153. package/dist/src/chunk-0rjm4nx7.js +0 -5
  154. package/dist/src/chunk-1zmh2eak.js +0 -4
  155. package/dist/src/chunk-28k63ha3.js +0 -5
  156. package/dist/src/chunk-2d4wmp8b.js +0 -4
  157. package/dist/src/chunk-2e6s75qv.js +0 -78
  158. package/dist/src/chunk-32jpxz2y.js +0 -323
  159. package/dist/src/chunk-3jmvqygr.js +0 -238
  160. package/dist/src/chunk-3swvfhqt.js +0 -5
  161. package/dist/src/chunk-44bt78m9.js +0 -5
  162. package/dist/src/chunk-488vt2bp.js +0 -4
  163. package/dist/src/chunk-4fgdhcwx.js +0 -5
  164. package/dist/src/chunk-4jp2se4w.js +0 -4
  165. package/dist/src/chunk-4vhr9e4t.js +0 -4
  166. package/dist/src/chunk-4xd6zs3y.js +0 -15
  167. package/dist/src/chunk-51567pg8.js +0 -4
  168. package/dist/src/chunk-5agqsfev.js +0 -4
  169. package/dist/src/chunk-5d706gmr.js +0 -4
  170. package/dist/src/chunk-5kd4rqk7.js +0 -5
  171. package/dist/src/chunk-61t538bm.js +0 -4
  172. package/dist/src/chunk-6d3mmqxt.js +0 -54
  173. package/dist/src/chunk-6hyt398x.js +0 -5
  174. package/dist/src/chunk-6mnr8kpm.js +0 -4
  175. package/dist/src/chunk-79q5xsdc.js +0 -42
  176. package/dist/src/chunk-7bdgand3.js +0 -4
  177. package/dist/src/chunk-84cp9dw5.js +0 -4
  178. package/dist/src/chunk-86f2h5ww.js +0 -5
  179. package/dist/src/chunk-8cghfzb2.js +0 -19
  180. package/dist/src/chunk-9tf2p9y9.js +0 -5
  181. package/dist/src/chunk-a2sc4jm0.js +0 -5
  182. package/dist/src/chunk-aa5ng3d1.js +0 -4
  183. package/dist/src/chunk-b3h0a6ca.js +0 -4
  184. package/dist/src/chunk-b6a737qt.js +0 -4
  185. package/dist/src/chunk-bn64x5z5.js +0 -5
  186. package/dist/src/chunk-bvh649za.js +0 -12
  187. package/dist/src/chunk-c9tyfg4j.js +0 -26
  188. package/dist/src/chunk-cfsdfqnm.js +0 -4
  189. package/dist/src/chunk-cxyf04jc.js +0 -7
  190. package/dist/src/chunk-da08tx5v.js +0 -4
  191. package/dist/src/chunk-f2tc08m2.js +0 -4
  192. package/dist/src/chunk-f8tnk179.js +0 -5
  193. package/dist/src/chunk-f9s1kkhx.js +0 -5
  194. package/dist/src/chunk-fbp8a4rx.js +0 -4
  195. package/dist/src/chunk-fd2z0sze.js +0 -4
  196. package/dist/src/chunk-fk927f4q.js +0 -54
  197. package/dist/src/chunk-fxmr3a0f.js +0 -4
  198. package/dist/src/chunk-gaqwhd44.js +0 -4
  199. package/dist/src/chunk-ggtd5x5a.js +0 -5
  200. package/dist/src/chunk-gndcfrm7.js +0 -4
  201. package/dist/src/chunk-h3n4adc6.js +0 -310
  202. package/dist/src/chunk-h5pjbb83.js +0 -4
  203. package/dist/src/chunk-hgnnh302.js +0 -4
  204. package/dist/src/chunk-hmke8nhx.js +0 -5
  205. package/dist/src/chunk-j2bx12hn.js +0 -4
  206. package/dist/src/chunk-j70y1a2g.js +0 -4
  207. package/dist/src/chunk-js1jpyxp.js +0 -6
  208. package/dist/src/chunk-kbjq0qvb.js +0 -4
  209. package/dist/src/chunk-kfwx57xd.js +0 -4
  210. package/dist/src/chunk-kzwctna2.js +0 -4
  211. package/dist/src/chunk-m9ehyehy.js +0 -4
  212. package/dist/src/chunk-mct91p1f.js +0 -4
  213. package/dist/src/chunk-mf9spd4g.js +0 -5
  214. package/dist/src/chunk-mgrcansb.js +0 -4
  215. package/dist/src/chunk-mqc1d0eb.js +0 -6
  216. package/dist/src/chunk-n1zmpk0z.js +0 -4
  217. package/dist/src/chunk-ncyjf63g.js +0 -4
  218. package/dist/src/chunk-nr64d21q.js +0 -973
  219. package/dist/src/chunk-nwm2pq5v.js +0 -168
  220. package/dist/src/chunk-pm06b05d.js +0 -9
  221. package/dist/src/chunk-q50s00ry.js +0 -4
  222. package/dist/src/chunk-qwye1958.js +0 -14
  223. package/dist/src/chunk-r8z9pfve.js +0 -5
  224. package/dist/src/chunk-rdpgkjf6.js +0 -4
  225. package/dist/src/chunk-rkr0dks9.js +0 -5
  226. package/dist/src/chunk-rn84t3sf.js +0 -11
  227. package/dist/src/chunk-s123kn7n.js +0 -4
  228. package/dist/src/chunk-s73qn3fx.js +0 -4
  229. package/dist/src/chunk-sjn0nj4p.js +0 -5
  230. package/dist/src/chunk-sns0c35v.js +0 -7
  231. package/dist/src/chunk-t4dr5c8v.js +0 -8
  232. package/dist/src/chunk-tc8k9c1j.js +0 -4
  233. package/dist/src/chunk-teznhase.js +0 -4
  234. package/dist/src/chunk-ty9xgmz4.js +0 -4
  235. package/dist/src/chunk-tyazf3sk.js +0 -145
  236. package/dist/src/chunk-v6npjrjm.js +0 -5
  237. package/dist/src/chunk-vbvtzkwz.js +0 -5
  238. package/dist/src/chunk-vefd8qee.js +0 -8
  239. package/dist/src/chunk-vs9r72ta.js +0 -17
  240. package/dist/src/chunk-w88k0bgr.js +0 -4
  241. package/dist/src/chunk-wzdt0enk.js +0 -4
  242. package/dist/src/chunk-xhhv854c.js +0 -4
  243. package/dist/src/chunk-y58270hd.js +0 -4
  244. package/dist/src/chunk-yk0hmbex.js +0 -4
  245. package/dist/src/chunk-ympy4fdz.js +0 -28
  246. package/dist/src/chunk-yw436axy.js +0 -4
  247. package/dist/src/chunk-z04jrpjy.js +0 -5
  248. package/dist/src/chunk-z2xznczw.js +0 -4
  249. package/dist/src/chunk-z4xnrgps.js +0 -4
  250. package/dist/src/chunk-z7csyszv.js +0 -4
  251. package/dist/src/chunk-z8t0v07j.js +0 -11
  252. package/dist/src/chunk-z916af47.js +0 -228
  253. package/dist/src/chunk-zcr8he8q.js +0 -5
  254. package/dist/src/chunk-zd9vmkzn.js +0 -9
  255. package/dist/src/chunk-zj939v0h.js +0 -5
  256. package/dist/src/chunk-zkdjwtc1.js +0 -4
  257. package/dist/src/chunk-zkv5kzt6.js +0 -4
@@ -0,0 +1,466 @@
1
+ // Simulator-only proof of concept. Uses public selectors and Objective-C runtime APIs.
2
+ // No hooks are installed unless a poster can be loaded. This is a process-wide fake
3
+ // video pipeline, not a complete AVFoundation device implementation.
4
+ #import <Foundation/Foundation.h>
5
+ #import <UIKit/UIKit.h>
6
+ #import <AVFoundation/AVFoundation.h>
7
+ #import <CoreImage/CoreImage.h>
8
+ #import <CoreMedia/CoreMedia.h>
9
+ #import <CoreVideo/CoreVideo.h>
10
+ #import <ImageIO/ImageIO.h>
11
+ #import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
12
+ #import <objc/runtime.h>
13
+ #import <math.h>
14
+
15
+ static const size_t AMWidth = 1080, AMHeight = 1440;
16
+ static CGImageRef AMOriginalImage, AMPosterImage;
17
+ static CVPixelBufferRef AMPixels;
18
+ static CMVideoFormatDescriptionRef AMFormat;
19
+ static NSData *AMPNG;
20
+ static AVCaptureDevice *AMDevice;
21
+ static NSArray<AVMetadataObject *> *AMCodes;
22
+ static Class AMDiscoveryClass, AMInputClass, AMCodeClass, AMPhotoClass, AMResolvedClass, AMConnectionClass;
23
+ static char AMStateKey, AMOutputKey, AMSessionKey, AMTypesKey, AMDeviceKey, AMResolvedKey, AMMessageKey, AMIDKey;
24
+
25
+ @interface AMOutputState : NSObject
26
+ @property(nonatomic, weak) id delegate;
27
+ @property(nonatomic, strong) dispatch_queue_t queue;
28
+ @property(nonatomic, weak) AVCaptureSession *session;
29
+ // One fake connection per output, reused for every frame: fake instances are never freed.
30
+ @property(nonatomic, strong) AVCaptureConnection *connection;
31
+ @end
32
+ @implementation AMOutputState
33
+ @end
34
+
35
+ @interface AMSessionState : NSObject
36
+ @property(nonatomic) BOOL running;
37
+ @property(nonatomic) NSUInteger generation;
38
+ @property(nonatomic) NSUInteger frame;
39
+ @property(nonatomic, copy) NSString *preset;
40
+ @property(nonatomic, strong) NSMutableArray *inputs;
41
+ @property(nonatomic, strong) NSMutableArray<AVCaptureOutput *> *outputs;
42
+ @property(nonatomic, strong) NSHashTable<AVCaptureVideoPreviewLayer *> *layers;
43
+ @property(nonatomic, strong) dispatch_source_t timer;
44
+ @end
45
+ @implementation AMSessionState
46
+ - (instancetype)init {
47
+ if ((self = [super init])) {
48
+ _inputs = [NSMutableArray array];
49
+ _outputs = [NSMutableArray array];
50
+ _layers = [NSHashTable weakObjectsHashTable];
51
+ _preset = AVCaptureSessionPresetPhoto;
52
+ }
53
+ return self;
54
+ }
55
+ - (void)dealloc { if (_timer) dispatch_source_cancel(_timer); }
56
+ @end
57
+
58
+ static id AMAssociated(id object, const void *key, Class type) {
59
+ @synchronized (object) {
60
+ id value = objc_getAssociatedObject(object, key);
61
+ if (!value) {
62
+ value = [type new];
63
+ objc_setAssociatedObject(object, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
64
+ }
65
+ return value;
66
+ }
67
+ }
68
+ static AMSessionState *AMState(id session) { return AMAssociated(session, &AMStateKey, AMSessionState.class); }
69
+ static AMOutputState *AMOutput(id output) { return AMAssociated(output, &AMOutputKey, AMOutputState.class); }
70
+ static void AMStore(id object, const void *key, id value) {
71
+ objc_setAssociatedObject(object, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
72
+ }
73
+ // class_replaceMethod adds an override when the public method is inherited;
74
+ // exchanging an inherited Method would accidentally change its superclass.
75
+ static void AMReplace(Class type, SEL selector, id block) {
76
+ Method method = class_getInstanceMethod(type, selector);
77
+ NSCAssert(method, @"Missing public method %@", NSStringFromSelector(selector));
78
+ class_replaceMethod(type, selector, imp_implementationWithBlock(block), method_getTypeEncoding(method));
79
+ }
80
+ static Class AMSubclass(Class parent, const char *name) {
81
+ Class type = objc_allocateClassPair(parent, name, 0);
82
+ objc_registerClassPair(type);
83
+ // Instances come from class_createInstance without the parent's init, so the parent's dealloc
84
+ // would tear down state that was never built. Leak them instead (a few small objects per run).
85
+ class_replaceMethod(type, sel_registerName("dealloc"), imp_implementationWithBlock(^(__unsafe_unretained id o) {}), "v@:");
86
+ return type;
87
+ }
88
+ static BOOL AMVideo(AVMediaType media) { return [media isEqualToString:AVMediaTypeVideo]; }
89
+ static BOOL AMMatches(NSArray *types, AVMediaType media) {
90
+ return AMVideo(media) && [types containsObject:AVCaptureDeviceTypeBuiltInWideAngleCamera];
91
+ }
92
+
93
+ // Core Image applies source orientation, then aspect-fills a deterministic canvas.
94
+ // Retain the original separately. PNG permits decoded-pixel comparisons without
95
+ // JPEG loss; its encoded bytes/hash need not match the original source file.
96
+ // Core Image also avoids adding CoreGraphics/QuartzCore link flags to the build.
97
+ static BOOL AMLoadPoster(NSString *path, NSString *qr) {
98
+ CIContext *context = [CIContext contextWithOptions:nil];
99
+ CIImage *source;
100
+ BOOL generated = !path.length;
101
+ if (!generated) {
102
+ source = [CIImage imageWithContentsOfURL:[NSURL fileURLWithPath:path]
103
+ options:@{kCIImageApplyOrientationProperty: @YES}];
104
+ } else {
105
+ CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
106
+ [filter setValue:[qr dataUsingEncoding:NSUTF8StringEncoding] forKey:@"inputMessage"];
107
+ [filter setValue:@"M" forKey:@"inputCorrectionLevel"];
108
+ CIImage *code = filter.outputImage;
109
+ if (!code) return NO;
110
+ // Integer scaling to approximately 640 px, with four white quiet modules.
111
+ CGFloat modules = code.extent.size.width;
112
+ CGFloat scale = MAX(1, floor(640 / (modules + 8)));
113
+ CGFloat side = (modules + 8) * scale;
114
+ CIImage *white = [[CIImage imageWithColor:[CIColor colorWithRed:1 green:1 blue:1]]
115
+ imageByCroppingToRect:CGRectMake(0, 0, side, side)];
116
+ code = [[code imageBySamplingNearest] imageByApplyingTransform:
117
+ (CGAffineTransform){scale, 0, 0, scale, 4 * scale, 4 * scale}];
118
+ source = [code imageByCompositingOverImage:white];
119
+ }
120
+ CGRect extent = source.extent;
121
+ if (!source || !isfinite(extent.size.width) || !isfinite(extent.size.height) ||
122
+ extent.size.width <= 0 || extent.size.height <= 0) return NO;
123
+ AMOriginalImage = [context createCGImage:source fromRect:extent];
124
+ if (!AMOriginalImage) return NO;
125
+ CGFloat sx = (CGFloat)AMWidth / extent.size.width, sy = (CGFloat)AMHeight / extent.size.height;
126
+ // Generated square QR gets white portrait padding to preserve its quiet zone;
127
+ // file posters use aspect-fill and may crop a QR placed near their edges.
128
+ CGFloat scale = generated ? MIN(sx, sy) : MAX(sx, sy);
129
+ CGFloat x = (AMWidth - extent.size.width * scale) / 2 - extent.origin.x * scale;
130
+ CGFloat y = (AMHeight - extent.size.height * scale) / 2 - extent.origin.y * scale;
131
+ if (generated) source = [source imageBySamplingNearest];
132
+ CIImage *scaled = [source imageByApplyingTransform:(CGAffineTransform){scale, 0, 0, scale, x, y}];
133
+ CGRect canvas = CGRectMake(0, 0, AMWidth, AMHeight);
134
+ CIImage *white = [[CIImage imageWithColor:[CIColor colorWithRed:1 green:1 blue:1]] imageByCroppingToRect:canvas];
135
+ CIImage *poster = [[scaled imageByCompositingOverImage:white] imageByCroppingToRect:canvas];
136
+ AMPosterImage = [context createCGImage:poster fromRect:canvas];
137
+ if (!AMPosterImage) return NO;
138
+ AMPNG = UIImagePNGRepresentation([UIImage imageWithCGImage:AMPosterImage]);
139
+ NSDictionary *attrs = @{(id)kCVPixelBufferCGImageCompatibilityKey: @YES,
140
+ (id)kCVPixelBufferIOSurfacePropertiesKey: @{}};
141
+ if (CVPixelBufferCreate(kCFAllocatorDefault, AMWidth, AMHeight, kCVPixelFormatType_32BGRA,
142
+ (__bridge CFDictionaryRef)attrs, &AMPixels) != kCVReturnSuccess) return NO;
143
+ [context render:[CIImage imageWithCGImage:AMPosterImage] toCVPixelBuffer:AMPixels bounds:canvas colorSpace:nil];
144
+ return AMPNG && CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, AMPixels, &AMFormat) == noErr;
145
+ }
146
+
147
+ // init-family replacements need actual init-family ARC ownership conventions.
148
+ @interface AVCaptureDeviceInput (AMPoster)
149
+ - (instancetype)am_initWithDevice:(AVCaptureDevice *)device error:(NSError **)error __attribute__((objc_method_family(init)));
150
+ @end
151
+ @implementation AVCaptureDeviceInput (AMPoster)
152
+ - (instancetype)am_initWithDevice:(AVCaptureDevice *)device error:(NSError **)error {
153
+ if (device != AMDevice) return [self am_initWithDevice:device error:error];
154
+ self = (id)class_createInstance(AMInputClass, 0);
155
+ if (error) *error = nil;
156
+ AMStore(self, &AMDeviceKey, device);
157
+ return self;
158
+ }
159
+ @end
160
+
161
+ static void AMBindLayer(AVCaptureVideoPreviewLayer *layer, AVCaptureSession *session) {
162
+ AVCaptureSession *old = objc_getAssociatedObject(layer, &AMSessionKey);
163
+ if (old) { AMSessionState *s = AMState(old); @synchronized (s) { [s.layers removeObject:layer]; } }
164
+ AMStore(layer, &AMSessionKey, session);
165
+ if (session) { AMSessionState *s = AMState(session); @synchronized (s) { [s.layers addObject:layer]; } }
166
+ layer.contents = session && session.isRunning ? (__bridge id)AMPosterImage : nil;
167
+ // Public CALayer string values avoid a separate QuartzCore link dependency.
168
+ layer.contentsScale = 1;
169
+ layer.minificationFilter = @"nearest"; layer.magnificationFilter = @"nearest";
170
+ }
171
+ @interface AVCaptureVideoPreviewLayer (AMPoster)
172
+ - (instancetype)am_initWithSession:(AVCaptureSession *)session __attribute__((objc_method_family(init)));
173
+ @end
174
+ @implementation AVCaptureVideoPreviewLayer (AMPoster)
175
+ - (instancetype)am_initWithSession:(AVCaptureSession *)session {
176
+ // After the exchange, am_initWithSession: is the stock implementation: run it with no session so
177
+ // AVFoundation's private layer state is initialised, then bind the poster session ourselves.
178
+ self = [self am_initWithSession:nil];
179
+ if (self) {
180
+ self.videoGravity = AVLayerVideoGravityResizeAspect;
181
+ AMBindLayer(self, session);
182
+ }
183
+ return self;
184
+ }
185
+ @end
186
+
187
+ static void AMExchange(Class cls, SEL original, SEL replacement) {
188
+ method_exchangeImplementations(class_getInstanceMethod(cls, original), class_getInstanceMethod(cls, replacement));
189
+ }
190
+ static void AMInstallObjects(void) {
191
+ Class device = AMSubclass(AVCaptureDevice.class, "AMPosterDevice");
192
+ AMReplace(device, @selector(uniqueID), ^id(id o) { return @"AMCameraPoster.back"; });
193
+ AMReplace(device, @selector(localizedName), ^id(id o) { return @"AutoMobile Poster Camera"; });
194
+ AMReplace(device, @selector(position), ^AVCaptureDevicePosition(id o) { return AVCaptureDevicePositionBack; });
195
+ AMReplace(device, @selector(deviceType), ^id(id o) { return AVCaptureDeviceTypeBuiltInWideAngleCamera; });
196
+ AMReplace(device, @selector(hasMediaType:), ^BOOL(id o, AVMediaType media) { return AMVideo(media); });
197
+ AMReplace(device, @selector(isConnected), ^BOOL(id o) { return YES; });
198
+ AMReplace(device, @selector(formats), ^id(id o) { return @[]; });
199
+ AMReplace(device, @selector(activeFormat), ^id(id o) { return nil; });
200
+ AMDevice = (id)class_createInstance(device, 0);
201
+ AMDiscoveryClass = AMSubclass(AVCaptureDeviceDiscoverySession.class, "AMPosterDiscovery");
202
+ AMReplace(AMDiscoveryClass, @selector(devices), ^id(id o) { return @[AMDevice]; });
203
+ AMReplace(AMDiscoveryClass, @selector(supportedMultiCamDeviceSets), ^id(id o) { return @[]; });
204
+ AMInputClass = AMSubclass(AVCaptureDeviceInput.class, "AMPosterInput");
205
+ AMReplace(AMInputClass, @selector(device), ^id(id o) { return objc_getAssociatedObject(o, &AMDeviceKey); });
206
+ AMReplace(AMInputClass, @selector(ports), ^id(id o) { return @[]; });
207
+ AMConnectionClass = AMSubclass(AVCaptureConnection.class, "AMPosterConnection");
208
+ AMReplace(AMConnectionClass, @selector(isEnabled), ^BOOL(id o) { return YES; });
209
+ AMReplace(AMConnectionClass, @selector(isActive), ^BOOL(id o) { return YES; });
210
+ AMReplace(AMConnectionClass, @selector(inputPorts), ^id(id o) { return @[]; });
211
+ AMReplace(AMConnectionClass, @selector(output), ^id(id o) { return objc_getAssociatedObject(o, &AMOutputKey); });
212
+ AMCodeClass = AMSubclass(AVMetadataMachineReadableCodeObject.class, "AMPosterCode");
213
+ AMReplace(AMCodeClass, @selector(stringValue), ^id(id o) { return objc_getAssociatedObject(o, &AMMessageKey); });
214
+ AMReplace(AMCodeClass, @selector(type), ^id(id o) { return AVMetadataObjectTypeQRCode; });
215
+ AMReplace(AMCodeClass, @selector(bounds), ^CGRect(id o) { return CGRectMake(0.1, 0.1, 0.8, 0.8); });
216
+ AMReplace(AMCodeClass, @selector(corners), ^id(id o) {
217
+ return @[@{@"X": @0.1, @"Y": @0.1}, @{@"X": @0.9, @"Y": @0.1},
218
+ @{@"X": @0.9, @"Y": @0.9}, @{@"X": @0.1, @"Y": @0.9}];
219
+ });
220
+ AMReplace(AMCodeClass, @selector(time), ^CMTime(id o) { return CMClockGetTime(CMClockGetHostTimeClock()); });
221
+ AMReplace(AMCodeClass, @selector(duration), ^CMTime(id o) { return CMTimeMake(1, 2); });
222
+ AMResolvedClass = AMSubclass(AVCaptureResolvedPhotoSettings.class, "AMPosterResolvedSettings");
223
+ AMReplace(AMResolvedClass, @selector(uniqueID), ^int64_t(id o) { return [objc_getAssociatedObject(o, &AMIDKey) longLongValue]; });
224
+ AMReplace(AMResolvedClass, @selector(photoDimensions), ^CMVideoDimensions(id o) { return (CMVideoDimensions){AMWidth, AMHeight}; });
225
+ AMReplace(AMResolvedClass, @selector(expectedPhotoCount), ^NSInteger(id o) { return 1; });
226
+ AMPhotoClass = AMSubclass(AVCapturePhoto.class, "AMPosterPhoto");
227
+ AMReplace(AMPhotoClass, @selector(fileDataRepresentation), ^id(id o) { return AMPNG; });
228
+ AMReplace(AMPhotoClass, @selector(CGImageRepresentation), ^CGImageRef(id o) { return AMPosterImage; });
229
+ AMReplace(AMPhotoClass, @selector(pixelBuffer), ^CVPixelBufferRef(id o) { return AMPixels; });
230
+ AMReplace(AMPhotoClass, @selector(metadata), ^id(id o) { return @{@"PixelWidth": @(AMWidth), @"PixelHeight": @(AMHeight)}; });
231
+ AMReplace(AMPhotoClass, @selector(resolvedSettings), ^id(id o) { return objc_getAssociatedObject(o, &AMResolvedKey); });
232
+ AMReplace(AMPhotoClass, @selector(photoCount), ^NSInteger(id o) { return 1; });
233
+ AMReplace(AMPhotoClass, @selector(timestamp), ^CMTime(id o) { return CMClockGetTime(CMClockGetHostTimeClock()); });
234
+ AMReplace(AMPhotoClass, @selector(isRawPhoto), ^BOOL(id o) { return NO; });
235
+ // Decode once; generated QR also goes through detection rather than echoing env.
236
+ CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil
237
+ options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
238
+ NSMutableArray *codes = [NSMutableArray array];
239
+ for (CIQRCodeFeature *feature in [detector featuresInImage:[CIImage imageWithCGImage:AMPosterImage]]) {
240
+ if (!feature.messageString) continue;
241
+ id code = class_createInstance(AMCodeClass, 0);
242
+ AMStore(code, &AMMessageKey, feature.messageString);
243
+ [codes addObject:code];
244
+ }
245
+ AMCodes = [codes copy];
246
+ }
247
+
248
+ static void AMInstallDiscovery(void) {
249
+ Class cls = object_getClass(AVCaptureDevice.class);
250
+ IMP original = class_getMethodImplementation(cls, @selector(defaultDeviceWithMediaType:));
251
+ AMReplace(cls, @selector(defaultDeviceWithMediaType:), ^id(id o, AVMediaType media) {
252
+ return AMVideo(media) ? AMDevice : ((id (*)(id, SEL, id))original)(o, @selector(defaultDeviceWithMediaType:), media);
253
+ });
254
+ IMP devices = class_getMethodImplementation(cls, @selector(devicesWithMediaType:));
255
+ AMReplace(cls, @selector(devicesWithMediaType:), ^id(id o, AVMediaType media) {
256
+ return AMVideo(media) ? @[AMDevice] : ((id (*)(id, SEL, id))devices)(o, @selector(devicesWithMediaType:), media);
257
+ });
258
+ IMP typed = class_getMethodImplementation(cls, @selector(defaultDeviceWithDeviceType:mediaType:position:));
259
+ AMReplace(cls, @selector(defaultDeviceWithDeviceType:mediaType:position:),
260
+ ^id(id o, AVCaptureDeviceType type, AVMediaType media, AVCaptureDevicePosition position) {
261
+ if (AMVideo(media)) return [type isEqual:AVCaptureDeviceTypeBuiltInWideAngleCamera] ? AMDevice : nil;
262
+ return ((id (*)(id, SEL, id, id, AVCaptureDevicePosition))typed)
263
+ (o, @selector(defaultDeviceWithDeviceType:mediaType:position:), type, media, position);
264
+ });
265
+ IMP auth = class_getMethodImplementation(cls, @selector(authorizationStatusForMediaType:));
266
+ AMReplace(cls, @selector(authorizationStatusForMediaType:), ^AVAuthorizationStatus(id o, AVMediaType media) {
267
+ return AMVideo(media) ? AVAuthorizationStatusAuthorized :
268
+ ((AVAuthorizationStatus (*)(id, SEL, id))auth)(o, @selector(authorizationStatusForMediaType:), media);
269
+ });
270
+ IMP access = class_getMethodImplementation(cls, @selector(requestAccessForMediaType:completionHandler:));
271
+ AMReplace(cls, @selector(requestAccessForMediaType:completionHandler:), ^(id o, AVMediaType media, void (^handler)(BOOL)) {
272
+ if (AMVideo(media)) { if (handler) dispatch_async(dispatch_get_main_queue(), ^{ handler(YES); }); }
273
+ else ((void (*)(id, SEL, id, id))access)(o, @selector(requestAccessForMediaType:completionHandler:), media, handler);
274
+ });
275
+ Class discovery = object_getClass(AVCaptureDeviceDiscoverySession.class);
276
+ IMP factory = class_getMethodImplementation(discovery, @selector(discoverySessionWithDeviceTypes:mediaType:position:));
277
+ AMReplace(discovery, @selector(discoverySessionWithDeviceTypes:mediaType:position:),
278
+ ^id(id o, NSArray *types, AVMediaType media, AVCaptureDevicePosition position) {
279
+ if (AMMatches(types, media)) return class_createInstance(AMDiscoveryClass, 0);
280
+ return ((id (*)(id, SEL, id, id, AVCaptureDevicePosition))factory)
281
+ (o, @selector(discoverySessionWithDeviceTypes:mediaType:position:), types, media, position);
282
+ });
283
+ AMExchange(AVCaptureDeviceInput.class, @selector(initWithDevice:error:), @selector(am_initWithDevice:error:));
284
+ AMReplace(object_getClass(AVCaptureDeviceInput.class), @selector(deviceInputWithDevice:error:),
285
+ ^id(id o, AVCaptureDevice *device, NSError **error) { return [[AVCaptureDeviceInput alloc] initWithDevice:device error:error]; });
286
+ }
287
+
288
+ static BOOL AMCurrent(AMSessionState *s, NSUInteger generation) {
289
+ @synchronized (s) { return s.running && s.generation == generation; }
290
+ }
291
+ static void AMTick(AVCaptureSession *session, NSUInteger generation) {
292
+ AMSessionState *s = AMState(session);
293
+ NSArray *outputs, *layers;
294
+ BOOL metadataTick;
295
+ @synchronized (s) {
296
+ if (!s.running || s.generation != generation) return;
297
+ outputs = [s.outputs copy]; layers = s.layers.allObjects;
298
+ metadataTick = (s.frame++ % 8 == 0); // 8/15 s, approximately 500 ms.
299
+ }
300
+ dispatch_async(dispatch_get_main_queue(), ^{
301
+ if (!AMCurrent(s, generation)) return;
302
+ for (AVCaptureVideoPreviewLayer *layer in layers)
303
+ if (layer.session == session) layer.contents = (__bridge id)AMPosterImage;
304
+ });
305
+ for (AVCaptureOutput *output in outputs) {
306
+ AMOutputState *binding = AMOutput(output);
307
+ id delegate; dispatch_queue_t queue; AVCaptureConnection *connection;
308
+ @synchronized (binding) {
309
+ delegate = binding.delegate; queue = binding.queue;
310
+ if (delegate && queue && !binding.connection) {
311
+ binding.connection = class_createInstance(AMConnectionClass, 0);
312
+ AMStore(binding.connection, &AMOutputKey, output);
313
+ }
314
+ connection = binding.connection;
315
+ }
316
+ if (!delegate || !queue) continue;
317
+ if ([output isKindOfClass:AVCaptureVideoDataOutput.class] &&
318
+ [delegate respondsToSelector:@selector(captureOutput:didOutputSampleBuffer:fromConnection:)]) {
319
+ CMSampleTimingInfo timing = {CMTimeMake(1, 15), CMClockGetTime(CMClockGetHostTimeClock()), kCMTimeInvalid};
320
+ CMSampleBufferRef sample = NULL;
321
+ if (CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, AMPixels, AMFormat, &timing, &sample) != noErr) continue;
322
+ dispatch_async(queue, ^{
323
+ if (AMCurrent(s, generation) && binding.session == session)
324
+ [delegate captureOutput:output didOutputSampleBuffer:sample fromConnection:connection];
325
+ CFRelease(sample);
326
+ });
327
+ } else if (metadataTick && AMCodes.count && [output isKindOfClass:AVCaptureMetadataOutput.class] &&
328
+ [delegate respondsToSelector:@selector(captureOutput:didOutputMetadataObjects:fromConnection:)]) {
329
+ dispatch_async(queue, ^{
330
+ NSArray *types = objc_getAssociatedObject(output, &AMTypesKey);
331
+ if (AMCurrent(s, generation) && binding.session == session && [types containsObject:AVMetadataObjectTypeQRCode])
332
+ [delegate captureOutput:(AVCaptureMetadataOutput *)output didOutputMetadataObjects:AMCodes fromConnection:connection];
333
+ });
334
+ }
335
+ }
336
+ }
337
+ static void AMInstallSession(void) {
338
+ Class cls = AVCaptureSession.class;
339
+ AMReplace(cls, @selector(canAddInput:), ^BOOL(id o, AVCaptureInput *input) { return input != nil; });
340
+ AMReplace(cls, @selector(addInput:), ^(id o, AVCaptureInput *input) {
341
+ AMSessionState *s = AMState(o); @synchronized (s) { if (input && ![s.inputs containsObject:input]) [s.inputs addObject:input]; }
342
+ });
343
+ AMReplace(cls, @selector(removeInput:), ^(id o, AVCaptureInput *input) {
344
+ AMSessionState *s = AMState(o); @synchronized (s) { [s.inputs removeObject:input]; }
345
+ });
346
+ AMReplace(cls, @selector(inputs), ^id(id o) { AMSessionState *s = AMState(o); @synchronized (s) { return [s.inputs copy]; } });
347
+ AMReplace(cls, @selector(canAddOutput:), ^BOOL(id o, AVCaptureOutput *output) { return output != nil; });
348
+ AMReplace(cls, @selector(addOutput:), ^(AVCaptureSession *o, AVCaptureOutput *output) {
349
+ AMSessionState *s = AMState(o); @synchronized (s) {
350
+ if (output && ![s.outputs containsObject:output]) { [s.outputs addObject:output]; AMOutput(output).session = o; }
351
+ }
352
+ });
353
+ AMReplace(cls, @selector(removeOutput:), ^(id o, AVCaptureOutput *output) {
354
+ AMSessionState *s = AMState(o); @synchronized (s) { [s.outputs removeObject:output]; AMOutput(output).session = nil; }
355
+ });
356
+ AMReplace(cls, @selector(outputs), ^id(id o) { AMSessionState *s = AMState(o); @synchronized (s) { return [s.outputs copy]; } });
357
+ AMReplace(cls, @selector(beginConfiguration), ^(id o) {});
358
+ AMReplace(cls, @selector(commitConfiguration), ^(id o) {});
359
+ AMReplace(cls, @selector(setSessionPreset:), ^(id o, NSString *preset) {
360
+ AMSessionState *s = AMState(o); @synchronized (s) { s.preset = preset; }
361
+ });
362
+ AMReplace(cls, @selector(sessionPreset), ^id(id o) { AMSessionState *s = AMState(o); @synchronized (s) { return s.preset; } });
363
+ AMReplace(cls, @selector(canSetSessionPreset:), ^BOOL(id o, NSString *preset) { return YES; });
364
+ AMReplace(cls, @selector(isRunning), ^BOOL(id o) { AMSessionState *s = AMState(o); @synchronized (s) { return s.running; } });
365
+ AMReplace(cls, @selector(isInterrupted), ^BOOL(id o) { return NO; });
366
+ AMReplace(cls, @selector(startRunning), ^(AVCaptureSession *o) {
367
+ AMSessionState *s = AMState(o); @synchronized (s) {
368
+ if (s.running) return;
369
+ s.running = YES; s.frame = 0;
370
+ NSUInteger generation = ++s.generation;
371
+ __weak AVCaptureSession *weakSession = o;
372
+ s.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
373
+ dispatch_queue_create("dev.automobile.camera-poster.frames", DISPATCH_QUEUE_SERIAL));
374
+ dispatch_source_set_timer(s.timer, DISPATCH_TIME_NOW, NSEC_PER_SEC / 15, NSEC_PER_MSEC * 3);
375
+ dispatch_source_set_event_handler(s.timer, ^{
376
+ @autoreleasepool { AVCaptureSession *session = weakSession; if (session) AMTick(session, generation); }
377
+ });
378
+ dispatch_resume(s.timer);
379
+ }
380
+ });
381
+ AMReplace(cls, @selector(stopRunning), ^(AVCaptureSession *o) {
382
+ AMSessionState *s = AMState(o); NSArray *layers;
383
+ @synchronized (s) {
384
+ s.running = NO; ++s.generation;
385
+ if (s.timer) dispatch_source_cancel(s.timer);
386
+ s.timer = nil; layers = s.layers.allObjects;
387
+ }
388
+ dispatch_async(dispatch_get_main_queue(), ^{
389
+ for (AVCaptureVideoPreviewLayer *layer in layers)
390
+ if (layer.session == o && !o.isRunning) layer.contents = nil;
391
+ });
392
+ });
393
+ }
394
+
395
+ static void AMInstallPreviewAndOutputs(void) {
396
+ Class layer = AVCaptureVideoPreviewLayer.class;
397
+ AMExchange(layer, @selector(initWithSession:), @selector(am_initWithSession:));
398
+ AMReplace(object_getClass(layer), @selector(layerWithSession:), ^id(id o, AVCaptureSession *s) {
399
+ return [[AVCaptureVideoPreviewLayer alloc] initWithSession:s];
400
+ });
401
+ AMReplace(layer, @selector(setSession:), ^(AVCaptureVideoPreviewLayer *o, AVCaptureSession *s) { AMBindLayer(o, s); });
402
+ AMReplace(layer, @selector(session), ^id(id o) { return objc_getAssociatedObject(o, &AMSessionKey); });
403
+ AMReplace(layer, @selector(setVideoGravity:), ^(AVCaptureVideoPreviewLayer *o, AVLayerVideoGravity gravity) {
404
+ o.contentsGravity = [gravity isEqual:AVLayerVideoGravityResizeAspectFill] ? @"resizeAspectFill" :
405
+ ([gravity isEqual:AVLayerVideoGravityResize] ? @"resize" : @"resizeAspect");
406
+ });
407
+ AMReplace(layer, @selector(videoGravity), ^id(AVCaptureVideoPreviewLayer *o) {
408
+ return [o.contentsGravity isEqual:@"resizeAspectFill"] ? AVLayerVideoGravityResizeAspectFill :
409
+ ([o.contentsGravity isEqual:@"resize"] ? AVLayerVideoGravityResize : AVLayerVideoGravityResizeAspect);
410
+ });
411
+ AMReplace(AVCaptureVideoDataOutput.class, @selector(setSampleBufferDelegate:queue:),
412
+ ^(id o, id delegate, dispatch_queue_t queue) {
413
+ AMOutputState *s = AMOutput(o); @synchronized (s) { s.delegate = delegate; s.queue = queue; }
414
+ });
415
+ AMReplace(AVCaptureMetadataOutput.class, @selector(setMetadataObjectsDelegate:queue:),
416
+ ^(id o, id delegate, dispatch_queue_t queue) {
417
+ AMOutputState *s = AMOutput(o); @synchronized (s) { s.delegate = delegate; s.queue = queue; }
418
+ });
419
+ AMReplace(AVCaptureMetadataOutput.class, @selector(setMetadataObjectTypes:), ^(id o, NSArray *types) { AMStore(o, &AMTypesKey, [types copy]); });
420
+ AMReplace(AVCaptureMetadataOutput.class, @selector(metadataObjectTypes), ^id(id o) { return objc_getAssociatedObject(o, &AMTypesKey) ?: @[]; });
421
+ AMReplace(AVCaptureMetadataOutput.class, @selector(availableMetadataObjectTypes), ^id(id o) { return @[AVMetadataObjectTypeQRCode]; });
422
+ AMReplace(AVCapturePhotoOutput.class, @selector(connectionWithMediaType:), ^id(id o, AVMediaType type) { return nil; });
423
+ AMReplace(AVCapturePhotoOutput.class, @selector(capturePhotoWithSettings:delegate:),
424
+ ^(AVCapturePhotoOutput *o, AVCapturePhotoSettings *settings, id<AVCapturePhotoCaptureDelegate> delegate) {
425
+ id resolved = class_createInstance(AMResolvedClass, 0);
426
+ AMStore(resolved, &AMIDKey, @(settings.uniqueID));
427
+ id photo = class_createInstance(AMPhotoClass, 0);
428
+ AMStore(photo, &AMResolvedKey, resolved);
429
+ AVCaptureSession *session = AMOutput(o).session;
430
+ NSError *error = session.isRunning ? nil : [NSError errorWithDomain:@"AMCameraPoster" code:1
431
+ userInfo:@{NSLocalizedDescriptionKey: @"Capture session is not running"}];
432
+ dispatch_async(dispatch_get_main_queue(), ^{
433
+ if ([delegate respondsToSelector:@selector(captureOutput:willBeginCaptureForResolvedSettings:)])
434
+ [delegate captureOutput:o willBeginCaptureForResolvedSettings:resolved];
435
+ if ([delegate respondsToSelector:@selector(captureOutput:didFinishProcessingPhoto:error:)])
436
+ [delegate captureOutput:o didFinishProcessingPhoto:photo error:error];
437
+ if ([delegate respondsToSelector:@selector(captureOutput:didFinishCaptureForResolvedSettings:error:)])
438
+ [delegate captureOutput:o didFinishCaptureForResolvedSettings:resolved error:error];
439
+ });
440
+ });
441
+ }
442
+
443
+ __attribute__((constructor)) static void AMActivate(void) {
444
+ @autoreleasepool {
445
+ NSDictionary *env = NSProcessInfo.processInfo.environment;
446
+ NSString *path = env[@"AM_CAMERA_POSTER_PATH"], *qr = env[@"AM_CAMERA_POSTER_QR"];
447
+ if (!path.length && !qr.length) return;
448
+ if (!AMLoadPoster(path, qr)) return; // Invalid path wins too: no QR fallback.
449
+ AMInstallObjects(); AMInstallDiscovery(); AMInstallSession(); AMInstallPreviewAndOutputs();
450
+ Class picker = object_getClass(UIImagePickerController.class);
451
+ IMP available = class_getMethodImplementation(picker, @selector(isSourceTypeAvailable:));
452
+ AMReplace(picker, @selector(isSourceTypeAvailable:), ^BOOL(id o, UIImagePickerControllerSourceType type) {
453
+ return type == UIImagePickerControllerSourceTypeCamera ||
454
+ ((BOOL (*)(id, SEL, UIImagePickerControllerSourceType))available)(o, @selector(isSourceTypeAvailable:), type);
455
+ });
456
+ IMP media = class_getMethodImplementation(picker, @selector(availableMediaTypesForSourceType:));
457
+ AMReplace(picker, @selector(availableMediaTypesForSourceType:), ^id(id o, UIImagePickerControllerSourceType type) {
458
+ return type == UIImagePickerControllerSourceTypeCamera ? @[UTTypeImage.identifier] :
459
+ ((id (*)(id, SEL, UIImagePickerControllerSourceType))media)(o, @selector(availableMediaTypesForSourceType:), type);
460
+ });
461
+ AMReplace(picker, @selector(isCameraDeviceAvailable:), ^BOOL(id o, UIImagePickerControllerCameraDevice device) { return YES; });
462
+ // Availability only: presenting/capturing with UIImagePickerController is
463
+ // outside this PoC. VisionKit/DataScanner hardware gates are not patched.
464
+ NSLog(@"[AMCameraPoster] active source=%@ size=%zux%zu", path.length ? @"path" : @"qr", AMWidth, AMHeight);
465
+ }
466
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaeawc/auto-mobile",
3
- "version": "0.0.92",
3
+ "version": "0.0.93",
4
4
  "description": "Mobile device interaction automation via MCP",
5
5
  "keywords": [
6
6
  "adb",
@@ -33,7 +33,8 @@
33
33
  "dist",
34
34
  "!dist/**/*.map",
35
35
  "schemas",
36
- "mcp-skills"
36
+ "mcp-skills",
37
+ "ios/CameraPosterInjection/Injection/CameraPosterInjection.m"
37
38
  ],
38
39
  "main": "dist/src/index.js",
39
40
  "publishConfig": {