@ateam-ai-mobile/expo 0.1.22 → 0.1.24

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.
package/dist/bootstrap.js CHANGED
@@ -3002,7 +3002,103 @@ function base64ToBytes(b64) {
3002
3002
  return o === out.length ? out : out.subarray(0, o);
3003
3003
  }
3004
3004
 
3005
+ // src/aiLive/motionGate.ts
3006
+ var SIG_W = 16;
3007
+ var SIG_H = 12;
3008
+ var SLACK_SEC = 0.15;
3009
+ var SENSITIVITY = Object.freeze({ low: 0.06, medium: 0.03, high: 0.015 });
3010
+ var GATE_DEFAULTS = Object.freeze({ enabled: true, sensitivity: "medium", preRollSec: 1, postRollSec: 1, heartbeatSec: 8 });
3011
+ function difference(a, b) {
3012
+ if (!a || !b || a.length !== b.length || !a.length) return 1;
3013
+ let sum = 0;
3014
+ for (let i = 0; i < a.length; i++) sum += Math.abs(a[i] - b[i]);
3015
+ return sum / (a.length * 255);
3016
+ }
3017
+ function gateConfigFrom(d) {
3018
+ const c = { ...GATE_DEFAULTS };
3019
+ if (!d || typeof d !== "object") return c;
3020
+ if (typeof d.enabled === "boolean") c.enabled = d.enabled;
3021
+ if (typeof d.sensitivity === "string" && Object.prototype.hasOwnProperty.call(SENSITIVITY, d.sensitivity)) c.sensitivity = d.sensitivity;
3022
+ for (const k of ["preRollSec", "postRollSec", "heartbeatSec"]) {
3023
+ const v = Number(d[k]);
3024
+ if (Number.isFinite(v) && v >= 0 && v <= 60) c[k] = v;
3025
+ }
3026
+ return c;
3027
+ }
3028
+ function createMotionGate(cfg = {}) {
3029
+ const c = { ...GATE_DEFAULTS, ...cfg };
3030
+ const threshold = SENSITIVITY[c.sensitivity] ?? SENSITIVITY.medium;
3031
+ let prevSig = null;
3032
+ let lastChangeT = -Infinity;
3033
+ let lastSentT = -Infinity;
3034
+ let held = [];
3035
+ const n = { offered: 0, sent: 0, held: 0, heartbeats: 0, changes: 0 };
3036
+ const sendNow = (t, item, reason, withPreRoll = false) => {
3037
+ const out = withPreRoll ? held.filter((h) => t - h.t <= c.preRollSec + SLACK_SEC).map((h) => h.item) : [];
3038
+ held = [];
3039
+ out.push(item);
3040
+ lastSentT = t;
3041
+ n.sent += out.length;
3042
+ return { send: out, reason };
3043
+ };
3044
+ return {
3045
+ offer({ t, sig, item }) {
3046
+ n.offered++;
3047
+ if (!c.enabled) {
3048
+ lastSentT = t;
3049
+ n.sent++;
3050
+ return { send: [item], reason: "off" };
3051
+ }
3052
+ const first = prevSig == null;
3053
+ const d = difference(prevSig, sig);
3054
+ prevSig = sig;
3055
+ if (first) return sendNow(t, item, "first");
3056
+ if (d > threshold) {
3057
+ lastChangeT = t;
3058
+ n.changes++;
3059
+ return sendNow(t, item, "change", true);
3060
+ }
3061
+ if (t - lastChangeT <= c.postRollSec + SLACK_SEC) return sendNow(t, item, "after");
3062
+ if (t - lastSentT >= c.heartbeatSec) {
3063
+ n.heartbeats++;
3064
+ return sendNow(t, item, "heartbeat");
3065
+ }
3066
+ held.push({ t, item });
3067
+ while (held.length && t - held[0].t > c.preRollSec + 1) held.shift();
3068
+ n.held++;
3069
+ return { send: [], reason: "quiet" };
3070
+ },
3071
+ counters: () => ({ ...n }),
3072
+ config: () => ({ ...c })
3073
+ };
3074
+ }
3075
+
3076
+ // src/aiLive/signature.ts
3077
+ var import_jpeg_js = require("jpeg-js");
3078
+ var SIG_JPEG_SIDE = 64;
3079
+ function signatureFromJpeg(b64) {
3080
+ const img = (0, import_jpeg_js.decode)(base64ToBytes(b64), { useTArray: true, formatAsRGBA: true, maxResolutionInMP: 1 });
3081
+ const { width: w, height: h, data } = img;
3082
+ if (!w || !h) throw new Error("empty picture");
3083
+ const sum = new Float64Array(SIG_W * SIG_H);
3084
+ const cnt = new Uint32Array(SIG_W * SIG_H);
3085
+ for (let y = 0; y < h; y++) {
3086
+ const cy = Math.min(SIG_H - 1, Math.floor(y * SIG_H / h));
3087
+ for (let x = 0; x < w; x++) {
3088
+ const cx = Math.min(SIG_W - 1, Math.floor(x * SIG_W / w));
3089
+ const i = (y * w + x) * 4;
3090
+ const k = cy * SIG_W + cx;
3091
+ sum[k] += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
3092
+ cnt[k]++;
3093
+ }
3094
+ }
3095
+ const out = new Uint8Array(SIG_W * SIG_H);
3096
+ for (let k = 0; k < out.length; k++) out[k] = cnt[k] ? Math.round(sum[k] / cnt[k]) : 0;
3097
+ return out;
3098
+ }
3099
+
3005
3100
  // src/aiLive/provider.ts
3101
+ var PHONE_MAX_PRE_ROLL_SEC = 5;
3006
3102
  var LIVE_KIND = "ai-models.live";
3007
3103
  var FRESH_MS2 = 3e3;
3008
3104
  var MAX_BUFFERED = 512 * 1024;
@@ -3016,13 +3112,14 @@ var defaultAiLiveDeps = () => ({
3016
3112
  token: import_core2.getToken,
3017
3113
  tenant: import_core2.getTenant,
3018
3114
  jpegById,
3115
+ signatureById: async (id) => signatureFromJpeg(await jpegById(id, SIG_JPEG_SIDE, 0.6)),
3019
3116
  now: Date.now,
3020
3117
  setInterval: (fn, ms) => setInterval(fn, ms),
3021
3118
  clearInterval: (h) => clearInterval(h),
3022
3119
  setTimeout: (fn, ms) => setTimeout(fn, ms),
3023
3120
  clearTimeout: (h) => clearTimeout(h)
3024
3121
  });
3025
- var idle = () => ({ state: "idle", camera: null, sent: 0, skipped: 0, lastEpisode: null, lastError: null });
3122
+ var idle = () => ({ state: "idle", camera: null, sent: 0, skipped: 0, heldBack: 0, lastEpisode: null, lastError: null });
3026
3123
  function createAiLiveProvider(overrides = {}) {
3027
3124
  const deps = { ...defaultAiLiveDeps(), ...overrides };
3028
3125
  let owner = null;
@@ -3038,9 +3135,11 @@ function createAiLiveProvider(overrides = {}) {
3038
3135
  let closingOnPurpose = false;
3039
3136
  let seq = 0;
3040
3137
  let busy = false;
3138
+ let owedGaps = 0;
3041
3139
  let gen = 0;
3042
3140
  let lastFrameId = null;
3043
3141
  let camera = DEFAULT_CAMERA;
3142
+ let gate = null;
3044
3143
  let lastOwner = null;
3045
3144
  const episodeCbs = /* @__PURE__ */ new Map();
3046
3145
  const snapCbs = /* @__PURE__ */ new Map();
@@ -3083,7 +3182,6 @@ function createAiLiveProvider(overrides = {}) {
3083
3182
  }
3084
3183
  }
3085
3184
  async function slot() {
3086
- const mySeq = seq++;
3087
3185
  const g = gen;
3088
3186
  const sock = ws;
3089
3187
  const current = () => g === gen && ws === sock && sock?.readyState === 1;
@@ -3092,13 +3190,22 @@ function createAiLiveProvider(overrides = {}) {
3092
3190
  if (g === gen) void teardown("stopped", import_plugin_runtime3.CONSENT_TAKEN_BACK);
3093
3191
  return true;
3094
3192
  };
3193
+ const gap = (lastError) => {
3194
+ seq++;
3195
+ set2(lastError ? { skipped: snap.skipped + 1, lastError } : { skipped: snap.skipped + 1 });
3196
+ };
3095
3197
  if (consentGone()) return;
3096
- if (busy || !current()) {
3198
+ if (busy && current()) {
3199
+ owedGaps++;
3097
3200
  set2({ skipped: snap.skipped + 1 });
3098
3201
  return;
3099
3202
  }
3203
+ if (busy || !current()) {
3204
+ gap();
3205
+ return;
3206
+ }
3100
3207
  if ((sock.bufferedAmount || 0) > MAX_BUFFERED) {
3101
- set2({ skipped: snap.skipped + 1 });
3208
+ gap();
3102
3209
  return;
3103
3210
  }
3104
3211
  busy = true;
@@ -3109,19 +3216,52 @@ function createAiLiveProvider(overrides = {}) {
3109
3216
  const id = f?.handle?.id;
3110
3217
  const t = Number(f?.timestamp);
3111
3218
  if (!id || id === lastFrameId || !(deps.now() - t < FRESH_MS2)) {
3112
- set2({ skipped: snap.skipped + 1 });
3219
+ gap();
3113
3220
  return;
3114
3221
  }
3115
- const b64 = await deps.jpegById(id, camera.maxPx, 0.7);
3116
- if (!current() || consentGone()) return;
3117
- const frame = encodeFrame({ seq: mySeq, tCapture: t, payload: base64ToBytes(b64) });
3118
- sock.send(frame.buffer);
3119
3222
  lastFrameId = id;
3120
- set2({ sent: snap.sent + 1 });
3223
+ let toSend = [{ id, t }];
3224
+ const myGate = gate;
3225
+ if (myGate) {
3226
+ let sig = null;
3227
+ try {
3228
+ sig = await deps.signatureById(id);
3229
+ } catch {
3230
+ sig = null;
3231
+ }
3232
+ if (!current() || consentGone()) return;
3233
+ toSend = myGate.offer({ t: t / 1e3, sig, item: { id, t } }).send;
3234
+ if (!toSend.length) {
3235
+ set2({ heldBack: (snap.heldBack ?? 0) + 1 });
3236
+ return;
3237
+ }
3238
+ }
3239
+ for (const it of toSend) {
3240
+ if (!current() || consentGone()) return;
3241
+ if ((sock.bufferedAmount || 0) > MAX_BUFFERED) {
3242
+ gap();
3243
+ continue;
3244
+ }
3245
+ let b64;
3246
+ try {
3247
+ b64 = await deps.jpegById(it.id, camera.maxPx, 0.7);
3248
+ } catch (e) {
3249
+ if (g === gen) gap(String(e?.message || e));
3250
+ continue;
3251
+ }
3252
+ if (!current() || consentGone()) return;
3253
+ const frame = encodeFrame({ seq: seq++, tCapture: it.t, payload: base64ToBytes(b64) });
3254
+ sock.send(frame.buffer);
3255
+ set2({ sent: snap.sent + 1 });
3256
+ }
3121
3257
  } catch (e) {
3122
- if (g === gen) set2({ skipped: snap.skipped + 1, lastError: String(e?.message || e) });
3258
+ if (g === gen) gap(String(e?.message || e));
3123
3259
  } finally {
3124
- if (g === gen) busy = false;
3260
+ if (g === gen) {
3261
+ busy = false;
3262
+ seq += owedGaps;
3263
+ owedGaps = 0;
3264
+ }
3125
3265
  }
3126
3266
  }
3127
3267
  function openSocket() {
@@ -3166,6 +3306,11 @@ function createAiLiveProvider(overrides = {}) {
3166
3306
  camera = c && Number(c.fps) > 0 && Number(c.maxPx) > 0 ? { fps: Number(c.fps), maxPx: Number(c.maxPx) } : DEFAULT_CAMERA;
3167
3307
  return;
3168
3308
  }
3309
+ if (ev.event === "motion" && !opened) {
3310
+ const c = gateConfigFrom(ev.data);
3311
+ gate = c.enabled ? createMotionGate({ ...c, preRollSec: Math.min(c.preRollSec, PHONE_MAX_PRE_ROLL_SEC) }) : null;
3312
+ return;
3313
+ }
3169
3314
  if (ev.event === "opened" && !opened) {
3170
3315
  opened = true;
3171
3316
  deps.clearTimeout(gaveUp);
@@ -3272,8 +3417,10 @@ function createAiLiveProvider(overrides = {}) {
3272
3417
  allowed = typeof ctx.allowed === "function" ? ctx.allowed : () => false;
3273
3418
  gen++;
3274
3419
  seq = 0;
3420
+ owedGaps = 0;
3275
3421
  lastFrameId = null;
3276
3422
  busy = false;
3423
+ gate = null;
3277
3424
  const g = gen;
3278
3425
  const cameraOfScreen = ctx.visual;
3279
3426
  snap = { ...idle(), camera: cam };